diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index 692de9dcb88b02cb4c01cb82e045cc2d57e2cf2c..378573548f824fce419150a952f3e5e3f07c79b3 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -15,3 +15,4 @@ export const TYPE_USER = 'User'; export const TYPE_VULNERABILITY = 'Vulnerability'; export const TYPE_NOTE = 'Note'; export const TYPE_DISCUSSION = 'Discussion'; +export const TYPE_PACKAGES_PACKAGE = 'Packages::Package'; diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload.vue b/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload.vue index 0e355f9d42e82cf764f83365fc54e0fc7258e234..338d88af8c72b123d20139d348a6432eb3307920 100644 --- a/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload.vue +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload.vue @@ -74,7 +74,11 @@ export default { addCorpus() { this.$apollo.mutate({ mutation: addCorpusMutation, - variables: { name: this.$options.i18n.newCorpus, projectPath: this.projectFullPath }, + variables: { + name: this.$options.i18n.newCorpus, + projectPath: this.projectFullPath, + packageId: this.states.uploadState.uploadedPackageId, + }, }); }, resetCorpus() { diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload_form.vue b/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload_form.vue index 485c74d568706214c3751dd2136a496ba132f63f..354ba04646be5b68bb8ddc01e12409cb50aefc5c 100644 --- a/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload_form.vue +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/components/corpus_upload_form.vue @@ -30,7 +30,7 @@ export default { corpusName: s__('CorpusManagement|Corpus name'), uploadButtonText: __('Choose File...'), uploadMessage: s__( - 'CorpusManagement|New corpus needs to be a upload in *.zip format. Maximum 10GB', + 'CorpusManagement|New corpus needs to be a upload in *.zip format. Maximum 5GB', ), }, data() { @@ -170,7 +170,9 @@ export default { <div v-if="isUploading" data-testid="upload-status" class="gl-mt-2"> <gl-loading-icon inline size="sm" /> {{ progressText }} - <gl-button size="small" @click="cancelUpload"> {{ __('Cancel') }} </gl-button> + <gl-button size="small" data-testid="cancel-upload" @click="cancelUpload"> + {{ __('Cancel') }} + </gl-button> </div> </gl-form> </template> diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/add_corpus.mutation.graphql b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/add_corpus.mutation.graphql index 4e982a106f0d31a46bb7f7eea88b0cacb9866aa3..a42d772e2a219063ff9f662d5428f5ea30f3c6b6 100644 --- a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/add_corpus.mutation.graphql +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/add_corpus.mutation.graphql @@ -1,5 +1,5 @@ -mutation addCorpus($projectPath: ID!, $name: String!) { - addCorpus(projectPath: $projectPath, name: $name) @client { +mutation addCorpus($projectPath: ID!, $name: String!, $packageId: Int!) { + addCorpus(projectPath: $projectPath, name: $name, packageId: $packageId) @client { errors } } diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/corpus_create.mutation.graphql b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/corpus_create.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..68e3a5ae3d56d23635a675a813e5dd563611600e --- /dev/null +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/corpus_create.mutation.graphql @@ -0,0 +1,5 @@ +mutation CorpusCreate($input: CorpusCreateInput!) { + corpusCreate(input: $input) { + errors + } +} diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/upload_complete.mutation.graphql b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/upload_complete.mutation.graphql new file mode 100644 index 0000000000000000000000000000000000000000..7c464d436c819c0a0c0565f4a3ad4d46fd7f7fe5 --- /dev/null +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/mutations/upload_complete.mutation.graphql @@ -0,0 +1,5 @@ +mutation uploadComplete($projectPath: ID!, $packageId: Int!) { + uploadComplete(projectPath: $projectPath, packageId: $packageId) @client { + errors + } +} diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/queries/get_corpuses.query.graphql b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/queries/get_corpuses.query.graphql index 88a939266d88bc5a44f0249add0e26e72b996141..3d3cccb91265d2f830df34f6cb6b2d3128655e50 100644 --- a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/queries/get_corpuses.query.graphql +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/queries/get_corpuses.query.graphql @@ -7,5 +7,6 @@ query getCorpuses($projectPath: ID!) { isUploading progress cancelSource + uploadedPackageId } } diff --git a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/resolvers/resolvers.js b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/resolvers/resolvers.js index 57b6ad7ffdb9ffbb60bffa9f1fc5008a8340ff5e..9041f15acb5c6835f53ff28a941c9b5c80733bf1 100644 --- a/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/resolvers/resolvers.js +++ b/ee/app/assets/javascripts/security_configuration/corpus_management/graphql/resolvers/resolvers.js @@ -2,8 +2,12 @@ import produce from 'immer'; import { corpuses } from 'ee_jest/security_configuration/corpus_management/mock_data'; import { publishPackage } from '~/api/packages_api'; import axios from '~/lib/utils/axios_utils'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_PACKAGES_PACKAGE } from '~/graphql_shared/constants'; import getCorpusesQuery from '../queries/get_corpuses.query.graphql'; import updateProgress from '../mutations/update_progress.mutation.graphql'; +import uploadComplete from '../mutations/upload_complete.mutation.graphql'; +import corpusCreate from '../mutations/corpus_create.mutation.graphql'; export default { Query: { @@ -22,12 +26,13 @@ export default { isUploading: false, progress: 0, cancelSource: null, + uploadedPackageId: null, __typename: 'UploadState', }; }, }, Mutation: { - addCorpus: (_, { name, projectPath }, { cache }) => { + addCorpus: (_, { name, projectPath, packageId }, { cache, client }) => { const sourceData = cache.readQuery({ query: getCorpusesQuery, variables: { projectPath }, @@ -54,6 +59,16 @@ export default { }); cache.writeQuery({ query: getCorpusesQuery, data, variables: { projectPath } }); + + client.mutate({ + mutation: corpusCreate, + variables: { + input: { + fullPath: projectPath, + packageId: convertToGraphQLId(TYPE_PACKAGES_PACKAGE, packageId), + }, + }, + }); }, deleteCorpus: (_, { name, projectPath }, { cache }) => { const sourceData = cache.readQuery({ @@ -91,19 +106,43 @@ export default { variables: { projectPath }, }); - const data = produce(sourceData, (draftState) => { + const targetData = produce(sourceData, (draftState) => { const { uploadState } = draftState; uploadState.isUploading = true; uploadState.cancelSource = source; }); - cache.writeQuery({ query: getCorpusesQuery, data, variables: { projectPath } }); + cache.writeQuery({ query: getCorpusesQuery, data: targetData, variables: { projectPath } }); publishPackage( { projectPath, name, version: 0, fileName: name, files }, { status: 'hidden', select: 'package_file' }, { onUploadProgress, cancelToken: source.token }, - ); + ) + .then(({ data }) => { + client.mutate({ + mutation: uploadComplete, + variables: { projectPath, packageId: data.package_id }, + }); + }) + .catch((e) => { + /* TODO: Error handling */ + }); + }, + uploadComplete: (_, { projectPath, packageId }, { cache }) => { + const sourceData = cache.readQuery({ + query: getCorpusesQuery, + variables: { projectPath }, + }); + + const data = produce(sourceData, (draftState) => { + const { uploadState } = draftState; + uploadState.isUploading = false; + uploadState.cancelSource = null; + uploadState.uploadedPackageId = packageId; + }); + + cache.writeQuery({ query: getCorpusesQuery, data, variables: { projectPath } }); }, updateProgress: (_, { projectPath, progress }, { cache }) => { const sourceData = cache.readQuery({ @@ -115,11 +154,6 @@ export default { const { uploadState } = draftState; uploadState.isUploading = true; uploadState.progress = progress; - - if (progress >= 100) { - uploadState.isUploading = false; - uploadState.cancelSource = null; - } }); cache.writeQuery({ query: getCorpusesQuery, data, variables: { projectPath } }); diff --git a/ee/spec/frontend/security_configuration/corpus_management/components/__snapshots__/corpus_upload_form_spec.js.snap b/ee/spec/frontend/security_configuration/corpus_management/components/__snapshots__/corpus_upload_form_spec.js.snap index 97aaf3cfc35b12a620fbb43727d4565b2843065d..00ffb8b7d10b04996b5a78ca4214d7596600d2cd 100644 --- a/ee/spec/frontend/security_configuration/corpus_management/components/__snapshots__/corpus_upload_form_spec.js.snap +++ b/ee/spec/frontend/security_configuration/corpus_management/components/__snapshots__/corpus_upload_form_spec.js.snap @@ -18,6 +18,7 @@ exports[`Corpus upload modal corpus modal uploading state does show the upload p <button class="btn btn-default btn-sm gl-button" + data-testid="cancel-upload" type="button" > <!----> @@ -27,7 +28,9 @@ exports[`Corpus upload modal corpus modal uploading state does show the upload p <span class="gl-button-text" > - Cancel + + Cancel + </span> </button> </div> diff --git a/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_form_spec.js b/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_form_spec.js index 64fbfb33b0f72b081c6f10a024fd5ca2b641b02a..3ed629f8b7705800d9e97501cd78b084a85c2a89 100644 --- a/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_form_spec.js +++ b/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_form_spec.js @@ -1,13 +1,8 @@ -import { createLocalVue, mount } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; +import { mount } from '@vue/test-utils'; import CorpusUploadForm from 'ee/security_configuration/corpus_management/components/corpus_upload_form.vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; const TEST_PROJECT_FULL_PATH = '/namespace/project'; -const localVue = createLocalVue(); -localVue.use(VueApollo); - describe('Corpus upload modal', () => { let wrapper; @@ -15,12 +10,12 @@ describe('Corpus upload modal', () => { const findUploadAttachment = () => wrapper.find('[data-testid="upload-attachment-button"]'); const findUploadCorpus = () => wrapper.find('[data-testid="upload-corpus"]'); const findUploadStatus = () => wrapper.find('[data-testid="upload-status"]'); + const findFileInput = () => wrapper.find({ ref: 'fileUpload' }); + const findCancelButton = () => wrapper.find('[data-testid="cancel-upload"]'); const createComponent = (propsData, options = {}) => { wrapper = mount(CorpusUploadForm, { - localVue, propsData, - apolloProvider: createMockApollo(), provide: { projectFullPath: TEST_PROJECT_FULL_PATH, }, @@ -46,10 +41,6 @@ describe('Corpus upload modal', () => { const props = { states: { - mockedPackages: { - totalSize: 0, - data: [], - }, uploadState: { isUploading: false, progress: 0, @@ -75,6 +66,15 @@ describe('Corpus upload modal', () => { it('does not show the upload progress', () => { expect(findUploadStatus().exists()).toBe(false); }); + + describe('selecting a file', () => { + it('transitions to selected state', async () => { + jest.spyOn(wrapper.vm, 'onFileUploadChange').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findFileInput().trigger('change'); + expect(wrapper.vm.onFileUploadChange).toHaveBeenCalled(); + }); + }); }); describe('file selected state', () => { @@ -87,16 +87,11 @@ describe('Corpus upload modal', () => { attachmentName, corpusName, files: [attachmentName], - uploadTimeout: null, }; }; const props = { states: { - mockedPackages: { - totalSize: 0, - data: [], - }, uploadState: { isUploading: false, progress: 0, @@ -122,6 +117,15 @@ describe('Corpus upload modal', () => { it('does not show the upload progress', () => { expect(findUploadStatus().exists()).toBe(false); }); + + describe('clicking upload file', () => { + it('begins the file upload', async () => { + jest.spyOn(wrapper.vm, 'beginFileUpload').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findUploadCorpus().trigger('click'); + expect(wrapper.vm.beginFileUpload).toHaveBeenCalled(); + }); + }); }); describe('uploading state', () => { @@ -134,16 +138,11 @@ describe('Corpus upload modal', () => { attachmentName, corpusName, files: [attachmentName], - uploadTimeout: null, }; }; const props = { states: { - mockedPackages: { - totalSize: 0, - data: [], - }, uploadState: { isUploading: true, progress: 25, @@ -171,6 +170,13 @@ describe('Corpus upload modal', () => { expect(findUploadStatus().exists()).toBe(true); expect(findUploadStatus().element).toMatchSnapshot(); }); + + describe('clicking cancel button', () => { + it('emits the reset corpus event', () => { + findCancelButton().trigger('click'); + expect(wrapper.emitted().resetCorpus).toBeTruthy(); + }); + }); }); describe('file uploaded state', () => { @@ -183,16 +189,11 @@ describe('Corpus upload modal', () => { attachmentName, corpusName, files: [attachmentName], - uploadTimeout: null, }; }; const props = { states: { - mockedPackages: { - totalSize: 0, - data: [], - }, uploadState: { isUploading: false, progress: 100, diff --git a/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_spec.js b/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_spec.js index 83de230d9696521635495e15538d82b4ed952c12..0710d072f1691fce56ae8d6aa09e10b6a8dd6a2c 100644 --- a/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_spec.js +++ b/ee/spec/frontend/security_configuration/corpus_management/components/corpus_upload_spec.js @@ -1,12 +1,16 @@ -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlModal } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import CorpusUpload from 'ee/security_configuration/corpus_management/components/corpus_upload.vue'; +import CorpusUploadForm from 'ee/security_configuration/corpus_management/components/corpus_upload_form.vue'; const TEST_PROJECT_FULL_PATH = '/namespace/project'; describe('Corpus Upload', () => { let wrapper; + const findModal = () => wrapper.findComponent(GlModal); + const findCorpusUploadForm = () => wrapper.findComponent(CorpusUploadForm); + const createComponentFactory = (mountFn = shallowMount) => (options = {}) => { const defaultProps = { totalSize: 4e8 }; wrapper = mountFn(CorpusUpload, { @@ -37,5 +41,43 @@ describe('Corpus Upload', () => { expect(wrapper.findComponent(GlButton).exists()).toBe(true); expect(wrapper.element).toMatchSnapshot(); }); + + describe('addCorpus mutation', () => { + it('gets called when the add button is clicked from the modal', async () => { + createComponent(); + jest.spyOn(wrapper.vm, 'addCorpus').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findModal().vm.$emit('primary'); + expect(wrapper.vm.addCorpus).toHaveBeenCalled(); + }); + }); + + describe('resetCorpus mutation', () => { + it('gets called when the cancel button is clicked from the modal', async () => { + createComponent(); + jest.spyOn(wrapper.vm, 'resetCorpus').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findModal().vm.$emit('canceled'); + expect(wrapper.vm.resetCorpus).toHaveBeenCalled(); + }); + + it('gets called when the upload form triggers a reset', async () => { + createComponent(); + jest.spyOn(wrapper.vm, 'resetCorpus').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findCorpusUploadForm().vm.$emit('resetCorpus'); + expect(wrapper.vm.resetCorpus).toHaveBeenCalled(); + }); + }); + + describe('uploadCorpus mutation', () => { + it('gets called when the upload file is clicked from the modal', async () => { + createComponent(); + jest.spyOn(wrapper.vm, 'beginFileUpload').mockImplementation(() => {}); + await wrapper.vm.$forceUpdate(); + findCorpusUploadForm().vm.$emit('beginFileUpload'); + expect(wrapper.vm.beginFileUpload).toHaveBeenCalled(); + }); + }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 173056721f00732181c3605261974ec00f6fbc1e..dd7dccbed0eebbde89d5520a77d289e398fc5460 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9563,7 +9563,7 @@ msgstr "" msgid "CorpusManagement|Latest Job:" msgstr "" -msgid "CorpusManagement|New corpus needs to be a upload in *.zip format. Maximum 10GB" +msgid "CorpusManagement|New corpus needs to be a upload in *.zip format. Maximum 5GB" msgstr "" msgid "CorpusManagement|New upload"