Commit 2f952919 authored by Nathan Friend's avatar Nathan Friend

Merge branch '210358-add-vulnerability-banner-for-day-1' into 'master'

Add an introduction banner

See merge request gitlab-org/gitlab!29311
parents 1754e149 b7787921
<script> <script>
import { GlBanner } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils';
import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue'; import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue'; import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
...@@ -6,6 +9,8 @@ import VulnerabilitiesCountList from 'ee/security_dashboard/components/vulnerabi ...@@ -6,6 +9,8 @@ import VulnerabilitiesCountList from 'ee/security_dashboard/components/vulnerabi
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
export const BANNER_COOKIE_KEY = 'hide_vulnerabilities_introduction_banner';
export default { export default {
components: { components: {
ProjectVulnerabilitiesApp, ProjectVulnerabilitiesApp,
...@@ -14,6 +19,7 @@ export default { ...@@ -14,6 +19,7 @@ export default {
VulnerabilitiesCountList, VulnerabilitiesCountList,
CsvExportButton, CsvExportButton,
Filters, Filters,
GlBanner,
}, },
props: { props: {
emptyStateSvgPath: { emptyStateSvgPath: {
...@@ -47,12 +53,17 @@ export default { ...@@ -47,12 +53,17 @@ export default {
data() { data() {
return { return {
filters: {}, filters: {},
isBannerVisible: !parseBoolean(Cookies.get(BANNER_COOKIE_KEY)),
}; };
}, },
methods: { methods: {
handleFilterChange(filters) { handleFilterChange(filters) {
this.filters = filters; this.filters = filters;
}, },
handleBannerClose() {
Cookies.set(BANNER_COOKIE_KEY, 'true', { expires: 365 * 10 });
this.isBannerVisible = false;
},
}, },
}; };
</script> </script>
...@@ -62,6 +73,23 @@ export default { ...@@ -62,6 +73,23 @@ export default {
<template v-if="hasPipelineData"> <template v-if="hasPipelineData">
<security-dashboard-layout> <security-dashboard-layout>
<template #header> <template #header>
<gl-banner
v-if="isBannerVisible"
class="mt-4"
variant="introduction"
:title="s__('SecurityDashboard|Introducing standalone vulnerabilities')"
:button-text="s__('SecurityDashboard|Learn More')"
:button-link="dashboardDocumentation"
@close="handleBannerClose"
>
<div class="mb-2">
{{
s__(
'SecurityDashboard|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans.',
)
}}
</div>
</gl-banner>
<div class="mt-4 d-flex"> <div class="mt-4 d-flex">
<h4 class="flex-grow mt-0 mb-0">{{ __('Vulnerabilities') }}</h4> <h4 class="flex-grow mt-0 mb-0">{{ __('Vulnerabilities') }}</h4>
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import FirstClassProjectSecurityDashboard from 'ee/security_dashboard/components/first_class_project_security_dashboard.vue'; import { GlBanner } from '@gitlab/ui';
import Cookies from 'js-cookie';
import FirstClassProjectSecurityDashboard, {
BANNER_COOKIE_KEY,
} from 'ee/security_dashboard/components/first_class_project_security_dashboard.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue'; import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue';
...@@ -22,6 +26,7 @@ describe('First class Project Security Dashboard component', () => { ...@@ -22,6 +26,7 @@ describe('First class Project Security Dashboard component', () => {
const findVulnerabilities = () => wrapper.find(ProjectVulnerabilitiesApp); const findVulnerabilities = () => wrapper.find(ProjectVulnerabilitiesApp);
const findUnconfiguredState = () => wrapper.find(ReportsNotConfigured); const findUnconfiguredState = () => wrapper.find(ReportsNotConfigured);
const findCsvExportButton = () => wrapper.find(CsvExportButton); const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findIntroductionBanner = () => wrapper.find(GlBanner);
const createComponent = options => { const createComponent = options => {
wrapper = shallowMount(FirstClassProjectSecurityDashboard, { wrapper = shallowMount(FirstClassProjectSecurityDashboard, {
...@@ -29,13 +34,14 @@ describe('First class Project Security Dashboard component', () => { ...@@ -29,13 +34,14 @@ describe('First class Project Security Dashboard component', () => {
...props, ...props,
...options.props, ...options.props,
}, },
stubs: { SecurityDashboardLayout }, stubs: { SecurityDashboardLayout, GlBanner },
...options, ...options,
}); });
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
Cookies.remove(BANNER_COOKIE_KEY);
}); });
describe('on render when pipeline has data', () => { describe('on render when pipeline has data', () => {
...@@ -70,6 +76,41 @@ describe('First class Project Security Dashboard component', () => { ...@@ -70,6 +76,41 @@ describe('First class Project Security Dashboard component', () => {
}); });
}); });
describe('when user visits for the first time', () => {
beforeEach(() => {
createComponent({ props: { hasPipelineData: true } });
});
it('displays a banner which the title highlights the new functionality', () => {
expect(findIntroductionBanner().text()).toContain('Introducing standalone vulnerabilities');
});
it('displays a banner which the content describes the new functionality', () => {
expect(findIntroductionBanner().text()).toContain(
'Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans.',
);
});
it('links the banner to the proper documentation page', () => {
expect(findIntroductionBanner().props('buttonLink')).toBe(props.dashboardDocumentation);
});
it('hides the banner when the user clicks on the dismiss button', () => {
findIntroductionBanner()
.find('button.close')
.trigger('click');
return wrapper.vm.$nextTick(() => {
expect(findIntroductionBanner().exists()).toBe(false);
// Also the newly created component should not display the banner
// because we're setting the cookie.
createComponent({ props: { hasPipelineData: true } });
expect(findIntroductionBanner().exists()).toBe(false);
});
});
});
describe('with filter data', () => { describe('with filter data', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
......
...@@ -18102,12 +18102,21 @@ msgstr "" ...@@ -18102,12 +18102,21 @@ msgstr ""
msgid "SecurityDashboard|Add projects" msgid "SecurityDashboard|Add projects"
msgstr "" msgstr ""
msgid "SecurityDashboard|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
msgstr ""
msgid "SecurityDashboard|Edit dashboard" msgid "SecurityDashboard|Edit dashboard"
msgstr "" msgstr ""
msgid "SecurityDashboard|Hide dismissed" msgid "SecurityDashboard|Hide dismissed"
msgstr "" msgstr ""
msgid "SecurityDashboard|Introducing standalone vulnerabilities"
msgstr ""
msgid "SecurityDashboard|Learn More"
msgstr ""
msgid "SecurityDashboard|Monitor vulnerabilities in your code" msgid "SecurityDashboard|Monitor vulnerabilities in your code"
msgstr "" msgstr ""
......
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