From 177242a031ee8413e777d28252086b07d218fc30 Mon Sep 17 00:00:00 2001
From: Samantha Ming <sming@gitlab.com>
Date: Sun, 24 Apr 2022 15:24:50 -0700
Subject: [PATCH] Display security training config based on license

Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/357327

Changelog: changed
---
 .../security_configuration/components/app.vue | 16 ++++++-
 .../components/constants.js                   |  4 ++
 .../graphql/current_license.query.graphql     |  6 +++
 .../components/app_spec.js                    | 47 +++++++++++++++++--
 .../security_configuration/mock_data.js       |  9 ++++
 5 files changed, 77 insertions(+), 5 deletions(-)
 create mode 100644 app/assets/javascripts/security_configuration/graphql/current_license.query.graphql

diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index ff0e0ed0ab2b5..866148596e381 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -4,9 +4,10 @@ import { __, s__ } from '~/locale';
 import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
 import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
 import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
+import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
 import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
 import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
-import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
+import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, LICENSE_ULTIMATE } from './constants';
 import FeatureCard from './feature_card.vue';
 import TrainingProviderList from './training_provider_list.vue';
 import UpgradeBanner from './upgrade_banner.vue';
@@ -50,6 +51,14 @@ export default {
     TrainingProviderList,
   },
   inject: ['projectFullPath', 'vulnerabilityTrainingDocsPath'],
