gitlab-org--gitlab-foss/app/assets/javascripts/vue_shared/security_reports/store/utils.js

154 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,
};
};