Commit 483817ce authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch...

Merge branch '350440-add-promotion-callout-for-security-training-feature-to-security-dashboard-ui-only' into 'master'

Add security training promo to security dashboard

See merge request gitlab-org/gitlab!78432
parents 6b4a983d 2260e639
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/dist/charts'; import { GlLineChart } from '@gitlab/ui/dist/charts';
import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql'; import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql';
import SecurityTrainingPromo from 'ee/security_dashboard/components/shared/security_training_promo.vue';
import { PROJECT_LOADING_ERROR_MESSAGE } from 'ee/security_dashboard/helpers'; import { PROJECT_LOADING_ERROR_MESSAGE } from 'ee/security_dashboard/helpers';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { formatDate, getDateInPast } from '~/lib/utils/datetime_utility'; import { formatDate, getDateInPast } from '~/lib/utils/datetime_utility';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
...@@ -26,9 +28,11 @@ export default { ...@@ -26,9 +28,11 @@ export default {
components: { components: {
ReportNotConfigured, ReportNotConfigured,
SecurityDashboardLayout, SecurityDashboardLayout,
SecurityTrainingPromo,
GlLoadingIcon, GlLoadingIcon,
GlLineChart, GlLineChart,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
projectFullPath: { projectFullPath: {
type: String, type: String,
...@@ -172,6 +176,7 @@ export default { ...@@ -172,6 +176,7 @@ export default {
<report-not-configured /> <report-not-configured />
</template> </template>
<template v-else-if="shouldShowCharts" #default> <template v-else-if="shouldShowCharts" #default>
<security-training-promo v-if="glFeatures.secureVulnerabilityTraining" />
<gl-line-chart <gl-line-chart
class="gl-mt-6" class="gl-mt-6"
:width="chartWidth" :width="chartWidth"
......
<script>
import { GlBanner } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlBanner,
},
inject: ['securityConfigurationPath'],
i18n: {
title: __('Reduce risk and triage fewer vulnerabilities with security training'),
buttonText: __('Enable security training'),
content: __(
'Enable security training to help your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability.',
),
},
computed: {
buttonLink() {
return `${this.securityConfigurationPath}?tab=vulnerability-management`;
},
},
};
</script>
<template>
<gl-banner
:title="$options.i18n.title"
:button-text="$options.i18n.buttonText"
:button-link="buttonLink"
variant="introduction"
>
<p>{{ $options.i18n.content }}</p>
</gl-banner>
</template>
...@@ -12,6 +12,7 @@ module Projects ...@@ -12,6 +12,7 @@ module Projects
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false) push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
push_frontend_feature_flag(:vulnerability_management_survey, type: :ops, default_enabled: :yaml) push_frontend_feature_flag(:vulnerability_management_survey, type: :ops, default_enabled: :yaml)
push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml) push_frontend_feature_flag(:sbom_survey, @user, default_enabled: :yaml)
push_frontend_feature_flag(:secure_vulnerability_training, @project, default_enabled: :yaml)
end end
feature_category :vulnerability_management feature_category :vulnerability_management
......
...@@ -204,7 +204,8 @@ module EE ...@@ -204,7 +204,8 @@ module EE
scanners: VulnerabilityScanners::ListService.new(project).execute.to_json, scanners: VulnerabilityScanners::ListService.new(project).execute.to_json,
can_admin_vulnerability: can?(current_user, :admin_vulnerability, project).to_s, can_admin_vulnerability: can?(current_user, :admin_vulnerability, project).to_s,
false_positive_doc_url: help_page_path('user/application_security/vulnerabilities/index'), false_positive_doc_url: help_page_path('user/application_security/vulnerabilities/index'),
can_view_false_positive: can_view_false_positive? can_view_false_positive: can_view_false_positive?,
security_configuration_path: project_security_configuration_path(@project)
}.merge!(security_dashboard_pipeline_data(project)) }.merge!(security_dashboard_pipeline_data(project))
end end
end end
......
...@@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo'; ...@@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo';
import ProjectSecurityDashboard from 'ee/security_dashboard/components/project/project_security_dashboard.vue'; import ProjectSecurityDashboard from 'ee/security_dashboard/components/project/project_security_dashboard.vue';
import ReportNotConfigured from 'ee/security_dashboard/components/shared/empty_states/report_not_configured_project.vue'; import ReportNotConfigured from 'ee/security_dashboard/components/shared/empty_states/report_not_configured_project.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/shared/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/shared/security_dashboard_layout.vue';
import SecurityTrainingPromo from 'ee/security_dashboard/components/shared/security_training_promo.vue';
import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql'; import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql';
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -26,15 +27,21 @@ describe('Project Security Dashboard component', () => { ...@@ -26,15 +27,21 @@ describe('Project Security Dashboard component', () => {
const projectFullPath = 'project/path'; const projectFullPath = 'project/path';
const helpPath = 'docs/security/dashboard'; const helpPath = 'docs/security/dashboard';
const findLineChart = () => wrapper.find(GlLineChart); const findLineChart = () => wrapper.findComponent(GlLineChart);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findEmptyState = () => wrapper.find(ReportNotConfigured); const findEmptyState = () => wrapper.findComponent(ReportNotConfigured);
const findSecurityTrainingPromo = () => wrapper.findComponent(SecurityTrainingPromo);
const createApolloProvider = (...queries) => { const createApolloProvider = (...queries) => {
return createMockApollo([...queries]); return createMockApollo([...queries]);
}; };
const createComponent = ({ query, propsData, chartWidth = 1024 }) => { const createComponent = ({
query,
propsData,
chartWidth = 1024,
secureVulnerabilityTrainingEnabled = true,
}) => {
const component = shallowMount(ProjectSecurityDashboard, { const component = shallowMount(ProjectSecurityDashboard, {
localVue, localVue,
apolloProvider: createApolloProvider([ apolloProvider: createApolloProvider([
...@@ -49,6 +56,9 @@ describe('Project Security Dashboard component', () => { ...@@ -49,6 +56,9 @@ describe('Project Security Dashboard component', () => {
provide: { provide: {
// To be consumed by SecurityDashboardLayout // To be consumed by SecurityDashboardLayout
sbomSurveySvgPath: '/', sbomSurveySvgPath: '/',
glFeatures: {
secureVulnerabilityTraining: secureVulnerabilityTrainingEnabled,
},
}, },
stubs: { stubs: {
SecurityDashboardLayout, SecurityDashboardLayout,
...@@ -125,6 +135,10 @@ describe('Project Security Dashboard component', () => { ...@@ -125,6 +135,10 @@ describe('Project Security Dashboard component', () => {
startValue: '2021-03-12', startValue: '2021-03-12',
}); });
}); });
it('contains a promotion for the security training feature', () => {
expect(findSecurityTrainingPromo().exists()).toBe(true);
});
}); });
describe('when there is no history data', () => { describe('when there is no history data', () => {
...@@ -169,4 +183,18 @@ describe('Project Security Dashboard component', () => { ...@@ -169,4 +183,18 @@ describe('Project Security Dashboard component', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
}); });
describe('with the "secureVulnerabilityTraining" feature flag disabled', () => {
it('does not contain a promotion for the security training feature', async () => {
wrapper = createComponent({
query: mockProjectSecurityChartsWithData(),
propsData: { hasVulnerabilities: true },
secureVulnerabilityTrainingEnabled: false,
});
await wrapper.vm.$nextTick();
expect(findSecurityTrainingPromo().exists()).toBe(false);
});
});
}); });
import { shallowMount } from '@vue/test-utils';
import { GlBanner } from '@gitlab/ui';
import SecurityTrainingPromo from 'ee/security_dashboard/components/shared/security_training_promo.vue';
const SECURITY_CONFIGURATION_PATH = 'foo/bar';
const VULNERABILITY_MANAGEMENT_TAB_NAME = 'vulnerability-management';
describe('Security training promo component', () => {
let wrapper;
const createWrapper = () =>
shallowMount(SecurityTrainingPromo, {
provide: {
securityConfigurationPath: SECURITY_CONFIGURATION_PATH,
},
});
beforeEach(() => {
wrapper = createWrapper();
});
afterEach(() => {
wrapper.destroy();
});
const findBanner = () => wrapper.findComponent(GlBanner);
describe('banner', () => {
it('should be an introduction that announces the security training feature', () => {
const { title, buttonText, content } = SecurityTrainingPromo.i18n;
expect(findBanner().props()).toMatchObject({
variant: 'introduction',
title,
buttonText,
});
expect(findBanner().text()).toBe(content);
});
it(`should link to the security configuration's vulnerability management tab`, () => {
expect(findBanner().props('buttonLink')).toBe(
`${SECURITY_CONFIGURATION_PATH}?tab=${VULNERABILITY_MANAGEMENT_TAB_NAME}`,
);
});
});
});
...@@ -228,7 +228,8 @@ RSpec.describe ProjectsHelper do ...@@ -228,7 +228,8 @@ RSpec.describe ProjectsHelper do
auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix'), auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix'),
scanners: '[{"id":123,"vendor":"Security Vendor","report_type":"SAST"}]', scanners: '[{"id":123,"vendor":"Security Vendor","report_type":"SAST"}]',
can_admin_vulnerability: 'true', can_admin_vulnerability: 'true',
can_view_false_positive: 'false' can_view_false_positive: 'false',
security_configuration_path: kind_of(String)
} }
end end
......
...@@ -13300,6 +13300,12 @@ msgstr "" ...@@ -13300,6 +13300,12 @@ msgstr ""
msgid "Enable repository checks" msgid "Enable repository checks"
msgstr "" msgstr ""
msgid "Enable security training"
msgstr ""
msgid "Enable security training to help your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability."
msgstr ""
msgid "Enable shared runners for all projects and subgroups in this group." msgid "Enable shared runners for all projects and subgroups in this group."
msgstr "" msgstr ""
...@@ -29353,6 +29359,9 @@ msgstr "" ...@@ -29353,6 +29359,9 @@ msgstr ""
msgid "Reduce project visibility" msgid "Reduce project visibility"
msgstr "" msgstr ""
msgid "Reduce risk and triage fewer vulnerabilities with security training"
msgstr ""
msgid "Reduce this project’s visibility?" msgid "Reduce this project’s visibility?"
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