Commit 4a5b54e4 authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Natalia Tepluhina

Add link to scanned resources in DAST reports

- Retrieve scanned URLs count and CI job URL from the API
- Show the information in the MR widget
- Force the help icon to stay on the same line as the preceding word for
  a better UX on mobile layouts
- Updated tests
parent 3824edb1
...@@ -53,11 +53,19 @@ export default { ...@@ -53,11 +53,19 @@ export default {
/> />
<ci-icon v-else :status="iconStatus" :size="24" /> <ci-icon v-else :status="iconStatus" :size="24" />
</div> </div>
<div class="report-block-list-issue-description"> <div class="report-block-list-issue-description">
<div class="report-block-list-issue-description-text">{{ summary }}</div> <div class="report-block-list-issue-description-text">
{{ summary
<popover v-if="popoverOptions" :options="popoverOptions" /> }}<span v-if="popoverOptions" class="text-nowrap"
>&nbsp;<popover v-if="popoverOptions" :options="popoverOptions" class="align-top" />
</span>
</div>
</div>
<div
v-if="$slots.default"
class="text-right flex-fill d-flex justify-content-end flex-column flex-sm-row"
>
<slot></slot>
</div> </div>
</div> </div>
</template> </template>
...@@ -174,6 +174,9 @@ export default { ...@@ -174,6 +174,9 @@ export default {
isMRBranchOutdated() { isMRBranchOutdated() {
return this.divergedCommitsCount > 0; return this.divergedCommitsCount > 0;
}, },
dastScans() {
return this.dast.scans.filter(scan => scan.scanned_resources_count > 0);
},
}, },
created() { created() {
...@@ -377,7 +380,20 @@ export default { ...@@ -377,7 +380,20 @@ export default {
:popover-options="dastPopover" :popover-options="dastPopover"
class="js-dast-widget" class="js-dast-widget"
data-qa-selector="dast_scan_report" data-qa-selector="dast_scan_report"
/> >
<template v-if="dastScans.length">
<div class="text-nowrap">
{{ n__('%d URL scanned', '%d URLs scanned', dastScans[0].scanned_resources_count) }}
</div>
<gl-link
class="ml-2"
data-qa-selector="dast-ci-job-link"
:href="dastScans[0].job_path"
>
{{ __('View details') }}
</gl-link>
</template>
</summary-row>
<issues-list <issues-list
v-if="dast.newIssues.length || dast.resolvedIssues.length" v-if="dast.newIssues.length || dast.resolvedIssues.length"
......
...@@ -80,6 +80,7 @@ export default { ...@@ -80,6 +80,7 @@ export default {
[types.RECEIVE_DAST_DIFF_SUCCESS](state, { diff, enrichData }) { [types.RECEIVE_DAST_DIFF_SUCCESS](state, { diff, enrichData }) {
const { added, fixed, existing } = parseDiff(diff, enrichData); const { added, fixed, existing } = parseDiff(diff, enrichData);
const baseReportOutofDate = diff.base_report_out_of_date || false; const baseReportOutofDate = diff.base_report_out_of_date || false;
const scans = diff.scans || [];
const hasBaseReport = Boolean(diff.base_report_created_at); const hasBaseReport = Boolean(diff.base_report_created_at);
Vue.set(state.dast, 'isLoading', false); Vue.set(state.dast, 'isLoading', false);
...@@ -88,6 +89,7 @@ export default { ...@@ -88,6 +89,7 @@ export default {
Vue.set(state.dast, 'allIssues', existing); Vue.set(state.dast, 'allIssues', existing);
Vue.set(state.dast, 'baseReportOutofDate', baseReportOutofDate); Vue.set(state.dast, 'baseReportOutofDate', baseReportOutofDate);
Vue.set(state.dast, 'hasBaseReport', hasBaseReport); Vue.set(state.dast, 'hasBaseReport', hasBaseReport);
Vue.set(state.dast, 'scans', scans);
}, },
[types.RECEIVE_DAST_DIFF_ERROR](state) { [types.RECEIVE_DAST_DIFF_ERROR](state) {
......
...@@ -41,6 +41,7 @@ export default () => ({ ...@@ -41,6 +41,7 @@ export default () => ({
resolvedIssues: [], resolvedIssues: [],
baseReportOutofDate: false, baseReportOutofDate: false,
hasBaseReport: false, hasBaseReport: false,
scans: [],
}, },
dependencyScanning: { dependencyScanning: {
......
---
title: Add scanned URL count and link to scanned resources in DAST reports
merge_request: 26825
author:
type: added
...@@ -10,6 +10,7 @@ import { waitForMutation } from 'helpers/vue_test_utils_helper'; ...@@ -10,6 +10,7 @@ import { waitForMutation } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { mrStates } from '~/mr_popover/constants'; import { mrStates } from '~/mr_popover/constants';
import { TEST_HOST } from 'helpers/test_constants';
import { import {
sastDiffSuccessMock, sastDiffSuccessMock,
...@@ -302,13 +303,22 @@ describe('Grouped security reports app', () => { ...@@ -302,13 +303,22 @@ describe('Grouped security reports app', () => {
}); });
describe('dast reports', () => { describe('dast reports', () => {
const scanUrl = `${TEST_HOST}/group/project/-/jobs/123546789`;
beforeEach(() => { beforeEach(() => {
gl.mrWidgetData = gl.mrWidgetData || {}; gl.mrWidgetData = gl.mrWidgetData || {};
gl.mrWidgetData.dast_comparison_path = DAST_DIFF_ENDPOINT; gl.mrWidgetData.dast_comparison_path = DAST_DIFF_ENDPOINT;
mock mock.onGet(DAST_DIFF_ENDPOINT).reply(200, {
.onGet(DAST_DIFF_ENDPOINT) ...dastDiffSuccessMock,
.reply(200, { ...dastDiffSuccessMock, base_report_out_of_date: true }); base_report_out_of_date: true,
scans: [
{
scanned_resources_count: 211,
job_path: scanUrl,
},
],
});
createWrapper({ createWrapper({
...props, ...props,
...@@ -329,6 +339,38 @@ describe('Grouped security reports app', () => { ...@@ -329,6 +339,38 @@ describe('Grouped security reports app', () => {
'DAST detected 1 new, and 2 fixed vulnerabilities', 'DAST detected 1 new, and 2 fixed vulnerabilities',
); );
}); });
it('shows the scanned URLs count and a link to the CI job if available', () => {
const jobLink = wrapper.find('[data-qa-selector="dast-ci-job-link"]');
expect(wrapper.text()).toContain('211 URLs scanned');
expect(jobLink.exists()).toBe(true);
expect(jobLink.text()).toBe('View details');
expect(jobLink.attributes('href')).toBe(scanUrl);
});
it('does not show scanned resources info if there is 0 scanned URL', () => {
mock.onGet(DAST_DIFF_ENDPOINT).reply(200, {
...dastDiffSuccessMock,
base_report_out_of_date: true,
scans: [
{
scanned_resources_count: 0,
job_path: scanUrl,
},
],
});
createWrapper({
...props,
enabledReports: {
dast: true,
},
});
return waitForMutation(wrapper.vm.$store, types.RECEIVE_DAST_DIFF_SUCCESS).then(() => {
expect(wrapper.text()).not.toContain('0 URLs scanned');
expect(wrapper.contains('[data-qa-selector="dast-ci-job-link"]')).toBe(false);
});
});
}); });
describe('sast reports', () => { describe('sast reports', () => {
......
...@@ -624,6 +624,16 @@ describe('security reports mutations', () => { ...@@ -624,6 +624,16 @@ describe('security reports mutations', () => {
describe('RECEIVE_DAST_DIFF_SUCCESS', () => { describe('RECEIVE_DAST_DIFF_SUCCESS', () => {
let reports = {}; let reports = {};
const scans = [
{
scanned_resources_count: 123,
job_path: '/group/project/-/jobs/123546789',
},
{
scanned_resources_count: 321,
job_path: '/group/project/-/jobs/987654321',
},
];
beforeEach(() => { beforeEach(() => {
reports = { reports = {
...@@ -635,6 +645,7 @@ describe('security reports mutations', () => { ...@@ -635,6 +645,7 @@ describe('security reports mutations', () => {
fixed: [{ name: 'fixed vuln 1', report_type: 'dast' }], fixed: [{ name: 'fixed vuln 1', report_type: 'dast' }],
existing: [{ name: 'existing vuln 1', report_type: 'dast' }], existing: [{ name: 'existing vuln 1', report_type: 'dast' }],
base_report_out_of_date: true, base_report_out_of_date: true,
scans,
}, },
}; };
mutations[types.RECEIVE_DAST_DIFF_SUCCESS](stateCopy, reports); mutations[types.RECEIVE_DAST_DIFF_SUCCESS](stateCopy, reports);
...@@ -644,6 +655,10 @@ describe('security reports mutations', () => { ...@@ -644,6 +655,10 @@ describe('security reports mutations', () => {
expect(stateCopy.dast.isLoading).toBe(false); expect(stateCopy.dast.isLoading).toBe(false);
}); });
it('should set scans', () => {
expect(stateCopy.dast.scans).toEqual(scans);
});
it('should set baseReportOutofDate to true', () => { it('should set baseReportOutofDate to true', () => {
expect(stateCopy.dast.baseReportOutofDate).toBe(true); expect(stateCopy.dast.baseReportOutofDate).toBe(true);
}); });
......
...@@ -71,6 +71,11 @@ msgstr "" ...@@ -71,6 +71,11 @@ msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\"" msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr "" msgstr ""
msgid "%d URL scanned"
msgid_plural "%d URLs scanned"
msgstr[0] ""
msgstr[1] ""
msgid "%d changed file" msgid "%d changed file"
msgid_plural "%d changed files" msgid_plural "%d changed files"
msgstr[0] "" msgstr[0] ""
...@@ -22621,6 +22626,9 @@ msgstr "" ...@@ -22621,6 +22626,9 @@ msgstr ""
msgid "View deployment" msgid "View deployment"
msgstr "" msgstr ""
msgid "View details"
msgstr ""
msgid "View details: %{details_url}" msgid "View details: %{details_url}"
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