+  apollo: {
+    currentLicensePlan: {
+      query: currentLicenseQuery,
+      update({ currentLicense }) {
+        return currentLicense?.plan;
+      },
+    },
+  },
   props: {
     augmentedSecurityFeatures: {
       type: Array,
@@ -89,6 +98,7 @@ export default {
     return {
       autoDevopsEnabledAlertDismissedProjects: [],
       errorMessage: '',
+      currentLicensePlan: '',
     };
   },
   computed: {
@@ -109,6 +119,9 @@ export default {
         !this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectFullPath)
       );
     },
+    shouldShowVulnerabilityManagementTab() {
+      return this.currentLicensePlan === LICENSE_ULTIMATE;
+    },
   },
   methods: {
     dismissAutoDevopsEnabledAlert() {
@@ -250,6 +263,7 @@ export default {
         </section-layout>
       </gl-tab>
       <gl-tab
+        v-if="shouldShowVulnerabilityManagementTab"
         data-testid="vulnerability-management-tab"
         :title="$options.i18n.vulnerabilityManagement"
         query-param-value="vulnerability-management"
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index 6db28ef0fadc6..2fa14d4fc53de 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -310,3 +310,7 @@ export const TEMP_PROVIDER_URLS = {
   Kontra: 'https://application.security/',
   [__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
 };
+
+export const LICENSE_ULTIMATE = 'ultimate';
+export const LICENSE_FREE = 'free';
+export const LICENSE_PREMIUM = 'premium';
diff --git a/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
new file mode 100644
index 0000000000000..9ab4f4d434759
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/graphql/current_license.query.graphql
@@ -0,0 +1,6 @@
+query getCurrentLicensePlan {
+  currentLicense {
+    id
+    plan
+  }
+}
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index f18225f124618..a983ec4355715 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -1,6 +1,8 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
 import { GlTab, GlTabs, GlLink } from '@gitlab/ui';
 import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+
 import { useLocalStorageSpy } from 'helpers/local_storage_helper';
 import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
 import stubChildren from 'helpers/stub_children';
@@ -18,15 +20,22 @@ import {
   LICENSE_COMPLIANCE_DESCRIPTION,
   LICENSE_COMPLIANCE_HELP_PATH,
   AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
+  LICENSE_ULTIMATE,
+  LICENSE_PREMIUM,
+  LICENSE_FREE,
 } from '~/security_configuration/components/constants';
 import FeatureCard from '~/security_configuration/components/feature_card.vue';
 import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import currentLicenseQuery from '~/security_configuration/graphql/current_license.query.graphql';
+import waitForPromises from 'helpers/wait_for_promises';
 
 import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
 import {
   REPORT_TYPE_LICENSE_COMPLIANCE,
   REPORT_TYPE_SAST,
 } from '~/vue_shared/security_reports/constants';
+import { getCurrentLicensePlanResponse } from '../mock_data';
 
 const upgradePath = '/upgrade';
 const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
@@ -36,14 +45,24 @@ const projectFullPath = 'namespace/project';
 const vulnerabilityTrainingDocsPath = 'user/application_security/vulnerabilities/index';
 
 useLocalStorageSpy();
+Vue.use(VueApollo);
 
 describe('App component', () => {
   let wrapper;
   let userCalloutDismissSpy;
+  let mockApollo;
 
-  const createComponent = ({ shouldShowCallout = true, ...propsData }) => {
+  const createComponent = ({
+    shouldShowCallout = true,
+    license = LICENSE_ULTIMATE,
+    ...propsData
+  }) => {
     userCalloutDismissSpy = jest.fn();
 
+    mockApollo = createMockApollo([
+      [currentLicenseQuery, jest.fn().mockResolvedValue(getCurrentLicensePlanResponse(license))],
+    ]);
+
     wrapper = extendedWrapper(
       mount(SecurityConfigurationApp, {
         propsData,
@@ -54,6 +73,7 @@ describe('App component', () => {
           projectFullPath,
           vulnerabilityTrainingDocsPath,
         },
+        apolloProvider: mockApollo,
         stubs: {
           ...stubChildren(SecurityConfigurationApp),
           GlLink: false,
@@ -128,14 +148,16 @@ describe('App component', () => {
 
   afterEach(() => {
     wrapper.destroy();
+    mockApollo = null;
   });
 
   describe('basic structure', () => {
-    beforeEach(() => {
+    beforeEach(async () => {
       createComponent({
         augmentedSecurityFeatures: securityFeaturesMock,
         augmentedComplianceFeatures: complianceFeaturesMock,
       });
+      await waitForPromises();
     });
 
     it('renders main-heading with correct text', () => {
@@ -438,11 +460,12 @@ describe('App component', () => {
   });
 
   describe('Vulnerability management', () => {
-    beforeEach(() => {
+    beforeEach(async () => {
       createComponent({
         augmentedSecurityFeatures: securityFeaturesMock,
         augmentedComplianceFeatures: complianceFeaturesMock,
       });
+      await waitForPromises();
     });
 
     it('renders TrainingProviderList component', () => {
@@ -459,5 +482,21 @@ describe('App component', () => {
       expect(trainingLink.text()).toBe('Learn more about vulnerability training');
       expect(trainingLink.attributes('href')).toBe(vulnerabilityTrainingDocsPath);
     });
+
+    it.each`
+      license             | display
+      ${LICENSE_ULTIMATE} | ${true}
+      ${LICENSE_PREMIUM}  | ${false}
+      ${LICENSE_FREE}     | ${false}
+      ${null}             | ${false}
+    `('displays $display for license $license', async ({ license, display }) => {
+      createComponent({
+        license,
+        augmentedSecurityFeatures: securityFeaturesMock,
+        augmentedComplianceFeatures: complianceFeaturesMock,
+      });
+      await waitForPromises();
+      expect(findVulnerabilityManagementTab().exists()).toBe(display);
+    });
   });
 });
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 18a480bf08223..94a36472a1d0b 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -111,3 +111,12 @@ export const tempProviderLogos = {
     svg: `<svg>${[testProviderName[1]]}</svg>`,
   },
 };
+
+export const getCurrentLicensePlanResponse = (plan) => ({
+  data: {
+    currentLicense: {
+      id: 'gid://gitlab/License/1',
+      plan,
+    },
+  },
+});
-- 
GitLab