Commit 5b21719b authored by Dave Pisek's avatar Dave Pisek

Refactor: pass error scan from parent

parent 61418b8a
......@@ -71,6 +71,20 @@ export default {
primaryButtonText: s__('SecurityReports|Learn more about setting up your dashboard'),
};
},
scansWithErrors() {
const getScans = (reportSummary) => reportSummary?.scans || [];
const hasErrors = (scan) => Boolean(scan.errors?.length);
return this.securityReportSummary
? Object.values(this.securityReportSummary)
// generate flat array of all scans
.flatMap(getScans)
.filter(hasErrors)
: [];
},
hasScansWithErrors() {
return this.scansWithErrors.length > 0;
},
},
created() {
this.setSourceBranch(this.pipeline.sourceBranch);
......@@ -87,7 +101,7 @@ export default {
<template>
<div>
<div v-if="securityReportSummary" class="gl-my-5">
<scan-errors-alert :security-report-summary="securityReportSummary" class="gl-mb-5" />
<scan-errors-alert v-if="hasScansWithErrors" :scans="scansWithErrors" class="gl-mb-5" />
<security-reports-summary :summary="securityReportSummary" />
</div>
<security-dashboard
......
<script>
import { GlAccordion, GlAccordionItem, GlAlert, GlButton, GlSprintf } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { s__ } from '~/locale';
export default {
components: {
......@@ -12,34 +12,17 @@ export default {
},
inject: ['securityReportHelpPageLink'],
props: {
securityReportSummary: {
type: Object,
required: false,
default: () => ({}),
scans: {
type: Array,
required: true,
},
},
computed: {
scansWithErrors() {
const getScans = (reportSummary) => reportSummary?.scans || [];
const hasErrors = (scan) => Boolean(scan.errors?.length);
const addTitle = (scan) => ({
scansWithTitles() {
return this.scans.map((scan) => ({
...scan,
title: sprintf(s__('SecurityReports|%{errorName} (%{errorCount})'), {
errorName: scan.name,
errorCount: scan.errors.length,
}),
});
return this.securityReportSummary
? Object.values(this.securityReportSummary)
// generate flat array of all scans
.flatMap(getScans)
.filter(hasErrors)
.map(addTitle)
: [];
},
hasScansWithErrors() {
return this.scansWithErrors.length > 0;
title: `${scan.name} (${scan.errors.length})`,
}));
},
},
i18n: {
......@@ -52,7 +35,7 @@ export default {
</script>
<template>
<gl-alert v-if="hasScansWithErrors" variant="danger" :dismissible="false">
<gl-alert variant="danger" :dismissible="false">
<strong role="heading">
{{ $options.i18n.title }}
</strong>
......@@ -72,7 +55,7 @@ export default {
</p>
<gl-accordion :header-level="3">
<gl-accordion-item
v-for="{ name, errors, title } in scansWithErrors"
v-for="{ name, errors, title } in scansWithTitles"
:key="name"
:title="title"
>
......
......@@ -150,32 +150,66 @@ describe('Pipeline Security Dashboard component', () => {
});
});
describe('scan errors alert', () => {
const securityReportSummary = {
dast: {
scans: [
{
name: 'dast',
errors: [],
describe('scans error alert', () => {
describe('with errors', () => {
const securityReportSummary = {
scanner_1: {
// this scan contains errors
scans: [
{ errors: ['scanner 1 - error 1', 'scanner 1 - error 2'], name: 'foo' },
{ errors: ['scanner 1 - error 3', 'scanner 1 - error 4'], name: 'bar' },
],
},
scanner_2: null,
scanner_3: {
// this scan contains errors
scans: [{ errors: ['scanner 3 - error 1', 'scanner 3 - error 2'], name: 'baz' }],
},
scanner_4: {
scans: [{ errors: [], name: 'quz' }],
},
};
const scansWithErrors = [
...securityReportSummary.scanner_1.scans,
...securityReportSummary.scanner_3.scans,
];
beforeEach(() => {
factory({
data: {
securityReportSummary,
},
],
},
};
});
});
beforeEach(() => {
factory({
data: {
securityReportSummary,
},
it('shows an alert with information about each scan with errors', () => {
expect(findScanErrorsAlert().props('scans')).toEqual(scansWithErrors);
});
});
it('includes the alert', () => {
expect(findScanErrorsAlert().exists()).toBe(true);
});
describe('without errors', () => {
const securityReportSummary = {
dast: {
scans: [
{
name: 'dast',
errors: [],
},
],
},
};
it('passes the security report summary to the alert', () => {
expect(findScanErrorsAlert().props('securityReportSummary')).toBe(securityReportSummary);
beforeEach(() => {
factory({
data: {
securityReportSummary,
},
});
});
it('does not show the alert', () => {
expect(findScanErrorsAlert().exists()).toBe(false);
});
});
});
......
......@@ -4,36 +4,22 @@ import PipelineScanErrorsAlert from 'ee/security_dashboard/components/pipeline/s
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
const TEST_SECURITY_REPORT_SUMMARY = {
scanner_1: {
// this scan contains errors
scans: [
{ errors: ['scanner 1 - error 1', 'scanner 1 - error 2'], name: 'foo' },
{ errors: ['scanner 1 - error 3', 'scanner 1 - error 4'], name: 'bar' },
],
},
scanner_2: null,
scanner_3: {
// this scan contains errors
scans: [{ errors: ['scanner 3 - error 1', 'scanner 3 - error 2'], name: 'baz' }],
},
scanner_4: {
scans: [{ errors: [], name: 'quz' }],
},
};
const TEST_HELP_PAGE_LINK = 'http://help.com';
const TEST_SCANS_WITH_ERRORS = [
...TEST_SECURITY_REPORT_SUMMARY.scanner_1.scans,
...TEST_SECURITY_REPORT_SUMMARY.scanner_3.scans,
{ errors: ['scanner 1 - error 1', 'scanner 1 - error 2'], name: 'foo' },
{ errors: ['scanner 1 - error 3', 'scanner 1 - error 4'], name: 'bar' },
{ errors: ['scanner 3 - error 1', 'scanner 3 - error 2'], name: 'baz' },
];
describe('ee/security_dashboard/components/pipeline_scan_errors_alert.vue', () => {
let wrapper;
const createWrapper = (options) =>
const createWrapper = () =>
extendedWrapper(
shallowMount(PipelineScanErrorsAlert, {
...options,
propsData: {
scans: TEST_SCANS_WITH_ERRORS,
},
provide: {
securityReportHelpPageLink: TEST_HELP_PAGE_LINK,
},
......@@ -55,70 +41,54 @@ describe('ee/security_dashboard/components/pipeline_scan_errors_alert.vue', () =
wrapper.destroy();
});
describe('without scanner errors', () => {
beforeEach(() => {
wrapper = createWrapper({ propsData: { securityReportSummary: {} } });
});
beforeEach(() => {
wrapper = createWrapper();
});
it('does not show error alert', () => {
expect(findAlert().exists()).toBe(false);
it('shows a non-dismissible error alert', () => {
expect(findAlert().props()).toMatchObject({
variant: 'danger',
dismissible: false,
});
});
describe('with scanner errors', () => {
beforeEach(() => {
wrapper = createWrapper({
propsData: { securityReportSummary: TEST_SECURITY_REPORT_SUMMARY },
});
});
it('shows the correct title for the error alert', () => {
expect(findAlert().text()).toContain('Error parsing security reports');
});
it('shows a non-dismissible error alert', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().props()).toMatchObject({
variant: 'danger',
dismissible: false,
});
});
it('shows the correct description for the error-alert', () => {
expect(trimText(findAlert().text())).toContain(
'The security reports below contain one or more vulnerability findings that could not be parsed and were not recorded. Download the artifacts in the job output to investigate. Ensure any security report created conforms to the relevant JSON schema',
);
});
it('shows the correct title for the error alert', () => {
expect(findAlert().text()).toContain('Error parsing security reports');
});
it('links to the security-report help page', () => {
expect(findHelpPageLink().attributes('href')).toBe(TEST_HELP_PAGE_LINK);
});
it('shows the correct description for the error-alert', () => {
expect(trimText(findAlert().text())).toContain(
'The security reports below contain one or more vulnerability findings that could not be parsed and were not recorded. Download the artifacts in the job output to investigate. Ensure any security report created conforms to the relevant JSON schema',
);
describe('errors details', () => {
it('shows an accordion containing a list of scans with errors', () => {
expect(findAccordion().exists()).toBe(true);
expect(findAllAccordionItems()).toHaveLength(TEST_SCANS_WITH_ERRORS.length);
});
it('links to the security-report help page', () => {
expect(findHelpPageLink().exists()).toBe(true);
expect(findHelpPageLink().attributes('href')).toBe(TEST_HELP_PAGE_LINK);
it('shows a list containing details about each error', () => {
expect(findErrorList().exists()).toBe(true);
});
describe('errors details', () => {
it('shows an accordion containing a list of scans with errors', () => {
expect(findAccordion().exists()).toBe(true);
expect(findAllAccordionItems()).toHaveLength(TEST_SCANS_WITH_ERRORS.length);
});
describe.each(TEST_SCANS_WITH_ERRORS)('scan errors', (scan) => {
const currentScanTitle = `${scan.name} (${scan.errors.length})`;
const findAllAccordionItemsForCurrentScan = () =>
findAccordionItemsWithTitle(currentScanTitle);
const findAccordionItemForCurrentScan = () => findAllAccordionItemsForCurrentScan().at(0);
it('shows a list containing details about each error', () => {
expect(findErrorList().exists()).toBe(true);
it(`contains an accordion item with the correct title for scan "${scan.name}"`, () => {
expect(findAllAccordionItemsForCurrentScan()).toHaveLength(1);
});
describe.each(TEST_SCANS_WITH_ERRORS)('scan errors', (scan) => {
const currentScanTitle = `${scan.name} (${scan.errors.length})`;
const findAllAccordionItemsForCurrentScan = () =>
findAccordionItemsWithTitle(currentScanTitle);
const findAccordionItemForCurrentScan = () => findAllAccordionItemsForCurrentScan().at(0);
it(`contains an accordion item with the correct title for scan "${scan.name}"`, () => {
expect(findAllAccordionItemsForCurrentScan()).toHaveLength(1);
});
it(`contains a detailed list of errors for scan "${scan.name}}"`, () => {
expect(findAccordionItemForCurrentScan().find('ul').exists()).toBe(true);
expect(findAccordionItemForCurrentScan().findAll('li')).toHaveLength(scan.errors.length);
});
it(`contains a detailed list of errors for scan "${scan.name}}"`, () => {
expect(findAccordionItemForCurrentScan().find('ul').exists()).toBe(true);
expect(findAccordionItemForCurrentScan().findAll('li')).toHaveLength(scan.errors.length);
});
});
});
......
......@@ -29078,9 +29078,6 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project"
msgstr ""
msgid "SecurityReports|%{errorName} (%{errorCount})"
msgstr ""
msgid "SecurityReports|%{firstProject} and %{secondProject}"
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