Skip to content

Commit 823ba1a

Browse files
author
AvrAlexandra
committed
Refactored metric calculation and updated chart generation
1 parent 80bd04b commit 823ba1a

File tree

5 files changed

+155
-153
lines changed

5 files changed

+155
-153
lines changed

__tests__/history-metrics.integration.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ import { runMetrics } from "../src/commands/history-metrics/metrics-command";
33
describe('test version change metric generation', () => {
44
it('should generate the version-changes metric', async () => {
55
await runMetrics('/Users/avram/OutputReportsOfHistory', {
6-
results: 'results-charts-version-changes',
6+
results: 'results-version-changes',
77
metric: 'version-changes',
88
chart: true,
99
chartType: ['line', 'bar', 'stacked', 'stacked-area'],
10-
inputFiles: [
11-
'dependency-history-java-2025-04-21T19-40-53-470Z'
12-
],
13-
plugin: 'java'
10+
inputFiles: ['dependency-history-2025-05-03T20-57-12-360Z',
11+
'commit-dependency-history-2025-05-03T20-57-12-360Z']
1412
});
1513

1614
console.log('✅ Done generating version-changes metric.');

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

Lines changed: 61 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,16 @@ export function generateGrowthPatternChartData(
118118

119119
const traceTotalChanges = {
120120
x: xValues,
121-
y: sorted.map(r => r.totalChanges ?? 0),
122-
name: 'Total Changes',
121+
y: (() => {
122+
const totals: number[] = [];
123+
let runningTotal = 0;
124+
for (const r of sorted) {
125+
runningTotal += r.totalChanges ?? 0;
126+
totals.push(runningTotal);
127+
}
128+
return totals;
129+
})(),
130+
name: 'Total Changes (Cumulative)',
123131
type: 'scatter',
124132
mode: 'lines+markers',
125133
line: { width: 2, dash: 'dot', color: '#a63603' },
@@ -152,7 +160,7 @@ export function generateGrowthPatternChartData(
152160
side: 'left'
153161
},
154162
yaxis2: {
155-
title: 'Total',
163+
title: 'Total (Cumulative)',
156164
overlaying: 'y',
157165
side: 'right',
158166
showgrid: false
@@ -173,96 +181,63 @@ export function generateGrowthPatternChartData(
173181
}
174182

175183
export function generateVersionChangeChartData(
176-
results: Record<string, Record<string, {
177-
upgrades: { from: string; to: string; date: string }[];
178-
downgrades: { from: string; to: string; date: string }[];
179-
}>>,
184+
results: Record<string, { upgrades: number; downgrades: number }>,
180185
options: MetricOptions
181186
): { data: any[]; layout: any }[] {
182-
const charts: { data: any[]; layout: any }[] = [];
183-
184-
// Prepare project-based daily counts
185-
const projectChartData: {
186-
[project: string]: { [date: string]: { upgrades: number; downgrades: number } }
187-
} = {};
188-
189-
for (const [project, deps] of Object.entries(results)) {
190-
if (!projectChartData[project]) projectChartData[project] = {};
191-
192-
for (const dep of Object.values(deps)) {
193-
for (const { date } of dep.upgrades) {
194-
const day = new Date(date).toISOString().split('T')[0];
195-
projectChartData[project][day] ??= { upgrades: 0, downgrades: 0 };
196-
projectChartData[project][day].upgrades += 1;
197-
}
198-
for (const { date } of dep.downgrades) {
199-
const day = new Date(date).toISOString().split('T')[0];
200-
projectChartData[project][day] ??= { upgrades: 0, downgrades: 0 };
201-
projectChartData[project][day].downgrades += 1;
202-
}
203-
}
204-
}
205-
206-
// Generate chart for each chartType
207-
for (const chartType of options.chartType) {
208-
const isLine = chartType === 'line';
209-
const isStacked = chartType === 'stacked';
210-
const isArea = chartType === 'stacked-area';
187+
const sortedDates = Object.keys(results).sort();
188+
const upgrades = sortedDates.map(date => results[date].upgrades);
189+
const downgrades = sortedDates.map(date => results[date].downgrades);
211190

212-
for (const [project, dateCounts] of Object.entries(projectChartData)) {
213-
const sortedDates = Object.keys(dateCounts).sort();
214-
const x = sortedDates;
215-
const upgrades = sortedDates.map(date => dateCounts[date].upgrades);
216-
const downgrades = sortedDates.map(date => dateCounts[date].downgrades);
217-
218-
const baseType = isLine || isArea ? 'scatter' : 'bar';
191+
return options.chartType.map(type => {
192+
const isLine = type === 'line';
193+
const isStacked = type === 'stacked';
194+
const isArea = type === 'stacked-area';
219195

220-
const commonTraceProps: Partial<any> = {
221-
type: baseType,
222-
mode: isLine || isArea ? 'lines+markers' : undefined,
223-
fill: isArea ? 'tonexty' : undefined,
224-
stackgroup: isArea ? 'one' : undefined
225-
};
196+
const traceType = isLine || isArea ? 'scatter' : 'bar';
197+
const commonTraceProps = {
198+
type: traceType,
199+
mode: isLine ? 'lines+markers' : undefined,
200+
fill: isArea ? 'tonexty' : undefined,
201+
stackgroup: isArea ? 'one' : undefined
202+
};
226203

227-
const data = [
228-
{
229-
x,
230-
y: upgrades,
231-
name: 'Upgrades',
232-
marker: { color: 'green' },
233-
...commonTraceProps
234-
},
235-
{
236-
x,
237-
y: downgrades,
238-
name: 'Downgrades',
239-
marker: { color: 'red' },
240-
...commonTraceProps
241-
}
242-
];
204+
const upgradeTrace = {
205+
x: sortedDates,
206+
y: upgrades,
207+
name: 'Upgrades',
208+
...commonTraceProps
209+
};
243210

244-
const layout = {
245-
title: `📦 Version Changes in "${project}" (${chartType})`,
246-
barmode: isStacked ? 'stack' : 'group',
247-
xaxis: {
248-
title: 'Date',
249-
tickangle: -45
250-
},
251-
yaxis: {
252-
title: 'Change Count'
253-
},
254-
margin: {
255-
l: 50,
256-
r: 50,
257-
t: 60,
258-
b: 100
259-
}
260-
};
211+
const downgradeTrace = {
212+
x: sortedDates,
213+
y: downgrades,
214+
name: 'Downgrades',
215+
...commonTraceProps
216+
};
261217

262-
charts.push({ data, layout });
263-
}
264-
}
218+
const layout = {
219+
title: `🔄 Version Changes Over Time (${type})`,
220+
barmode: isStacked ? 'stack' : (traceType === 'bar' ? 'group' : undefined),
221+
xaxis: {
222+
title: 'Date',
223+
tickangle: -45,
224+
automargin: true
225+
},
226+
yaxis: {
227+
title: 'Change Count'
228+
},
229+
margin: {
230+
l: 50,
231+
r: 30,
232+
t: 60,
233+
b: 120
234+
}
235+
};
265236

266-
return charts;
237+
return {
238+
data: [upgradeTrace, downgradeTrace],
239+
layout
240+
};
241+
});
267242
}
268243

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const runMetricsCommand = new Command()
1313
.option('--chart', 'Generate chart visualization', false)
1414
.option('--chartType <chartType>', 'Chart type (bar | line | stacked)', 'bar')
1515
.option('--inputFiles <inputFiles...>', 'List of input files to use (without .json)')
16-
.option('--plugin <plugin>', 'Plugin name to resolve input file prefixes')
1716
.action(runMetrics);
1817

1918
export interface MetricOptions {
@@ -22,7 +21,6 @@ export interface MetricOptions {
2221
chart: boolean;
2322
chartType: ('line' | 'bar' | 'stacked' | 'stacked-area')[];
2423
inputFiles: string[];
25-
plugin: string;
2624
}
2725

2826
export async function runMetrics(folder: string, options: MetricOptions): Promise<void> {
@@ -32,39 +30,49 @@ export async function runMetrics(folder: string, options: MetricOptions): Promis
3230
return;
3331
}
3432

33+
const requiredPrefixes = getRequiredInputFilesForMetric(options.metric);
3534
if (!options.inputFiles || options.inputFiles.length === 0) {
3635
console.error('❌ No input files provided.');
3736
return;
3837
}
3938

40-
const basePrefixes = getRequiredInputFilesForMetric(options.metric);
41-
const requiredPrefixes = getFilePrefixesForPlugin(basePrefixes, options.plugin);
42-
const validInputFiles = filterValidInputFiles(options.inputFiles, requiredPrefixes);
39+
const validInputFiles = options.inputFiles.filter(file =>
40+
requiredPrefixes.some(prefix => file.startsWith(prefix))
41+
);
4342

4443
if (validInputFiles.length === 0) {
45-
console.warn(`⚠️ No input files matched the required pattern for metric '${options.metric}'`);
44+
console.warn(`⚠️ No input files match the required prefixes: ${requiredPrefixes.join(', ')}`);
4645
return;
4746
}
4847

48+
console.log(validInputFiles);
49+
console.log(metricProcessor);
50+
4951
for (const relativePath of validInputFiles) {
5052
const fileName = relativePath.endsWith('.json') ? relativePath : `${relativePath}.json`;
5153
const filePath = path.join(folder, fileName);
54+
55+
if (!fs.existsSync(filePath)) {
56+
console.warn(`⚠️ File not found: ${filePath}`);
57+
continue;
58+
}
59+
5260
const fileContents = fs.readFileSync(filePath, 'utf-8');
5361
const data = JSON.parse(fileContents);
5462
const results = metricProcessor(data);
5563

56-
const resultsFolder = path.join(path.dirname(filePath), options.results);
64+
const resultsFolder = path.join(folder, options.results);
5765
fs.mkdirSync(resultsFolder, { recursive: true });
5866

59-
const outputFile = path.join(resultsFolder, `${path.parse(filePath).name}-${options.metric}-metric.json`);
67+
const outputFile = path.join(resultsFolder, `${path.parse(fileName).name}-${options.metric}-metric.json`);
6068
fs.writeFileSync(outputFile, JSON.stringify(results, null, 2));
6169

6270
if (options.chart) {
6371
const chartConfigs = getChartDataForMetric(options.metric, results, options);
6472
if (chartConfigs?.length) {
6573
await generateHtmlChart(outputFile, chartConfigs);
6674
} else {
67-
console.warn(`⚠️ No chart generator defined for metric '${options.metric}'. Skipping chart.`);
75+
console.warn(`⚠️ No chart generator defined for metric '${options.metric}'. Skipping chart.`);
6876
}
6977
}
7078

@@ -109,12 +117,3 @@ function getChartDataForMetric(
109117
}
110118
}
111119

112-
function getFilePrefixesForPlugin(basePrefixes: string[], plugin?: string): string[] {
113-
return basePrefixes.map(base => (plugin ? `${base}-${plugin}` : base));
114-
}
115-
116-
function filterValidInputFiles(inputFiles: string[], requiredPrefixes: string[]): string[] {
117-
return inputFiles.filter(file =>
118-
requiredPrefixes.some(prefix => file.startsWith(prefix))
119-
);
120-
}

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

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,36 +43,38 @@ export function GrowthPatternMetric(data: CommitDependencyHistory): any {
4343
return Object.values(summary);
4444
}
4545

46+
export function VersionChangeMetric(dependencies: Record<string, { history: any[] }>): any {
47+
const results: Record<
48+
string,
49+
{
50+
upgrades: number;
51+
downgrades: number;
52+
}
53+
> = {};
4654

47-
export function VersionChangeMetric(data: Record<string, { history: any[] }>) {
48-
const result: Record<string, Record<string, {
49-
upgrades: { from: string, to: string, date: string }[],
50-
downgrades: { from: string, to: string, date: string }[]
51-
}>> = {};
52-
53-
for (const [depName, depInfo] of Object.entries(data)) {
54-
for (const entry of depInfo.history) {
55-
if (entry.action === 'MODIFIED') {
56-
const { fromVersion, toVersion, project, date } = entry;
55+
for (const { history } of Object.values(dependencies)) {
56+
for (const entry of history) {
57+
if (
58+
entry.action !== 'MODIFIED' ||
59+
!entry.fromVersion ||
60+
!entry.toVersion ||
61+
!semver.valid(entry.fromVersion) ||
62+
!semver.valid(entry.toVersion) ||
63+
!entry.date
64+
) {
65+
continue;
66+
}
5767

58-
if (!result[project]) {
59-
result[project] = {};
60-
}
61-
if (!result[project][depName]) {
62-
result[project][depName] = { upgrades: [], downgrades: [] };
63-
}
68+
const dateKey = new Date(entry.date).toISOString().split('T')[0];
69+
const changeType = semver.gt(entry.toVersion, entry.fromVersion) ? 'upgrades' : 'downgrades';
6470

65-
if (semver.valid(fromVersion) && semver.valid(toVersion)) {
66-
if (semver.gt(toVersion, fromVersion)) {
67-
result[project][depName].upgrades.push({ from: fromVersion, to: toVersion, date });
68-
} else if (semver.lt(toVersion, fromVersion)) {
69-
result[project][depName].downgrades.push({ from: fromVersion, to: toVersion, date });
70-
}
71-
}
71+
if (!results[dateKey]) {
72+
results[dateKey] = { upgrades: 0, downgrades: 0 };
7273
}
74+
75+
results[dateKey][changeType]++;
7376
}
7477
}
7578

76-
return result;
79+
return results;
7780
}
78-

0 commit comments

Comments
 (0)