diff --git a/apps/fabro-web/app/routes/runs.test.tsx b/apps/fabro-web/app/routes/runs.test.tsx index 52196878b..71f30dd43 100644 --- a/apps/fabro-web/app/routes/runs.test.tsx +++ b/apps/fabro-web/app/routes/runs.test.tsx @@ -3,6 +3,7 @@ import type { BoardColumn, Run } from "@qltysh/fabro-api-client"; import { buildBoardColumns, + buildFilterOptions, loadStoredRunsWorkspaceSearchParams, placeArchivedColumnLast, persistRunsWorkspacePreferences, @@ -11,6 +12,7 @@ import { shouldRefreshBoardForEvent, } from "./runs"; import { summarizeBatchLifecycleAction } from "../components/runs-list/batch-lifecycle"; +import { mapRunListItem } from "../data/runs"; import { TEST_PRINCIPAL } from "../lib/test-fixtures"; function boardRun(id: string, column: BoardColumn, questionText?: string): Run { @@ -217,6 +219,38 @@ describe("runs route board mapping", () => { }); }); +describe("runs route filter options", () => { + function runWith(id: string, repoName: string, workflowName: string): Run { + const run = boardRun(id, "running"); + return { + ...run, + repository: { ...run.repository, name: repoName }, + workflow: { ...run.workflow, name: workflowName }, + }; + } + + test("derives sorted unique options from run items", () => { + const items = [ + runWith("a", "qlty/beta", "release"), + runWith("b", "qlty/alpha", "hello"), + runWith("c", "qlty/beta", "release"), + ].map(mapRunListItem); + + expect(buildFilterOptions(items, (item) => item.repo, "all")).toEqual(["alpha", "beta"]); + expect(buildFilterOptions(items, (item) => item.workflow, "all")).toEqual([ + "hello", + "release", + ]); + }); + + test("keeps the active selection when no loaded run matches it", () => { + const items = [runWith("a", "qlty/beta", "release")].map(mapRunListItem); + + expect(buildFilterOptions(items, (item) => item.repo, "gamma")).toEqual(["beta", "gamma"]); + expect(buildFilterOptions([], (item) => item.workflow, "release")).toEqual(["release"]); + }); +}); + describe("runs route workspace preferences", () => { class MemoryStorage { values = new Map(); diff --git a/apps/fabro-web/app/routes/runs.tsx b/apps/fabro-web/app/routes/runs.tsx index b1e09bf67..9aeceb78c 100644 --- a/apps/fabro-web/app/routes/runs.tsx +++ b/apps/fabro-web/app/routes/runs.tsx @@ -140,6 +140,18 @@ export function buildBoardColumns( }); } +export function buildFilterOptions( + items: RunItem[], + pick: (item: RunItem) => string, + selected: string, +): string[] { + const values = new Set(items.map(pick)); + // Keep the active selection visible even when no loaded run matches it, + // e.g. a stored repo filter while paginating the list view. + if (selected !== "all") values.add(selected); + return Array.from(values).sort(); +} + export function placeArchivedColumnLast(columns: Column[], includeArchived: boolean): Column[] { if (!includeArchived) return columns; const archived = columns.find((column) => column.id === "archived"); @@ -771,18 +783,18 @@ export default function Runs() { ); const hasGitHubAuth = authConfig.data?.methods.includes("github") === true; const serverUrl = systemInfo.data?.server_url; - const allRepos = Array.from( - new Set( - initialColumns.flatMap((col: Column) => col.items.map((item: RunItem) => String(item.repo))), - ), + // Filter options come from the loaded runs: all runs in columns view, the + // current page in list view (until a facets endpoint provides the full set). + const filterSourceItems: RunItem[] = + view === "list" + ? (listRunsPage.data?.data ?? []).map(mapRunListItem) + : initialColumns.flatMap((col: Column) => col.items); + const allRepos = buildFilterOptions(filterSourceItems, (item) => item.repo, repoFilter); + const allWorkflows = buildFilterOptions( + filterSourceItems, + (item) => item.workflow, + workflowFilter, ); - allRepos.sort(); - const allWorkflows = Array.from( - new Set( - initialColumns.flatMap((col: Column) => col.items.map((item: RunItem) => String(item.workflow))), - ), - ); - allWorkflows.sort(); const [columnsState, setColumnsState] = useState(() => ({ base: initialColumns, columns: initialColumns,