Commit 466c2d47 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '294444-apollo-management-table-part-1' into 'master'

Render Corpus Management Table -  Part 1

See merge request gitlab-org/gitlab!57424
parents dcf9b96e 86e72448
<script> <script>
import { GlTable } from '@gitlab/ui'; import { GlTable } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import UserDate from '~/vue_shared/components/user_date.vue';
import UserActions from './user_actions.vue'; import UserActions from './user_actions.vue';
import UserAvatar from './user_avatar.vue'; import UserAvatar from './user_avatar.vue';
import UserDate from './user_date.vue';
const DEFAULT_TH_CLASSES = const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!';
......
...@@ -2,8 +2,6 @@ import { s__, __ } from '~/locale'; ...@@ -2,8 +2,6 @@ import { s__, __ } from '~/locale';
export const USER_AVATAR_SIZE = 32; export const USER_AVATAR_SIZE = 32;
export const SHORT_DATE_FORMAT = 'd mmm, yyyy';
export const LENGTH_OF_USER_NOTE_TOOLTIP = 100; export const LENGTH_OF_USER_NOTE_TOOLTIP = 100;
export const I18N_USER_ACTIONS = { export const I18N_USER_ACTIONS = {
......
...@@ -8,6 +8,8 @@ const INTERVALS = { ...@@ -8,6 +8,8 @@ const INTERVALS = {
export const FILE_SYMLINK_MODE = '120000'; export const FILE_SYMLINK_MODE = '120000';
export const SHORT_DATE_FORMAT = 'd mmm, yyyy';
export const timeRanges = [ export const timeRanges = [
{ {
label: __('30 minutes'), label: __('30 minutes'),
......
<script>
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlButton,
GlModal,
},
directives: {
GlModalDirective,
},
props: {
corpus: {
type: Object,
required: true,
},
},
i18n: {
deleteCorpusMessage: s__('Corpus Management|Are you sure you want to delete the corpus?'),
},
modal: {
actionPrimary: {
text: s__('Delete'),
attributes: { variant: 'danger', 'data-testid': 'modal-confirm' },
},
actionCancel: {
text: s__('Cancel'),
},
},
computed: {
downloadPath() {
/*
* TODO: Replace with relative path when we complete backend
* https://gitlab.com/gitlab-org/gitlab/-/issues/321618
*/
return `https://www.gitlab.com/${this.corpus.downloadPath}`;
},
},
};
</script>
<template>
<span>
<gl-button
class="gl-mr-2"
icon="download"
category="secondary"
variant="confirm"
:href="downloadPath"
/>
<gl-button
v-gl-modal-directive="`confirmation-modal-${corpus.name}`"
icon="remove"
category="secondary"
variant="danger"
/>
<gl-modal
header-class="gl-border-b-initial"
body-class="gl-display-none"
size="sm"
:title="$options.i18n.deleteCorpusMessage"
:modal-id="`confirmation-modal-${corpus.name}`"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
@primary="$emit('delete', corpus)"
/>
</span>
</template>
<script>
import { GlLink } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { s__ } from '~/locale';
export default {
components: {
GlLink,
},
props: {
corpus: {
type: Object,
required: true,
},
},
i18n: {
latestJob: s__('CorpusManagement|Latest Job:'),
},
computed: {
fileSize() {
return numberToHumanSize(this.corpus.size);
},
jobUrl() {
/*
* TODO: Replace with relative path when we complete backend
* https://gitlab.com/gitlab-org/gitlab/-/issues/321618
*/
return `https://www.gitlab.com/${this.corpus.latestJobPath}`;
},
jobPath() {
return this.corpus.latestJobPath;
},
},
};
</script>
<template>
<div>
<div class="gl-text-gray-900" data-testid="corpus-name">
{{ corpus.name }} <span data-testid="file-size">{{ fileSize }}</span
>)
</div>
<div data-testid="latest-job">
{{ this.$options.i18n.latestJob }}
<gl-link v-if="jobPath" class="gl-display-inline-block" :href="jobUrl" target="_blank">
{{ jobPath }}
</gl-link>
<template v-else>-</template>
</div>
</div>
</template>
<script>
import { GlIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlIcon,
},
props: {
target: {
type: String,
required: true,
},
},
i18n: {
notSet: s__('CorpusManagement|Not Set'),
},
computed: {
hasTarget() {
return Boolean(this.target);
},
},
};
</script>
<template>
<span v-if="hasTarget" class="gl-text-gray-900"> {{ target }} </span>
<span v-else class="gl-text-gray-900">
<gl-icon class="gl-mr-1" :size="16" name="information-o" />
{{ this.$options.i18n.notSet }}
</span>
</template>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Name component renders name with correct file size 1`] = `
<div
class="gl-text-gray-900"
data-testid="corpus-name"
>
Test corpus 1
<span
data-testid="file-size"
>
381.47 MiB
</span>
)
</div>
`;
exports[`Name component renders the latest job 1`] = `
<div
class="gl-text-gray-900"
data-testid="corpus-name"
>
Test corpus 1
<span
data-testid="file-size"
>
381.47 MiB
</span>
)
</div>
`;
exports[`Name component without job path renders a - string instead of a link 1`] = `
<div
class="gl-text-gray-900"
data-testid="corpus-name"
>
Corpus-sample-2-1431-4425
<span
data-testid="file-size"
>
306.13 MiB
</span>
)
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Target component renders target 1`] = `
<span
class="gl-text-gray-900"
>
294444-apollo-management-table
</span>
`;
exports[`Target component without target renders Not Set with icon 1`] = `
<span
class="gl-text-gray-900"
>
<gl-icon-stub
class="gl-mr-1"
name="information-o"
size="16"
/>
Not Set
</span>
`;
import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Actions from 'ee/security_configuration/corpus_management/components/columns/actions.vue';
import { corpuses } from '../../mock_data';
describe('Action buttons', () => {
let wrapper;
const createComponentFactory = (mountFn = shallowMount) => (options = {}) => {
const defaultProps = {
corpus: corpuses[0],
};
wrapper = mountFn(Actions, {
propsData: defaultProps,
...options,
});
};
const createComponent = createComponentFactory();
afterEach(() => {
wrapper.destroy();
});
describe('corpus management', () => {
it('renders the action buttons', () => {
createComponent();
expect(wrapper.findAll(GlButton)).toHaveLength(2);
});
describe('delete confirmation modal', () => {
beforeEach(() => {
createComponent({ stubs: { GlModal } });
});
it('calls the deleteCorpus method', async () => {
wrapper.findComponent(GlModal).vm.$emit('primary');
await nextTick();
expect(wrapper.emitted().delete).toBeTruthy();
});
});
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Name from 'ee/security_configuration/corpus_management/components/columns/name.vue';
import { corpuses } from '../../mock_data';
describe('Name', () => {
let wrapper;
const findName = () => wrapper.find('[data-testid="corpus-name"]');
const findFileSize = () => wrapper.find('[data-testid="file-size"]');
const createComponentFactory = (mountFn = shallowMount) => (options = {}) => {
const defaultProps = {
corpus: corpuses[0],
};
wrapper = mountFn(Name, {
propsData: defaultProps,
...options,
});
};
const createComponent = createComponentFactory();
afterEach(() => {
wrapper.destroy();
});
describe('component', () => {
it('renders name with correct file size', () => {
createComponent();
expect(findFileSize().text()).toBe('381.47 MiB');
expect(findName().element).toMatchSnapshot();
});
it('renders the latest job', () => {
createComponent();
expect(wrapper.findComponent(GlLink).exists()).toBe(true);
expect(findFileSize().text()).toBe('381.47 MiB');
expect(findName().element).toMatchSnapshot();
});
describe('without job path', () => {
it('renders a - string instead of a link', () => {
createComponent({ propsData: { corpus: corpuses[2] } });
expect(wrapper.findComponent(GlLink).exists()).toBe(false);
expect(findFileSize().text()).toBe('306.13 MiB');
expect(findName().element).toMatchSnapshot();
});
});
});
});
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Target from 'ee/security_configuration/corpus_management/components/columns/target.vue';
describe('Target', () => {
let wrapper;
const createComponentFactory = (mountFn = shallowMount) => (options = {}) => {
const defaultProps = { target: '294444-apollo-management-table' };
wrapper = mountFn(Target, {
propsData: defaultProps,
...options,
});
};
const createComponent = createComponentFactory();
afterEach(() => {
wrapper.destroy();
});
describe('component', () => {
it('renders target', () => {
createComponent();
expect(wrapper.findComponent(GlIcon).exists()).toBe(false);
expect(wrapper.element).toMatchSnapshot();
});
describe('without target', () => {
it('renders Not Set with icon', () => {
createComponent({ propsData: { target: '' } });
expect(wrapper.findComponent(GlIcon).exists()).toBe(true);
expect(wrapper.element).toMatchSnapshot();
});
});
});
});
export const corpuses = [
{
name: 'Test corpus 1',
lastUpdated: new Date(2021, 1, 12),
lastUsed: new Date(2020, 4, 3),
latestJobPath: 'gitlab-examples/security/security-reports/-/jobs/1107103952',
target: '',
downloadPath: 'farias-gl/go-fuzzing-example/-/jobs/959593462/artifacts/download',
size: 4e8,
},
{
name: 'Corpus-sample-1-5830-2393',
lastUpdated: new Date(2021, 2, 5),
lastUsed: new Date(2021, 6, 3),
latestJobPath: 'gitlab-examples/security/security-reports/-/jobs/1107103952',
downloadPath: 'farias-gl/go-fuzzing-example/-/jobs/959593462/artifacts/download',
target: '294444-apollo-management-table',
size: 2.34e8,
},
{
name: 'Corpus-sample-2-1431-4425',
lastUpdated: new Date(2021, 1, 17),
lastUsed: new Date(2020, 4, 23),
latestJobPath: '',
target: '120498-another-branch',
size: 3.21e8,
},
{
name: 'Corpus-sample-5-5830-1393',
lastUpdated: new Date(2021, 4, 9),
lastUsed: new Date(2020, 8, 4),
latestJobPath: 'gitlab-examples/security/security-reports/-/jobs/1107103952',
downloadPath: 'farias-gl/go-fuzzing-example/-/jobs/959593462/artifacts/download',
target: '120341-feature-branch',
size: 1.34e8,
},
{
name: 'Corpus-sample-8-1830-1393',
lastUpdated: new Date(2020, 4, 9),
lastUsed: new Date(2019, 8, 4),
latestJobPath: 'gitlab-examples/security/security-reports/-/jobs/1107103952',
downloadPath: 'farias-gl/go-fuzzing-example/-/jobs/959593462/artifacts/download',
target: '',
size: 6.34e8,
},
{
name: 'Corpus-sample-9-2450-2393',
lastUpdated: new Date(2019, 4, 6),
lastUsed: new Date(2020, 3, 12),
latestJobPath: 'gitlab-examples/security/security-reports/-/jobs/1107103952',
downloadPath: 'farias-gl/go-fuzzing-example/-/jobs/959593462/artifacts/download',
target: '403847-feature-branch',
size: 3.22e8,
},
];
...@@ -8684,12 +8684,21 @@ msgstr "" ...@@ -8684,12 +8684,21 @@ msgstr ""
msgid "Copy value" msgid "Copy value"
msgstr "" msgstr ""
msgid "Corpus Management|Are you sure you want to delete the corpus?"
msgstr ""
msgid "CorpusManagement|Fuzz testing corpus management" msgid "CorpusManagement|Fuzz testing corpus management"
msgstr "" msgstr ""
msgid "CorpusManagement|Latest Job:"
msgstr ""
msgid "CorpusManagement|New corpus" msgid "CorpusManagement|New corpus"
msgstr "" msgstr ""
msgid "CorpusManagement|Not Set"
msgstr ""
msgid "CorpusManagement|Total Size: %{totalSize}" msgid "CorpusManagement|Total Size: %{totalSize}"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import UserDate from '~/admin/users/components/user_date.vue'; import UserDate from '~/vue_shared/components/user_date.vue';
import { users } from '../mock_data'; import { users } from '../mock_data';
const mockDate = users[0].createdAt; const mockDate = users[0].createdAt;
......
...@@ -3,8 +3,8 @@ import { mount } from '@vue/test-utils'; ...@@ -3,8 +3,8 @@ import { mount } from '@vue/test-utils';
import AdminUserActions from '~/admin/users/components/user_actions.vue'; import AdminUserActions from '~/admin/users/components/user_actions.vue';
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue'; import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
import AdminUserDate from '~/admin/users/components/user_date.vue';
import AdminUsersTable from '~/admin/users/components/users_table.vue'; import AdminUsersTable from '~/admin/users/components/users_table.vue';
import AdminUserDate from '~/vue_shared/components/user_date.vue';
import { users, paths } from '../mock_data'; import { users, paths } from '../mock_data';
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment