Commit 41611031 authored by Mark Florian's avatar Mark Florian

Merge branch '267103-update-mr-widget-vulnerability-message' into 'master'

Update MR widget vulnerability message

See merge request gitlab-org/gitlab!46167
parents 0d0f9a2e 3ca43788
...@@ -2,27 +2,58 @@ ...@@ -2,27 +2,58 @@
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { SEVERITY_CLASS_NAME_MAP } from './constants'; import { SEVERITY_CLASS_NAME_MAP } from './constants';
const makeSeveritySlot = (createElement, severity) => ({ content }) =>
createElement('strong', { class: SEVERITY_CLASS_NAME_MAP[severity] }, content);
export default { export default {
functional: true, components: {
GlSprintf,
},
props: { props: {
message: { message: {
type: String, type: Object,
required: true, required: true,
}, },
}, },
render(createElement, context) { computed: {
const { message } = context.props; shouldShowCountMessage() {
return !this.message.status && Boolean(this.message.countMessage);
return createElement(GlSprintf, { },
props: { message }, },
scopedSlots: { methods: {
critical: makeSeveritySlot(createElement, 'critical'), getSeverityClass(severity) {
high: makeSeveritySlot(createElement, 'high'), return SEVERITY_CLASS_NAME_MAP[severity];
}, },
}); },
slotNames: ['critical', 'high', 'other'],
spacingClasses: {
critical: 'gl-pl-4',
high: 'gl-px-2',
other: 'gl-px-2',
}, },
}; };
</script> </script>
<template>
<span>
<gl-sprintf :message="message.message">
<template #total="{content}">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
<span v-if="shouldShowCountMessage" class="gl-font-sm">
<gl-sprintf :message="message.countMessage">
<template v-for="slotName in $options.slotNames" #[slotName]="{content}">
<span :key="slotName">
<strong
v-if="message[slotName] > 0"
:class="[getSeverityClass(slotName), $options.spacingClasses[slotName]]"
>
{{ content }}
</strong>
<span v-else :class="$options.spacingClasses[slotName]">
{{ content }}
</span>
</span>
</template>
</gl-sprintf>
</span>
</span>
</template>
...@@ -555,7 +555,6 @@ export default { ...@@ -555,7 +555,6 @@ export default {
<template v-if="hasCoverageFuzzingReports"> <template v-if="hasCoverageFuzzingReports">
<summary-row <summary-row
:summary="groupedCoverageFuzzingText"
:status-icon="coverageFuzzingStatusIcon" :status-icon="coverageFuzzingStatusIcon"
:popover-options="coverageFuzzingPopover" :popover-options="coverageFuzzingPopover"
class="js-coverage-fuzzing-widget" class="js-coverage-fuzzing-widget"
......
...@@ -64,20 +64,20 @@ export const groupedSummaryText = (state, getters) => { ...@@ -64,20 +64,20 @@ export const groupedSummaryText = (state, getters) => {
// All reports are loading // All reports are loading
if (getters.areAllReportsLoading) { if (getters.areAllReportsLoading) {
return sprintf(messages.TRANSLATION_IS_LOADING, { reportType }); return { message: sprintf(messages.TRANSLATION_IS_LOADING, { reportType }) };
} }
// All reports returned error // All reports returned error
if (getters.allReportsHaveError) { if (getters.allReportsHaveError) {
return s__('ciReport|Security scanning failed loading any results'); return { message: s__('ciReport|Security scanning failed loading any results') };
} }
if (getters.areReportsLoading && getters.anyReportHasError) { if (getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|(is loading, errors when loading results)'); status = s__('ciReport|is loading, errors when loading results');
} else if (getters.areReportsLoading && !getters.anyReportHasError) { } else if (getters.areReportsLoading && !getters.anyReportHasError) {
status = s__('ciReport|(is loading)'); status = s__('ciReport|is loading');
} else if (!getters.areReportsLoading && getters.anyReportHasError) { } else if (!getters.areReportsLoading && getters.anyReportHasError) {
status = s__('ciReport|(errors when loading results)'); status = s__('ciReport|: Loading resulted in an error');
} }
const { critical, high, other } = getters.summaryCounts; const { critical, high, other } = getters.summaryCounts;
......
...@@ -9,15 +9,38 @@ import { __, n__, sprintf } from '~/locale'; ...@@ -9,15 +9,38 @@ import { __, n__, sprintf } from '~/locale';
export const findIssueIndex = (issues, issue) => export const findIssueIndex = (issues, issue) =>
issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint); issues.findIndex(el => el.project_fingerprint === issue.project_fingerprint);
const createCountMessage = ({ critical, high, other, total }) => {
const otherMessage = n__('%d Other', '%d Others', other);
const countMessage = __(
'%{criticalStart}%{critical} Critical%{criticalEnd} %{highStart}%{high} High%{highEnd} and %{otherStart}%{otherMessage}%{otherEnd}',
);
return total ? sprintf(countMessage, { critical, high, otherMessage }) : '';
};
const createStatusMessage = ({ reportType, status, total }) => {
const vulnMessage = n__('vulnerability', 'vulnerabilities', total);
let message;
if (status) {
message = __('%{reportType} %{status}');
} else if (!total) {
message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.');
} else {
message = __(
'%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}',
);
}
return sprintf(message, { reportType, status, total, vulnMessage });
};
/** /**
* Takes an object of options and returns an externalized string representing * Takes an object of options and returns the object with an externalized string representing
* the critical, high, and other severity vulnerabilities for a given report. * the critical, high, and other severity vulnerabilities for a given report.
* *
* The resulting string _may_ still contain sprintf-style placeholders. These * The resulting string _may_ still contain sprintf-style placeholders. These
* are left in place so they can be replaced with markup, via the * are left in place so they can be replaced with markup, via the
* SecuritySummary component. * SecuritySummary component.
* @param {{reportType: string, status: string, critical: number, high: number, other: number}} options * @param {{reportType: string, status: string, critical: number, high: number, other: number}} options
* @returns {string} * @returns {Object} the parameters with an externalized string
*/ */
export const groupedTextBuilder = ({ export const groupedTextBuilder = ({
reportType = '', reportType = '',
...@@ -26,92 +49,17 @@ export const groupedTextBuilder = ({ ...@@ -26,92 +49,17 @@ export const groupedTextBuilder = ({
high = 0, high = 0,
other = 0, other = 0,
} = {}) => { } = {}) => {
// This approach uses bitwise (ish) flags to determine which vulnerabilities const total = critical + high + other;
// we have, without the need for too many nested levels of if/else statements.
//
// Here's a video explaining how it works
// https://youtu.be/qZzKNC7TPbA
//
// Here's a link to a similar approach on MDN:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Examples
let options = 0;
const HAS_CRITICAL = 1;
const HAS_HIGH = 2;
const HAS_OTHER = 4;
let message;
if (critical) {
options += HAS_CRITICAL;
}
if (high) {
options += HAS_HIGH;
}
if (other) {
options += HAS_OTHER;
}
switch (options) {
case HAS_CRITICAL:
message = n__(
'%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerability.',
'%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerabilities.',
critical,
);
break;
case HAS_HIGH: return {
message = n__( countMessage: createCountMessage({ critical, high, other, total }),
'%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerability.', message: createStatusMessage({ reportType, status, total }),
'%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerabilities.',
high,
);
break;
case HAS_OTHER:
message = n__(
'%{reportType} %{status} detected %{other} vulnerability.',
'%{reportType} %{status} detected %{other} vulnerabilities.',
other,
);
break;
case HAS_CRITICAL + HAS_HIGH:
message = __(
'%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} and %{highStart}%{high} high%{highEnd} severity vulnerabilities.',
);
break;
case HAS_CRITICAL + HAS_OTHER:
message = __(
'%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerabilities out of %{total}.',
);
break;
case HAS_HIGH + HAS_OTHER:
message = __(
'%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerabilities out of %{total}.',
);
break;
case HAS_CRITICAL + HAS_HIGH + HAS_OTHER:
message = __(
'%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} and %{highStart}%{high} high%{highEnd} severity vulnerabilities out of %{total}.',
);
break;
default:
message = __('%{reportType} %{status} detected no vulnerabilities.');
}
return sprintf(message, {
reportType,
status,
critical, critical,
high, high,
other, other,
total: critical + high + other, status,
}).replace(/\s\s+/g, ' '); total,
};
}; };
export const statusIcon = (loading = false, failed = false, newIssues = 0, neutralIssues = 0) => { export const statusIcon = (loading = false, failed = false, newIssues = 0, neutralIssues = 0) => {
...@@ -156,11 +104,11 @@ export const countVulnerabilities = (vulnerabilities = []) => { ...@@ -156,11 +104,11 @@ export const countVulnerabilities = (vulnerabilities = []) => {
*/ */
export const groupedReportText = (report, reportType, errorMessage, loadingMessage) => { export const groupedReportText = (report, reportType, errorMessage, loadingMessage) => {
if (report.hasError) { if (report.hasError) {
return errorMessage; return { message: errorMessage };
} }
if (report.isLoading) { if (report.isLoading) {
return loadingMessage; return { message: loadingMessage };
} }
return groupedTextBuilder({ return groupedTextBuilder({
......
---
title: Update MR widget vulnerability message
merge_request: 46167
author:
type: changed
...@@ -140,7 +140,7 @@ describe('ee merge request widget options', () => { ...@@ -140,7 +140,7 @@ describe('ee merge request widget options', () => {
`${SAST_SELECTOR} .report-block-list-issue-description`, `${SAST_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
).toEqual('SAST detected 1 critical severity vulnerability.'); ).toEqual('SAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others');
done(); done();
}); });
}); });
...@@ -235,7 +235,9 @@ describe('ee merge request widget options', () => { ...@@ -235,7 +235,9 @@ describe('ee merge request widget options', () => {
`${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`, `${DEPENDENCY_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
).toEqual('Dependency scanning detected 1 critical and 1 high severity vulnerabilities.'); ).toEqual(
'Dependency scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
);
done(); done();
}); });
}); });
...@@ -660,7 +662,9 @@ describe('ee merge request widget options', () => { ...@@ -660,7 +662,9 @@ describe('ee merge request widget options', () => {
`${CONTAINER_SCANNING_SELECTOR} .report-block-list-issue-description`, `${CONTAINER_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
).toEqual('Container scanning detected 1 critical and 1 high severity vulnerabilities.'); ).toEqual(
'Container scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
);
done(); done();
}); });
}); });
...@@ -730,10 +734,12 @@ describe('ee merge request widget options', () => { ...@@ -730,10 +734,12 @@ describe('ee merge request widget options', () => {
it('should render provided data', done => { it('should render provided data', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findExtendedSecurityWidget() trimText(
.querySelector(`${DAST_SELECTOR} .report-block-list-issue-description`) findExtendedSecurityWidget().querySelector(
.textContent.trim(), `${DAST_SELECTOR} .report-block-list-issue-description`,
).toEqual('DAST detected 1 critical severity vulnerability.'); ).textContent,
),
).toEqual('DAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others');
done(); done();
}); });
}); });
...@@ -806,10 +812,14 @@ describe('ee merge request widget options', () => { ...@@ -806,10 +812,14 @@ describe('ee merge request widget options', () => {
it('should render provided data', done => { it('should render provided data', done => {
setImmediate(() => { setImmediate(() => {
expect( expect(
findExtendedSecurityWidget() trimText(
.querySelector(`${COVERAGE_FUZZING_SELECTOR} .report-block-list-issue-description`) findExtendedSecurityWidget().querySelector(
.textContent.trim(), `${COVERAGE_FUZZING_SELECTOR} .report-block-list-issue-description`,
).toEqual('Coverage fuzzing detected 1 critical and 1 high severity vulnerabilities.'); ).textContent,
),
).toEqual(
'Coverage fuzzing detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
);
done(); done();
}); });
}); });
...@@ -884,7 +894,9 @@ describe('ee merge request widget options', () => { ...@@ -884,7 +894,9 @@ describe('ee merge request widget options', () => {
`${SECRET_SCANNING_SELECTOR} .report-block-list-issue-description`, `${SECRET_SCANNING_SELECTOR} .report-block-list-issue-description`,
).textContent, ).textContent,
), ),
).toEqual('Secret scanning detected 1 critical and 1 high severity vulnerabilities.'); ).toEqual(
'Secret scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
);
done(); done();
}); });
}); });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Severity Summary given the message "" interpolates correctly 1`] = `<div />`; exports[`Severity Summary given the message {"countMessage": "%{criticalStart}0 Critical%{criticalEnd} %{highStart}1 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 0, "high": 1, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
<span>
exports[`Severity Summary given the message "%{criticalStart}1 critical%{criticalEnd} and %{highStart}2 high%{highEnd}" interpolates correctly 1`] = ` Security scanning detected
<div> <strong>
<strong 1
class="text-danger-800"
>
1 critical
</strong> </strong>
and potential vulnerability
<strong <span
class="text-danger-600" class="gl-font-sm"
> >
2 high <span>
</strong> <span
</div> class="gl-pl-4"
>
0 Critical
</span>
</span>
<span>
<strong
class="text-danger-600 gl-px-2"
>
1 High
</strong>
</span>
and
<span>
<span
class="gl-px-2"
>
0 Others
</span>
</span>
</span>
</span>
`; `;
exports[`Severity Summary given the message "%{criticalStart}1 critical%{criticalEnd}" interpolates correctly 1`] = ` exports[`Severity Summary given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 0, "message": "Security scanning detected %{totalStart}1%{totalEnd} potential vulnerability", "other": 0, "status": "", "total": 1} interpolates correctly 1`] = `
<div> <span>
<strong Security scanning detected
class="text-danger-800" <strong>
> 1
1 critical
</strong> </strong>
</div> potential vulnerability
<span
class="gl-font-sm"
>
<span>
<strong
class="text-danger-800 gl-pl-4"
>
1 Critical
</strong>
</span>
<span>
<span
class="gl-px-2"
>
0 High
</span>
</span>
and
<span>
<span
class="gl-px-2"
>
0 Others
</span>
</span>
</span>
</span>
`; `;
exports[`Severity Summary given the message "%{highStart}1 high%{highEnd}" interpolates correctly 1`] = ` exports[`Severity Summary given the message {"countMessage": "%{criticalStart}1 Critical%{criticalEnd} %{highStart}2 High%{highEnd} and %{otherStart}0 Others%{otherEnd}", "critical": 1, "high": 2, "message": "Security scanning detected %{totalStart}3%{totalEnd} potential vulnerabilities", "other": 0, "status": "", "total": 3} interpolates correctly 1`] = `
<div> <span>
<strong Security scanning detected
class="text-danger-600" <strong>
> 3
1 high
</strong> </strong>
</div> potential vulnerabilities
<span
class="gl-font-sm"
>
<span>
<strong
class="text-danger-800 gl-pl-4"
>
1 Critical
</strong>
</span>
<span>
<strong
class="text-danger-600 gl-px-2"
>
2 High
</strong>
</span>
and
<span>
<span
class="gl-px-2"
>
0 Others
</span>
</span>
</span>
</span>
`;
exports[`Severity Summary given the message {"message": ""} interpolates correctly 1`] = `
<span>
<!---->
</span>
`; `;
exports[`Severity Summary given the message "foo" interpolates correctly 1`] = ` exports[`Severity Summary given the message {"message": "foo"} interpolates correctly 1`] = `
<div> <span>
foo foo
</div> <!---->
</span>
`; `;
import { mount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import { groupedTextBuilder } from 'ee/vue_shared/security_reports/store/utils';
import SecuritySummary from 'ee/vue_shared/security_reports/components/security_summary.vue'; import SecuritySummary from 'ee/vue_shared/security_reports/components/security_summary.vue';
describe('Severity Summary', () => { describe('Severity Summary', () => {
let wrapper; let wrapper;
const createWrapper = message => { const createWrapper = message => {
wrapper = mount({ wrapper = shallowMount(SecuritySummary, {
components: { propsData: { message },
SecuritySummary, stubs: {
GlSprintf,
}, },
data() {
return {
message,
};
},
template: `<div><security-summary :message="message" /></div>`,
}); });
}; };
...@@ -24,11 +21,11 @@ describe('Severity Summary', () => { ...@@ -24,11 +21,11 @@ describe('Severity Summary', () => {
}); });
describe.each([ describe.each([
'', { message: '' },
'foo', { message: 'foo' },
'%{criticalStart}1 critical%{criticalEnd}', groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 0, total: 1 }),
'%{highStart}1 high%{highEnd}', groupedTextBuilder({ reportType: 'Security scanning', critical: 0, high: 1, total: 1 }),
'%{criticalStart}1 critical%{criticalEnd} and %{highStart}2 high%{highEnd}', groupedTextBuilder({ reportType: 'Security scanning', critical: 1, high: 2, total: 3 }),
])('given the message %p', message => { ])('given the message %p', message => {
beforeEach(() => { beforeEach(() => {
createWrapper(message); createWrapper(message);
......
...@@ -93,7 +93,8 @@ describe('Grouped security reports app', () => { ...@@ -93,7 +93,8 @@ describe('Grouped security reports app', () => {
}); });
afterEach(() => { afterEach(() => {
wrapper.vm.$destroy(); wrapper.destroy();
wrapper = null;
mock.restore(); mock.restore();
}); });
...@@ -143,9 +144,11 @@ describe('Grouped security reports app', () => { ...@@ -143,9 +144,11 @@ describe('Grouped security reports app', () => {
it('renders error state', () => { it('renders error state', () => {
expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(wrapper.vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(
'Security scanning failed loading any results', wrapper.vm.$el
); .querySelector('[data-testid="report-section-code-text"]')
.textContent.trim(),
).toEqual('Security scanning failed loading any results');
expect(wrapper.vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual( expect(wrapper.vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual(
'Expand', 'Expand',
...@@ -184,9 +187,11 @@ describe('Grouped security reports app', () => { ...@@ -184,9 +187,11 @@ describe('Grouped security reports app', () => {
it('renders loading summary text + spinner', () => { it('renders loading summary text + spinner', () => {
expect(wrapper.vm.$el.querySelector('.gl-spinner')).not.toBeNull(); expect(wrapper.vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(wrapper.vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(
'Security scanning is loading', wrapper.vm.$el
); .querySelector('[data-testid="report-section-code-text"]')
.textContent.trim(),
).toEqual('Security scanning is loading');
expect(wrapper.vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual( expect(wrapper.vm.$el.querySelector('.js-collapse-btn').textContent.trim()).toEqual(
'Expand', 'Expand',
...@@ -227,9 +232,11 @@ describe('Grouped security reports app', () => { ...@@ -227,9 +232,11 @@ describe('Grouped security reports app', () => {
expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull();
// Renders the summary text // Renders the summary text
expect(wrapper.vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(
'Security scanning detected no vulnerabilities.', wrapper.vm.$el
); .querySelector('[data-testid="report-section-code-text"]')
.textContent.trim(),
).toEqual('Security scanning detected no vulnerabilities.');
// Renders Sast result // Renders Sast result
expect(trimText(wrapper.vm.$el.textContent)).toContain('SAST detected no vulnerabilities.'); expect(trimText(wrapper.vm.$el.textContent)).toContain('SAST detected no vulnerabilities.');
...@@ -275,8 +282,12 @@ describe('Grouped security reports app', () => { ...@@ -275,8 +282,12 @@ describe('Grouped security reports app', () => {
expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(wrapper.vm.$el.querySelector('.gl-spinner')).toBeNull();
// Renders the summary text // Renders the summary text
expect(wrapper.vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( expect(
'Security scanning detected 6 critical and 4 high severity vulnerabilities.', trimText(
wrapper.vm.$el.querySelector('[data-testid="report-section-code-text"]').textContent,
),
).toEqual(
'Security scanning detected 10 potential vulnerabilities 6 Critical 4 High and 0 Others',
); );
// Renders the expand button // Renders the expand button
...@@ -286,27 +297,27 @@ describe('Grouped security reports app', () => { ...@@ -286,27 +297,27 @@ describe('Grouped security reports app', () => {
// Renders Sast result // Renders Sast result
expect(trimText(wrapper.vm.$el.textContent)).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'SAST detected 1 critical severity vulnerability', 'SAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others',
); );
// Renders DSS result // Renders DSS result
expect(trimText(wrapper.vm.$el.textContent)).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'Dependency scanning detected 1 critical and 1 high severity vulnerabilities.', 'Dependency scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
// Renders container scanning result // Renders container scanning result
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'Container scanning detected 1 critical and 1 high severity vulnerabilities.', 'Container scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
// Renders DAST result // Renders DAST result
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'DAST detected 1 critical severity vulnerability.', 'DAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others',
); );
// Renders container scanning result // Renders container scanning result
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'Coverage fuzzing detected 1 critical and 1 high severity vulnerabilities.', 'Coverage fuzzing detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
}); });
...@@ -427,8 +438,8 @@ describe('Grouped security reports app', () => { ...@@ -427,8 +438,8 @@ describe('Grouped security reports app', () => {
}); });
it('should display the correct numbers of vulnerabilities', () => { it('should display the correct numbers of vulnerabilities', () => {
expect(wrapper.text()).toContain( expect(trimText(wrapper.text())).toContain(
'Container scanning detected 1 critical and 1 high severity vulnerabilities.', 'Container scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
}); });
}); });
...@@ -457,8 +468,8 @@ describe('Grouped security reports app', () => { ...@@ -457,8 +468,8 @@ describe('Grouped security reports app', () => {
}); });
it('should display the correct numbers of vulnerabilities', () => { it('should display the correct numbers of vulnerabilities', () => {
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'Dependency scanning detected 1 critical and 1 high severity vulnerabilities.', 'Dependency scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
}); });
}); });
...@@ -488,8 +499,8 @@ describe('Grouped security reports app', () => { ...@@ -488,8 +499,8 @@ describe('Grouped security reports app', () => {
}); });
it('should display the correct numbers of vulnerabilities', () => { it('should display the correct numbers of vulnerabilities', () => {
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'DAST detected 1 critical severity vulnerability', 'DAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others',
); );
}); });
...@@ -570,8 +581,8 @@ describe('Grouped security reports app', () => { ...@@ -570,8 +581,8 @@ describe('Grouped security reports app', () => {
}); });
it('should display the correct numbers of vulnerabilities', () => { it('should display the correct numbers of vulnerabilities', () => {
expect(wrapper.text()).toContain( expect(trimText(wrapper.text())).toContain(
'Secret scanning detected 1 critical and 1 high severity vulnerabilities.', 'Secret scanning detected 2 potential vulnerabilities 1 Critical 1 High and 0 Others',
); );
}); });
}); });
...@@ -609,8 +620,8 @@ describe('Grouped security reports app', () => { ...@@ -609,8 +620,8 @@ describe('Grouped security reports app', () => {
}); });
it('should display the correct numbers of vulnerabilities', () => { it('should display the correct numbers of vulnerabilities', () => {
expect(wrapper.vm.$el.textContent).toContain( expect(trimText(wrapper.vm.$el.textContent)).toContain(
'SAST detected 1 critical severity vulnerability.', 'SAST detected 1 potential vulnerability 1 Critical 0 High and 0 Others',
); );
}); });
}); });
......
import createState from 'ee/vue_shared/security_reports/store/state'; import createState from 'ee/vue_shared/security_reports/store/state';
import createSastState from 'ee/vue_shared/security_reports/store/modules/sast/state'; import createSastState from 'ee/vue_shared/security_reports/store/modules/sast/state';
import { groupedTextBuilder } from 'ee/vue_shared/security_reports/store/utils';
import { import {
groupedContainerScanningText, groupedContainerScanningText,
groupedDastText, groupedDastText,
...@@ -47,20 +48,14 @@ describe('Security reports getters', () => { ...@@ -47,20 +48,14 @@ describe('Security reports getters', () => {
${'DAST'} | ${'dast'} | ${groupedDastText} ${'DAST'} | ${'dast'} | ${groupedDastText}
${'Coverage fuzzing'} | ${'coverageFuzzing'} | ${groupedCoverageFuzzingText} ${'Coverage fuzzing'} | ${'coverageFuzzing'} | ${groupedCoverageFuzzingText}
`('grouped text for $name', ({ name, scanner, getter }) => { `('grouped text for $name', ({ name, scanner, getter }) => {
describe('with no issues', () => {
it('returns no issues text', () => {
expect(getter(state)).toEqual(`${name} detected no vulnerabilities.`);
});
});
it.each` it.each`
vulnerabilities | message vulnerabilities | message
${[]} | ${`${name} detected no vulnerabilities.`} ${[]} | ${groupedTextBuilder({ reportType: name, critical: 0, high: 0, other: 0 })}
${[generateVuln(CRITICAL), generateVuln(CRITICAL)]} | ${`${name} detected %{criticalStart}2 critical%{criticalEnd} severity vulnerabilities.`} ${[generateVuln(CRITICAL), generateVuln(CRITICAL)]} | ${groupedTextBuilder({ reportType: name, critical: 2, high: 0, other: 0 })}
${[generateVuln(HIGH), generateVuln(HIGH)]} | ${`${name} detected %{highStart}2 high%{highEnd} severity vulnerabilities.`} ${[generateVuln(HIGH), generateVuln(HIGH)]} | ${groupedTextBuilder({ reportType: name, critical: 0, high: 2, other: 0 })}
${[generateVuln(LOW), generateVuln(MEDIUM)]} | ${`${name} detected 2 vulnerabilities.`} ${[generateVuln(LOW), generateVuln(MEDIUM)]} | ${groupedTextBuilder({ reportType: name, critical: 0, high: 0, other: 2 })}
${[generateVuln(CRITICAL), generateVuln(HIGH)]} | ${`${name} detected %{criticalStart}1 critical%{criticalEnd} and %{highStart}1 high%{highEnd} severity vulnerabilities.`} ${[generateVuln(CRITICAL), generateVuln(HIGH)]} | ${groupedTextBuilder({ reportType: name, critical: 1, high: 1, other: 0 })}
${[generateVuln(CRITICAL), generateVuln(LOW)]} | ${`${name} detected %{criticalStart}1 critical%{criticalEnd} severity vulnerabilities out of 2.`} ${[generateVuln(CRITICAL), generateVuln(LOW)]} | ${groupedTextBuilder({ reportType: name, critical: 1, high: 0, other: 1 })}
`('should build the message as "$message"', ({ vulnerabilities, message }) => { `('should build the message as "$message"', ({ vulnerabilities, message }) => {
state[scanner].newIssues = vulnerabilities; state[scanner].newIssues = vulnerabilities;
expect(getter(state)).toEqual(message); expect(getter(state)).toEqual(message);
...@@ -113,7 +108,7 @@ describe('Security reports getters', () => { ...@@ -113,7 +108,7 @@ describe('Security reports getters', () => {
areReportsLoading: false, areReportsLoading: false,
summaryCounts: {}, summaryCounts: {},
}), }),
).toEqual('Security scanning failed loading any results'); ).toEqual({ message: 'Security scanning failed loading any results' });
}); });
it('returns is loading text', () => { it('returns is loading text', () => {
...@@ -123,21 +118,14 @@ describe('Security reports getters', () => { ...@@ -123,21 +118,14 @@ describe('Security reports getters', () => {
areReportsLoading: true, areReportsLoading: true,
summaryCounts: {}, summaryCounts: {},
}), }),
).toContain('(is loading)');
});
it('returns vulnerabilities while loading text', () => {
expect(
groupedSummaryText(state, {
allReportsHaveError: false,
areReportsLoading: true,
summaryCounts: {
critical: 2,
high: 4,
},
}),
).toEqual( ).toEqual(
'Security scanning (is loading) detected %{criticalStart}2 critical%{criticalEnd} and %{highStart}4 high%{highEnd} severity vulnerabilities.', groupedTextBuilder({
reportType: 'Security scanning',
critical: 0,
high: 0,
other: 0,
status: 'is loading',
}),
); );
}); });
...@@ -148,7 +136,15 @@ describe('Security reports getters', () => { ...@@ -148,7 +136,15 @@ describe('Security reports getters', () => {
areReportsLoading: false, areReportsLoading: false,
summaryCounts: {}, summaryCounts: {},
}), }),
).toEqual('Security scanning detected no vulnerabilities.'); ).toEqual(
groupedTextBuilder({
reportType: 'Security scanning',
critical: 0,
high: 0,
other: 0,
status: '',
}),
);
}); });
}); });
......
...@@ -15,21 +15,29 @@ describe('groupedSastText', () => { ...@@ -15,21 +15,29 @@ describe('groupedSastText', () => {
const sast = createReport({ hasError: true }); const sast = createReport({ hasError: true });
const result = getters.groupedSastText(sast); const result = getters.groupedSastText(sast);
expect(result).toBe(SAST_HAS_ERROR); expect(result).toStrictEqual({ message: SAST_HAS_ERROR });
}); });
it("should return the loading message if it's still loading", () => { it("should return the loading message if it's still loading", () => {
const sast = createReport({ isLoading: true }); const sast = createReport({ isLoading: true });
const result = getters.groupedSastText(sast); const result = getters.groupedSastText(sast);
expect(result).toBe(SAST_IS_LOADING); expect(result).toStrictEqual({ message: SAST_IS_LOADING });
}); });
it('should call groupedTextBuilder if everything is fine', () => { it('should call groupedTextBuilder if everything is fine', () => {
const sast = createReport(); const sast = createReport();
const result = getters.groupedSastText(sast); const result = getters.groupedSastText(sast);
expect(result).toBe('SAST detected no vulnerabilities.'); expect(result).toStrictEqual({
countMessage: '',
critical: 0,
high: 0,
message: 'SAST detected %{totalStart}no%{totalEnd} vulnerabilities.',
other: 0,
status: '',
total: 0,
});
}); });
}); });
......
...@@ -116,39 +116,52 @@ describe('security reports utils', () => { ...@@ -116,39 +116,52 @@ describe('security reports utils', () => {
const other = 7; const other = 7;
it.each` it.each`
vulnerabilities | message vulnerabilities | message | countMessage
${undefined} | ${' detected no vulnerabilities.'} ${undefined} | ${' detected %{totalStart}no%{totalEnd} vulnerabilities.'} | ${''}
${{ critical }} | ${' detected %{criticalStart}2 critical%{criticalEnd} severity vulnerabilities.'} ${{ critical }} | ${` detected %{totalStart}2%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}2 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}0 Others%{otherEnd}`}
${{ high }} | ${' detected %{highStart}4 high%{highEnd} severity vulnerabilities.'} ${{ high }} | ${` detected %{totalStart}4%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}0 Critical%{criticalEnd} %{highStart}4 High%{highEnd} and %{otherStart}0 Others%{otherEnd}`}
${{ other }} | ${' detected 7 vulnerabilities.'} ${{ other }} | ${` detected %{totalStart}7%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}0 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}7 Others%{otherEnd}`}
${{ critical, high }} | ${' detected %{criticalStart}2 critical%{criticalEnd} and %{highStart}4 high%{highEnd} severity vulnerabilities.'} ${{ critical, high }} | ${` detected %{totalStart}6%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}2 Critical%{criticalEnd} %{highStart}4 High%{highEnd} and %{otherStart}0 Others%{otherEnd}`}
${{ critical, other }} | ${' detected %{criticalStart}2 critical%{criticalEnd} severity vulnerabilities out of 9.'} ${{ critical, other }} | ${` detected %{totalStart}9%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}2 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}7 Others%{otherEnd}`}
${{ high, other }} | ${' detected %{highStart}4 high%{highEnd} severity vulnerabilities out of 11.'} ${{ high, other }} | ${` detected %{totalStart}11%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}0 Critical%{criticalEnd} %{highStart}4 High%{highEnd} and %{otherStart}7 Others%{otherEnd}`}
${{ critical, high, other }} | ${' detected %{criticalStart}2 critical%{criticalEnd} and %{highStart}4 high%{highEnd} severity vulnerabilities out of 13.'} ${{ critical, high, other }} | ${` detected %{totalStart}13%{totalEnd} potential vulnerabilities`} | ${`%{criticalStart}2 Critical%{criticalEnd} %{highStart}4 High%{highEnd} and %{otherStart}7 Others%{otherEnd}`}
`('should build the message as "$message"', ({ vulnerabilities, message }) => { `('should build the message as "$message"', ({ vulnerabilities, message, countMessage }) => {
expect(groupedTextBuilder(vulnerabilities)).toEqual(message); expect(groupedTextBuilder(vulnerabilities).message).toEqual(message);
expect(groupedTextBuilder(vulnerabilities).countMessage).toEqual(countMessage);
}); });
it.each` it.each`
vulnerabilities | message vulnerabilities | message | countMessage
${{ critical: 1 }} | ${' detected %{criticalStart}1 critical%{criticalEnd} severity vulnerability.'} ${{ critical: 1 }} | ${` detected %{totalStart}1%{totalEnd} potential vulnerability`} | ${`%{criticalStart}1 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}0 Others%{otherEnd}`}
${{ high: 1 }} | ${' detected %{highStart}1 high%{highEnd} severity vulnerability.'} ${{ high: 1 }} | ${` detected %{totalStart}1%{totalEnd} potential vulnerability`} | ${`%{criticalStart}0 Critical%{criticalEnd} %{highStart}1 High%{highEnd} and %{otherStart}0 Others%{otherEnd}`}
${{ other: 1 }} | ${' detected 1 vulnerability.'} ${{ other: 1 }} | ${` detected %{totalStart}1%{totalEnd} potential vulnerability`} | ${`%{criticalStart}0 Critical%{criticalEnd} %{highStart}0 High%{highEnd} and %{otherStart}1 Other%{otherEnd}`}
`('should handle single vulnerabilities for "$message"', ({ vulnerabilities, message }) => { `(
expect(groupedTextBuilder(vulnerabilities)).toEqual(message); 'should handle single vulnerabilities for "$message"',
}); ({ vulnerabilities, message, countMessage }) => {
expect(groupedTextBuilder(vulnerabilities).message).toEqual(message);
expect(groupedTextBuilder(vulnerabilities).countMessage).toEqual(countMessage);
},
);
it('should pass through the report type', () => { it('should pass through the report type', () => {
const reportType = 'HAL'; const reportType = 'HAL';
expect(groupedTextBuilder({ reportType })).toEqual('HAL detected no vulnerabilities.'); expect(groupedTextBuilder({ reportType }).message).toEqual(
'HAL detected %{totalStart}no%{totalEnd} vulnerabilities.',
);
}); });
it('should pass through the status', () => { it('should pass through the status', () => {
const reportType = 'HAL'; const reportType = 'HAL';
const status = '(is loading)'; const status = 'is loading';
expect(groupedTextBuilder({ reportType, status })).toEqual( expect(groupedTextBuilder({ reportType, status })).toEqual({
'HAL (is loading) detected no vulnerabilities.', countMessage: '',
); critical: 0,
high: 0,
message: 'HAL is loading',
other: 0,
status: 'is loading',
total: 0,
});
}); });
}); });
...@@ -187,7 +200,7 @@ describe('security reports utils', () => { ...@@ -187,7 +200,7 @@ describe('security reports utils', () => {
${[{ severity: LOW }, { severity: MEDIUM }]} | ${{ critical: 0, high: 0, other: 2 }} ${[{ severity: LOW }, { severity: MEDIUM }]} | ${{ critical: 0, high: 0, other: 2 }}
${[{ severity: CRITICAL }, { severity: HIGH }]} | ${{ critical: 1, high: 1, other: 0 }} ${[{ severity: CRITICAL }, { severity: HIGH }]} | ${{ critical: 1, high: 1, other: 0 }}
${[{ severity: CRITICAL }, { severity: LOW }]} | ${{ critical: 1, high: 0, other: 1 }} ${[{ severity: CRITICAL }, { severity: LOW }]} | ${{ critical: 1, high: 0, other: 1 }}
`('should count the vulnerabilities correctly', ({ vulnerabilities, response }) => { `('should total the vulnerabilities correctly', ({ vulnerabilities, response }) => {
expect(countVulnerabilities(vulnerabilities)).toEqual(response); expect(countVulnerabilities(vulnerabilities)).toEqual(response);
}); });
}); });
...@@ -202,21 +215,29 @@ describe('security reports utils', () => { ...@@ -202,21 +215,29 @@ describe('security reports utils', () => {
const report = { ...baseReport, hasError: true }; const report = { ...baseReport, hasError: true };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage); const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(errorMessage); expect(result).toStrictEqual({ message: errorMessage });
}); });
it("should return the loading message when it's loading", () => { it("should return the loading message when it's loading", () => {
const report = { ...baseReport, isLoading: true }; const report = { ...baseReport, isLoading: true };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage); const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(loadingMessage); expect(result).toStrictEqual({ message: loadingMessage });
}); });
it("should call groupedTextBuilder if it isn't loading and doesn't have an error", () => { it("should call groupedTextBuilder if it isn't loading and doesn't have an error", () => {
const report = { ...baseReport }; const report = { ...baseReport };
const result = groupedReportText(report, reportType, errorMessage, loadingMessage); const result = groupedReportText(report, reportType, errorMessage, loadingMessage);
expect(result).toBe(`${reportType} detected no vulnerabilities.`); expect(result).toStrictEqual({
countMessage: '',
critical: 0,
high: 0,
message: 'dummyReport detected %{totalStart}no%{totalEnd} vulnerabilities.',
other: 0,
status: '',
total: 0,
});
}); });
}); });
}); });
...@@ -82,6 +82,11 @@ msgid_plural "%d Approvals" ...@@ -82,6 +82,11 @@ msgid_plural "%d Approvals"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%d Other"
msgid_plural "%d Others"
msgstr[0] ""
msgstr[1] ""
msgid "%d Package" msgid "%d Package"
msgid_plural "%d Packages" msgid_plural "%d Packages"
msgstr[0] "" msgstr[0] ""
...@@ -445,6 +450,9 @@ msgstr "" ...@@ -445,6 +450,9 @@ msgstr ""
msgid "%{count} total weight" msgid "%{count} total weight"
msgstr "" msgstr ""
msgid "%{criticalStart}%{critical} Critical%{criticalEnd} %{highStart}%{high} High%{highEnd} and %{otherStart}%{otherMessage}%{otherEnd}"
msgstr ""
msgid "%{dashboard_path} could not be found." msgid "%{dashboard_path} could not be found."
msgstr "" msgstr ""
...@@ -710,34 +718,13 @@ msgstr[1] "" ...@@ -710,34 +718,13 @@ msgstr[1] ""
msgid "%{remaining_approvals} left" msgid "%{remaining_approvals} left"
msgstr "" msgstr ""
msgid "%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} and %{highStart}%{high} high%{highEnd} severity vulnerabilities out of %{total}." msgid "%{reportType} %{status}"
msgstr "" msgstr ""
msgid "%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} and %{highStart}%{high} high%{highEnd} severity vulnerabilities." msgid "%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}"
msgstr "" msgstr ""
msgid "%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerabilities out of %{total}." msgid "%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities."
msgstr ""
msgid "%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerability."
msgid_plural "%{reportType} %{status} detected %{criticalStart}%{critical} critical%{criticalEnd} severity vulnerabilities."
msgstr[0] ""
msgstr[1] ""
msgid "%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerabilities out of %{total}."
msgstr ""
msgid "%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerability."
msgid_plural "%{reportType} %{status} detected %{highStart}%{high} high%{highEnd} severity vulnerabilities."
msgstr[0] ""
msgstr[1] ""
msgid "%{reportType} %{status} detected %{other} vulnerability."
msgid_plural "%{reportType} %{status} detected %{other} vulnerabilities."
msgstr[0] ""
msgstr[1] ""
msgid "%{reportType} %{status} detected no vulnerabilities."
msgstr "" msgstr ""
msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}." msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}."
...@@ -31551,13 +31538,7 @@ msgstr "" ...@@ -31551,13 +31538,7 @@ msgstr ""
msgid "ciReport|%{sameNum} same" msgid "ciReport|%{sameNum} same"
msgstr "" msgstr ""
msgid "ciReport|(errors when loading results)" msgid "ciReport|: Loading resulted in an error"
msgstr ""
msgid "ciReport|(is loading)"
msgstr ""
msgid "ciReport|(is loading, errors when loading results)"
msgstr "" msgstr ""
msgid "ciReport|All projects" msgid "ciReport|All projects"
...@@ -31724,6 +31705,12 @@ msgstr[1] "" ...@@ -31724,6 +31705,12 @@ msgstr[1] ""
msgid "ciReport|View full report" msgid "ciReport|View full report"
msgstr "" msgstr ""
msgid "ciReport|is loading"
msgstr ""
msgid "ciReport|is loading, errors when loading results"
msgstr ""
msgid "closed issue" msgid "closed issue"
msgstr "" msgstr ""
...@@ -32826,6 +32813,11 @@ msgstr "" ...@@ -32826,6 +32813,11 @@ msgstr ""
msgid "view the source" msgid "view the source"
msgstr "" msgstr ""
msgid "vulnerability"
msgid_plural "vulnerabilities"
msgstr[0] ""
msgstr[1] ""
msgid "vulnerability|Add a comment" msgid "vulnerability|Add a comment"
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