Commit 6699578e authored by Savas Vedova's avatar Savas Vedova

Merge branch 'jnnkl-refactor-survey-banner-to-use-shared-component' into 'master'

Refactored Vulnerability Management feature Survey Banner

See merge request gitlab-org/gitlab!77583
parents cd1e5920 8e809cfc
......@@ -24,7 +24,6 @@ export default {
<template>
<div>
<slot name="loading"></slot>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner v-if="!$slots.loading" class="gl-mt-5" />
<sbom-banner
v-if="!$slots.loading && showSbomSurvey"
......
<script>
import { GlButton, GlBanner, GlSprintf } from '@gitlab/ui';
import {
SURVEY_BANNER_LOCAL_STORAGE_KEY,
SURVEY_BANNER_CURRENT_ID,
SURVEY_LINK,
SURVEY_DAYS_TO_ASK_LATER,
SURVEY_TITLE,
SURVEY_TOAST_MESSAGE,
SURVEY_BUTTON_TEXT,
SURVEY_DESCRIPTION,
} from 'ee/security_dashboard/constants';
import { s__, __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import showToast from '~/vue_shared/plugins/global_toast';
const SURVEY_LINK = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_7UMsVhPbjmwCp1k';
const DAYS_TO_ASK_LATER = 7;
import SurveyBanner from 'ee/vue_shared/survey_banner/survey_banner.vue';
export default {
components: { GlButton, GlBanner, GlSprintf, LocalStorageSync },
inject: ['surveyRequestSvgPath'],
data: () => ({
surveyShowDate: null,
}),
computed: {
shouldShowSurvey() {
const { surveyShowDate } = this;
const isFeatureEnabled = Boolean(gon.features?.vulnerabilityManagementSurvey);
const date = new Date(surveyShowDate);
// Survey is not enabled or user dismissed the survey by clicking the close icon.
if (!isFeatureEnabled || surveyShowDate === SURVEY_BANNER_CURRENT_ID) {
return false;
}
// Date is invalid, we should show the survey.
else if (Number.isNaN(date.getDate())) {
return true;
}
return date <= Date.now();
},
name: 'VulnManagementFeatureSurvey',
components: {
SurveyBanner,
},
methods: {
hideSurvey() {
this.surveyShowDate = SURVEY_BANNER_CURRENT_ID;
},
askLater() {
const date = new Date();
date.setDate(date.getDate() + DAYS_TO_ASK_LATER);
this.surveyShowDate = date.toISOString();
inject: ['surveyRequestSvgPath'],
showToast(this.$options.i18n.toastMessage);
},
},
i18n: {
title: s__('SecurityReports|Vulnerability Management feature survey'),
buttonText: s__('SecurityReports|Take survey'),
askAgainLater: __('Ask again later'),
description: s__(
`SecurityReports|At GitLab, we're all about iteration and feedback. That's why we are reaching out to customers like you to help guide what we work on this year for Vulnerability Management. We have a lot of exciting ideas and ask that you assist us by taking a short survey %{boldStart}no longer than 10 minutes%{boldEnd} to evaluate a few of our potential features.`,
),
toastMessage: s__(
'SecurityReports|Your feedback is important to us! We will ask again in a week.',
),
},
storageKey: SURVEY_BANNER_LOCAL_STORAGE_KEY,
surveyLink: SURVEY_LINK,
daysToAskLater: SURVEY_DAYS_TO_ASK_LATER,
bannerId: SURVEY_BANNER_CURRENT_ID,
title: SURVEY_TITLE,
toastMessage: SURVEY_TOAST_MESSAGE,
buttonText: SURVEY_BUTTON_TEXT,
description: SURVEY_DESCRIPTION,
};
</script>
<template>
<local-storage-sync v-model="surveyShowDate" :storage-key="$options.storageKey">
<gl-banner
v-if="shouldShowSurvey"
:title="$options.i18n.title"
:button-text="$options.i18n.buttonText"
:svg-path="surveyRequestSvgPath"
:button-link="$options.surveyLink"
@close="hideSurvey"
>
<p>
<gl-sprintf :message="$options.i18n.description">
<template #bold="{ content }">
<span class="gl-font-weight-bold">{{ content }}</span>
</template>
</gl-sprintf>
</p>
<template #actions>
<gl-button variant="link" class="gl-ml-5" data-testid="ask-later-button" @click="askLater">
{{ $options.i18n.askAgainLater }}
</gl-button>
</template>
</gl-banner>
</local-storage-sync>
<survey-banner
:svg-path="surveyRequestSvgPath"
:survey-link="$options.surveyLink"
:days-to-ask-later="$options.daysToAskLater"
:title="$options.title"
:button-text="$options.buttonText"
:description="$options.description"
:toast-message="$options.toastMessage"
:storage-key="$options.storageKey"
:banner-id="$options.bannerId"
class="gl-mt-5"
/>
</template>
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { PortalTarget } from 'portal-vue';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......@@ -121,7 +121,6 @@ export default {
<template>
<div>
<template v-if="!isDashboardConfigured">
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner v-if="shouldShowSurvey" class="gl-mt-5" />
<report-not-configured-group v-if="isGroup" />
<report-not-configured-instance v-else-if="isInstance" />
......@@ -135,7 +134,6 @@ export default {
/>
<vulnerability-report-layout>
<template v-if="!isPipeline" #header>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner class="gl-mt-5" />
<header class="gl-mt-6 gl-mb-3 gl-display-flex gl-align-items-center">
<h2 class="gl-flex-grow-1 gl-my-0">
......
......@@ -58,7 +58,6 @@ export default {
<template>
<div>
<!-- TODO: this component needs to be refactored to use the shared survey-banner component, tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/348190 -->
<survey-request-banner class="gl-mt-5" />
<vulnerability-report-header />
......
import { s__ } from '~/locale';
export const COLLAPSE_SECURITY_REPORTS_SUMMARY_LOCAL_STORAGE_KEY =
'hide_pipelines_security_reports_summary_details';
export const SURVEY_BANNER_LOCAL_STORAGE_KEY = 'vulnerability_management_survey_request';
// NOTE: This string needs to parse to an invalid date. Do not put any characters in between the
// word 'survey' and the number, or else it will parse to a valid date.
export const SURVEY_BANNER_CURRENT_ID = 'survey1';
export const SURVEY_LINK = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_7UMsVhPbjmwCp1k';
export const SURVEY_DAYS_TO_ASK_LATER = 7;
export const SURVEY_TITLE = s__('SecurityReports|Vulnerability Management feature survey');
export const SURVEY_BUTTON_TEXT = s__('SecurityReports|Take survey');
export const SURVEY_DESCRIPTION = s__(
`SecurityReports|At GitLab, we're all about iteration and feedback. That's why we are reaching out to customers like you to help guide what we work on this year for Vulnerability Management. We have a lot of exciting ideas and ask that you assist us by taking a short survey %{boldStart}no longer than 10 minutes%{boldEnd} to evaluate a few of our potential features.`,
);
export const SURVEY_TOAST_MESSAGE = s__(
'SecurityReports|Your feedback is important to us! We will ask again in a week.',
);
export const DEFAULT_SCANNER = 'GitLab';
export const SCANNER_ID_PREFIX = 'gid://gitlab/Vulnerabilities::Scanner/';
......@@ -11,8 +11,6 @@ export const placeholderEpic = {
};
export const SBOM_BANNER_LOCAL_STORAGE_KEY = 'sbom_survey_request';
// NOTE: This string needs to parse to an invalid date. Do not put any characters in between the
// word 'survey' and the number, or else it will parse to a valid date.
export const SBOM_BANNER_CURRENT_ID = 'sbom1';
export const SBOM_SURVEY_LINK = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_es038rUv1VFqmXk';
export const SBOM_SURVEY_DAYS_TO_ASK_LATER = 7;
......
import { GlBanner, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SurveyRequestBanner from 'ee/security_dashboard/components/shared/survey_request_banner.vue';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
SURVEY_BANNER_LOCAL_STORAGE_KEY,
SURVEY_BANNER_CURRENT_ID,
SURVEY_LINK,
SURVEY_DAYS_TO_ASK_LATER,
SURVEY_TITLE,
SURVEY_TOAST_MESSAGE,
SURVEY_BUTTON_TEXT,
SURVEY_DESCRIPTION,
} from 'ee/security_dashboard/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import toast from '~/vue_shared/plugins/global_toast';
jest.mock('~/vue_shared/plugins/global_toast');
import SurveyRequestBanner from 'ee/security_dashboard/components/shared/survey_request_banner.vue';
import sharedSurveyBanner from 'ee/vue_shared/survey_banner/survey_banner.vue';
describe('Survey Request Banner component', () => {
describe('SurveyRequestBanner Component', () => {
let wrapper;
const findSharedSurveyBanner = () => wrapper.findComponent(sharedSurveyBanner);
const surveyRequestSvgPath = 'icon.svg';
const SURVEY_REQUEST_SVG_PATH = 'foo.svg';
const findGlBanner = () => wrapper.findComponent(GlBanner);
const findAskLaterButton = () => wrapper.findByTestId('ask-later-button');
const getOffsetDateString = (days) => {
const date = new Date();
date.setDate(date.getDate() + days);
return date.toISOString();
};
const createWrapper = () => {
const createComponent = (sbomSurvey = { sbomSurvey: true }) => {
wrapper = extendedWrapper(
shallowMount(SurveyRequestBanner, {
provide: { surveyRequestSvgPath },
stubs: { GlBanner, GlButton, LocalStorageSync },
mount(SurveyRequestBanner, {
provide: { glFeatures: { ...sbomSurvey }, surveyRequestSvgPath: SURVEY_REQUEST_SVG_PATH },
}),
);
};
beforeEach(() => {
gon.features = {};
});
afterEach(() => {
wrapper.destroy();
localStorage.removeItem(SURVEY_BANNER_LOCAL_STORAGE_KEY);
});
describe('feature flag disabled', () => {
it('should not show banner regardless of localStorage value', () => {
[
getOffsetDateString(1),
getOffsetDateString(-1),
SURVEY_BANNER_CURRENT_ID,
'SOME OTHER ID',
].forEach((localStorageValue) => {
localStorage.setItem(SURVEY_BANNER_LOCAL_STORAGE_KEY, localStorageValue);
createWrapper();
expect(findGlBanner().exists()).toBe(false);
});
});
});
describe('feature flag enabled', () => {
beforeEach(() => {
gon.features.vulnerabilityManagementSurvey = true;
});
it('shows the banner with the correct components and props', () => {
createWrapper();
const { title, buttonText, description } = wrapper.vm.$options.i18n;
expect(findGlBanner().html()).toContain(description);
expect(findAskLaterButton().exists()).toBe(true);
expect(findGlBanner().props()).toMatchObject({
title,
buttonText,
svgPath: surveyRequestSvgPath,
});
});
it.each`
showOrHide | phrase | localStorageValue | isShown
${'hides'} | ${'a future date'} | ${getOffsetDateString(1)} | ${false}
${'shows'} | ${'a past date'} | ${getOffsetDateString(-1)} | ${true}
${'hides'} | ${'the current survey ID'} | ${SURVEY_BANNER_CURRENT_ID} | ${false}
${'shows'} | ${'a different survey ID'} | ${'SOME OTHER ID'} | ${true}
`(
'$showOrHide the banner if the localStorage value is $phrase',
async ({ localStorageValue, isShown }) => {
localStorage.setItem(SURVEY_BANNER_LOCAL_STORAGE_KEY, localStorageValue);
createWrapper();
await wrapper.vm.$nextTick();
expect(findGlBanner().exists()).toBe(isShown);
},
);
beforeEach(() => {
createComponent();
});
describe('closing the banner', () => {
beforeEach(() => {
gon.features.vulnerabilityManagementSurvey = true;
});
it('hides the banner and will set it to reshow later if the "Ask again later" button is clicked', async () => {
createWrapper();
expect(findGlBanner().exists()).toBe(true);
findAskLaterButton().vm.$emit('click');
await wrapper.vm.$nextTick();
const date = new Date(localStorage.getItem(SURVEY_BANNER_LOCAL_STORAGE_KEY));
expect(findGlBanner().exists()).toBe(false);
expect(toast).toHaveBeenCalledTimes(1);
expect(date > new Date()).toBe(true);
});
it('hides the banner and sets it to never show again if the close button is clicked', async () => {
createWrapper();
expect(findGlBanner().exists()).toBe(true);
findGlBanner().vm.$emit('close');
await wrapper.vm.$nextTick();
expect(findGlBanner().exists()).toBe(false);
expect(localStorage.getItem(SURVEY_BANNER_LOCAL_STORAGE_KEY)).toBe(SURVEY_BANNER_CURRENT_ID);
it('renders the SurveyRequestBanner component with the right props', () => {
const surveyBanner = findSharedSurveyBanner();
expect(surveyBanner.exists()).toBe(true);
expect(surveyBanner.props()).toMatchObject({
bannerId: SURVEY_BANNER_CURRENT_ID,
storageKey: SURVEY_BANNER_LOCAL_STORAGE_KEY,
daysToAskLater: SURVEY_DAYS_TO_ASK_LATER,
surveyLink: SURVEY_LINK,
svgPath: SURVEY_REQUEST_SVG_PATH,
title: SURVEY_TITLE,
toastMessage: SURVEY_TOAST_MESSAGE,
});
expect(surveyBanner.props('buttonText')).toContain(SURVEY_BUTTON_TEXT);
expect(surveyBanner.props('description')).toContain(SURVEY_DESCRIPTION);
});
});
......@@ -15,6 +15,7 @@ const TEST_DATASET = {
noVulnerabilitiesSvgPath: '/test/no_vulnerability_state.svg',
securityDashboardHelpPath: '/test/security_dashboard_page',
svgPath: '/test/no_changes_state.svg',
surveyRequestSvgPath: 'surveyRequest.svg',
vulnerabilitiesExportEndpoint: '/test/export-vulnerabilities',
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment