From aee0a117a889461ce8ced6fcf73207fe017f1d99 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Dec 2021 13:37:47 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-6-stable-ee --- .../components/pagination_bar_spec.js | 92 ------------------ .../import_groups/components/import_table_spec.js | 107 +++++++++++++++++++-- .../components/import_target_cell_spec.js | 11 ++- .../import_groups/graphql/client_factory_spec.js | 59 +++++++++++- .../import_groups/graphql/fixtures.js | 9 +- 5 files changed, 170 insertions(+), 108 deletions(-) delete mode 100644 spec/frontend/import_entities/components/pagination_bar_spec.js (limited to 'spec/frontend/import_entities') diff --git a/spec/frontend/import_entities/components/pagination_bar_spec.js b/spec/frontend/import_entities/components/pagination_bar_spec.js deleted file mode 100644 index 163ce11a8db..00000000000 --- a/spec/frontend/import_entities/components/pagination_bar_spec.js +++ /dev/null @@ -1,92 +0,0 @@ -import { GlPagination, GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import PaginationBar from '~/import_entities/components/pagination_bar.vue'; -import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; - -describe('Pagination bar', () => { - const DEFAULT_PROPS = { - pageInfo: { - total: 50, - page: 1, - perPage: 20, - }, - itemsCount: 17, - }; - let wrapper; - - const createComponent = (propsData) => { - wrapper = mount(PaginationBar, { - propsData: { - ...DEFAULT_PROPS, - ...propsData, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('events', () => { - beforeEach(() => { - createComponent(); - }); - - it('emits set-page event when page is selected', () => { - const NEXT_PAGE = 3; - // PaginationLinks uses prop instead of event for handling page change - // So we go one level deep to test this - wrapper - .findComponent(PaginationLinks) - .findComponent(GlPagination) - .vm.$emit('input', NEXT_PAGE); - expect(wrapper.emitted('set-page')).toEqual([[NEXT_PAGE]]); - }); - - it('emits set-page-size event when page size is selected', () => { - const firstItemInPageSizeDropdown = wrapper.findComponent(GlDropdownItem); - firstItemInPageSizeDropdown.vm.$emit('click'); - - const [emittedPageSizeChange] = wrapper.emitted('set-page-size')[0]; - expect(firstItemInPageSizeDropdown.text()).toMatchInterpolatedText( - `${emittedPageSizeChange} items per page`, - ); - }); - }); - - it('renders current page size', () => { - const CURRENT_PAGE_SIZE = 40; - - createComponent({ - pageInfo: { - ...DEFAULT_PROPS.pageInfo, - perPage: CURRENT_PAGE_SIZE, - }, - }); - - expect(wrapper.find(GlDropdown).find('button').text()).toMatchInterpolatedText( - `${CURRENT_PAGE_SIZE} items per page`, - ); - }); - - it('renders current page information', () => { - createComponent(); - - expect(wrapper.find('[data-testid="information"]').text()).toMatchInterpolatedText( - 'Showing 1 - 17 of 50', - ); - }); - - it('renders current page information when total count is over 1000', () => { - createComponent({ - pageInfo: { - ...DEFAULT_PROPS.pageInfo, - total: 1200, - }, - }); - - expect(wrapper.find('[data-testid="information"]').text()).toMatchInterpolatedText( - 'Showing 1 - 17 of 1000+', - ); - }); -}); diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js index 6e3df21e30a..b17ff2e0f52 100644 --- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js @@ -1,4 +1,4 @@ -import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; +import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -33,13 +33,23 @@ describe('import table', () => { generateFakeEntry({ id: 2, status: STATUSES.FINISHED }), ]; const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 }; + const FAKE_VERSION_VALIDATION = { + features: { + projectMigration: { available: false, minVersion: '14.8.0' }, + sourceInstanceVersion: '14.6.0', + }, + }; const findImportSelectedButton = () => wrapper.findAll('button').wrappers.find((w) => w.text() === 'Import selected'); const findImportButtons = () => wrapper.findAll('button').wrappers.filter((w) => w.text() === 'Import'); - const findPaginationDropdown = () => wrapper.find('[aria-label="Page size"]'); + const findPaginationDropdown = () => wrapper.find('[data-testid="page-size"]'); const findPaginationDropdownText = () => findPaginationDropdown().find('button').text(); + const findSelectionCount = () => wrapper.find('[data-test-id="selection-count"]'); + + const triggerSelectAllCheckbox = () => + wrapper.find('thead input[type=checkbox]').trigger('click'); const selectRow = (idx) => wrapper.findAll('tbody td input[type=checkbox]').at(idx).trigger('click'); @@ -104,6 +114,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: [], pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -117,6 +128,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: FAKE_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -129,6 +141,7 @@ describe('import table', () => { bulkImportSourceGroups: jest.fn().mockResolvedValue({ nodes: [], pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -138,7 +151,11 @@ describe('import table', () => { it('invokes importGroups mutation when row button is clicked', async () => { createComponent({ - bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }), + bulkImportSourceGroups: () => ({ + nodes: [FAKE_GROUP], + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }), }); jest.spyOn(apolloProvider.defaultClient, 'mutate'); @@ -162,7 +179,11 @@ describe('import table', () => { it('displays error if importing group fails', async () => { createComponent({ - bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }), + bulkImportSourceGroups: () => ({ + nodes: [FAKE_GROUP], + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }), importGroups: () => { throw new Error(); }, @@ -182,9 +203,11 @@ describe('import table', () => { }); describe('pagination', () => { - const bulkImportSourceGroupsQueryMock = jest - .fn() - .mockResolvedValue({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }); + const bulkImportSourceGroupsQueryMock = jest.fn().mockResolvedValue({ + nodes: [FAKE_GROUP], + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }); beforeEach(() => { createComponent({ @@ -205,7 +228,13 @@ describe('import table', () => { const otherOption = findPaginationDropdown().findAll('li p').at(1); expect(otherOption.text()).toMatchInterpolatedText('50 items per page'); + bulkImportSourceGroupsQueryMock.mockResolvedValue({ + nodes: [FAKE_GROUP], + pageInfo: { ...FAKE_PAGE_INFO, perPage: 50 }, + versionValidation: FAKE_VERSION_VALIDATION, + }); await otherOption.trigger('click'); + await waitForPromises(); expect(findPaginationDropdownText()).toMatchInterpolatedText('50 items per page'); @@ -234,6 +263,7 @@ describe('import table', () => { perPage: 20, totalPages: 2, }, + versionValidation: FAKE_VERSION_VALIDATION, }); wrapper.find(PaginationLinks).props().change(REQUESTED_PAGE); await waitForPromises(); @@ -243,9 +273,11 @@ describe('import table', () => { }); describe('filters', () => { - const bulkImportSourceGroupsQueryMock = jest - .fn() - .mockResolvedValue({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }); + const bulkImportSourceGroupsQueryMock = jest.fn().mockResolvedValue({ + nodes: [FAKE_GROUP], + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }); beforeEach(() => { createComponent({ @@ -313,11 +345,28 @@ describe('import table', () => { }); describe('bulk operations', () => { + it('import all button correctly selects/deselects all groups', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ + nodes: FAKE_GROUPS, + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }), + }); + await waitForPromises(); + expect(findSelectionCount().text()).toMatchInterpolatedText('0 selected'); + await triggerSelectAllCheckbox(); + expect(findSelectionCount().text()).toMatchInterpolatedText('2 selected'); + await triggerSelectAllCheckbox(); + expect(findSelectionCount().text()).toMatchInterpolatedText('0 selected'); + }); + it('import selected button is disabled when no groups selected', async () => { createComponent({ bulkImportSourceGroups: () => ({ nodes: FAKE_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -330,6 +379,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: FAKE_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -346,6 +396,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: NEW_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -368,6 +419,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: NEW_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); await waitForPromises(); @@ -391,6 +443,7 @@ describe('import table', () => { bulkImportSourceGroups: () => ({ nodes: NEW_GROUPS, pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, }), }); jest.spyOn(apolloProvider.defaultClient, 'mutate'); @@ -421,4 +474,38 @@ describe('import table', () => { }); }); }); + + describe('unavailable features warning', () => { + it('renders alert when there are unavailable features', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ + nodes: FAKE_GROUPS, + pageInfo: FAKE_PAGE_INFO, + versionValidation: FAKE_VERSION_VALIDATION, + }), + }); + await waitForPromises(); + + expect(wrapper.find(GlAlert).exists()).toBe(true); + expect(wrapper.find(GlAlert).text()).toContain('projects (require v14.8.0)'); + }); + + it('does not renders alert when there are no unavailable features', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ + nodes: FAKE_GROUPS, + pageInfo: FAKE_PAGE_INFO, + versionValidation: { + features: { + projectMigration: { available: true, minVersion: '14.8.0' }, + sourceInstanceVersion: '14.6.0', + }, + }, + }), + }); + await waitForPromises(); + + expect(wrapper.find(GlAlert).exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js index 3c2367e22f5..d3f86672f33 100644 --- a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js @@ -123,13 +123,22 @@ describe('import target cell', () => { }); describe('when entity is available for import', () => { + const FAKE_PROGRESS_MESSAGE = 'progress message'; beforeEach(() => { - group = generateFakeTableEntry({ id: 1, flags: { isAvailableForImport: true } }); + group = generateFakeTableEntry({ + id: 1, + flags: { isAvailableForImport: true }, + progress: { message: FAKE_PROGRESS_MESSAGE }, + }); createComponent({ group }); }); it('renders namespace dropdown as enabled', () => { expect(findNamespaceDropdown().attributes('disabled')).toBe(undefined); }); + + it('renders progress message as error if it exists', () => { + expect(wrapper.find('[role=alert]').text()).toBe(FAKE_PROGRESS_MESSAGE); + }); }); }); diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js index f3447494578..c6ddce17fe4 100644 --- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js +++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js @@ -163,12 +163,34 @@ describe('Bulk import resolvers', () => { }); describe('mutations', () => { - beforeEach(() => { - axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 }); - }); + beforeEach(() => {}); describe('importGroup', () => { - it('sets import status to CREATED when request completes', async () => { + it('sets import status to CREATED for successful groups when request completes', async () => { + axiosMockAdapter + .onPost(FAKE_ENDPOINTS.createBulkImport) + .reply(httpStatus.OK, [{ success: true, id: 1 }]); + + await client.mutate({ + mutation: importGroupsMutation, + variables: { + importRequests: [ + { + sourceGroupId: statusEndpointFixture.importable_data[0].id, + newName: 'test', + targetNamespace: 'root', + }, + ], + }, + }); + + await axios.waitForAll(); + expect(results[0].progress.status).toBe(STATUSES.CREATED); + }); + + it('sets import status to CREATED for successful groups when request completes with legacy response', async () => { + axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 }); + await client.mutate({ mutation: importGroupsMutation, variables: { @@ -185,9 +207,37 @@ describe('Bulk import resolvers', () => { await axios.waitForAll(); expect(results[0].progress.status).toBe(STATUSES.CREATED); }); + + it('sets import status to FAILED and sets progress message for failed groups when request completes', async () => { + const FAKE_ERROR_MESSAGE = 'foo'; + axiosMockAdapter + .onPost(FAKE_ENDPOINTS.createBulkImport) + .reply(httpStatus.OK, [{ success: false, id: 1, message: FAKE_ERROR_MESSAGE }]); + + await client.mutate({ + mutation: importGroupsMutation, + variables: { + importRequests: [ + { + sourceGroupId: statusEndpointFixture.importable_data[0].id, + newName: 'test', + targetNamespace: 'root', + }, + ], + }, + }); + + await axios.waitForAll(); + expect(results[0].progress.status).toBe(STATUSES.FAILED); + expect(results[0].progress.message).toBe(FAKE_ERROR_MESSAGE); + }); }); it('updateImportStatus updates status', async () => { + axiosMockAdapter + .onPost(FAKE_ENDPOINTS.createBulkImport) + .reply(httpStatus.OK, [{ success: true, id: 1 }]); + const NEW_STATUS = 'dummy'; await client.mutate({ mutation: importGroupsMutation, @@ -216,6 +266,7 @@ describe('Bulk import resolvers', () => { expect(statusInResponse).toStrictEqual({ __typename: clientTypenames.BulkImportProgress, id, + message: null, status: NEW_STATUS, }); }); diff --git a/spec/frontend/import_entities/import_groups/graphql/fixtures.js b/spec/frontend/import_entities/import_groups/graphql/fixtures.js index 5f6f9987a8f..ed4e343f331 100644 --- a/spec/frontend/import_entities/import_groups/graphql/fixtures.js +++ b/spec/frontend/import_entities/import_groups/graphql/fixtures.js @@ -1,7 +1,7 @@ import { STATUSES } from '~/import_entities/constants'; import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory'; -export const generateFakeEntry = ({ id, status, ...rest }) => ({ +export const generateFakeEntry = ({ id, status, message, ...rest }) => ({ __typename: clientTypenames.BulkImportSourceGroup, webUrl: `https://fake.host/${id}`, fullPath: `fake_group_${id}`, @@ -18,6 +18,7 @@ export const generateFakeEntry = ({ id, status, ...rest }) => ({ : { id, status, + message: message || '', }, ...rest, }); @@ -49,6 +50,12 @@ export const statusEndpointFixture = { web_url: 'https://gitlab.com/groups/gitlab-examples', }, ], + version_validation: { + features: { + project_migration: { available: false, min_version: '14.8.0' }, + source_instance_version: '14.6.0', + }, + }, }; export const availableNamespacesFixture = Object.freeze([ -- cgit v1.2.1