From 6509f8f185983b3dd9bce06a87a01a1da12f54c1 Mon Sep 17 00:00:00 2001
From: Savas Vedova <svedova@gitlab.com>
Date: Thu, 3 Oct 2024 11:37:18 +0000
Subject: [PATCH] Display page header and subheader

Add a page title to the project security dashboard page.

EE: true
Changelog: added
---
 .../project/project_security_dashboard.vue    | 49 ++++++++++++++-----
 .../security_dashboard/constants.js           |  5 ++
 .../project_security_dashboard_spec.js        | 21 ++++++--
 locale/gitlab.pot                             |  3 ++
 4 files changed, 64 insertions(+), 14 deletions(-)

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 3db91c1db0065..03fa8b9fdcc0c 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 28c606f2a5126..c7e5567e67b31 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 a2888e1767e52..afb47deb9930c 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 22789b0e2dcc8..5e8d30f12841c 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 ""
 
-- 
GitLab