Commit db70a774 authored by Alexander Turinske's avatar Alexander Turinske Committed by Andrew Fontaine

Add identifier column to the project security dash

- take vulnerability identifiers and get primary identifer
  and display it
- conditionally render the column on the project-level
  security dashboard and not on the group/instance-level
  security dashboards
- add tests
- update docs
parent d20e41ca
......@@ -74,7 +74,7 @@ You can also dismiss vulnerabilities in the table:
1. Select the checkbox for each vulnerability you want to dismiss.
1. In the menu that appears, select the reason for dismissal and click **Dismiss Selected**.
![Project Security Dashboard](img/project_security_dashboard_v13_2_noNav.png)
![Project Security Dashboard](img/project_security_dashboard_v13_2.png)
## Group Security Dashboard
......
......@@ -39,7 +39,7 @@ export default {
return this.vulnerability.severity || ' ';
},
vulnerabilityIdentifier() {
return getPrimaryIdentifier(this.vulnerability.identifiers);
return getPrimaryIdentifier(this.vulnerability.identifiers, 'external_type');
},
vulnerabilityNamespace() {
const { project, location } = this.vulnerability;
......
......@@ -5,8 +5,8 @@ import { PRIMARY_IDENTIFIER_TYPE } from 'ee/security_dashboard/store/constants';
* @param {Array} identifiers all available identifiers
* @returns {String} the primary identifier's name
*/
const getPrimaryIdentifier = (identifiers = []) => {
const identifier = identifiers.find(value => value.external_type === PRIMARY_IDENTIFIER_TYPE);
const getPrimaryIdentifier = (identifiers = [], property) => {
const identifier = identifiers.find(value => value[property] === PRIMARY_IDENTIFIER_TYPE);
return identifier?.name || identifiers[0]?.name || '';
};
......
......@@ -109,7 +109,8 @@ export default {
:empty-state-svg-path="emptyStateSvgPath"
:filters="filters"
:vulnerabilities="vulnerabilities"
should-show-report-type
:should-show-identifier="true"
:should-show-report-type="true"
@refetch-vulnerabilities="refetchVulnerabilities"
>
<template #emptyState>
......
......@@ -2,6 +2,7 @@
import { s__, __ } from '~/locale';
import { GlEmptyState, GlFormCheckbox, GlLink, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import RemediatedBadge from './remediated_badge.vue';
import getPrimaryIdentifier from 'ee/vue_shared/security_reports/store/utils/get_primary_identifier';
import SeverityBadge from 'ee/vue_shared/security_reports/components/severity_badge.vue';
import SelectionSummary from 'ee/security_dashboard/components/selection_summary.vue';
import IssueLink from './issue_link.vue';
......@@ -37,6 +38,11 @@ export default {
required: false,
default: null,
},
shouldShowIdentifier: {
type: Boolean,
required: false,
default: false,
},
shouldShowReportType: {
type: Boolean,
required: false,
......@@ -108,6 +114,15 @@ export default {
});
}
if (this.shouldShowIdentifier) {
baseFields.push({
key: 'identifier',
label: s__('Vulnerability|Identifier'),
thClass: commonThClass,
tdClass: 'gl-word-break-all',
});
}
if (this.shouldShowReportType) {
baseFields.push({
key: 'reportType',
......@@ -136,6 +151,9 @@ export default {
deselectAllVulnerabilities() {
this.selectedVulnerabilities = {};
},
primaryIdentifier(identifiers) {
return getPrimaryIdentifier(identifiers, 'externalType');
},
isSelected(vulnerability = {}) {
return Boolean(this.selectedVulnerabilities[vulnerability.id]);
},
......@@ -251,6 +269,12 @@ export default {
<remediated-badge v-if="item.resolved_on_default_branch" class="ml-2" />
</template>
<template #cell(identifier)="{ item }">
<span data-testid="vulnerability-identifier">
{{ primaryIdentifier(item.identifiers) }}
</span>
</template>
<template #cell(reportType)="{ item }">
<span data-testid="vulnerability-report-type" class="text-capitalize">{{
useConvertReportType(item.reportType)
......
......@@ -15,6 +15,10 @@ fragment Vulnerability on Vulnerability {
}
}
}
identifiers {
externalType
name
}
location {
... on VulnerabilityLocationContainerScanning {
image
......
---
title: Add identifier column to the project security dashoard
merge_request: 35760
author:
type: changed
......@@ -52,6 +52,7 @@ describe('First Class Group Dashboard Vulnerabilities Component', () => {
emptyStateSvgPath,
filters: null,
isLoading: true,
shouldShowIdentifier: false,
shouldShowReportType: false,
shouldShowSelection: true,
shouldShowProjectNamespace: true,
......@@ -144,6 +145,7 @@ describe('First Class Group Dashboard Vulnerabilities Component', () => {
emptyStateSvgPath,
filters: null,
isLoading: false,
shouldShowIdentifier: false,
shouldShowReportType: false,
shouldShowSelection: true,
shouldShowProjectNamespace: true,
......
......@@ -77,6 +77,7 @@ describe('First Class Instance Dashboard Vulnerabilities Component', () => {
emptyStateSvgPath,
filters: null,
isLoading: true,
shouldShowIdentifier: false,
shouldShowReportType: false,
shouldShowSelection: true,
shouldShowProjectNamespace: true,
......@@ -160,6 +161,7 @@ describe('First Class Instance Dashboard Vulnerabilities Component', () => {
emptyStateSvgPath,
filters: null,
isLoading: false,
shouldShowIdentifier: false,
shouldShowReportType: false,
shouldShowSelection: true,
shouldShowProjectNamespace: true,
......
......@@ -107,10 +107,13 @@ describe('security reports utils', () => {
{ external_type: 'gemnaisum', name: 'GEMNASIUM-1337' },
];
it('should return the `cve` identifier if a `cve` identifier does exist', () => {
expect(getPrimaryIdentifiers(identifiers)).toBe(identifiers[0].name);
expect(getPrimaryIdentifiers(identifiers, 'external_type')).toBe(identifiers[0].name);
});
it('should return the first identifier if the property for type does not exist', () => {
expect(getPrimaryIdentifiers(identifiers, 'externalType')).toBe(identifiers[0].name);
});
it('should return the first identifier if a `cve` identifier does not exist', () => {
expect(getPrimaryIdentifiers([identifiers[1]])).toBe(identifiers[1].name);
expect(getPrimaryIdentifiers([identifiers[1]], 'external_type')).toBe(identifiers[1].name);
});
it('should return an empty string if identifiers is empty', () => {
expect(getPrimaryIdentifiers()).toBe('');
......
export const generateVulnerabilities = () => [
{
id: 'id_0',
identifiers: [
{
externalType: 'cve',
name: 'CVE-2018-1234',
},
{
externalType: 'gemnasium',
name: 'Gemnasium-2018-1234',
},
],
title: 'Vulnerability 0',
severity: 'critical',
state: 'dismissed',
......@@ -15,6 +25,12 @@ export const generateVulnerabilities = () => [
},
{
id: 'id_1',
identifiers: [
{
externalType: 'gemnasium',
name: 'Gemnasium-2018-1234',
},
],
title: 'Vulnerability 1',
severity: 'high',
state: 'opened',
......
......@@ -131,9 +131,14 @@ describe('Vulnerability list component', () => {
);
});
it('should not display the vulnerability identifier', () => {
const cell = findDataCell('vulnerability-identifier');
expect(cell.exists()).toBe(false);
});
it('should not display the vulnerability report type', () => {
const scannerCell = findRow().find('[data-testid="vulnerability-report-type"');
expect(scannerCell.exists()).toBe(false);
const cell = findDataCell('vulnerability-report-type');
expect(cell.exists()).toBe(false);
});
it('should not display the vulnerability locations', () => {
......@@ -166,7 +171,11 @@ describe('Vulnerability list component', () => {
beforeEach(() => {
newVulnerabilities = generateVulnerabilities();
wrapper = createWrapper({
props: { vulnerabilities: newVulnerabilities, shouldShowReportType: true },
props: {
vulnerabilities: newVulnerabilities,
shouldShowIdentifier: true,
shouldShowReportType: true,
},
});
});
......@@ -185,6 +194,13 @@ describe('Vulnerability list component', () => {
);
});
it('should correctly render the identifier', () => {
const cells = findDataCells('vulnerability-identifier');
expect(cells.at(0).text()).toBe(newVulnerabilities[0].identifiers[0].name);
expect(cells.at(1).text()).toBe(newVulnerabilities[1].identifiers[0].name);
});
it('should display the vulnerability report type', () => {
const cells = findDataCells('vulnerability-report-type');
expect(cells.at(0).text()).toBe('SAST');
......
......@@ -25677,6 +25677,9 @@ msgstr ""
msgid "Vulnerability|File"
msgstr ""
msgid "Vulnerability|Identifier"
msgstr ""
msgid "Vulnerability|Identifiers"
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