153 lines
4.7 KiB
JavaScript
153 lines
4.7 KiB
JavaScript
import axios from '~/lib/utils/axios_utils';
|
|
import pollUntilComplete from '~/lib/utils/poll_until_complete';
|
|
import { __, n__, sprintf } from '~/locale';
|
|
import { CRITICAL, HIGH } from '~/vulnerabilities/constants';
|
|
import {
|
|
FEEDBACK_TYPE_DISMISSAL,
|
|
FEEDBACK_TYPE_ISSUE,
|
|
FEEDBACK_TYPE_MERGE_REQUEST,
|
|
} from '../constants';
|
|
|
|
export const fetchDiffData = (state, endpoint, category) => {
|
|
const requests = [pollUntilComplete(endpoint)];
|
|
|
|
if (state.canReadVulnerabilityFeedback) {
|
|
requests.push(axios.get(state.vulnerabilityFeedbackPath, { params: { category } }));
|
|
}
|
|
|
|
return Promise.all(requests).then(([diffResponse, enrichResponse]) => ({
|
|
diff: diffResponse.data,
|
|
enrichData: enrichResponse?.data ?? [],
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Returns given vulnerability enriched with the corresponding
|
|
* feedback (`dismissal` or `issue` type)
|
|
* @param {Object} vulnerability
|
|
* @param {Array} feedback
|
|
*/
|
|
export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) =>
|
|
feedback
|
|
.filter((fb) => fb.project_fingerprint === vulnerability.project_fingerprint)
|
|
.reduce((vuln, fb) => {
|
|
if (fb.feedback_type === FEEDBACK_TYPE_DISMISSAL) {
|
|
return {
|
|
...vuln,
|
|
isDismissed: true,
|
|
dismissalFeedback: fb,
|
|
};
|
|
}
|
|
if (fb.feedback_type === FEEDBACK_TYPE_ISSUE && fb.issue_iid) {
|
|
return {
|
|
...vuln,
|
|
hasIssue: true,
|
|
issue_feedback: fb,
|
|
};
|
|
}
|
|
if (fb.feedback_type === FEEDBACK_TYPE_MERGE_REQUEST && fb.merge_request_iid) {
|
|
return {
|
|
...vuln,
|
|
hasMergeRequest: true,
|
|
merge_request_feedback: fb,
|
|
};
|
|
}
|
|
return vuln;
|
|
}, vulnerability);
|
|
|
|
/**
|
|
* Generates the added, fixed, and existing vulnerabilities from the API report.
|
|
*
|
|
* @param {Object} diff The original reports.
|
|
* @param {Object} enrichData Feedback data to add to the reports.
|
|
* @returns {Object}
|
|
*/
|
|
export const parseDiff = (diff, enrichData) => {
|
|
const enrichVulnerability = (vulnerability) => ({
|
|
...enrichVulnerabilityWithFeedback(vulnerability, enrichData),
|
|
category: vulnerability.report_type,
|
|
title: vulnerability.message || vulnerability.name,
|
|
});
|
|
|
|
return {
|
|
added: diff.added ? diff.added.map(enrichVulnerability) : [],
|
|
fixed: diff.fixed ? diff.fixed.map(enrichVulnerability) : [],
|
|
existing: diff.existing ? diff.existing.map(enrichVulnerability) : [],
|
|
};
|
|
};
|
|
|
|
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 });
|
|
};
|
|
|
|
/**
|
|
* Counts vulnerabilities.
|
|
* Returns the amount of critical, high, and other vulnerabilities.
|
|
*
|
|
* @param {Array} vulnerabilities The raw vulnerabilities to parse
|
|
* @returns {{critical: number, high: number, other: number}}
|
|
*/
|
|
export const countVulnerabilities = (vulnerabilities = []) =>
|
|
vulnerabilities.reduce(
|
|
(acc, { severity }) => {
|
|
if (severity === CRITICAL) {
|
|
acc.critical += 1;
|
|
} else if (severity === HIGH) {
|
|
acc.high += 1;
|
|
} else {
|
|
acc.other += 1;
|
|
}
|
|
|
|
return acc;
|
|
},
|
|
{ critical: 0, high: 0, other: 0 },
|
|
);
|
|
|
|
/**
|
|
* 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 resulting string _may_ still contain sprintf-style placeholders. These
|
|
* are left in place so they can be replaced with markup, via the
|
|
* SecuritySummary component.
|
|
* @param {{reportType: string, status: string, critical: number, high: number, other: number}} options
|
|
* @returns {Object} the parameters with an externalized string
|
|
*/
|
|
export const groupedTextBuilder = ({
|
|
reportType = '',
|
|
status = '',
|
|
critical = 0,
|
|
high = 0,
|
|
other = 0,
|
|
} = {}) => {
|
|
const total = critical + high + other;
|
|
|
|
return {
|
|
countMessage: createCountMessage({ critical, high, other, total }),
|
|
message: createStatusMessage({ reportType, status, total }),
|
|
critical,
|
|
high,
|
|
other,
|
|
status,
|
|
total,
|
|
};
|
|
};
|