Skip to content

Commit

Permalink
Display vulnerabilities summary table per target (#2568)
Browse files Browse the repository at this point in the history
Closes #2487

Signed-off-by: Cintia Sanchez Garcia <[email protected]>
  • Loading branch information
cynthia-sg authored Dec 2, 2022
1 parent d9e20de commit 945fe47
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 57 deletions.
58 changes: 2 additions & 56 deletions web/src/layout/package/securityReport/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classnames from 'classnames';
import { filter, isEmpty, isNull, isUndefined } from 'lodash';
import { isEmpty, isNull, isUndefined } from 'lodash';
import moment from 'moment';
import { useEffect, useRef, useState } from 'react';
import { HiClipboardList } from 'react-icons/hi';
Expand All @@ -11,14 +11,11 @@ import {
RepositoryKind,
SearchFiltersURL,
SecurityReport,
SecurityReportResult,
SecurityReportSummary,
Vulnerability,
} from '../../../types';
import alertDispatcher from '../../../utils/alertDispatcher';
import formatSecurityReport from '../../../utils/formatSecurityReport';
import isFuture from '../../../utils/isFuture';
import sumObjectValues from '../../../utils/sumObjectValues';
import { filterFixableVulnerabilities, prepareFixableSummary } from '../../../utils/vulnerabilities';
import Modal from '../../common/Modal';
import styles from './Modal.module.css';
import OldVulnerabilitiesWarning from './OldVulnerabilitiesWarning';
Expand Down Expand Up @@ -80,57 +77,6 @@ const SecurityModal = (props: Props) => {
}
};

const prepareFixableSummary = (
fixableVulnerabilities: SecurityReport | null | undefined
): FixableVulnerabilitiesInReport => {
let fixReport: FixableVulnerabilitiesInReport = { report: {}, summary: {}, total: 0 };
if (fixableVulnerabilities) {
let allVulnerabilities: Vulnerability[] = [];
Object.keys(fixableVulnerabilities).forEach((image: string) => {
let vulnerabilitiesList: Vulnerability[] = [];
fixableVulnerabilities[image].Results.forEach((targetReport: SecurityReportResult) => {
if (targetReport.Vulnerabilities) {
vulnerabilitiesList = [...vulnerabilitiesList, ...targetReport.Vulnerabilities];
allVulnerabilities = [...allVulnerabilities, ...targetReport.Vulnerabilities];
}
});
const { summary } = formatSecurityReport(vulnerabilitiesList);
const total = sumObjectValues(summary);
fixReport.report[image] = {
summary: summary,
total: total,
};
});
fixReport.summary = formatSecurityReport(allVulnerabilities).summary;
fixReport.total = sumObjectValues(formatSecurityReport(allVulnerabilities).summary);
}
return fixReport;
};

const filterFixableVulnerabilities = (currentReport: SecurityReport | null): SecurityReport | null => {
if (isNull(currentReport)) return null;

let tmpReport: SecurityReport = {};
Object.keys(currentReport).forEach((img: string) => {
currentReport[img].Results.forEach((target: SecurityReportResult) => {
let vulnerabilities: null | Vulnerability[] = [];
const filteredVulnerabilities = filter(
target.Vulnerabilities,
(v: Vulnerability) => !isUndefined(v.FixedVersion)
);
vulnerabilities = isEmpty(target.Vulnerabilities) ? [] : filteredVulnerabilities;
if (!isNull(vulnerabilities)) {
if (isUndefined(tmpReport[img])) {
tmpReport[img] = { Results: [{ ...target, Vulnerabilities: vulnerabilities }] };
} else {
tmpReport[img].Results.push({ ...target, Vulnerabilities: vulnerabilities });
}
}
});
});
return tmpReport;
};

