diff --git a/ee/app/assets/javascripts/security_dashboard/components/project/security_training_promo_banner.vue b/ee/app/assets/javascripts/security_dashboard/components/project/security_training_promo_banner.vue index 1127f623e09b152b8b52030e46d41cfb25eb1bee..9e22deb899efd3a94b821f9e4c0e64ece887481b 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/project/security_training_promo_banner.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/project/security_training_promo_banner.vue @@ -1,50 +1,32 @@ <script> import { GlBanner } from '@gitlab/ui'; import { __ } from '~/locale'; -import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; -import Tracking from '~/tracking'; -import { - TRACK_PROMOTION_BANNER_CTA_CLICK_ACTION, - TRACK_PROMOTION_BANNER_CTA_CLICK_LABEL, -} from '~/security_configuration/constants'; +import SecurityTrainingPromo from 'ee/vue_shared/security_reports/components/security_training_promo.vue'; export default { components: { GlBanner, - UserCalloutDismisser, + SecurityTrainingPromo, }, - mixins: [Tracking.mixin()], inject: ['securityConfigurationPath', 'projectFullPath'], i18n: { title: __('Reduce risk and triage fewer vulnerabilities with security training'), - buttonText: __('Enable security training'), content: __( 'Enable security training to help your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability.', ), }, - computed: { - buttonLink() { - return `${this.securityConfigurationPath}?tab=vulnerability-management`; - }, - }, - methods: { - trackCTAClick() { - this.track(TRACK_PROMOTION_BANNER_CTA_CLICK_ACTION, { - label: TRACK_PROMOTION_BANNER_CTA_CLICK_LABEL, - property: this.projectFullPath, - }); - }, - }, }; </script> <template> - <user-callout-dismisser feature-name="security_training_feature_promotion"> - <template #default="{ dismiss, shouldShowCallout }"> + <security-training-promo + :security-configuration-path="securityConfigurationPath" + :project-full-path="projectFullPath" + > + <template #default="{ buttonLink, buttonText, dismiss, trackCTAClick }"> <gl-banner - v-if="shouldShowCallout" :title="$options.i18n.title" - :button-text="$options.i18n.buttonText" + :button-text="buttonText" :button-link="buttonLink" variant="introduction" @primary="trackCTAClick" @@ -53,5 +35,5 @@ export default { <p>{{ $options.i18n.content }}</p> </gl-banner> </template> - </user-callout-dismisser> + </security-training-promo> </template> diff --git a/ee/spec/frontend/security_dashboard/components/project/security_training_promo_banner_spec.js b/ee/spec/frontend/security_dashboard/components/project/security_training_promo_banner_spec.js index 93af3cf21f0d2b9bb9c9278e9125a7c9cdbd4f47..a5cd7d33ac0ca65901f5a232b857dfdad51575ce 100644 --- a/ee/spec/frontend/security_dashboard/components/project/security_training_promo_banner_spec.js +++ b/ee/spec/frontend/security_dashboard/components/project/security_training_promo_banner_spec.js @@ -1,103 +1,87 @@ import { shallowMount } from '@vue/test-utils'; import { GlBanner } from '@gitlab/ui'; -import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import SecurityTrainingPromoBanner from 'ee/security_dashboard/components/project/security_training_promo_banner.vue'; -import { - TRACK_PROMOTION_BANNER_CTA_CLICK_ACTION, - TRACK_PROMOTION_BANNER_CTA_CLICK_LABEL, -} from '~/security_configuration/constants'; +import { stubComponent } from 'helpers/stub_component'; +import SecurityTrainingPromo from 'ee/vue_shared/security_reports/components/security_training_promo.vue'; + +const dismissSpy = jest.fn(); +const trackCTAClickSpy = jest.fn(); const SECURITY_CONFIGURATION_PATH = 'foo/bar'; -const VULNERABILITY_MANAGEMENT_TAB_NAME = 'vulnerability-management'; const PROJECT_FULL_PATH = 'namespace/project'; +const MOCK_SLOT_PROPS = { + buttonText: 'Enable security training', + buttonLink: 'some/link', + trackCTAClick: trackCTAClickSpy, + dismiss: dismissSpy, +}; describe('Security training promo banner component', () => { let wrapper; - const userCalloutDismissSpy = jest.fn(); - const createWrapper = ({ shouldShowCallout = true } = {}) => - shallowMount(SecurityTrainingPromoBanner, { + const createWrapper = () => { + wrapper = shallowMount(SecurityTrainingPromoBanner, { provide: { projectFullPath: PROJECT_FULL_PATH, securityConfigurationPath: SECURITY_CONFIGURATION_PATH, }, stubs: { - UserCalloutDismisser: makeMockUserCalloutDismisser({ - dismiss: userCalloutDismissSpy, - shouldShowCallout, + SecurityTrainingPromo: stubComponent(SecurityTrainingPromo, { + render() { + return this.$scopedSlots.default(MOCK_SLOT_PROPS); + }, }), }, }); + }; afterEach(() => { wrapper.destroy(); }); + const findSecurityTrainingPromo = () => wrapper.findComponent(SecurityTrainingPromo); const findBanner = () => wrapper.findComponent(GlBanner); - describe('banner', () => { - beforeEach(() => { - wrapper = createWrapper(); + beforeEach(() => { + createWrapper(); + }); + + describe('SecurityTrainingPromo', () => { + it('renders the component with the correct props', () => { + expect(findSecurityTrainingPromo().props()).toMatchObject({ + securityConfigurationPath: SECURITY_CONFIGURATION_PATH, + projectFullPath: PROJECT_FULL_PATH, + }); }); + }); - it('should be an introduction that announces the security training feature', () => { - const { title, buttonText, content } = SecurityTrainingPromoBanner.i18n; + describe('GlBanner', () => { + it('renders the component with the correct props', () => { + const { buttonText, buttonLink } = MOCK_SLOT_PROPS; + const { title } = SecurityTrainingPromoBanner.i18n; expect(findBanner().props()).toMatchObject({ variant: 'introduction', - title, buttonText, + buttonLink, + title, }); - expect(findBanner().text()).toBe(content); }); - it(`should link to the security configuration's vulnerability management tab`, () => { - expect(findBanner().props('buttonLink')).toBe( - `${SECURITY_CONFIGURATION_PATH}?tab=${VULNERABILITY_MANAGEMENT_TAB_NAME}`, - ); - }); - }); - - describe('dismissal', () => { - it('should dismiss the callout when the banner is closed', () => { - wrapper = createWrapper(); - - expect(userCalloutDismissSpy).not.toHaveBeenCalled(); + it('should trigger the dismiss method when the banner is closed', () => { + expect(dismissSpy).not.toHaveBeenCalled(); findBanner().vm.$emit('close'); - expect(userCalloutDismissSpy).toHaveBeenCalled(); - }); - - it('should not show the banner once it has been dismissed', () => { - wrapper = createWrapper({ shouldShowCallout: false }); - - expect(findBanner().exists()).toBe(false); + expect(dismissSpy).toHaveBeenCalled(); }); - }); - describe('metrics', () => { - let trackingSpy; - - beforeEach(async () => { - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - wrapper = createWrapper(); - }); - - afterEach(() => { - unmockTracking(); - }); - - it('tracks clicks on the CTA button', () => { - expect(trackingSpy).not.toHaveBeenCalled(); + it('should trigger the trackCTAClick method when the banner is clicked', () => { + expect(trackCTAClickSpy).not.toHaveBeenCalled(); findBanner().vm.$emit('primary'); - expect(trackingSpy).toHaveBeenCalledWith(undefined, TRACK_PROMOTION_BANNER_CTA_CLICK_ACTION, { - label: TRACK_PROMOTION_BANNER_CTA_CLICK_LABEL, - property: PROJECT_FULL_PATH, - }); + expect(trackCTAClickSpy).toHaveBeenCalled(); }); }); }); diff --git a/ee/spec/frontend_integration/security_dashboard/vulnerability_report_init_integration_spec.js b/ee/spec/frontend_integration/security_dashboard/vulnerability_report_init_integration_spec.js index de5d2cc48b23365135b7cae369ac7aaa34ad88ac..d01f4ffaa22226bb0c8df6e85f9cd69d2afdb194 100644 --- a/ee/spec/frontend_integration/security_dashboard/vulnerability_report_init_integration_spec.js +++ b/ee/spec/frontend_integration/security_dashboard/vulnerability_report_init_integration_spec.js @@ -52,6 +52,7 @@ describe('Vulnerability Report', () => { pipelineSecurityBuildsFailedCount: 1, pipelineSecurityBuildsFailedPath: '/test/faild_pipeline_02', projectFullPath: '/test/project', + securityConfigurationPath: '/test/configuration', }, type: DASHBOARD_TYPES.PROJECT, });