Commit cbe138c1 authored by Jannik Lehmann's avatar Jannik Lehmann Committed by Olena Horal-Koretska

AutoDevOps enabled alert on security Config Page

parent ae1cc3a5
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlAlert,
GlLink,
GlSprintf,
},
inject: ['autoDevopsHelpPagePath'],
i18n: {
body: s__(
'AutoDevopsAlert|Security testing tools enabled with %{linkStart}Auto DevOps%{linkEnd}',
),
},
};
</script>
<template>
<gl-alert variant="success" @dismiss="$emit('dismiss')">
<gl-sprintf :message="$options.i18n.body">
<template #link="{ content }">
<gl-link :href="autoDevopsHelpPagePath">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</template>
...@@ -305,3 +305,6 @@ export const featureToMutationMap = { ...@@ -305,3 +305,6 @@ export const featureToMutationMap = {
}), }),
}, },
}; };
export const AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY =
'security_configuration_auto_devops_enabled_dismissed_projects';
<script> <script>
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui'; import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue'; import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue'; import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue'; import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue'; import UpgradeBanner from './upgrade_banner.vue';
...@@ -25,16 +28,19 @@ export const i18n = { ...@@ -25,16 +28,19 @@ export const i18n = {
export default { export default {
i18n, i18n,
components: { components: {
GlTab, AutoDevOpsAlert,
AutoDevOpsEnabledAlert,
FeatureCard,
GlLink, GlLink,
GlTabs,
GlSprintf, GlSprintf,
FeatureCard, GlTab,
GlTabs,
LocalStorageSync,
SectionLayout, SectionLayout,
UpgradeBanner, UpgradeBanner,
AutoDevOpsAlert,
UserCalloutDismisser, UserCalloutDismisser,
}, },
inject: ['projectPath'],
props: { props: {
augmentedSecurityFeatures: { augmentedSecurityFeatures: {
type: Array, type: Array,
...@@ -70,6 +76,11 @@ export default { ...@@ -70,6 +76,11 @@ export default {
default: '', default: '',
}, },
}, },
data() {
return {
autoDevopsEnabledAlertDismissedProjects: [],
};
},
computed: { computed: {
canUpgrade() { canUpgrade() {
return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some( return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
...@@ -82,12 +93,32 @@ export default { ...@@ -82,12 +93,32 @@ export default {
shouldShowDevopsAlert() { shouldShowDevopsAlert() {
return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops; return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops;
}, },
shouldShowAutoDevopsEnabledAlert() {
return (
this.autoDevopsEnabled &&
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectPath)
);
},
},
methods: {
dismissAutoDevopsEnabledAlert() {
const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects);
dismissedProjects.add(this.projectPath);
this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects);
},
}, },
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
}; };
</script> </script>
<template> <template>
<article> <article>
<local-storage-sync
v-model="autoDevopsEnabledAlertDismissedProjects"
:storage-key="$options.autoDevopsEnabledAlertStorageKey"
as-json
/>
<user-callout-dismisser <user-callout-dismisser
v-if="shouldShowDevopsAlert" v-if="shouldShowDevopsAlert"
feature-name="security_configuration_devops_alert" feature-name="security_configuration_devops_alert"
...@@ -105,8 +136,14 @@ export default { ...@@ -105,8 +136,14 @@ export default {
</template> </template>
</user-callout-dismisser> </user-callout-dismisser>
<gl-tabs content-class="gl-pt-6"> <gl-tabs content-class="gl-pt-0">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting"> <gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<auto-dev-ops-enabled-alert
v-if="shouldShowAutoDevopsEnabledAlert"
class="gl-mt-3"
@dismiss="dismissAutoDevopsEnabledAlert"
/>
<section-layout :heading="$options.i18n.securityTesting"> <section-layout :heading="$options.i18n.securityTesting">
<template #description> <template #description>
<p> <p>
......
...@@ -11,7 +11,7 @@ export default { ...@@ -11,7 +11,7 @@ export default {
</script> </script>
<template> <template>
<div class="row gl-line-height-20"> <div class="row gl-line-height-20 gl-pt-6">
<div class="col-lg-4"> <div class="col-lg-4">
<h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2> <h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2>
<slot name="description"></slot> <slot name="description"></slot>
......
...@@ -4769,6 +4769,9 @@ msgstr "" ...@@ -4769,6 +4769,9 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found." msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found."
msgstr "" msgstr ""
msgid "AutoDevopsAlert|Security testing tools enabled with %{linkStart}Auto DevOps%{linkEnd}"
msgstr ""
msgid "AutoRemediation| 1 Merge Request" msgid "AutoRemediation| 1 Merge Request"
msgstr "" msgstr ""
......
import { GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
describe('AutoDevopsEnabledAlert component', () => {
let wrapper;
const createComponent = () => {
wrapper = mount(AutoDevopsEnabledAlert, {
provide: {
autoDevopsHelpPagePath,
},
});
};
const findAlert = () => wrapper.findComponent(GlAlert);
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('contains correct body text', () => {
expect(wrapper.text()).toMatchInterpolatedText(AutoDevopsEnabledAlert.i18n.body);
});
it('renders the link correctly', () => {
const link = wrapper.find('a[href]');
expect(link.attributes('href')).toBe(autoDevopsHelpPagePath);
expect(link.text()).toBe('Auto DevOps');
});
it('bubbles up dismiss events from the GlAlert', () => {
expect(wrapper.emitted('dismiss')).toBe(undefined);
findAlert().vm.$emit('dismiss');
expect(wrapper.emitted('dismiss')).toEqual([[]]);
});
});
import { GlTab } from '@gitlab/ui'; import { GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue'; import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
import { import {
SAST_NAME, SAST_NAME,
SAST_SHORT_NAME, SAST_SHORT_NAME,
...@@ -12,6 +15,7 @@ import { ...@@ -12,6 +15,7 @@ import {
LICENSE_COMPLIANCE_NAME, LICENSE_COMPLIANCE_NAME,
LICENSE_COMPLIANCE_DESCRIPTION, LICENSE_COMPLIANCE_DESCRIPTION,
LICENSE_COMPLIANCE_HELP_PATH, LICENSE_COMPLIANCE_HELP_PATH,
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
} from '~/security_configuration/components/constants'; } from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue'; import FeatureCard from '~/security_configuration/components/feature_card.vue';
...@@ -28,6 +32,9 @@ const upgradePath = '/upgrade'; ...@@ -28,6 +32,9 @@ const upgradePath = '/upgrade';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath'; const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
const autoDevopsPath = '/autoDevopsPath'; const autoDevopsPath = '/autoDevopsPath';
const gitlabCiHistoryPath = 'test/historyPath'; const gitlabCiHistoryPath = 'test/historyPath';
const projectPath = 'namespace/project';
useLocalStorageSpy();
describe('redesigned App component', () => { describe('redesigned App component', () => {
let wrapper; let wrapper;
...@@ -43,8 +50,14 @@ describe('redesigned App component', () => { ...@@ -43,8 +50,14 @@ describe('redesigned App component', () => {
upgradePath, upgradePath,
autoDevopsHelpPagePath, autoDevopsHelpPagePath,
autoDevopsPath, autoDevopsPath,
projectPath,
}, },
stubs: { stubs: {
...stubChildren(RedesignedSecurityConfigurationApp),
GlLink: false,
GlSprintf: false,
LocalStorageSync: false,
SectionLayout: false,
UserCalloutDismisser: makeMockUserCalloutDismisser({ UserCalloutDismisser: makeMockUserCalloutDismisser({
dismiss: userCalloutDismissSpy, dismiss: userCalloutDismissSpy,
shouldShowCallout, shouldShowCallout,
...@@ -83,6 +96,7 @@ describe('redesigned App component', () => { ...@@ -83,6 +96,7 @@ describe('redesigned App component', () => {
}); });
const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner); const findUpgradeBanner = () => wrapper.findComponent(UpgradeBanner);
const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert); const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert);
const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert);
const securityFeaturesMock = [ const securityFeaturesMock = [
{ {
...@@ -161,7 +175,7 @@ describe('redesigned App component', () => { ...@@ -161,7 +175,7 @@ describe('redesigned App component', () => {
}); });
}); });
describe('autoDevOpsAlert', () => { describe('Auto DevOps hint alert', () => {
describe('given the right props', () => { describe('given the right props', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
...@@ -199,6 +213,76 @@ describe('redesigned App component', () => { ...@@ -199,6 +213,76 @@ describe('redesigned App component', () => {
}); });
}); });
describe('Auto DevOps enabled alert', () => {
describe.each`
context | autoDevopsEnabled | localStorageValue | shouldRender
${'enabled'} | ${true} | ${null} | ${true}
${'enabled, alert dismissed on other project'} | ${true} | ${['foo/bar']} | ${true}
${'enabled, alert dismissed on this project'} | ${true} | ${[projectPath]} | ${false}
${'not enabled'} | ${false} | ${null} | ${false}
`('given Auto DevOps is $context', ({ autoDevopsEnabled, localStorageValue, shouldRender }) => {
beforeEach(() => {
if (localStorageValue !== null) {
window.localStorage.setItem(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(localStorageValue),
);
}
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled,
});
});
it(shouldRender ? 'renders' : 'does not render', () => {
expect(findAutoDevopsEnabledAlert().exists()).toBe(shouldRender);
});
});
describe('dismissing', () => {
describe.each`
dismissedProjects | expectedWrittenValue
${null} | ${[projectPath]}
${[]} | ${[projectPath]}
${['foo/bar']} | ${['foo/bar', projectPath]}
${[projectPath]} | ${[projectPath]}
`(
'given dismissed projects $dismissedProjects',
({ dismissedProjects, expectedWrittenValue }) => {
beforeEach(() => {
if (dismissedProjects !== null) {
window.localStorage.setItem(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(dismissedProjects),
);
}
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
augmentedComplianceFeatures: complianceFeaturesMock,
autoDevopsEnabled: true,
});
findAutoDevopsEnabledAlert().vm.$emit('dismiss');
});
it('adds current project to localStorage value', () => {
expect(window.localStorage.setItem).toHaveBeenLastCalledWith(
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
JSON.stringify(expectedWrittenValue),
);
});
it('hides the alert', () => {
expect(findAutoDevopsEnabledAlert().exists()).toBe(false);
});
},
);
});
});
describe('upgrade banner', () => { describe('upgrade banner', () => {
const makeAvailable = (available) => (feature) => ({ ...feature, available }); const makeAvailable = (available) => (feature) => ({ ...feature, available });
......
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