fabro/apps/fabro-web/app/lib/queries.ts
Bryan Helmkamp c8336a250e
feat: show archived runs on the runs page
Extend GET /api/v1/boards/runs with include_archived=true (matching the
existing flag on listRuns), add an Archived BoardColumn that the server
appends only when the flag is set, and surface a "Show archived" toggle
on /runs that flips between request shapes. Default behavior is unchanged
— archived runs stay hidden.

Server: list_board_runs now takes ListRunsParams; board_column maps
RunStatus::Archived to BoardColumn::Archived; board_columns(include_archived)
appends the column conditionally. Two new handler tests cover the default
and flag-on paths.

Web: useBoardsRuns(includeArchived) keys requests so SWR refetches on
toggle; columnStatuses + columnStatusDisplay + columnStyles get an
"archived" entry; buildSkeletonColumns filters by the flag so the loading
state matches the eventual response. Two new buildBoardColumns tests cover
both column shapes.

Touched generated TS client files include unrelated whitespace drift from
openapi-generator-cli; including them keeps the working tree consistent
with what `bun run generate` produces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 08:36:04 -07:00

200 lines
No EOL
5.1 KiB
TypeScript

import useSWR, { type SWRConfiguration } from "swr";
import type {
ApiQuestion,
EventEnvelope,
PaginatedBoardRunList,
PaginatedRunFileList,
PaginatedRunList,
PaginatedRunStageList,
CommandLogResponse,
CommandOutputStream,
RunBilling,
RunProjection,
ServerSettings,
RunSummary,
SystemInfoResponse,
WorkflowSettings,
} from "@qltysh/fabro-api-client";
import type { PaginatedWorkflowListResponse, WorkflowDetailResponse } from "./workflow-api";
import {
apiFetcher,
apiNullableFetcher,
apiNullableTextFetcher,
apiPaginatedFetcher,
apiTextFetcher,
fetchAllStageEvents,
type PaginatedEnvelope,
} from "./api-client";
import { queryKeys } from "./query-keys";
const immutableOptions: SWRConfiguration = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
type BoardRunsEnvelope = PaginatedEnvelope<PaginatedBoardRunList["data"][number]> &
Pick<PaginatedBoardRunList, "columns">;
export function useAuthConfig() {
return useSWR<{ methods: string[] }>(queryKeys.auth.config(), apiFetcher, immutableOptions);
}
export function useAuthMe() {
return useSWR<{
user: {
login: string;
name: string;
email: string;
avatarUrl: string;
userUrl: string;
};
provider: string;
demoMode: boolean;
}>(queryKeys.auth.me(), apiFetcher, { dedupingInterval: 10_000 });
}
export function useSystemInfo() {
return useSWR<SystemInfoResponse>(
queryKeys.system.info(),
apiFetcher,
immutableOptions,
);
}
export function useBoardsRuns(includeArchived: boolean = false) {
return useSWR<BoardRunsEnvelope>(
queryKeys.boards.runs(includeArchived),
apiPaginatedFetcher,
);
}
export function useRun(id: string | undefined) {
return useSWR<RunSummary | null>(
id ? queryKeys.runs.detail(id) : null,
apiNullableFetcher,
);
}
export function useRunState(id: string | undefined) {
return useSWR<RunProjection | null>(
id ? queryKeys.runs.state(id) : null,
apiNullableFetcher,
);
}
export function useRunFiles(id: string | undefined) {
return useSWR<PaginatedRunFileList | null>(
id ? queryKeys.runs.files(id) : null,
apiNullableFetcher,
{ keepPreviousData: true },
);
}
export function useRunStages(id: string | undefined) {
return useSWR<PaginatedRunStageList | null>(
id ? queryKeys.runs.stages(id) : null,
apiNullableFetcher,
);
}
export function useRunGraph(id: string | undefined, direction?: "LR" | "TB") {
return useSWR<string | null>(
id ? queryKeys.runs.graph(id, direction) : null,
apiNullableTextFetcher,
);
}
export function useRunGraphSource(id: string | undefined, enabled: boolean) {
return useSWR<string | null>(
id && enabled ? queryKeys.runs.graphSource(id) : null,
apiNullableTextFetcher,
);
}
export function useRunLogs(id: string | undefined, refreshInterval?: number) {
return useSWR<string | null>(
id ? queryKeys.runs.logs(id) : null,
apiNullableTextFetcher,
refreshInterval ? { refreshInterval } : undefined,
);
}
export function useRunSettings<T = WorkflowSettings>(id: string | undefined) {
return useSWR<T>(
id ? queryKeys.runs.settings(id) : null,
apiFetcher,
immutableOptions,
);
}
export function useRunBilling(id: string | undefined) {
return useSWR<RunBilling>(id ? queryKeys.runs.billing(id) : null, apiFetcher);
}
export function useRunQuestions(id: string | undefined, enabled: boolean) {
return useSWR<ApiQuestion[]>(
id && enabled ? queryKeys.runs.questions(id, 25, 0) : null,
async (key) => {
const payload = await apiNullableFetcher<{ data: ApiQuestion[] }>(key);
return payload?.data ?? [];
},
);
}
export function useRunStageEvents(id: string | undefined, stageId: string | undefined) {
return useSWR<EventEnvelope[]>(
id && stageId ? queryKeys.runs.stageEvents(id, stageId) : null,
fetchAllStageEvents<EventEnvelope>,
);
}
export function fetchRunCommandLog(
id: string,
stageId: string,
stream: CommandOutputStream,
offset: number,
limit?: number,
) {
return apiFetcher<CommandLogResponse>(
queryKeys.runs.stageLog(id, stageId, stream, offset, limit),
);
}
export function useWorkflows() {
return useSWR<PaginatedWorkflowListResponse | null>(
queryKeys.workflows.list(),
apiNullableFetcher,
immutableOptions,
);
}
export function useWorkflow(name: string | undefined) {
return useSWR<WorkflowDetailResponse | null>(
name ? queryKeys.workflows.detail(name) : null,
apiNullableFetcher,
immutableOptions,
);
}
export function useWorkflowRuns(name: string | undefined) {
return useSWR<PaginatedRunList | null>(
name ? queryKeys.workflows.runs(name) : null,
apiNullableFetcher,
);
}
export function useInsightsQueries() {
return useSWR(queryKeys.insights.queries(), apiFetcher, immutableOptions);
}
export function useInsightsHistory() {
return useSWR(queryKeys.insights.history(), apiFetcher, immutableOptions);
}
export function useServerSettings() {
return useSWR<ServerSettings>(queryKeys.settings.server(), apiFetcher, immutableOptions);
}
export { apiTextFetcher };