From d1cfed3dfe5075179f33932b5281e1eac88388f0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 13 Jul 2026 08:07:37 +0000 Subject: [PATCH 1/3] feat(web): add outlier and pipeline-error filters to runs list Add runListFilters helpers and wire /runs plus /api/runs with outlier=true and errors=true query params. Link from pipeline errors and outliers pages. Co-authored-by: Eamon Boyle --- apps/web/app/api/runs/route.ts | 38 +++++++--- apps/web/app/errors/page.tsx | 3 + apps/web/app/outliers/page.tsx | 6 ++ apps/web/app/runs/page.tsx | 57 ++++++++++++--- apps/web/lib/runListFilters.test.ts | 104 ++++++++++++++++++++++++++++ apps/web/lib/runListFilters.ts | 53 ++++++++++++++ 6 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 apps/web/lib/runListFilters.test.ts create mode 100644 apps/web/lib/runListFilters.ts diff --git a/apps/web/app/api/runs/route.ts b/apps/web/app/api/runs/route.ts index c5bc604..b01bf95 100644 --- a/apps/web/app/api/runs/route.ts +++ b/apps/web/app/api/runs/route.ts @@ -1,23 +1,43 @@ -import { filterRunArtifacts, loadRunArtifacts } from "../../../lib/data"; +import { filterRunArtifacts, loadAnalysisIndex, loadRunArtifacts } from "../../../lib/data"; import { resolveRunSortOrder, sortRunArtifacts, } from "../../../lib/artifactSort"; import { runArtifactsToCsv } from "../../../lib/listExport"; +import { buildOutlierRunIdSet } from "../../../lib/outlierLookup"; +import { + applyRunListExtraFilters, + buildPipelineErrorRunIdSet, + parseRunListExtraFilters, +} from "../../../lib/runListFilters"; import { parseListPagination } from "../_shared/pagination"; export async function GET(request: Request) { const url = new URL(request.url); const format = url.searchParams.get("format") ?? "json"; - const runs = await loadRunArtifacts(); - const filtered = filterRunArtifacts(runs, { - q: url.searchParams.get("q") ?? undefined, - model: url.searchParams.get("model") ?? undefined, - preset: url.searchParams.get("preset") ?? undefined, - fast: url.searchParams.get("fast") ?? undefined, - from: url.searchParams.get("from") ?? undefined, - to: url.searchParams.get("to") ?? undefined, + const [runs, index] = await Promise.all([ + loadRunArtifacts(), + loadAnalysisIndex(), + ]); + const extraFilters = parseRunListExtraFilters({ + outlier: url.searchParams.get("outlier") ?? undefined, + errors: url.searchParams.get("errors") ?? undefined, }); + const filtered = applyRunListExtraFilters( + filterRunArtifacts(runs, { + q: url.searchParams.get("q") ?? undefined, + model: url.searchParams.get("model") ?? undefined, + preset: url.searchParams.get("preset") ?? undefined, + fast: url.searchParams.get("fast") ?? undefined, + from: url.searchParams.get("from") ?? undefined, + to: url.searchParams.get("to") ?? undefined, + }), + extraFilters, + { + outlierRunIds: buildOutlierRunIdSet(index), + errorRunIds: buildPipelineErrorRunIdSet(runs), + }, + ); const sort = resolveRunSortOrder(url.searchParams.get("sort") ?? undefined); const { offset, limit, page } = parseListPagination(url.searchParams); const sorted = sortRunArtifacts(filtered, sort); diff --git a/apps/web/app/errors/page.tsx b/apps/web/app/errors/page.tsx index cfb8cc5..5d93768 100644 --- a/apps/web/app/errors/page.tsx +++ b/apps/web/app/errors/page.tsx @@ -89,6 +89,9 @@ export default async function PipelineErrorsPage({ Pipeline timing + + Runs with errors + diff --git a/apps/web/app/outliers/page.tsx b/apps/web/app/outliers/page.tsx index 77ae2f3..16c0662 100644 --- a/apps/web/app/outliers/page.tsx +++ b/apps/web/app/outliers/page.tsx @@ -79,6 +79,12 @@ export default async function OutliersPage({ All benchmarks + + Outlier runs list + diff --git a/apps/web/app/runs/page.tsx b/apps/web/app/runs/page.tsx index c083b2a..a9a828c 100644 --- a/apps/web/app/runs/page.tsx +++ b/apps/web/app/runs/page.tsx @@ -17,6 +17,12 @@ import { } from "../../lib/data"; import { buildIndexRunLookup } from "../../lib/indexRunLookup"; import { buildOutlierRunIdSet } from "../../lib/outlierLookup"; +import { + applyRunListExtraFilters, + buildPipelineErrorRunIdSet, + hasActiveRunListExtraFilters, + parseRunListExtraFilters, +} from "../../lib/runListFilters"; import { resolveRunSortOrder, sortRunArtifacts, @@ -35,6 +41,8 @@ type RunsSearchParams = { fast?: string; from?: string; to?: string; + outlier?: string; + errors?: string; sort?: string; page?: string; pageSize?: string; @@ -50,18 +58,24 @@ export default async function RunsPage({ loadBenchmarkArtifacts(), loadAnalysisIndex(), ]); + const params = await searchParams; const indexLookup = index ? buildIndexRunLookup(index) : null; const outlierRunIds = buildOutlierRunIdSet(index); + const errorRunIds = buildPipelineErrorRunIdSet(runs); + const extraFilters = parseRunListExtraFilters(params); const { models, presets } = collectArtifactFacets(runs, benchmarks); - const params = await searchParams; - const filtered = filterRunArtifacts(runs, { - q: params.q, - model: params.model, - preset: params.preset, - fast: params.fast, - from: params.from, - to: params.to, - }); + const filtered = applyRunListExtraFilters( + filterRunArtifacts(runs, { + q: params.q, + model: params.model, + preset: params.preset, + fast: params.fast, + from: params.from, + to: params.to, + }), + extraFilters, + { outlierRunIds, errorRunIds }, + ); const sort = resolveRunSortOrder(params.sort); const sorted = sortRunArtifacts(filtered, sort); const paging = paginateItems(sorted, params, { @@ -120,6 +134,24 @@ export default async function RunsPage({ className="input" title="Created at or before" /> + +