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