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