Commit 3431b247 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '332691-security-configuration-upgrade-banner' into 'master'

Add upgrade banner to Security Configuration page

See merge request gitlab-org/gitlab!63448
parents b27cf9d5 85827e9e
<script>
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
compliance: s__('SecurityConfiguration|Compliance'),
......@@ -25,6 +27,8 @@ export default {
GlSprintf,
FeatureCard,
SectionLayout,
UpgradeBanner,
UserCalloutDismisser,
},
props: {
augmentedSecurityFeatures: {
......@@ -52,6 +56,11 @@ export default {
},
},
computed: {
canUpgrade() {
return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
({ available }) => !available,
);
},
canViewCiHistory() {
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
},
......@@ -65,6 +74,12 @@ export default {
<h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1>
</header>
<user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner">
<template #default="{ dismiss, shouldShowCallout }">
<upgrade-banner v-if="shouldShowCallout" @close="dismiss" />
</template>
</user-callout-dismisser>
<gl-tabs content-class="gl-pt-6">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<section-layout :heading="$options.i18n.securityTesting">
......
<script>
import { GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlBanner,
},
inject: ['upgradePath'],
i18n: {
title: s__('SecurityConfiguration|Secure your project with Ultimate'),
bodyStart: s__(
`SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities
that may lead to unauthorized access, data leaks, and denial of service
attacks. Its features include:`,
),
bodyListItems: [
s__('SecurityConfiguration|Vulnerability details and statistics in the merge request.'),
s__('SecurityConfiguration|High-level vulnerability statistics across projects and groups.'),
s__('SecurityConfiguration|Runtime security metrics for application environments.'),
],
bodyEnd: s__(
'SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab.',
),
buttonText: s__('SecurityConfiguration|Upgrade or start a free trial'),
},
};
</script>
<template>
<gl-banner
:title="$options.i18n.title"
:button-text="$options.i18n.buttonText"
:button-link="upgradePath"
v-on="$listeners"
>
<p>{{ $options.i18n.bodyStart }}</p>
<ul>
<li v-for="bodyListItem in $options.i18n.bodyListItems" :key="bodyListItem">
{{ bodyListItem }}
</li>
</ul>
<p>{{ $options.i18n.bodyEnd }}</p>
</gl-banner>
</template>
......@@ -30,7 +30,8 @@ class UserCallout < ApplicationRecord
eoa_bronze_plan_banner: 28, # EE-only
pipeline_needs_banner: 29,
pipeline_needs_hover_tip: 30,
web_ide_ci_environments_guidance: 31
web_ide_ci_environments_guidance: 31,
security_configuration_upgrade_banner: 32
}
validates :user, presence: true
......
......@@ -14824,6 +14824,7 @@ Name of the feature that the callout is for.
| <a id="usercalloutfeaturenameenumpipeline_needs_banner"></a>`PIPELINE_NEEDS_BANNER` | Callout feature name for pipeline_needs_banner. |
| <a id="usercalloutfeaturenameenumpipeline_needs_hover_tip"></a>`PIPELINE_NEEDS_HOVER_TIP` | Callout feature name for pipeline_needs_hover_tip. |
| <a id="usercalloutfeaturenameenumregistration_enabled_callout"></a>`REGISTRATION_ENABLED_CALLOUT` | Callout feature name for registration_enabled_callout. |
| <a id="usercalloutfeaturenameenumsecurity_configuration_upgrade_banner"></a>`SECURITY_CONFIGURATION_UPGRADE_BANNER` | Callout feature name for security_configuration_upgrade_banner. |
| <a id="usercalloutfeaturenameenumservice_templates_deprecated_callout"></a>`SERVICE_TEMPLATES_DEPRECATED_CALLOUT` | Callout feature name for service_templates_deprecated_callout. |
| <a id="usercalloutfeaturenameenumsuggest_pipeline"></a>`SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. |
| <a id="usercalloutfeaturenameenumsuggest_popover_dismissed"></a>`SUGGEST_POPOVER_DISMISSED` | Callout feature name for suggest_popover_dismissed. |
......
......@@ -28844,6 +28844,12 @@ msgstr ""
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
msgid "SecurityConfiguration|GitLab Ultimate checks your application for security vulnerabilities that may lead to unauthorized access, data leaks, and denial of service attacks. Its features include:"
msgstr ""
msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups."
msgstr ""
msgid "SecurityConfiguration|Manage"
msgstr ""
......@@ -28856,12 +28862,18 @@ msgstr ""
msgid "SecurityConfiguration|Not enabled"
msgstr ""
msgid "SecurityConfiguration|Runtime security metrics for application environments."
msgstr ""
msgid "SecurityConfiguration|SAST Analyzers"
msgstr ""
msgid "SecurityConfiguration|SAST Configuration"
msgstr ""
msgid "SecurityConfiguration|Secure your project with Ultimate"
msgstr ""
msgid "SecurityConfiguration|Security Control"
msgstr ""
......@@ -28877,12 +28889,21 @@ msgstr ""
msgid "SecurityConfiguration|The status of the tools only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
msgstr ""
msgid "SecurityConfiguration|Upgrade or start a free trial"
msgstr ""
msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}"
msgstr ""
msgid "SecurityConfiguration|View history"
msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request."
msgstr ""
msgid "SecurityConfiguration|With the information provided, you can immediately begin risk analysis and remediation within GitLab."
msgstr ""
msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}."
msgstr ""
......
/**
* Mock factory for the UserCalloutDismisser component.
* @param {slotProps} The slot props to pass to the default slot content.
* @returns {VueComponent}
*/
export const makeMockUserCalloutDismisser = ({
dismiss = () => {},
shouldShowCallout = true,
} = {}) => ({
render() {
return this.$scopedSlots.default({
dismiss,
shouldShowCallout,
});
},
});
import { GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
SAST_NAME,
......@@ -15,18 +16,33 @@ import FeatureCard from '~/security_configuration/components/feature_card.vue';
import RedesignedSecurityConfigurationApp, {
i18n,
} from '~/security_configuration/components/redesigned_app.vue';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import {
REPORT_TYPE_LICENSE_COMPLIANCE,
REPORT_TYPE_SAST,
} from '~/vue_shared/security_reports/constants';
const upgradePath = '/upgrade';
describe('redesigned App component', () => {
let wrapper;
let userCalloutDismissSpy;
const createComponent = ({ shouldShowCallout = true, ...propsData }) => {
userCalloutDismissSpy = jest.fn();
const createComponent = (propsData) => {
wrapper = extendedWrapper(
mount(RedesignedSecurityConfigurationApp, {
propsData,
provide: {
upgradePath,
},
stubs: {
UserCalloutDismisser: makeMockUserCalloutDismisser({
dismiss: userCalloutDismissSpy,
shouldShowCallout,
}),
},
}),
);
};
......@@ -38,6 +54,7 @@ describe('redesigned App component', () => {
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
const findComplianceViewHistoryLink = () => findByTestId('compliance-view-history-link');
const findSecurityViewHistoryLink = () => findByTestId('security-view-history-link');
const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner);
const securityFeaturesMock = [
{
......@@ -112,6 +129,58 @@ describe('redesigned App component', () => {
});
});
describe('upgrade banner', () => {
const makeAvailable = (available) => (feature) => ({ ...feature, available });
describe('given at least one unavailable feature', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)),
});
});
it('renders the banner', () => {
expect(findUpgradeBanner().exists()).toBe(true);
});
it('calls the dismiss callback when closing the banner', () => {
expect(userCalloutDismissSpy).not.toHaveBeenCalled();
findUpgradeBanner().vm.$emit('close');
expect(userCalloutDismissSpy).toHaveBeenCalledTimes(1);
});
});
describe('given at least one unavailable feature, but banner is already dismissed', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)),
shouldShowCallout: false,
});
});
it('does not render the banner', () => {
expect(findUpgradeBanner().exists()).toBe(false);
});
});
describe('given all features are available', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock.map(makeAvailable(true)),
augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(true)),
});
});
it('does not render the banner', () => {
expect(findUpgradeBanner().exists()).toBe(false);
});
});
});
describe('when given latestPipelinePath props', () => {
beforeEach(() => {
createComponent({
......
import { GlBanner } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
const upgradePath = '/upgrade';
describe('UpgradeBanner component', () => {
let wrapper;
let closeSpy;
const createComponent = (propsData) => {
closeSpy = jest.fn();
wrapper = shallowMountExtended(UpgradeBanner, {
provide: {
upgradePath,
},
propsData,
listeners: {
close: closeSpy,
},
});
};
const findGlBanner = () => wrapper.findComponent(GlBanner);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('passes the expected props to GlBanner', () => {
expect(findGlBanner().props()).toMatchObject({
title: UpgradeBanner.i18n.title,
buttonText: UpgradeBanner.i18n.buttonText,
buttonLink: upgradePath,
});
});
it('renders the list of benefits', () => {
const wrapperText = wrapper.text();
expect(wrapperText).toContain('GitLab Ultimate checks your application');
expect(wrapperText).toContain('statistics in the merge request');
expect(wrapperText).toContain('statistics across projects');
expect(wrapperText).toContain('Runtime security metrics');
expect(wrapperText).toContain('risk analysis and remediation');
});
it(`re-emits GlBanner's close event`, () => {
expect(closeSpy).not.toHaveBeenCalled();
wrapper.findComponent(GlBanner).vm.$emit('close');
expect(closeSpy).toHaveBeenCalledTimes(1);
});
});
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