From d4ad278c25f96ec2070cafb1a0d75c75519e60fd Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 25 Apr 2024 12:07:10 +0200 Subject: [PATCH 1/5] Frontend/Backend integration for Beyond Identity exclusions Added Frontend/Backend integration for Beyond Identity exclusions --- .../components/exclusions_list.vue | 154 +++++++++++++--- .../integrations/beyond_identity/constants.js | 10 + ...beyond_identity_exclusion.mutation.graphql | 13 ++ ...beyond_identity_exclusion.mutation.graphql | 10 + .../beyond_identity_exclusions.query.graphql | 22 +++ .../project/integrations/beyond_identity.md | 24 +++ locale/gitlab.pot | 9 + .../components/exclusions_list_spec.js | 174 +++++++++++++++--- .../beyond_identity/components/mock_data.js | 55 ++++++ 9 files changed, 425 insertions(+), 46 deletions(-) create mode 100644 app/assets/javascripts/integrations/beyond_identity/constants.js create mode 100644 app/assets/javascripts/integrations/beyond_identity/graphql/mutations/create_beyond_identity_exclusion.mutation.graphql create mode 100644 app/assets/javascripts/integrations/beyond_identity/graphql/mutations/delete_beyond_identity_exclusion.mutation.graphql create mode 100644 app/assets/javascripts/integrations/beyond_identity/graphql/queries/beyond_identity_exclusions.query.graphql diff --git a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue index 84ad156c26f32f..a81f93d76aa337 100644 --- a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue +++ b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue @@ -1,9 +1,23 @@ diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 475a3ef0a2a7d8..293bc9742a6260 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -27602,9 +27602,6 @@ msgstr "" msgid "Integrations|Group-level integration management" msgstr "" -msgid "Integrations|Groups and projects in this list no longer require commits to be signed." -msgstr "" - msgid "Integrations|Includes Standard, plus the entire commit message, commit hash, and issue IDs" msgstr "" @@ -27632,6 +27629,9 @@ msgstr "" msgid "Integrations|Perform common tasks with slash commands." msgstr "" +msgid "Integrations|Projects in this list no longer require commits to be signed." +msgstr "" + msgid "Integrations|Projects using custom settings" msgstr "" diff --git a/spec/frontend/integrations/beyond_identity/components/add_exclusions_drawer_spec.js b/spec/frontend/integrations/beyond_identity/components/add_exclusions_drawer_spec.js index 728962f817541d..826a087dd72f5b 100644 --- a/spec/frontend/integrations/beyond_identity/components/add_exclusions_drawer_spec.js +++ b/spec/frontend/integrations/beyond_identity/components/add_exclusions_drawer_spec.js @@ -76,7 +76,7 @@ describe('AddExclusionsDrawer component', () => { it('renders a list selector for projects', () => { expect(findProjectsListSelector().props()).toMatchObject({ type: 'projects', - autofocus: false, + autofocus: true, }); }); diff --git a/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js b/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js index 99804a3ff3eca8..e136ec14b23218 100644 --- a/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js +++ b/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js @@ -66,9 +66,7 @@ describe('ExclusionsList component', () => { it('renders help text', () => { expect( - findByText( - 'Groups and projects in this list no longer require commits to be signed.', - ).exists(), + findByText('Projects in this list no longer require commits to be signed.').exists(), ).toBe(true); }); -- GitLab From 666191054105c0c54abaa4a8e0a941a58d55d4dc Mon Sep 17 00:00:00 2001 From: Jacques Date: Fri, 3 May 2024 16:16:03 +0200 Subject: [PATCH 4/5] Address maintainer review comments General cleanup of the code --- .../components/exclusions_list.vue | 24 +++++++++---------- .../integrations/beyond_identity/constants.js | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue index 4f0c763df2e713..e02a4d0a593cac 100644 --- a/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue +++ b/app/assets/javascripts/integrations/beyond_identity/components/exclusions_list.vue @@ -15,7 +15,6 @@ import { BEYOND_IDENTITY_INTEGRATION_NAME, PROJECT_TYPE, GROUP_TYPE, - ENTRIES_PER_PAGE, DEFAULT_CURSOR, } from '../constants'; import ExclusionsTabs from './exclusions_tabs.vue'; @@ -52,9 +51,9 @@ export default { variables() { return this.cursor; }, - update(data) { - this.pageInfo = data?.integrationExclusions?.pageInfo || {}; - return data?.integrationExclusions?.nodes || []; + update({ integrationExclusions }) { + this.pageInfo = integrationExclusions?.pageInfo || {}; + return integrationExclusions?.nodes || []; }, error() { this.handleError(this.$options.i18n.errorFetch); @@ -85,15 +84,12 @@ export default { methods: { async handleAddExclusions(exclusions) { const uniqueList = differenceBy(exclusions, this.exclusions, (v) => [v.id, v.type].join()); - const projectIds = uniqueList - .filter((exclusion) => exclusion.type === PROJECT_TYPE) - .map((exclusion) => convertToGraphQLId(TYPENAME_PROJECT, exclusion.id)); const { data } = await this.$apollo.mutate({ mutation: createExclusion, variables: { input: { - projectIds, + projectIds: this.extractProjectIds(uniqueList), integrationName: BEYOND_IDENTITY_INTEGRATION_NAME, }, }, @@ -112,11 +108,16 @@ export default { this.$apollo.queries.exclusions.refetch(); } }, + extractProjectIds(exclusions) { + return exclusions + .filter((exclusion) => exclusion.type === PROJECT_TYPE) + .map((exclusion) => convertToGraphQLId(TYPENAME_PROJECT, exclusion.id)); + }, nextPage(item) { - this.cursor = { first: ENTRIES_PER_PAGE, after: item, last: null, before: null }; + this.cursor = { after: item, last: null, before: null }; }, prevPage(item) { - this.cursor = { first: null, after: null, last: ENTRIES_PER_PAGE, before: item }; + this.cursor = { first: null, after: null, before: item }; }, resetPagination() { this.cursor = DEFAULT_CURSOR; @@ -208,10 +209,9 @@ export default { -
+
Date: Fri, 3 May 2024 16:59:11 +0200 Subject: [PATCH 5/5] Add test for when pagination is not visible Added a test for when the data is still loading --- .../components/exclusions_list_spec.js | 63 ++++++++++++------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js b/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js index e136ec14b23218..060d44ebd8a853 100644 --- a/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js +++ b/spec/frontend/integrations/beyond_identity/components/exclusions_list_spec.js @@ -43,7 +43,7 @@ describe('ExclusionsList component', () => { const createMutationMock = jest.fn().mockResolvedValue(createExclusionMutationResponse); const deleteMutationMock = jest.fn().mockResolvedValue(deleteExclusionMutationResponse); - const createComponent = async ({ + const createComponent = ({ querySuccessHandler = fetchExclusionsSuccessHandler, createSuccessHandler = createMutationMock, deleteSuccessHandler = deleteMutationMock, @@ -54,10 +54,13 @@ describe('ExclusionsList component', () => { [deleteExclusion, deleteSuccessHandler], ]); wrapper = shallowMountExtended(ExclusionsList, { apolloProvider: fakeApollo }); - await waitForPromises(); }; - beforeEach(() => createComponent()); + beforeEach(async () => { + createComponent(); + + await waitForPromises(); + }); describe('default behavior', () => { it('renders tabs', () => { @@ -75,7 +78,9 @@ describe('ExclusionsList component', () => { }); it('renders an Empty state', async () => { - await createComponent({ querySuccessHandler: jest.fn().mockResolvedValue([]) }); + createComponent({ querySuccessHandler: jest.fn().mockResolvedValue([]) }); + + await waitForPromises(); expect(findEmptyState().props('title')).toBe('There are no exclusions'); }); @@ -88,30 +93,42 @@ describe('ExclusionsList component', () => { expect(findPagination().exists()).toBe(false); }); - it('renders pagination', async () => { - await createComponent({ - querySuccessHandler: jest.fn().mockResolvedValue({ - data: { - integrationExclusions: { - nodes: [], - pageInfo: { - __typename: 'PageInfo', - startCursor: '12345', - endCursor: '6789', - hasNextPage: true, - hasPreviousPage: true, + describe('pagination', () => { + beforeEach(() => { + createComponent({ + querySuccessHandler: jest.fn().mockResolvedValue({ + data: { + integrationExclusions: { + nodes: [], + pageInfo: { + __typename: 'PageInfo', + startCursor: '12345', + endCursor: '6789', + hasNextPage: true, + hasPreviousPage: true, + }, }, }, - }, - }), + }), + }); }); - expect(findPagination().exists()).toBe(true); + it('does not render pagination while loading', () => { + expect(findPagination().exists()).toBe(false); + }); + + it('renders pagination when done loading', async () => { + await waitForPromises(); + + expect(findPagination().exists()).toBe(true); + }); }); it('renders an error when fetching exclusions fail', async () => { const error = new Error('Network error!'); - await createComponent({ querySuccessHandler: jest.fn().mockRejectedValue({ error }) }); + createComponent({ querySuccessHandler: jest.fn().mockRejectedValue({ error }) }); + + await waitForPromises(); expect(createAlert).toHaveBeenCalledWith({ message: 'Failed to fetch the exclusions. Try refreshing the page.', @@ -161,7 +178,7 @@ describe('ExclusionsList component', () => { }, }; - await createComponent({ createSuccessHandler: jest.fn().mockResolvedValue(response) }); + createComponent({ createSuccessHandler: jest.fn().mockResolvedValue(response) }); findDrawer().vm.$emit('add', projectExclusionsMock); await waitForPromises(); }); @@ -224,7 +241,9 @@ describe('ExclusionsList component', () => { }, }; - await createComponent({ deleteSuccessHandler: jest.fn().mockResolvedValue(response) }); + createComponent({ deleteSuccessHandler: jest.fn().mockResolvedValue(response) }); + await waitForPromises(); + findListItems().at(1).vm.$emit('remove'); await waitForPromises(); findConfirmRemoveModal().vm.$emit('primary'); -- GitLab