async function getSecurityReports(eventId?: string) {
try {
setIsLoading(true);
Expand Down
4 changes: 4 additions & 0 deletions web/src/layout/package/securityReport/Table.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@
.badge:hover {
text-decoration: none;
}

.summaryTable > :not(caption) > * > * {
padding: 0.25rem 0.75rem;
}
94 changes: 93 additions & 1 deletion web/src/layout/package/securityReport/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import classNames from 'classnames';
import { isNull, isUndefined, slice } from 'lodash';
import { Dispatch, Fragment, SetStateAction, useState } from 'react';
import { FaCaretDown, FaCaretRight } from 'react-icons/fa';

import { SecurityReportResult, Vulnerability } from '../../../types';
import { SecurityReportResult, SecurityReportSummary, Vulnerability, VulnerabilitySeverity } from '../../../types';
import { SEVERITY_ORDER } from '../../../utils/data';
import formatSecurityReport from '../../../utils/formatSecurityReport';
import getTextBetweenParenthesis from '../../../utils/getTextBetweenParenthesis';
import sumObjectValues from '../../../utils/sumObjectValues';
import SecurityRating from '../../common/SecurityRating';
import SecurityCell from './Cell';
import ImageBtn from './ImageBtn';
Expand All @@ -27,8 +30,89 @@ interface Props {
showOnlyFixableVulnerabilities: boolean;
}

interface TargetProps {
targetImage: string;
list: Vulnerability[];
summary: SecurityReportSummary;
fixableReport?: SecurityReportResult;
}

const MAX_VULNERABILITY_NUMBER = 100;

const SummaryTarget = (props: TargetProps): JSX.Element => {
const total = sumObjectValues(props.summary);
const fixableVulnerabilities = formatSecurityReport(
!isUndefined(props.fixableReport) && !isNull(props.fixableReport.Vulnerabilities)
? props.fixableReport.Vulnerabilities
: []
);
const fixableTotal = sumObjectValues(fixableVulnerabilities.summary);
const allVulnerabilitiesAreFixable = fixableTotal === total;

return (
<table className={`table table-bordered table-sm mb-4 mt-2 ${styles.table} ${styles.summaryTable}`}>
<thead>
<tr className={styles.tableTitle}>
{!allVulnerabilitiesAreFixable && <th scope="col">Type</th>}

{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => (
<th key={`col_${severity}`} className="col text-center text-capitalize">
{severity}
</th>
))}
<th scope="col" className="text-center">
Total
</th>
</tr>
</thead>
<tbody>
{!allVulnerabilitiesAreFixable ? (
<tr>
<td className={styles.narrowCell}>Fixable</td>
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => (
<td key={`col_fix_${severity}_${props.targetImage}`} className={`text-center ${styles.narrowCell}`}>
{fixableVulnerabilities.summary[severity] || 0}
</td>
))}
<td className={`text-center fw-bold ${styles.narrowCell}`}>{fixableTotal}</td>
</tr>
) : (
<tr>
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => (
<td
key={`col_${severity}_${props.targetImage}`}
className={classNames('text-center', {
[styles.narrowCell]: !allVulnerabilitiesAreFixable,
})}
>
{props.summary[severity] || 0}
</td>
))}
<td
className={classNames('text-center fw-bold', {
[styles.narrowCell]: !allVulnerabilitiesAreFixable,
})}
>
{total}
</td>
</tr>
)}
{!allVulnerabilitiesAreFixable && (
<tr>
<td className={styles.narrowCell}>All</td>
{SEVERITY_ORDER.map((severity: VulnerabilitySeverity) => (
<td key={`col_${severity}_${props.targetImage}`} className={`text-center ${styles.narrowCell}`}>
{props.summary[severity] || 0}
</td>
))}
<td className={`text-center fw-bold ${styles.narrowCell}`}>{total}</td>
</tr>
)}
</tbody>
</table>
);
};

const SecurityTable = (props: Props) => {
const [visibleVulnerability, setVisibleVulnerability] = useState<string | undefined>();

Expand Down Expand Up @@ -84,6 +168,9 @@ const SecurityTable = (props: Props) => {
: false;
const isExpanded = props.expandedTarget === targetImageName;
const isLastTarget = props.lastReport && index === props.reports.length - 1;
const fixableReport = props.fixableReports.find(
(fixableItem: SecurityReportResult) => item.Target === fixableItem.Target
);

return (
<Fragment key={`table_${targetImageName}`}>
Expand Down Expand Up @@ -136,6 +223,11 @@ const SecurityTable = (props: Props) => {

{isExpanded && (
<div className="w-100 overflow-auto mb-2">
<SummaryTarget
{...allVulnerabilities}
fixableReport={fixableReport}
targetImage={targetImageName}
/>
<table className={`table table-sm table-hover ${styles.table}`}>
<thead>
<tr className="text-uppercase text-muted">
Expand Down
58 changes: 58 additions & 0 deletions web/src/utils/vulnerabilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { filter, isEmpty, isNull, isUndefined } from 'lodash';

import { FixableVulnerabilitiesInReport, SecurityReport, SecurityReportResult, Vulnerability } from '../types';
import formatSecurityReport from './formatSecurityReport';
import sumObjectValues from './sumObjectValues';

const prepareFixableSummary = (
fixableVulnerabilities: SecurityReport | null | undefined
): FixableVulnerabilitiesInReport => {
let fixReport: FixableVulnerabilitiesInReport = { report: {}, summary: {}, total: 0 };
if (fixableVulnerabilities) {
let allVulnerabilities: Vulnerability[] = [];
Object.keys(fixableVulnerabilities).forEach((image: string) => {
let vulnerabilitiesList: Vulnerability[] = [];
fixableVulnerabilities[image].Results.forEach((targetReport: SecurityReportResult) => {
if (targetReport.Vulnerabilities) {
vulnerabilitiesList = [...vulnerabilitiesList, ...targetReport.Vulnerabilities];
allVulnerabilities = [...allVulnerabilities, ...targetReport.Vulnerabilities];
}
});
const { summary } = formatSecurityReport(vulnerabilitiesList);
const total = sumObjectValues(summary);
fixReport.report[image] = {
summary: summary,
total: total,
};
});
fixReport.summary = formatSecurityReport(allVulnerabilities).summary;
fixReport.total = sumObjectValues(formatSecurityReport(allVulnerabilities).summary);
}
return fixReport;
};

const filterFixableVulnerabilities = (currentReport: SecurityReport | null): SecurityReport | null => {
if (isNull(currentReport)) return null;

let tmpReport: SecurityReport = {};
Object.keys(currentReport).forEach((img: string) => {
currentReport[img].Results.forEach((target: SecurityReportResult) => {
let vulnerabilities: null | Vulnerability[] = [];
const filteredVulnerabilities = filter(
target.Vulnerabilities,
(v: Vulnerability) => !isUndefined(v.FixedVersion)
);
vulnerabilities = isEmpty(target.Vulnerabilities) ? [] : filteredVulnerabilities;
if (!isNull(vulnerabilities)) {
if (isUndefined(tmpReport[img])) {
tmpReport[img] = { Results: [{ ...target, Vulnerabilities: vulnerabilities }] };
} else {
tmpReport[img].Results.push({ ...target, Vulnerabilities: vulnerabilities });
}
}
});
});
return tmpReport;
};

export { filterFixableVulnerabilities, prepareFixableSummary };

0 comments on commit 945fe47

Please sign in to comment.