Commit 9936151e authored by Savas Vedova's avatar Savas Vedova

Add export button to group dashboard

- Add tests
- Add documentation
parent c6158f0a
...@@ -120,6 +120,21 @@ vulnerabilities are not included either. ...@@ -120,6 +120,21 @@ vulnerabilities are not included either.
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities). Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
### Export vulnerabilities
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213013) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
You can export all your vulnerabilities as CSV by clicking the **{upload}** **Export** button
located at the top right of the **Group Security Dashboard**. After the report builds, the CSV
report downloads to your local machine. The report contains all vulnerabilities for the projects
defined in the **Group Security Dashboard**, as filters don't apply to the export function.
NOTE: **Note:**
It may take several minutes for the download to start if your project contains thousands of
vulnerabilities. Don't close the page until the download finishes.
![CSV Export Button](img/group_security_dashboard_export_csv_v13_1.png)
## Instance Security Dashboard ## Instance Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8.
......
...@@ -3,6 +3,7 @@ import SecurityDashboardLayout from 'ee/security_dashboard/components/security_d ...@@ -3,6 +3,7 @@ import SecurityDashboardLayout from 'ee/security_dashboard/components/security_d
import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue'; import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import CsvExportButton from './csv_export_button.vue';
import VulnerabilitySeverity from './vulnerability_severity.vue'; import VulnerabilitySeverity from './vulnerability_severity.vue';
import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.graphql'; import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.graphql';
...@@ -13,6 +14,7 @@ export default { ...@@ -13,6 +14,7 @@ export default {
VulnerabilitySeverity, VulnerabilitySeverity,
VulnerabilityChart, VulnerabilityChart,
Filters, Filters,
CsvExportButton,
}, },
props: { props: {
dashboardDocumentation: { dashboardDocumentation: {
...@@ -31,6 +33,10 @@ export default { ...@@ -31,6 +33,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerabilitiesExportEndpoint: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -52,6 +58,12 @@ export default { ...@@ -52,6 +58,12 @@ export default {
<template> <template>
<security-dashboard-layout> <security-dashboard-layout>
<template #header>
<header class="page-title-holder flex-fill d-flex align-items-center">
<h2 class="page-title flex-grow">{{ s__('SecurityReports|Group Security Dashboard') }}</h2>
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</header>
</template>
<template #sticky> <template #sticky>
<filters :projects="projects" @filterChange="handleFilterChange" /> <filters :projects="projects" @filterChange="handleFilterChange" />
</template> </template>
......
...@@ -46,7 +46,6 @@ export default { ...@@ -46,7 +46,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerabilitiesExportEndpoint: { vulnerabilitiesExportEndpoint: {
type: String, type: String,
required: true, required: true,
......
...@@ -42,6 +42,7 @@ export default ( ...@@ -42,6 +42,7 @@ export default (
securityDashboardHelpPath: el.dataset.securityDashboardHelpPath, securityDashboardHelpPath: el.dataset.securityDashboardHelpPath,
projectAddEndpoint: el.dataset.projectAddEndpoint, projectAddEndpoint: el.dataset.projectAddEndpoint,
projectListEndpoint: el.dataset.projectListEndpoint, projectListEndpoint: el.dataset.projectListEndpoint,
vulnerabilitiesExportEndpoint: el.dataset.vulnerabilitiesExportEndpoint,
}; };
let component; let component;
...@@ -49,7 +50,6 @@ export default ( ...@@ -49,7 +50,6 @@ export default (
if (dashboardType === DASHBOARD_TYPES.PROJECT) { if (dashboardType === DASHBOARD_TYPES.PROJECT) {
component = FirstClassProjectSecurityDashboard; component = FirstClassProjectSecurityDashboard;
props.projectFullPath = el.dataset.projectFullPath; props.projectFullPath = el.dataset.projectFullPath;
props.vulnerabilitiesExportEndpoint = el.dataset.vulnerabilitiesExportEndpoint;
props.userCalloutId = el.dataset.userCalloutId; props.userCalloutId = el.dataset.userCalloutId;
props.userCalloutsPath = el.dataset.userCalloutsPath; props.userCalloutsPath = el.dataset.userCalloutsPath;
props.showIntroductionBanner = parseBoolean(el.dataset.showIntroductionBanner); props.showIntroductionBanner = parseBoolean(el.dataset.showIntroductionBanner);
...@@ -60,7 +60,6 @@ export default ( ...@@ -60,7 +60,6 @@ export default (
} else if (dashboardType === DASHBOARD_TYPES.INSTANCE) { } else if (dashboardType === DASHBOARD_TYPES.INSTANCE) {
component = FirstClassInstanceSecurityDashboard; component = FirstClassInstanceSecurityDashboard;
props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint; props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint;
props.vulnerabilitiesExportEndpoint = el.dataset.vulnerabilitiesExportEndpoint;
} }
const router = createRouter(); const router = createRouter();
......
---
title: Add csv export button to group security dashboard
merge_request: 34374
author:
type: added
...@@ -4,6 +4,7 @@ import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_cla ...@@ -4,6 +4,7 @@ import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_cla
import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import VulnerabilitySeverity from 'ee/security_dashboard/components/vulnerability_severity.vue'; import VulnerabilitySeverity from 'ee/security_dashboard/components/vulnerability_severity.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue'; import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
describe('First Class Group Dashboard Component', () => { describe('First Class Group Dashboard Component', () => {
...@@ -13,10 +14,12 @@ describe('First Class Group Dashboard Component', () => { ...@@ -13,10 +14,12 @@ describe('First Class Group Dashboard Component', () => {
const emptyStateSvgPath = 'empty-state-path'; const emptyStateSvgPath = 'empty-state-path';
const groupFullPath = 'group-full-path'; const groupFullPath = 'group-full-path';
const vulnerableProjectsEndpoint = '/vulnerable/projects'; const vulnerableProjectsEndpoint = '/vulnerable/projects';
const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports';
const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities); const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity); const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart); const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findFilters = () => wrapper.find(Filters); const findFilters = () => wrapper.find(Filters);
const createWrapper = () => { const createWrapper = () => {
...@@ -26,6 +29,7 @@ describe('First Class Group Dashboard Component', () => { ...@@ -26,6 +29,7 @@ describe('First Class Group Dashboard Component', () => {
emptyStateSvgPath, emptyStateSvgPath,
groupFullPath, groupFullPath,
vulnerableProjectsEndpoint, vulnerableProjectsEndpoint,
vulnerabilitiesExportEndpoint,
}, },
stubs: { stubs: {
SecurityDashboardLayout, SecurityDashboardLayout,
...@@ -78,4 +82,10 @@ describe('First Class Group Dashboard Component', () => { ...@@ -78,4 +82,10 @@ describe('First Class Group Dashboard Component', () => {
it('displays the vulnerability severity in an aside', () => { it('displays the vulnerability severity in an aside', () => {
expect(findVulnerabilitySeverity().exists()).toBe(true); expect(findVulnerabilitySeverity().exists()).toBe(true);
}); });
it('displays the csv export button', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe(
vulnerabilitiesExportEndpoint,
);
});
}); });
...@@ -19647,6 +19647,9 @@ msgstr "" ...@@ -19647,6 +19647,9 @@ msgstr ""
msgid "SecurityReports|False positive" msgid "SecurityReports|False positive"
msgstr "" msgstr ""
msgid "SecurityReports|Group Security Dashboard"
msgstr ""
msgid "SecurityReports|Hide dismissed" msgid "SecurityReports|Hide dismissed"
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