From 1df32b1ffac098c12f400a15a44ac79ae691a8d9 Mon Sep 17 00:00:00 2001 From: Nataliia Radina <nradina@gitlab.com> Date: Wed, 27 Nov 2024 14:42:11 +0000 Subject: [PATCH] Fetch compliance requirements controls data Remove mocks, switch to using API --- .../components/requirements_section.vue | 17 +-- ...pliance_requirement_controls.query.graphql | 24 ++++- .../compliance_dashboard/graphql/resolvers.js | 80 +------------- .../components/requirements_section_spec.js | 100 ++++++++++++++++-- .../compliance_dashboard/mock_data.js | 51 +++------ locale/gitlab.pot | 3 + 6 files changed, 146 insertions(+), 129 deletions(-) diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section.vue b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section.vue index 1efdee21c5d83..4fd1919018795 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section.vue @@ -1,11 +1,11 @@ <script> import { GlLoadingIcon, GlTable, GlButton } from '@gitlab/ui'; -import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { s__, __ } from '~/locale'; +import { createAlert } from '~/alert'; import { emptyRequirement } from '../constants'; -import complianceRequirementControls from '../../../../graphql/compliance_requirement_controls.query.graphql'; +import complianceRequirementControlsQuery from '../../../../graphql/compliance_requirement_controls.query.graphql'; import EditSection from './edit_section.vue'; import RequirementModal from './requirement_modal.vue'; @@ -30,11 +30,16 @@ export default { }, apollo: { complianceRequirementControls: { - query: complianceRequirementControls, - update: (data) => data?.mockControls?.controls || [], + query: complianceRequirementControlsQuery, + update: (data) => data.complianceRequirementControls.controlExpressions || [], error(e) { - Sentry.captureException(e); - this.hasQueryError = true; + createAlert({ + message: s__( + 'ComplianceFrameworks|Error fetching compliance requirements controls data. Please refresh the page.', + ), + captureException: true, + error: e, + }); }, }, }, diff --git a/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_requirement_controls.query.graphql b/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_requirement_controls.query.graphql index 54f5068e8d3c8..6ff3355c7eb01 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_requirement_controls.query.graphql +++ b/ee/app/assets/javascripts/compliance_dashboard/graphql/compliance_requirement_controls.query.graphql @@ -1,5 +1,25 @@ query complianceRequirementControls { - mockControls @client { - controls + complianceRequirementControls { + controlExpressions { + id + name + expression { + ... on BooleanExpression { + field + operator + value + } + ... on IntegerExpression { + field + operator + value + } + ... on StringExpression { + field + operator + value + } + } + } } } diff --git a/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js b/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js index 7814f809c14d6..3dacb09763e9c 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js +++ b/ee/app/assets/javascripts/compliance_dashboard/graphql/resolvers.js @@ -1,74 +1,4 @@ -/* eslint-disable @gitlab/require-i18n-strings */ export const resolvers = { - Query: { - mockControls: () => ({ - controls: [ - { - id: 'scanner_sast_running', - name: 'SAST Running', - expression: { - field: 'scanner_sast_running', - operator: '=', - value: true, - }, - }, - { - id: 'minimum_approvals_required_2', - name: 'At least two approvals', - expression: { - field: 'minimum_approvals_required', - operator: '=', - value: 2, - }, - }, - { - id: 'minimum_approvals_required_3', - name: 'At least three approvals', - value: { - field: 'minimum_approvals_required', - operator: '=', - value: 3, - }, - }, - { - id: 'merge_request_prevent_author_approval', - name: 'Author approved merge request', - expression: { - field: 'merge_request_prevent_author_approval', - operator: '=', - value: true, - }, - }, - { - id: 'merge_request_prevent_committers_approval', - name: 'Committers approved merge request', - expression: { - field: 'merge_request_prevent_committers_approval', - operator: '=', - value: true, - }, - }, - { - id: 'project_visibility_not_internal', - name: 'Internal visibility is forbidden', - expression: { - field: 'project_visibility', - operator: '=', - value: 'internal', - }, - }, - { - id: 'default_branch_protected', - name: 'Default branch protected', - expression: { - field: 'default_branch_protected', - operator: '=', - value: true, - }, - }, - ], - }), - }, ComplianceRequirement: { controlExpression: () => `{ "operator": "AND", @@ -77,14 +7,14 @@ export const resolvers = { "id": "minimum_approvals_required_2", "field": "minimum_approvals_required", "operator": "=", - "value": "2" + "value": 2 }, { - "id": "minimum_approvals_required_3", - "field": "minimum_approvals_required", + "id": "scanner_sast_running", + "field": "scanner_sast_running", "operator": "=", - "value": "3" - } + "value": true + } ] }`, }, diff --git a/ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section_spec.js b/ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section_spec.js index 37cd7dccd9808..a37c4c7945698 100644 --- a/ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section_spec.js @@ -1,28 +1,56 @@ import { GlTable } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import RequirementsSection from 'ee/compliance_dashboard/components/frameworks_report/edit_framework/components/requirements_section.vue'; +import RequirementModal from 'ee/compliance_dashboard/components/frameworks_report/edit_framework/components/requirement_modal.vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; -import { mockRequirements } from 'ee_jest/compliance_dashboard/mock_data'; +import { mockRequirements, mockRequirementControls } from 'ee_jest/compliance_dashboard/mock_data'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import controlsQuery from 'ee/compliance_dashboard/graphql/compliance_requirement_controls.query.graphql'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createAlert } from '~/alert'; + +jest.mock('~/alert'); + +Vue.use(VueApollo); describe('Requirements section', () => { let wrapper; + const error = new Error('GraphQL error'); + + let controlsQueryHandler; + const findTable = () => wrapper.findComponent(GlTable); const findTableRow = (idx) => findTable().findAll('tbody > tr').at(idx); const findTableRowData = (idx) => findTableRow(idx).findAll('td'); const findNewRequirementButton = () => wrapper.findByTestId('add-requirement-button'); + const findRequirementModal = () => wrapper.findComponent(RequirementModal); + + const createComponent = async (controlsQueryHandlerMockResponse = controlsQueryHandler) => { + const mockApollo = createMockApollo([[controlsQuery, controlsQueryHandlerMockResponse]]); - const createComponent = () => { wrapper = mountExtended(RequirementsSection, { propsData: { requirements: mockRequirements, isNewFramework: true, }, + apolloProvider: mockApollo, }); + + await waitForPromises(); }; describe('Rendering', () => { - beforeEach(() => { - createComponent(); + controlsQueryHandler = jest.fn().mockResolvedValue({ + data: { + complianceRequirementControls: { + controlExpressions: mockRequirementControls, + }, + }, + }); + beforeEach(async () => { + await createComponent(); }); it('Has title', () => { @@ -42,20 +70,70 @@ describe('Requirements section', () => { expect(items).toHaveLength(mockRequirements.length); }); - it.each(Object.keys(mockRequirements))('has the correct data for row %s', (idx) => { - const frameworkRequirements = findTableRowData(idx).wrappers.map((d) => d.text()); + it.each` + idx | name | description | controls + ${0} | ${'SOC2'} | ${'Controls for SOC2'} | ${['Minimum approvals required']} + ${1} | ${'GitLab'} | ${'Controls used by GitLab'} | ${['Minimum approvals required', 'SAST Running']} + `('has the correct data for row $idx', ({ idx, name, description, controls }) => { + const frameworkRequirements = findTableRowData(idx); + + expect(frameworkRequirements.at(0).text()).toBe(name); + expect(frameworkRequirements.at(1).text()).toBe(description); + expect( + frameworkRequirements + .at(2) + .findAll('li') + .wrappers.map((w) => w.text()), + ).toEqual(controls); + }); + + describe('Create requirement button', () => { + beforeEach(() => { + createComponent(); + }); - expect(frameworkRequirements[0]).toMatch(mockRequirements[idx].name); - expect(frameworkRequirements[1]).toMatch(mockRequirements[idx].description); + it('renders create requirement', () => { + expect(findNewRequirementButton().text()).toBe('New requirement'); + }); }); }); - describe('Create requirement button', () => { + + describe('Fetching data', () => { beforeEach(() => { + controlsQueryHandler = jest.fn().mockResolvedValue({ + data: { + complianceRequirementControls: { + controlExpressions: mockRequirementControls, + }, + }, + }); createComponent(); }); - it('renders create requirement', () => { - expect(findNewRequirementButton().text()).toBe('New requirement'); + it('calls the complianceRequirementControls query', () => { + expect(controlsQueryHandler).toHaveBeenCalled(); + }); + + it('updates data', async () => { + await findNewRequirementButton().trigger('click'); + expect(findRequirementModal().props('requirementControls')).toMatchObject( + mockRequirementControls, + ); + }); + }); + + describe('Error handling', () => { + beforeEach(async () => { + controlsQueryHandler = jest.fn().mockRejectedValue(error); + await createComponent(controlsQueryHandler); + }); + + it('calls createAlert with the correct message on query error', () => { + expect(createAlert).toHaveBeenCalledWith({ + message: 'Error fetching compliance requirements controls data. Please refresh the page.', + captureException: true, + error, + }); }); }); }); diff --git a/ee/spec/frontend/compliance_dashboard/mock_data.js b/ee/spec/frontend/compliance_dashboard/mock_data.js index 41f69e03ce577..7e84c4170293e 100644 --- a/ee/spec/frontend/compliance_dashboard/mock_data.js +++ b/ee/spec/frontend/compliance_dashboard/mock_data.js @@ -441,7 +441,7 @@ export const mockRequirements = [ description: 'Controls for SOC2', controlExpression: JSON.stringify({ operator: 'AND', - conditions: [{ id: 'minimum_approvals_required_2' }], + conditions: [{ id: 'minimum_approvals_required' }], }), }, { @@ -450,7 +450,7 @@ export const mockRequirements = [ description: 'Controls used by GitLab', controlExpression: JSON.stringify({ operator: 'AND', - conditions: [{ id: 'minimum_approvals_required_2' }, { id: 'minimum_approvals_required_3' }], + conditions: [{ id: 'minimum_approvals_required' }, { id: 'scanner_sast_running' }], }), }, ]; @@ -463,52 +463,31 @@ export const mockRequirementControls = [ field: 'scanner_sast_running', operator: '=', value: true, + __typename: 'BooleanExpression', }, + __typename: 'ControlExpression', }, { - id: 'minimum_approvals_required_2', - name: 'At least two approvals', + id: 'minimum_approvals_required', + name: 'Minimum approvals required', expression: { field: 'minimum_approvals_required', operator: '=', - value: 2, + value: 1, + __typename: 'IntegerExpression', }, + __typename: 'ControlExpression', }, { - id: 'minimum_approvals_required_3', - name: 'At least three approvals', + id: 'minimum_approvals_required_2', + name: 'At least two approvals', expression: { field: 'minimum_approvals_required', operator: '=', - value: 3, - }, - }, - { - id: 'merge_request_prevent_author_approval', - name: 'Author approved merge request', - expression: { - field: 'merge_request_prevent_author_approval', - operator: '=', - value: true, - }, - }, - { - id: 'merge_request_prevent_committers_approval', - name: 'Committers approved merge request', - expression: { - field: 'merge_request_prevent_committers_approval', - operator: '=', - value: true, - }, - }, - { - id: 'project_visibility_not_internal', - name: 'Internal visibility is forbidden', - expression: { - field: 'project_visibility', - operator: '=', - value: 'internal', + value: 2, + __typename: 'IntegerExpression', }, + __typename: 'ControlExpression', }, { id: 'default_branch_protected', @@ -517,6 +496,8 @@ export const mockRequirementControls = [ field: 'default_branch_protected', operator: '=', value: true, + __typename: 'BooleanExpression', }, + __typename: 'ControlExpression', }, ]; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 52a2be8a0c0ee..0e8ea13a6eb47 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14060,6 +14060,9 @@ msgstr "" msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page or try a different framework" msgstr "" +msgid "ComplianceFrameworks|Error fetching compliance requirements controls data. Please refresh the page." +msgstr "" + msgid "ComplianceFrameworks|Go to the %{linkStart}compliance center / project page%{linkEnd} to apply projects for this framework." msgstr "" -- GitLab