Skip to content

Commit 4e6c8dc

Browse files
author
AvrAlexandra
committed
added chart generation for vulnerability fix metrics
1 parent bf5f484 commit 4e6c8dc

File tree

2 files changed

+139
-3
lines changed

2 files changed

+139
-3
lines changed

src/commands/history-metrics/chart-generator.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,135 @@ export function generateVersionChangeChartData(
241241
});
242242
}
243243

244+
export function generateVulnerabilityFixBySeverityChartData(
245+
results: Record<string, Record<string, number>>,
246+
options: MetricOptions
247+
): { data: any[]; layout: any }[] {
248+
const months = Object.keys(results).sort();
249+
250+
const formattedMonths = months.map(month => {
251+
const [year, monthPart] = month.split('-');
252+
const date = new Date(`${year}-${monthPart}-01`);
253+
return date.toLocaleString('en-US', { month: 'short', year: 'numeric' }); // e.g., "May 2025"
254+
});
255+
256+
const allSeverities = Array.from(new Set(months.flatMap(month => Object.keys(results[month]))));
257+
258+
return options.chartType.map(type => {
259+
const isLine = type === 'line';
260+
const isStacked = type === 'stacked';
261+
const isArea = type === 'stacked-area';
262+
263+
const traceType = isLine || isArea ? 'scatter' : 'bar';
264+
const commonTraceProps = {
265+
type: traceType,
266+
mode: isLine ? 'lines+markers' : undefined,
267+
fill: isArea ? 'tonexty' : undefined,
268+
stackgroup: isArea ? 'one' : undefined
269+
};
270+
271+
const traces = allSeverities.map(severity => ({
272+
x: formattedMonths,
273+
y: months.map(month => results[month]?.[severity] || 0),
274+
name: severity,
275+
...commonTraceProps
276+
}));
277+
278+
const layout = {
279+
title: `🛡️ Fixed Vulnerabilities by Severity Over Time (${type})`,
280+
barmode: isStacked ? 'stack' : (traceType === 'bar' ? 'group' : undefined),
281+
xaxis: {
282+
title: 'Month',
283+
tickangle: -45,
284+
automargin: true
285+
},
286+
yaxis: {
287+
title: 'Fix Count'
288+
},
289+
margin: {
290+
l: 50,
291+
r: 30,
292+
t: 60,
293+
b: 120
294+
}
295+
};
296+
297+
return {
298+
data: traces,
299+
layout
300+
};
301+
});
302+
}
303+
304+
export function generateVulnerabilityFixTimelinessChartData(
305+
results: Record<string, Record<string, { fixedInTime: number; fixedLate: number }>>,
306+
options: MetricOptions
307+
): { data: any[]; layout: any }[] {
308+
const months = Object.keys(results).sort();
309+
310+
const formattedMonths = months.map(month => {
311+
const [year, monthPart] = month.split('-');
312+
return new Date(`${year}-${monthPart}-01`).toLocaleString('en-US', {
313+
month: 'short',
314+
year: 'numeric'
315+
});
316+
});
317+
318+
const allSeverities = Array.from(
319+
new Set(months.flatMap(m => Object.keys(results[m])))
320+
);
321+
322+
return options.chartType.map(type => {
323+
const traceType = type === 'line' || type === 'stacked-area' ? 'scatter' : 'bar';
324+
const isStacked = type === 'stacked';
325+
const isArea = type === 'stacked-area';
326+
327+
const commonProps = {
328+
type: traceType,
329+
mode: traceType === 'scatter' ? 'lines+markers' : undefined,
330+
fill: isArea ? 'tonexty' : undefined,
331+
stackgroup: isArea ? 'one' : undefined
332+
};
333+
334+
const traces = allSeverities.flatMap(severity => {
335+
const fixedInTime = {
336+
x: formattedMonths,
337+
y: months.map(m => results[m]?.[severity]?.fixedInTime || 0),
338+
name: `${severity} - Fixed In Time`,
339+
...commonProps
340+
};
341+
342+
const fixedLate = {
343+
x: formattedMonths,
344+
y: months.map(m => results[m]?.[severity]?.fixedLate || 0),
345+
name: `${severity} - Fixed Late`,
346+
...commonProps
347+
};
348+
349+
return [fixedInTime, fixedLate];
350+
});
351+
352+
const layout = {
353+
title: `⏱️ Timeliness of Vulnerability Fixes per Month according to ISO (${type})`,
354+
barmode: isStacked ? 'stack' : (traceType === 'bar' ? 'group' : undefined),
355+
xaxis: {
356+
title: 'Month',
357+
tickangle: -45,
358+
automargin: true
359+
},
360+
yaxis: {
361+
title: 'Fix Count'
362+
},
363+
margin: {
364+
l: 50,
365+
r: 30,
366+
t: 60,
367+
b: 120
368+
}
369+
};
370+
371+
return { data: traces, layout };
372+
});
373+
}
374+
375+

src/commands/history-metrics/metrics-command.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
import {
1111
generateGrowthPatternChartData,
1212
generateVersionChangeChartData,
13-
generateHtmlChart
13+
generateHtmlChart,
14+
generateVulnerabilityFixBySeverityChartData,
15+
generateVulnerabilityFixTimelinessChartData
1416
} from './chart-generator'
1517

1618
export const runMetricsCommand = new Command()
@@ -59,11 +61,13 @@ const metricsRegistry: Record<MetricType, MetricConfig> = {
5961
},
6062
'vulnerability-fixes-by-severity': {
6163
processor: VulnerabilityFixBySeverityMetric,
62-
requiredPrefixes: ['commit-dependency-history', 'library-info']
64+
requiredPrefixes: ['commit-dependency-history', 'library-info'],
65+
chartGenerator: generateVulnerabilityFixBySeverityChartData
6366
},
6467
'vulnerability-fix-timeliness': {
6568
processor: VulnerabilityFixTimelinessMetric,
66-
requiredPrefixes: ['commit-dependency-history', 'library-info']
69+
requiredPrefixes: ['commit-dependency-history', 'library-info'],
70+
chartGenerator: generateVulnerabilityFixTimelinessChartData
6771
}
6872
};
6973

0 commit comments

Comments
 (0)