Commit f285c491 authored by Fernando's avatar Fernando Committed by Fernando Arias

Show CSV and json artifact download on security tab and merge request

Users can now download the DAST scanned urls and vulnerability
artifacts from the merge request widget and the pipeline
security tab.

Changelog: changed
parent 47a89684
......@@ -26,6 +26,11 @@ export default {
type: Number,
required: true,
},
injectedArtifacts: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
......@@ -56,6 +61,9 @@ export default {
isLoadingReportArtifacts() {
return this.$apollo.queries.reportArtifacts.loading;
},
mergedReportArtifacts() {
return [...this.reportArtifacts, ...this.injectedArtifacts];
},
},
methods: {
showError(error) {
......@@ -77,7 +85,7 @@ export default {
<template>
<security-report-download-dropdown
:title="s__('SecurityReports|Download results')"
:artifacts="reportArtifacts"
:artifacts="mergedReportArtifacts"
:loading="isLoadingReportArtifacts"
/>
</template>
......@@ -12,9 +12,13 @@ import { getFormattedSummary } from 'ee/security_dashboard/helpers';
import Modal from 'ee/vue_shared/security_reports/components/dast_modal.vue';
import AccessorUtilities from '~/lib/utils/accessor';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { __ } from '~/locale';
import { s__, __ } from '~/locale';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import { extractSecurityReportArtifacts } from '~/vue_shared/security_reports/utils';
import {
SECURITY_REPORT_TYPE_ENUM_DAST,
REPORT_TYPE_DAST,
} from 'ee/vue_shared/security_reports/constants';
export default {
name: 'SecurityReportsSummary',
......@@ -77,6 +81,12 @@ export default {
hasScannedResources(scanSummary) {
return scanSummary.scannedResources?.nodes?.length > 0;
},
hasDastArtifactDownload(scanSummary) {
return (
Boolean(scanSummary.scannedResourcesCsvPath) ||
this.findArtifacts(SECURITY_REPORT_TYPE_ENUM_DAST).length > 0
);
},
downloadLink(scanSummary) {
return scanSummary.scannedResourcesCsvPath || '';
},
......@@ -84,6 +94,15 @@ export default {
const snakeCase = convertToSnakeCase(scanType.toLowerCase());
return extractSecurityReportArtifacts([snakeCase], this.jobs);
},
buildDastArtifacts(scanSummary) {
const csvArtifact = {
name: s__('SecurityReports|scanned resources'),
path: this.downloadLink(scanSummary),
reportType: REPORT_TYPE_DAST,
};
return [...this.findArtifacts(SECURITY_REPORT_TYPE_ENUM_DAST), csvArtifact];
},
},
};
</script>
......@@ -145,16 +164,12 @@ export default {
/>
</template>
<template v-else-if="scanSummary.scannedResourcesCsvPath">
<gl-button
icon="download"
size="small"
:href="downloadLink(scanSummary)"
class="gl-ml-1"
<template v-else-if="hasDastArtifactDownload(scanSummary)">
<security-report-download-dropdown
:text="s__('SecurityReports|Download results')"
:artifacts="buildDastArtifacts(scanSummary)"
data-testid="download-link"
>
{{ s__('SecurityReports|Download scanned URLs') }}
</gl-button>
/>
</template>
<security-report-download-dropdown
......
......@@ -4,6 +4,10 @@ import {
reportTypeToSecurityReportTypeEnum as reportTypeToSecurityReportTypeEnumCE,
REPORT_TYPE_API_FUZZING,
REPORT_TYPE_COVERAGE_FUZZING,
REPORT_TYPE_DAST,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_CONTAINER_SCANNING,
REPORT_TYPE_CLUSTER_IMAGE_SCANNING,
} from '~/vue_shared/security_reports/constants';
export * from '~/vue_shared/security_reports/constants';
......@@ -15,6 +19,10 @@ export * from '~/vue_shared/security_reports/constants';
*/
export const SECURITY_REPORT_TYPE_ENUM_API_FUZZING = 'API_FUZZING';
export const SECURITY_REPORT_TYPE_ENUM_COVERAGE_FUZZING = 'COVERAGE_FUZZING';
export const SECURITY_REPORT_TYPE_ENUM_DAST = 'DAST';
export const SECURITY_REPORT_TYPE_ENUM_DEPENDENCY_SCANNING = 'DEPENDENCY_SCANNING';
export const SECURITY_REPORT_TYPE_ENUM_CONTAINER_SCANNING = 'CONTAINER_SCANNING';
export const SECURITY_REPORT_TYPE_ENUM_CLUSTER_IMAGE_SCANNING = 'CLUSTER_IMAGE_SCANNING';
/* Override CE Definitions */
......@@ -25,6 +33,10 @@ export const reportTypeToSecurityReportTypeEnum = {
...reportTypeToSecurityReportTypeEnumCE,
[REPORT_TYPE_API_FUZZING]: SECURITY_REPORT_TYPE_ENUM_API_FUZZING,
[REPORT_TYPE_COVERAGE_FUZZING]: SECURITY_REPORT_TYPE_ENUM_COVERAGE_FUZZING,
[REPORT_TYPE_DAST]: SECURITY_REPORT_TYPE_ENUM_DAST,
[REPORT_TYPE_DEPENDENCY_SCANNING]: SECURITY_REPORT_TYPE_ENUM_DEPENDENCY_SCANNING,
[REPORT_TYPE_CONTAINER_SCANNING]: SECURITY_REPORT_TYPE_ENUM_CONTAINER_SCANNING,
[REPORT_TYPE_CLUSTER_IMAGE_SCANNING]: SECURITY_REPORT_TYPE_ENUM_CLUSTER_IMAGE_SCANNING,
};
/**
......
......@@ -8,6 +8,7 @@ import {
GlModalDirective,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import { componentNames } from 'ee/reports/components/issue_body';
import { fetchPolicies } from '~/lib/graphql';
import { mrStates } from '~/mr_popover/constants';
......@@ -18,9 +19,13 @@ import { LOADING } from '~/reports/constants';
import Tracking from '~/tracking';
import MergeRequestArtifactDownload from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
import SecuritySummary from '~/vue_shared/security_reports/components/security_summary.vue';
import {
REPORT_TYPE_DAST,
securityReportTypeEnumToReportType,
} from 'ee/vue_shared/security_reports/constants';
import DastModal from './components/dast_modal.vue';
import IssueModal from './components/modal.vue';
import { securityReportTypeEnumToReportType } from './constants';
import securityReportSummaryQuery from './graphql/mr_security_report_summary.graphql';
import securityReportsMixin from './mixins/security_report_mixin';
import { vulnerabilityModalMixin } from './mixins/vulnerability_modal_mixin';
......@@ -348,6 +353,15 @@ export default {
shouldShowDownloadGuidance() {
return this.targetProjectFullPath && this.mrIid && this.summaryStatus !== LOADING;
},
dastCsvArtifacts() {
return [
{
name: s__('SecurityReports|scanned resources'),
path: this.dastDownloadLink,
reportType: REPORT_TYPE_DAST,
},
];
},
},
created() {
......@@ -454,6 +468,7 @@ export default {
reportTypes: {
API_FUZZING: [securityReportTypeEnumToReportType.API_FUZZING],
COVERAGE_FUZZING: [securityReportTypeEnumToReportType.COVERAGE_FUZZING],
DAST: [securityReportTypeEnumToReportType.DAST],
},
};
</script>
......@@ -614,14 +629,12 @@ export default {
/>
</template>
<template v-else-if="dastDownloadLink">
<gl-button
v-gl-tooltip
:title="s__('SecurityReports|Download scanned resources')"
download
size="small"
icon="download"
:href="dastDownloadLink"
class="gl-ml-1"
<merge-request-artifact-download
v-if="shouldShowDownloadGuidance"
:report-types="$options.reportTypes.DAST"
:target-project-full-path="targetProjectFullPath"
:mr-iid="mrIid"
:injected-artifacts="dastCsvArtifacts"
data-testid="download-link"
/>
</template>
......
......@@ -30,7 +30,7 @@ describe('Security reports summary component', () => {
const findToggleButton = () => wrapper.find('[data-testid="collapse-button"]');
const findModalButton = () => wrapper.find('[data-testid="modal-button"]');
const findDownloadLink = () => wrapper.find('[data-testid="download-link"]');
const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown);
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
......@@ -113,7 +113,7 @@ describe('Security reports summary component', () => {
},
});
expect(wrapper.findComponent(SecurityReportDownloadDropdown).exists()).toBe(hasDropdown);
expect(findDownloadDropdown().exists()).toBe(hasDropdown);
},
);
......@@ -248,8 +248,8 @@ describe('Security reports summary component', () => {
});
});
it('should contain a download link', () => {
expect(findDownloadLink().attributes('href')).toBe('/download/path');
it('should contain a artifact download dropdown', () => {
expect(findDownloadDropdown().exists()).toBe(true);
});
});
});
......@@ -15,6 +15,7 @@ import axios from '~/lib/utils/axios_utils';
import { mrStates } from '~/mr_popover/constants';
import GroupedIssuesList from '~/reports/components/grouped_issues_list.vue';
import ReportSection from '~/reports/components/report_section.vue';
import MergeRequestArtifactDownload from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
import {
sastDiffSuccessMock,
......@@ -606,11 +607,9 @@ describe('Grouped security reports app', () => {
);
return waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_SUCCESS).then(() => {
const findDownloadLink = wrapper.find('[data-testid="download-link"]');
const findDownloadDropdown = wrapper.findComponent(MergeRequestArtifactDownload);
expect(findDownloadLink.find('[data-testid="download-icon"]').exists()).toBe(true);
expect(findDownloadLink.exists()).toBe(true);
expect(findDownloadLink.attributes('href')).toBe('http://test');
expect(findDownloadDropdown.exists()).toBe(true);
});
});
});
......
......@@ -30452,9 +30452,6 @@ msgstr ""
msgid "SecurityReports|Download scanned URLs"
msgstr ""
msgid "SecurityReports|Download scanned resources"
msgstr ""
msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
......@@ -30665,6 +30662,9 @@ msgstr ""
msgid "SecurityReports|Your feedback is important to us! We will ask again in a week."
msgstr ""
msgid "SecurityReports|scanned resources"
msgstr ""
msgid "See example DevOps Score page in our documentation."
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