diff --git a/ee/app/assets/javascripts/security_dashboard/components/project/project_security_dashboard.vue b/ee/app/assets/javascripts/security_dashboard/components/project/project_security_dashboard.vue index 3db91c1db006512c1a61dda7536fada176213a64..03fa8b9fdcc0cf77ea9b6ef708cd0c153b3c766c 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/project/project_security_dashboard.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/project/project_security_dashboard.vue @@ -1,10 +1,11 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui'; import { GlLineChart } from '@gitlab/ui/dist/charts'; import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql'; import severitiesCountQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql'; import SecurityTrainingPromoBanner from 'ee/security_dashboard/components/project/security_training_promo_banner.vue'; import { PROJECT_LOADING_ERROR_MESSAGE } from 'ee/security_dashboard/helpers'; +import { DOC_PATH_PROJECT_SECURITY_DASHBOARD } from 'ee/security_dashboard/constants'; import { createAlert } from '~/alert'; import { formatDate, getDateInPast } from '~/lib/utils/datetime_utility'; import { s__, __ } from '~/locale'; @@ -26,6 +27,8 @@ export default { SecurityTrainingPromoBanner, GlLoadingIcon, GlLineChart, + GlSprintf, + GlLink, }, props: { projectFullPath: { @@ -158,20 +161,44 @@ export default { }; }, }, + i18n: { + title: s__('SecurityReports|Security dashboard'), + subtitle: s__( + 'SecurityReports|Historical view of open vulnerabilities in the default branch. Excludes vulnerabilities that were resolved or dismissed. %{linkStart}Learn more.%{linkEnd}', + ), + }, + DOC_PATH_PROJECT_SECURITY_DASHBOARD, }; </script> <template> - <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-6" /> - - <div v-else> + <div> <security-training-promo-banner v-if="shouldShowPromoBanner" /> - <gl-line-chart - class="gl-mt-6" - :data="dataSeries" - :option="chartOptions" - responsive - :include-legend-avg-max="false" - /> + + <header class="vulnerability-header gl-my-5 gl-grid"> + <h2 class="header-title gl-my-0"> + {{ $options.i18n.title }} + </h2> + <p class="header-description gl-mb-0" data-testid="security-dashboard-description"> + <gl-sprintf :message="$options.i18n.subtitle"> + <template #link="{ content }"> + <gl-link :href="$options.DOC_PATH_PROJECT_SECURITY_DASHBOARD" target="_blank">{{ + content + }}</gl-link> + </template> + </gl-sprintf> + </p> + </header> + <div> + <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-6" /> + <gl-line-chart + v-else + class="gl-mt-6" + :data="dataSeries" + :option="chartOptions" + responsive + :include-legend-avg-max="false" + /> + </div> </div> </template> diff --git a/ee/app/assets/javascripts/security_dashboard/constants.js b/ee/app/assets/javascripts/security_dashboard/constants.js index 28c606f2a5126908406000dc9bd6e488511c80fc..c7e5567e67b318ff91e89043bd6b4a028a0c2148 100644 --- a/ee/app/assets/javascripts/security_dashboard/constants.js +++ b/ee/app/assets/javascripts/security_dashboard/constants.js @@ -26,6 +26,11 @@ export const DOC_PATH_SECURITY_SCANNER_INTEGRATION_RETENTION_PERIOD = helpPagePa { anchor: 'retention-period-for-findings' }, ); +export const DOC_PATH_PROJECT_SECURITY_DASHBOARD = helpPagePath( + 'user/application_security/security_dashboard/index', + { anchor: 'project-security-dashboard' }, +); + export const severityLevels = { CRITICAL: 'critical', HIGH: 'high', diff --git a/ee/spec/frontend/security_dashboard/components/project/project_security_dashboard_spec.js b/ee/spec/frontend/security_dashboard/components/project/project_security_dashboard_spec.js index a2888e1767e52e900a4ab09b06606fe378fadd1e..afb47deb9930c0d99c1722e320d9674aa103b11e 100644 --- a/ee/spec/frontend/security_dashboard/components/project/project_security_dashboard_spec.js +++ b/ee/spec/frontend/security_dashboard/components/project/project_security_dashboard_spec.js @@ -1,12 +1,12 @@ -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui'; import { GlLineChart } from '@gitlab/ui/dist/charts'; -import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import SecurityTrainingPromoBanner from 'ee/security_dashboard/components/project/security_training_promo_banner.vue'; import ProjectSecurityDashboard from 'ee/security_dashboard/components/project/project_security_dashboard.vue'; import projectsHistoryQuery from 'ee/security_dashboard/graphql/queries/project_vulnerabilities_by_day_and_count.query.graphql'; import severitiesCountQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { useFakeDate } from 'helpers/fake_date'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -35,7 +35,7 @@ describe('Project Security Dashboard component', () => { } = {}) => { severitiesCountQueryHandler = jest.fn().mockResolvedValue(severitiesCountQueryData); - wrapper = shallowMount(ProjectSecurityDashboard, { + wrapper = shallowMountExtended(ProjectSecurityDashboard, { apolloProvider: createApolloProvider( [projectsHistoryQuery, jest.fn().mockResolvedValue(historyQueryData)], [severitiesCountQuery, severitiesCountQueryHandler], @@ -44,9 +44,24 @@ describe('Project Security Dashboard component', () => { projectFullPath, shouldShowPromoBanner, }, + stubs: { + GlSprintf, + }, }); }; + it('should display page header and subheader', () => { + createWrapper(); + + expect(wrapper.findByText('Security dashboard').exists()).toBe(true); + expect(wrapper.findByTestId('security-dashboard-description').text()).toBe( + 'Historical view of open vulnerabilities in the default branch. Excludes vulnerabilities that were resolved or dismissed. Learn more.', + ); + expect(wrapper.findComponent(GlLink).attributes('href')).toBe( + '/help/user/application_security/security_dashboard/index#project-security-dashboard', + ); + }); + it('should fetch the latest vulnerability count for "detected" and "confirmed" states', () => { createWrapper(); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 22789b0e2dcc88eb7da4878fa9d94b12d40200ff..5e8d30f12841cb08974cbd602e15823f975a2862 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -50378,6 +50378,9 @@ msgstr "" msgid "SecurityReports|Hide dismissed" msgstr "" +msgid "SecurityReports|Historical view of open vulnerabilities in the default branch. Excludes vulnerabilities that were resolved or dismissed. %{linkStart}Learn more.%{linkEnd}" +msgstr "" + msgid "SecurityReports|Image" msgstr ""