Commit 791cadae authored by Filipa Lacerda's avatar Filipa Lacerda

Updates summary widget to render individual reports

parent bc248a8a
......@@ -8,7 +8,7 @@ import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
import SecurityReportApp from 'ee/pipelines/components/security_reports/security_report_app.vue'; // eslint-disable-line import/first
import SastSummaryWidget from 'ee/pipelines/components/security_reports/sast_report_summary_widget.vue'; // eslint-disable-line import/first
import SastSummaryWidget from 'ee/pipelines/components/security_reports/report_summary_widget.vue'; // eslint-disable-line import/first
Vue.use(Translate);
......@@ -142,7 +142,10 @@ export default () => {
render(createElement) {
return createElement('sast-summary-widget', {
props: {
unresolvedIssues: this.mediator.store.state.securityReports.sast.newIssues.length +
hasDependencyScanning: dependencyScanningEndpoint !== undefined,
hasSast: endpoint !== undefined,
sastIssues: this.mediator.store.state.securityReports.sast.newIssues.length,
dependencyScanningIssues:
this.mediator.store.state.securityReports.dependencyScanning.newIssues.length,
},
});
......
......@@ -4,30 +4,65 @@
import CiIcon from '~/vue_shared/components/ci_icon.vue';
export default {
name: 'SastSummaryReport',
name: 'SummaryReport',
components: {
CiIcon,
},
props: {
unresolvedIssues: {
sastIssues: {
type: Number,
required: false,
default: 0,
},
dependencyScanningIssues: {
type: Number,
required: false,
default: 0,
},
hasDependencyScanning: {
type: Boolean,
required: false,
default: false,
},
hasSast: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
sastLink() {
if (this.unresolvedIssues > 0) {
return this.link(this.sastIssues);
},
dependencyScanningLink() {
return this.link(this.dependencyScanningIssues);
},
sastIcon() {
return this.statusIcon(this.sastIssues);
},
dependencyScanningIcon() {
return this.statusIcon(this.dependencyScanningIssues);
},
},
methods: {
openTab() {
// This opens a tab outside of this Vue application
// It opens the securty report tab in the pipelines page and updates the URL
// This is needed because the tabs are built in haml+jquery
$('.pipelines-tabs a[data-action="security"]').tab('show');
},
link(issues) {
if (issues > 0) {
return n__(
'%d security vulnerability',
'%d security vulnerabilities',
this.unresolvedIssues,
'%d vulnerability',
'%d vulnerabilities',
issues,
);
}
return s__('ciReport|no security vulnerabilities');
return s__('ciReport|no vulnerabilities');
},
statusIcon() {
if (this.unresolvedIssues > 0) {
statusIcon(issues) {
if (issues > 0) {
return {
group: 'warning',
icon: 'status_warning',
......@@ -39,34 +74,53 @@
};
},
},
methods: {
openTab() {
// This opens a tab outside of this Vue application
// It opens the securty report tab in the pipelines page and updates the URL
// This is needed because the tabs are built in haml+jquery
$('.pipelines-tabs a[data-action="security"]').tab('show');
},
},
};
</script>
<template>
<div class="well-segment flex">
<ci-icon
:status="statusIcon"
class="flex flex-align-self-center"
/>
<div>
<div
class="well-segment flex js-sast-summary"
v-if="hasSast"
>
<ci-icon
:status="sastIcon"
class="flex flex-align-self-center"
/>
<span
class="prepend-left-10 flex flex-align-self-center"
<span
class="prepend-left-10 flex flex-align-self-center"
>
{{ s__('ciReport|SAST detected') }}
<button
type="button"
class="btn-link btn-blank prepend-left-5"
@click="openTab"
>
{{ sastLink }}
</button>
</span>
</div>
<div
class="well-segment flex js-dss-summary"
v-if="hasDependencyScanning"
>
{{ s__('ciReport|Security reports detected') }}
<button
type="button"
class="btn-link btn-blank prepend-left-5"
@click="openTab"
<ci-icon
:status="dependencyScanningIcon"
class="flex flex-align-self-center"
/>
<span
class="prepend-left-10 flex flex-align-self-center"
>
{{ sastLink }}
</button>
</span>
{{ s__('ciReport|Dependency scanning detected') }}
<button
type="button"
class="btn-link btn-blank prepend-left-5"
@click="openTab"
>
{{ dependencyScanningLink }}
</button>
</span>
</div>
</div>
</template>
---
title: Render dependency scanning in MR widget and CI view
merge_request:
author:
type: added
import Vue from 'vue';
import reportSummary from 'ee/pipelines/components/security_reports/sast_report_summary_widget.vue';
import reportSummary from 'ee/pipelines/components/security_reports/report_summary_widget.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { parsedSastIssuesHead } from 'spec/vue_shared/security_reports/mock_data';
describe('SAST report summary widget', () => {
describe('Report summary widget', () => {
let vm;
let Component;
......@@ -18,25 +17,40 @@ describe('SAST report summary widget', () => {
describe('with vulnerabilities', () => {
beforeEach(() => {
vm = mountComponent(Component, {
unresolvedIssues: parsedSastIssuesHead,
sastIssues: 2,
dependencyScanningIssues: 4,
hasSast: true,
hasDependencyScanning: true,
});
});
it('renders summary text with warning icon', () => {
expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('SAST degraded on 2 security vulnerabilities');
expect(vm.$el.querySelector('span').classList).toContain('ci-status-icon-warning');
it('renders summary text with warning icon for sast', () => {
expect(vm.$el.querySelector('.js-sast-summary').textContent.trim().replace(/\s\s+/g, ' ')).toEqual('SAST detected 2 vulnerabilities');
expect(vm.$el.querySelector('.js-sast-summary span').classList).toContain('ci-status-icon-warning');
});
it('renders summary text with warning icon for dependency scanning', () => {
expect(vm.$el.querySelector('.js-dss-summary').textContent.trim().replace(/\s\s+/g, ' ')).toEqual('Dependency scanning detected 4 vulnerabilities');
expect(vm.$el.querySelector('.js-dss-summary span').classList).toContain('ci-status-icon-warning');
});
});
describe('without vulnerabilities', () => {
beforeEach(() => {
vm = mountComponent(Component, {
hasSast: true,
hasDependencyScanning: true,
});
});
it('render summary text with success icon', () => {
expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toEqual('SAST detected no security vulnerabilities');
expect(vm.$el.querySelector('span').classList).toContain('ci-status-icon-success');
it('render summary text with success icon for sast', () => {
expect(vm.$el.querySelector('.js-sast-summary').textContent.trim().replace(/\s\s+/g, ' ')).toEqual('SAST detected no vulnerabilities');
expect(vm.$el.querySelector('.js-sast-summary span').classList).toContain('ci-status-icon-success');
});
it('render summary text with success icon for dependecy scanning', () => {
expect(vm.$el.querySelector('.js-dss-summary').textContent.trim().replace(/\s\s+/g, ' ')).toEqual('Dependency scanning detected no vulnerabilities');
expect(vm.$el.querySelector('.js-dss-summary span').classList).toContain('ci-status-icon-success');
});
});
});
......@@ -26,15 +26,34 @@ describe('Security Report App', () => {
resolvedIssues: [],
allIssues: [],
},
dependencyScanning: {
isLoading: false,
hasError: false,
newIssues: parsedSastIssuesHead,
resolvedIssues: [],
allIssues: [],
},
},
hasDependencyScanning: true,
hasSast: true,
});
});
it('renders the sast report', () => {
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual('SAST degraded on 2 security vulnerabilities');
expect(vm.$el.querySelectorAll('.js-mr-code-new-issues li').length).toEqual(parsedSastIssuesHead.length);
expect(vm.$el.querySelector('.js-sast-widget .js-code-text').textContent.trim()).toEqual('SAST degraded on 2 security vulnerabilities');
expect(vm.$el.querySelectorAll('.js-sast-widget .js-mr-code-new-issues li').length).toEqual(parsedSastIssuesHead.length);
const issue = vm.$el.querySelector('.js-sast-widget .js-mr-code-new-issues li').textContent;
expect(issue).toContain(parsedSastIssuesHead[0].message);
expect(issue).toContain(parsedSastIssuesHead[0].path);
});
it('renders the dependency scanning report', () => {
expect(vm.$el.querySelector('.js-dependency-scanning-widget .js-code-text').textContent.trim()).toEqual('Dependency scanning degraded on 2 security vulnerabilities');
expect(vm.$el.querySelectorAll('.js-dependency-scanning-widget .js-mr-code-new-issues li').length).toEqual(parsedSastIssuesHead.length);
const issue = vm.$el.querySelector('.js-mr-code-new-issues li').textContent;
const issue = vm.$el.querySelector('.js-dependency-scanning-widget .js-mr-code-new-issues li').textContent;
expect(issue).toContain(parsedSastIssuesHead[0].message);
expect(issue).toContain(parsedSastIssuesHead[0].path);
......
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