Skip to content
Snippets Groups Projects
Commit 3c70b090 authored by Jannik Lehmann's avatar Jannik Lehmann Committed by Kushal Pandya
Browse files

Add Sbom Survey Banner

parent 06d96858
No related branches found
No related tags found
No related merge requests found
Showing
with 295 additions and 18 deletions
Loading
Loading
@@ -4,4 +4,5 @@
#js-dependencies-app{ data: { endpoint: project_dependencies_path(@project, format: :json),
documentation_path: help_page_path('user/application_security/dependency_list/index'),
support_documentation_path: help_page_path('user/application_security/dependency_scanning/index', anchor: 'supported-languages-and-package-managers'),
empty_state_svg_path: image_path('illustrations/Dependency-list-empty-state.svg') } }
empty_state_svg_path: image_path('illustrations/Dependency-list-empty-state.svg'),
sbom_survey_svg_path: image_path('illustrations/monitoring/tracing.svg')} }
Loading
Loading
@@ -10,6 +10,8 @@ import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import { TEST_HOST } from 'helpers/test_constants';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
 
describe('DependenciesApp component', () => {
let store;
Loading
Loading
@@ -19,6 +21,7 @@ describe('DependenciesApp component', () => {
const basicAppProps = {
endpoint: '/foo',
emptyStateSvgPath: '/bar.svg',
sbomSurveySvgPath: '/foo.svg',
documentationPath: TEST_HOST,
supportDocumentationPath: `${TEST_HOST}/dependency_scanning#supported-languages`,
};
Loading
Loading
@@ -29,12 +32,20 @@ describe('DependenciesApp component', () => {
 
const stubs = Object.keys(DependenciesApp.components).filter((name) => name !== 'GlSprintf');
 
wrapper = mount(DependenciesApp, {
store,
propsData: { ...props },
stubs,
...options,
});
window.gon = {
features: {
sbomSurvey: true,
},
};
wrapper = extendedWrapper(
mount(DependenciesApp, {
store,
propsData: { ...props },
stubs,
...options,
}),
);
};
 
const setStateJobNotRun = () => {
Loading
Loading
@@ -96,6 +107,7 @@ describe('DependenciesApp component', () => {
const findJobFailedAlert = () => wrapper.find(DependencyListJobFailedAlert);
const findIncompleteListAlert = () => wrapper.find(DependencyListIncompleteAlert);
const findDependenciesTables = () => wrapper.findAll(PaginatedDependenciesTable);
const findSbomBanner = () => wrapper.findComponent(SbomBanner);
 
const findHeader = () => wrapper.find('section > header');
const findHeaderHelpLink = () => findHeader().find(GlLink);
Loading
Loading
@@ -139,6 +151,7 @@ describe('DependenciesApp component', () => {
};
 
afterEach(() => {
window.gon = {};
wrapper.destroy();
});
 
Loading
Loading
@@ -205,6 +218,12 @@ describe('DependenciesApp component', () => {
expectComponentWithProps(DependenciesActions, { namespace: allNamespace });
});
 
it('renders the SbomBannercomponent with the right props', () => {
const sbomBanner = findSbomBanner();
expect(sbomBanner.exists()).toBe(true);
expect(sbomBanner.props().sbomSurveySvgPath).toEqual(wrapper.props().sbomSurveySvgPath);
});
describe('given the user has public permissions', () => {
beforeEach(() => {
store.state[allNamespace].reportInfo.generatedAt = '';
Loading
Loading
Loading
Loading
@@ -19,6 +19,7 @@ import {
import setWindowLocation from 'helpers/set_window_location_helper';
import { stubTransition } from 'helpers/stub_transition';
import { TEST_HOST } from 'helpers/test_constants';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
 
Vue.use(Vuex);
 
Loading
Loading
@@ -29,6 +30,7 @@ const managedLicenses = [approvedLicense, blacklistedLicense];
const licenses = [{}, {}];
const emptyStateSvgPath = '/';
const documentationPath = '/';
const sbomSurveySvgPath = '/';
 
const noop = () => {};
 
Loading
Loading
@@ -74,6 +76,7 @@ const createComponent = ({ state, props, options }) => {
propsData: {
emptyStateSvgPath,
documentationPath,
sbomSurveySvgPath,
readLicensePoliciesEndpoint,
...props,
},
Loading
Loading
@@ -84,9 +87,19 @@ const createComponent = ({ state, props, options }) => {
};
 
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findSbomBanner = () => wrapper.findComponent(SbomBanner);
 
describe('Project Licenses', () => {
beforeEach(() => {
window.gon = {
features: {
sbomSurvey: true,
},
};
});
afterEach(() => {
window.gon = {};
wrapper.destroy();
wrapper = null;
});
Loading
Loading
@@ -174,6 +187,12 @@ describe('Project Licenses', () => {
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
 
it('renders the SbomBannercomponent with the right props', () => {
const sbomBanner = findSbomBanner();
expect(sbomBanner.exists()).toBe(true);
expect(sbomBanner.props().sbomSurveySvgPath).toEqual(wrapper.props().sbomSurveySvgPath);
});
it('renders a "Detected in project" tab and a "Policies" tab', () => {
expect(wrapper.find(GlTabs).exists()).toBe(true);
expect(wrapper.find(GlTab).exists()).toBe(true);
Loading
Loading
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
SBOM_BANNER_LOCAL_STORAGE_KEY,
SBOM_BANNER_CURRENT_ID,
SBOM_SURVEY_LINK,
SBOM_SURVEY_DAYS_TO_ASK_LATER,
SBOM_SURVEY_TITLE,
SBOM_SURVEY_BUTTON_TEXT,
SBOM_SURVEY_DESCRIPTION,
SBOM_SURVEY_TOAST_MESSAGE,
} from 'ee/vue_shared/constants';
import sbomBanner from 'ee/sbom_banner/components/app.vue';
import sharedSurveyBanner from 'ee/vue_shared/survey_banner/survey_banner.vue';
describe('Sbom Banner Component', () => {
let wrapper;
const findSharedSurveyBanner = () => wrapper.findComponent(sharedSurveyBanner);
const createComponent = (sbomSurvey = { sbomSurvey: true }) => {
wrapper = extendedWrapper(
mount(sbomBanner, {
propsData: {
sbomSurveySvgPath: 'foo.svg',
},
provide: { glFeatures: { ...sbomSurvey } },
}),
);
};
afterEach(() => {
wrapper.destroy();
});
describe('given a true sbom_survey flag', () => {
beforeEach(() => {
createComponent();
});
it('renders the SBOM Banner component with the right props', () => {
const surveyBanner = findSharedSurveyBanner();
expect(surveyBanner.exists()).toBe(true);
expect(surveyBanner.props()).toMatchObject({
bannerId: SBOM_BANNER_CURRENT_ID,
storageKey: SBOM_BANNER_LOCAL_STORAGE_KEY,
daysToAskLater: SBOM_SURVEY_DAYS_TO_ASK_LATER,
surveyLink: SBOM_SURVEY_LINK,
svgPath: wrapper.props().sbomSurveySvgPath,
title: SBOM_SURVEY_TITLE,
toastMessage: SBOM_SURVEY_TOAST_MESSAGE,
});
expect(surveyBanner.props('buttonText')).toContain(SBOM_SURVEY_BUTTON_TEXT);
expect(surveyBanner.props('description')).toContain(SBOM_SURVEY_DESCRIPTION);
});
});
describe('given a false sbom_survey flag', () => {
beforeEach(() => {
createComponent({ sbomSurvey: false });
});
it('does not render the SBOM Banner component', () => {
const surveyBanner = findSharedSurveyBanner();
expect(surveyBanner.exists()).toBe(false);
});
});
});
Loading
Loading
@@ -23,6 +23,8 @@ describe('Group Security Dashboard component', () => {
let wrapper;
 
const groupFullPath = `${TEST_HOST}/group/5`;
// To be consumed by SecurityDashboardLayout
const sbomSurveySvgPath = '/';
 
const findSecurityChartsLayoutComponent = () => wrapper.find(SecurityDashboardLayout);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
Loading
Loading
@@ -41,7 +43,7 @@ describe('Group Security Dashboard component', () => {
},
},
},
provide: { groupFullPath },
provide: { groupFullPath, sbomSurveySvgPath },
stubs: {
SecurityDashboardLayout,
},
Loading
Loading
Loading
Loading
@@ -44,6 +44,10 @@ describe('Instance Security Dashboard component', () => {
stubs: {
SecurityDashboardLayout,
},
provide: {
// to be consumed by SecurityDashboardLayout
sbomSurveySvgPath: '/',
},
});
};
 
Loading
Loading
@@ -62,6 +66,7 @@ describe('Instance Security Dashboard component', () => {
const vulnerabilitySeverities = findVulnerabilitySeverities();
 
expect(securityChartsLayout.exists()).toBe(true);
expect(securityChartsLayout.props().showSbomSurvey).toBe(false);
expect(reportNotConfigured.exists()).toBe(false);
expect(loadingIcon.exists()).toBe(true);
expect(vulnerabilitiesOverTimeChart.exists()).toBe(false);
Loading
Loading
@@ -78,6 +83,7 @@ describe('Instance Security Dashboard component', () => {
const vulnerabilitySeverities = findVulnerabilitySeverities();
 
expect(securityChartsLayout.exists()).toBe(true);
expect(securityChartsLayout.props().showSbomSurvey).toBe(false);
expect(reportNotConfigured.exists()).toBe(true);
expect(loadingIcon.exists()).toBe(false);
expect(vulnerabilitiesOverTimeChart.exists()).toBe(false);
Loading
Loading
@@ -96,6 +102,7 @@ describe('Instance Security Dashboard component', () => {
const vulnerabilitySeverities = findVulnerabilitySeverities();
 
expect(securityChartsLayout.exists()).toBe(true);
expect(securityChartsLayout.props().showSbomSurvey).toBe(false);
expect(reportNotConfigured.exists()).toBe(false);
expect(loadingIcon.exists()).toBe(false);
expect(vulnerabilitiesOverTimeChart.props()).toEqual({ query: vulnerabilityHistoryQuery });
Loading
Loading
Loading
Loading
@@ -46,6 +46,10 @@ describe('Project Security Dashboard component', () => {
helpPath,
...propsData,
},
provide: {
// To be consumed by SecurityDashboardLayout
sbomSurveySvgPath: '/',
},
stubs: {
SecurityDashboardLayout,
},
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import SecurityDashboardLayout from 'ee/security_dashboard/components/shared/security_dashboard_layout.vue';
import SurveyRequestBanner from 'ee/security_dashboard/components/shared/survey_request_banner.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SbomBanner from 'ee/sbom_banner/components/app.vue';
 
describe('Security Dashboard Layout component', () => {
let wrapper;
Loading
Loading
@@ -14,21 +15,33 @@ describe('Security Dashboard Layout component', () => {
const findDummyComponent = () => wrapper.findComponent(DummyComponent);
const findTitle = () => wrapper.findByTestId('title');
const findSurveyBanner = () => wrapper.findComponent(SurveyRequestBanner);
const findSbomBanner = () => wrapper.findComponent(SbomBanner);
 
const createWrapper = (slots) => {
wrapper = extendedWrapper(shallowMount(SecurityDashboardLayout, { slots }));
const createWrapper = (slots, props = { showSbomSurvey: true }) => {
wrapper = extendedWrapper(
shallowMount(SecurityDashboardLayout, {
provide: {
sbomSurveySvgPath: '/',
},
propsData: {
...props,
},
slots,
}),
);
};
 
afterEach(() => {
wrapper.destroy();
beforeEach(() => {
window.gon = {
features: {
sbomSurvey: true,
},
};
});
 
it('should render the default slot and survey banner', () => {
createWrapper({ default: DummyComponent });
expect(findDummyComponent().exists()).toBe(true);
expect(findTitle().exists()).toBe(true);
expect(findSurveyBanner().exists()).toBe(true);
afterEach(() => {
wrapper.destroy();
window.gon = {};
});
 
it('should render the empty-state slot and survey banner', () => {
Loading
Loading
@@ -46,4 +59,25 @@ describe('Security Dashboard Layout component', () => {
expect(findTitle().exists()).toBe(false);
expect(findSurveyBanner().exists()).toBe(false);
});
describe('given a false showSbowmSurvey prop', () => {
beforeEach(() => {
createWrapper({}, { showSbomSurvey: false });
});
it('does not render the SBOM Banner component', () => {
const sbomBanner = findSbomBanner();
expect(sbomBanner.exists()).toBe(false);
});
});
describe('given a true showSbowmSurvey prop', () => {
beforeEach(() => {
createWrapper({}, { showSbomSurvey: true });
});
it('does not render the SBOM Banner component', () => {
const sbomBanner = findSbomBanner();
expect(sbomBanner.exists()).toBe(true);
expect(sbomBanner.props().sbomSurveySvgPath).toBe(wrapper.vm.sbomSurveySvgPath);
});
});
});
import { GlBanner, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SharedSurveyBanner from 'ee/vue_shared/survey_banner/survey_banner.vue';
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';
const TEST_LOCAL_STORAGE_KEY = 'testLocalStorageKey';
const TEST_BANNER_ID = 'testBannerId';
jest.mock('~/vue_shared/plugins/global_toast');
describe('Shared Survey Banner component', () => {
let wrapper;
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 = (props = {}) => {
wrapper = extendedWrapper(
shallowMount(SharedSurveyBanner, {
propsData: {
surveyLink: 'foo.bar',
daysToAskLater: 7,
title: 'testTitle',
buttonText: 'buttonText',
description: 'description',
toastMessage: 'toastMessage',
storageKey: TEST_LOCAL_STORAGE_KEY,
bannerId: TEST_BANNER_ID,
svgPath: '/foo.svg',
...props,
},
stubs: { GlBanner, GlButton, LocalStorageSync },
}),
);
};
beforeEach(() => {
gon.features = {};
});
afterEach(() => {
wrapper.destroy();
localStorage.removeItem(TEST_LOCAL_STORAGE_KEY);
});
beforeEach(() => {
createWrapper();
});
it('shows the banner with the correct components and props', () => {
const { title, buttonText, description, svgPath } = wrapper.props();
expect(findGlBanner().html()).toContain(description);
expect(findAskLaterButton().exists()).toBe(true);
expect(findGlBanner().props()).toMatchObject({
title,
buttonText,
svgPath,
});
});
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'} | ${TEST_BANNER_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(TEST_LOCAL_STORAGE_KEY, localStorageValue);
createWrapper();
await wrapper.vm.$nextTick();
expect(findGlBanner().exists()).toBe(isShown);
},
);
describe('closing the banner', () => {
beforeEach(() => {
createWrapper();
});
it('hides the banner and will set it to reshow later if the "Ask again later" button is clicked', async () => {
expect(findGlBanner().exists()).toBe(true);
findAskLaterButton().vm.$emit('click');
await wrapper.vm.$nextTick();
const date = new Date(localStorage.getItem(TEST_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 () => {
expect(findGlBanner().exists()).toBe(true);
findGlBanner().vm.$emit('close');
await wrapper.vm.$nextTick();
expect(findGlBanner().exists()).toBe(false);
expect(localStorage.getItem(TEST_LOCAL_STORAGE_KEY)).toBe(TEST_BANNER_ID);
});
});
});
Loading
Loading
@@ -10,6 +10,7 @@ const TEST_DATASET = {
svgPath: '/test/no_changes_state.svg',
dashboardDocumentation: '/test/dashboard_page',
emptyStateSvgPath: '/test/empty_state.svg',
sbomSurveySvgPath: '/',
};
 
describe('Security Dashboard', () => {
Loading
Loading
Loading
Loading
@@ -72,6 +72,7 @@
group_full_path: group.full_path,
no_vulnerabilities_svg_path: helper.image_path('illustrations/issues.svg'),
empty_state_svg_path: helper.image_path('illustrations/security-dashboard-empty-state.svg'),
sbom_survey_svg_path: helper.image_path('illustrations/monitoring/tracing.svg'),
operational_empty_state_svg_path: helper.image_path('illustrations/security-dashboard_empty.svg'),
operational_help_path: help_page_path('user/application_security/policies/index'),
survey_request_svg_path: helper.image_path('illustrations/security-dashboard_empty.svg'),
Loading
Loading
Loading
Loading
@@ -193,6 +193,7 @@
operational_empty_state_svg_path: kind_of(String),
operational_help_path: kind_of(String),
survey_request_svg_path: start_with('/assets/illustrations/security-dashboard_empty'),
sbom_survey_svg_path: start_with('/assets/illustrations/monitoring/tracing'),
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
project_full_path: project.full_path,
no_vulnerabilities_svg_path: start_with('/assets/illustrations/issues-'),
Loading
Loading
@@ -217,6 +218,7 @@
operational_empty_state_svg_path: kind_of(String),
operational_help_path: kind_of(String),
survey_request_svg_path: start_with('/assets/illustrations/security-dashboard_empty'),
sbom_survey_svg_path: start_with('/assets/illustrations/monitoring/tracing'),
dashboard_documentation: '/help/user/application_security/security_dashboard/index',
false_positive_doc_url: help_page_path('user/application_security/vulnerabilities/index'),
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
Loading
Loading
Loading
Loading
@@ -31549,6 +31549,9 @@ msgstr ""
msgid "SecurityReports|Take survey"
msgstr ""
 
msgid "SecurityReports|The Composition Analysis group is planning significant updates to how we make available the list of software and container dependency information in your projects. Therefore, we ask that you assist us by taking a short -no longer than 5 minute- survey to help align our direction with your needs."
msgstr ""
msgid "SecurityReports|The Vulnerability Report shows the results of the latest successful pipeline on your project's default branch, as well as vulnerabilities from your latest container scan. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
 
Loading
Loading
@@ -31624,6 +31627,9 @@ msgstr ""
msgid "SecurityReports|You must sign in as an authorized user to see this report"
msgstr ""
 
msgid "SecurityReports|Your feedback is important to us! We will ask again in 7 days."
msgstr ""
msgid "SecurityReports|Your feedback is important to us! We will ask again in a week."
msgstr ""
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment