diff --git a/report/src/filter.test.ts b/report/src/filter.test.ts index 4457708..7d13e18 100644 --- a/report/src/filter.test.ts +++ b/report/src/filter.test.ts @@ -70,6 +70,58 @@ describe("BenchmarkVariables", () => { }); }); + it("ranks ClientVersion values by their most recent run", () => { + const versionedRuns: BenchmarkRun[] = [ + { + sourceFile: "v1-old.json", + testName: "old v1", + testDescription: "", + outputDir: "/tmp/v1-old", + createdAt: "2026-09-01T00:00:00.000Z", + testConfig: { ClientVersion: "base/v1", NodeType: "reth" }, + result: { success: true }, + }, + { + sourceFile: "v2.json", + testName: "v2", + testDescription: "", + outputDir: "/tmp/v2", + createdAt: "2026-09-02T00:00:00.000Z", + testConfig: { ClientVersion: "base/v2", NodeType: "reth" }, + result: { success: true }, + }, + { + sourceFile: "v1-latest.json", + testName: "latest v1", + testDescription: "", + outputDir: "/tmp/v1-latest", + createdAt: "2026-09-03T00:00:00.000Z", + testConfig: { ClientVersion: "base/v1", NodeType: "reth" }, + result: { success: true }, + }, + { + sourceFile: "unknown.json", + testName: "unknown", + testDescription: "", + outputDir: "/tmp/unknown", + createdAt: "not-a-timestamp", + testConfig: { ClientVersion: "base/unknown", NodeType: "reth" }, + result: { success: true }, + }, + ]; + + const result = getBenchmarkVariables(versionedRuns, { + params: {}, + byMetric: "NodeType", + }); + + expect(result.variables.ClientVersion).toEqual([ + "base/v1", + "base/v2", + "base/unknown", + ]); + }); + it("should provide options even if the current selection has no matches", () => { const result = getBenchmarkVariables(sampleRuns, { params: { GasLimit: 100, ExtraParam: false }, // This specific combo has no runs diff --git a/report/src/filter.ts b/report/src/filter.ts index c8b3a97..95b40e4 100644 --- a/report/src/filter.ts +++ b/report/src/filter.ts @@ -16,6 +16,53 @@ function matchRuns }>( }); } +/** + * Client versions identify builds, not a naturally lexical configuration + * dimension. Show the version whose latest result is newest first. + */ +function sortFilterValues< + T extends { + testConfig: Record; + createdAt?: string; + }, +>(key: string, values: Set, runs: T[]): FilterValue[] { + const sorted = [...values]; + if (key !== "ClientVersion") { + return sorted.sort(); + } + + const latestByVersion = new Map(); + for (const run of runs) { + const version = run.testConfig.ClientVersion; + if (version === undefined) continue; + const timestamp = Date.parse(run.createdAt ?? ""); + if (Number.isNaN(timestamp)) continue; + const versionKey = String(version); + latestByVersion.set( + versionKey, + Math.max( + latestByVersion.get(versionKey) ?? Number.NEGATIVE_INFINITY, + timestamp, + ), + ); + } + + return sorted.sort((left, right) => { + const leftTimestamp = latestByVersion.get(String(left)); + const rightTimestamp = latestByVersion.get(String(right)); + if ( + leftTimestamp !== undefined && + rightTimestamp !== undefined && + leftTimestamp !== rightTimestamp + ) { + return rightTimestamp - leftTimestamp; + } + if (leftTimestamp !== undefined) return -1; + if (rightTimestamp !== undefined) return 1; + return String(left).localeCompare(String(right)); + }); +} + /** * Extracts variables, calculates available filter options, and filters runs based on selections. * Ensures that filter options remain available even if the current selection yields no results. @@ -47,7 +94,7 @@ export function getBenchmarkVariables< return Object.fromEntries( Object.entries(allPossibleValues) .filter(([, values]) => values.size > 1) - .map(([key, values]) => [key, [...values].sort()]), + .map(([key, values]) => [key, sortFilterValues(key, values, runs)]), ); })(); // Immediately invoke the IIFE if needed