Commit da8bb7ab authored by Alexander Turinske's avatar Alexander Turinske Committed by Mike Greiling

Add identifiers column to pipeline security tab

- create utility method to find primary identifier out of array of
  identifiers
- add tests
parent 5a5b0156
...@@ -45,7 +45,7 @@ At the pipeline level, the Security section displays the vulnerabilities present ...@@ -45,7 +45,7 @@ At the pipeline level, the Security section displays the vulnerabilities present
Visit the page for any pipeline which has run any of the [supported reports](#supported-reports). Click the **Security** tab to view the Security findings. Visit the page for any pipeline which has run any of the [supported reports](#supported-reports). Click the **Security** tab to view the Security findings.
![Pipeline Security Dashboard](img/pipeline_security_dashboard_v12_6.png) ![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_2.png)
NOTE: **Note:** NOTE: **Note:**
A pipeline consists of multiple jobs, including SAST and DAST scanning. If any job fails to finish for any reason, the security dashboard will not show SAST scanner output. For example, if the SAST job finishes but the DAST job fails, the security dashboard will not show SAST results. The analyzer will output an [exit code](../../../development/integrations/secure.md#exit-code) on failure. A pipeline consists of multiple jobs, including SAST and DAST scanning. If any job fails to finish for any reason, the security dashboard will not show SAST scanner output. For example, if the SAST job finishes but the DAST job fails, the security dashboard will not show SAST results. The analyzer will output an [exit code](../../../development/integrations/secure.md#exit-code) on failure.
......
...@@ -105,7 +105,7 @@ back to both GitLab and GitHub when completed. ...@@ -105,7 +105,7 @@ back to both GitLab and GitHub when completed.
1. The result of the job will be visible directly from the pipeline view: 1. The result of the job will be visible directly from the pipeline view:
![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v12_6.png) ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v13_2.png)
NOTE: **Note:** NOTE: **Note:**
If you don't commit very often to your project, you may want to use If you don't commit very often to your project, you may want to use
......
...@@ -77,6 +77,9 @@ export default { ...@@ -77,6 +77,9 @@ export default {
<div class="table-section flex-grow-1" role="rowheader"> <div class="table-section flex-grow-1" role="rowheader">
{{ s__('Reports|Vulnerability') }} {{ s__('Reports|Vulnerability') }}
</div> </div>
<div class="table-section section-15" role="rowheader">
{{ s__('Reports|Identifier') }}
</div>
<div class="table-section section-20" role="rowheader"></div> <div class="table-section section-20" role="rowheader"></div>
</div> </div>
......
...@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue'; ...@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import VulnerabilityActionButtons from './vulnerability_action_buttons.vue'; import VulnerabilityActionButtons from './vulnerability_action_buttons.vue';
import VulnerabilityIssueLink from './vulnerability_issue_link.vue'; import VulnerabilityIssueLink from './vulnerability_issue_link.vue';
import { DASHBOARD_TYPES } from '../store/constants'; import { DASHBOARD_TYPES } from '../store/constants';
import getPrimaryIdentifier from 'ee/vue_shared/security_reports/store/utils/get_primary_identifier';
export default { export default {
name: 'SecurityDashboardTableRow', name: 'SecurityDashboardTableRow',
...@@ -36,6 +37,9 @@ export default { ...@@ -36,6 +37,9 @@ export default {
severity() { severity() {
return this.vulnerability.severity || ' '; return this.vulnerability.severity || ' ';
}, },
vulnerabilityIdentifier() {
return getPrimaryIdentifier(this.vulnerability.identifiers);
},
vulnerabilityNamespace() { vulnerabilityNamespace() {
const { project, location } = this.vulnerability; const { project, location } = this.vulnerability;
if (this.dashboardType === DASHBOARD_TYPES.GROUP) { if (this.dashboardType === DASHBOARD_TYPES.GROUP) {
...@@ -135,6 +139,13 @@ export default { ...@@ -135,6 +139,13 @@ export default {
</div> </div>
</div> </div>
<div class="table-section section-15">
<div class="table-mobile-header" role="rowheader">{{ s__('Reports|Identifier') }}</div>
<div class="table-mobile-content">
{{ vulnerabilityIdentifier }}
</div>
</div>
<div class="table-section section-20"> <div class="table-section section-20">
<div class="table-mobile-header" role="rowheader">{{ s__('Reports|Actions') }}</div> <div class="table-mobile-header" role="rowheader">{{ s__('Reports|Actions') }}</div>
<div class="table-mobile-content action-buttons d-flex justify-content-end"> <div class="table-mobile-content action-buttons d-flex justify-content-end">
......
...@@ -30,3 +30,5 @@ export const UNSCANNED_PROJECTS_DATE_RANGES = [ ...@@ -30,3 +30,5 @@ export const UNSCANNED_PROJECTS_DATE_RANGES = [
{ description: s__('UnscannedProjects|30 or more days'), fromDay: 30, toDay: 60 }, { description: s__('UnscannedProjects|30 or more days'), fromDay: 30, toDay: 60 },
{ description: s__('UnscannedProjects|60 or more days'), fromDay: 60, toDay: Infinity }, { description: s__('UnscannedProjects|60 or more days'), fromDay: 60, toDay: Infinity },
]; ];
export const PRIMARY_IDENTIFIER_TYPE = 'cve';
import { PRIMARY_IDENTIFIER_TYPE } from 'ee/security_dashboard/store/constants';
/**
* Finds the name of the primary identifier or returns the name of the first identifier
*
* @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);
return identifier?.name || identifiers[0]?.name || '';
};
export default getPrimaryIdentifier;
---
title: Add identifiers column to pipeline security tab
merge_request: 35284
author:
type: changed
...@@ -74,16 +74,20 @@ describe('Security Dashboard Table Row', () => { ...@@ -74,16 +74,20 @@ describe('Security Dashboard Table Row', () => {
findContent(0) findContent(0)
.text() .text()
.toLowerCase(), .toLowerCase(),
).toContain(wrapper.props().vulnerability.severity); ).toContain(vulnerability.severity);
});
it('should render the identifier name', () => {
expect(findContent(2).text()).toContain(vulnerability.identifiers[0].name);
}); });
describe('the project name', () => { describe('the project name', () => {
it('should render the name', () => { it('should render the name', () => {
expect(findContent(1).text()).toContain(wrapper.props().vulnerability.name); expect(findContent(1).text()).toContain(vulnerability.name);
}); });
it('should render the project namespace', () => { it('should render the project namespace', () => {
expect(findContent(1).text()).toContain(wrapper.props().vulnerability.location.file); expect(findContent(1).text()).toContain(vulnerability.location.file);
}); });
it('should fire the openModal action when clicked', () => { it('should fire the openModal action when clicked', () => {
...@@ -108,7 +112,7 @@ describe('Security Dashboard Table Row', () => { ...@@ -108,7 +112,7 @@ describe('Security Dashboard Table Row', () => {
}); });
it('should contain project name as the namespace', () => { it('should contain project name as the namespace', () => {
expect(findContent(1).text()).toContain(wrapper.props().vulnerability.project.full_name); expect(findContent(1).text()).toContain(vulnerability.project.full_name);
}); });
}); });
...@@ -121,7 +125,7 @@ describe('Security Dashboard Table Row', () => { ...@@ -121,7 +125,7 @@ describe('Security Dashboard Table Row', () => {
}); });
it('should contain container image as the namespace', () => { it('should contain container image as the namespace', () => {
expect(findContent(1).text()).toContain(wrapper.props().vulnerability.location.image); expect(findContent(1).text()).toContain(vulnerability.location.image);
}); });
}); });
}); });
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
MEDIUM, MEDIUM,
LOW, LOW,
} from 'ee/security_dashboard/store/modules/vulnerabilities/constants'; } from 'ee/security_dashboard/store/modules/vulnerabilities/constants';
import getPrimaryIdentifiers from 'ee/vue_shared/security_reports/store/utils/get_primary_identifier';
describe('security reports utils', () => { describe('security reports utils', () => {
describe('findIssueIndex', () => { describe('findIssueIndex', () => {
...@@ -81,6 +82,22 @@ describe('security reports utils', () => { ...@@ -81,6 +82,22 @@ describe('security reports utils', () => {
}); });
}); });
describe('getPrimaryIdentifier', () => {
const identifiers = [
{ external_type: 'cve', name: 'CVE-1337' },
{ 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);
});
it('should return the first identifier if a `cve` identifier does not exist', () => {
expect(getPrimaryIdentifiers([identifiers[1]])).toBe(identifiers[1].name);
});
it('should return an empty string if identifiers is empty', () => {
expect(getPrimaryIdentifiers()).toBe('');
});
});
describe('groupedTextBuilder', () => { describe('groupedTextBuilder', () => {
const critical = 2; const critical = 2;
const high = 4; const high = 4;
......
...@@ -19150,6 +19150,9 @@ msgstr "" ...@@ -19150,6 +19150,9 @@ msgstr ""
msgid "Reports|Failure" msgid "Reports|Failure"
msgstr "" msgstr ""
msgid "Reports|Identifier"
msgstr ""
msgid "Reports|Metrics reports are loading" msgid "Reports|Metrics reports are loading"
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