mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-11 03:40:05 +00:00
refactor(api): reuse board column contract across clients
Make BoardColumnDefinition.id reference the existing BoardColumn schema and carry that typed contract through generated TypeScript, server responses, demo data, and the runs board UI.
This commit is contained in:
parent
63940fdddc
commit
b5b08e78d3
8 changed files with 74 additions and 51 deletions
|
|
@ -1,8 +1,10 @@
|
|||
import { formatElapsedSecs, formatDurationSecs } from "../lib/format";
|
||||
import type {
|
||||
RunListItem,
|
||||
RunStatus as ApiRunStatus,
|
||||
RunSummary,
|
||||
import {
|
||||
BoardColumn,
|
||||
type BoardColumn as ApiBoardColumn,
|
||||
type RunListItem,
|
||||
type RunStatus as ApiRunStatus,
|
||||
type RunSummary,
|
||||
} from "@qltysh/fabro-api-client";
|
||||
|
||||
export type CiStatus = "passing" | "failing" | "pending";
|
||||
|
|
@ -37,7 +39,16 @@ export interface RunItem {
|
|||
sourceDirectory?: string;
|
||||
}
|
||||
|
||||
export type ColumnStatus = "queued" | "initializing" | "running" | "blocked" | "succeeded" | "failed";
|
||||
export type ColumnStatus = ApiBoardColumn;
|
||||
|
||||
export const columnStatuses = [
|
||||
BoardColumn.QUEUED,
|
||||
BoardColumn.INITIALIZING,
|
||||
BoardColumn.RUNNING,
|
||||
BoardColumn.BLOCKED,
|
||||
BoardColumn.SUCCEEDED,
|
||||
BoardColumn.FAILED,
|
||||
] as const satisfies readonly ColumnStatus[];
|
||||
|
||||
export const columnStatusDisplay: Record<ColumnStatus, { label: string; dot: string; text: string }> = {
|
||||
queued: { label: "Queued", dot: "bg-fg-muted", text: "text-fg-muted" },
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ const immutableOptions: SWRConfiguration = {
|
|||
revalidateOnReconnect: false,
|
||||
};
|
||||
|
||||
type BoardRunsEnvelope = PaginatedEnvelope<PaginatedBoardRunList["data"][number]> &
|
||||
Pick<PaginatedBoardRunList, "columns">;
|
||||
|
||||
export function useAuthConfig() {
|
||||
return useSWR<{ methods: string[] }>(queryKeys.auth.config(), apiFetcher, immutableOptions);
|
||||
}
|
||||
|
|
@ -61,11 +64,7 @@ export function useSystemInfo() {
|
|||
}
|
||||
|
||||
export function useBoardsRuns() {
|
||||
return useSWR<
|
||||
PaginatedEnvelope<PaginatedBoardRunList["data"][number]> & {
|
||||
columns: { id: string; name: string }[];
|
||||
}
|
||||
>(queryKeys.boards.runs(), apiPaginatedFetcher);
|
||||
return useSWR<BoardRunsEnvelope>(queryKeys.boards.runs(), apiPaginatedFetcher);
|
||||
}
|
||||
|
||||
export function useRun(id: string | undefined) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
arrayMove,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { ciConfig, columnStatusDisplay, deriveCiStatus, mapRunListItem } from "../data/runs";
|
||||
import { ciConfig, columnStatusDisplay, columnStatuses, deriveCiStatus, mapRunListItem } from "../data/runs";
|
||||
import type { CiStatus, CheckRun, CheckStatus, RunItem, RunWithStatus, ColumnStatus } from "../data/runs";
|
||||
import { EmptyState } from "../components/state";
|
||||
import { shouldRefreshBoardForEvent, useBoardEvents } from "../lib/board-events";
|
||||
|
|
@ -50,7 +50,7 @@ const defaultColumnStyle: ColumnStyle = { iconType: "branch", actions: [] };
|
|||
const defaultColumnColors = { dot: "bg-fg-muted", text: "text-fg-muted" };
|
||||
|
||||
interface BoardRunsResponse {
|
||||
columns: { id: string; name: string }[];
|
||||
columns: PaginatedBoardRunList["columns"];
|
||||
data: PaginatedBoardRunList["data"];
|
||||
meta: PaginatedBoardRunList["meta"];
|
||||
}
|
||||
|
|
@ -65,17 +65,8 @@ type Column = {
|
|||
items: RunItem[];
|
||||
};
|
||||
|
||||
const SKELETON_STATUSES: ColumnStatus[] = [
|
||||
"queued",
|
||||
"initializing",
|
||||
"running",
|
||||
"blocked",
|
||||
"succeeded",
|
||||
"failed",
|
||||
];
|
||||
|
||||
function buildSkeletonColumns(): Column[] {
|
||||
return SKELETON_STATUSES.map((id) => {
|
||||
return columnStatuses.map((id) => {
|
||||
const colors = columnStatusDisplay[id];
|
||||
return {
|
||||
id,
|
||||
|
|
@ -100,7 +91,7 @@ export function buildBoardColumns(response: BoardRunsResponse): Column[] {
|
|||
}
|
||||
|
||||
return response.columns.map((col) => {
|
||||
const id = col.id as ColumnStatus;
|
||||
const id = col.id;
|
||||
const colors = columnStatusDisplay[id] ?? defaultColumnColors;
|
||||
return {
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -5670,7 +5670,7 @@ components:
|
|||
- name
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
$ref: "#/components/schemas/BoardColumn"
|
||||
name:
|
||||
type: string
|
||||
|
||||
|
|
|
|||
|
|
@ -975,27 +975,27 @@ mod runs {
|
|||
pub(super) fn columns() -> Vec<BoardColumnDefinition> {
|
||||
vec![
|
||||
BoardColumnDefinition {
|
||||
id: "queued".into(),
|
||||
id: BoardColumn::Queued,
|
||||
name: "Queued".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: "initializing".into(),
|
||||
id: BoardColumn::Initializing,
|
||||
name: "Initializing".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: "running".into(),
|
||||
id: BoardColumn::Running,
|
||||
name: "Running".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: "blocked".into(),
|
||||
id: BoardColumn::Blocked,
|
||||
name: "Blocked".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: "succeeded".into(),
|
||||
id: BoardColumn::Succeeded,
|
||||
name: "Succeeded".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: "failed".into(),
|
||||
id: BoardColumn::Failed,
|
||||
name: "Failed".into(),
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ use axum::{Json, Router};
|
|||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use bytes::Bytes;
|
||||
use fabro_api::types::{RunManifest, RunStatusResponse, SubmitAnswerRequest};
|
||||
use fabro_api::types::{
|
||||
BoardColumn, BoardColumnDefinition, RunManifest, RunStatusResponse, SubmitAnswerRequest,
|
||||
};
|
||||
use fabro_config::Storage;
|
||||
use fabro_interview::AnswerSubmission;
|
||||
use fabro_types::{
|
||||
|
|
@ -82,27 +84,45 @@ impl ListRunsParams {
|
|||
}
|
||||
}
|
||||
|
||||
fn board_column(status: RunStatus) -> Option<&'static str> {
|
||||
fn board_column(status: RunStatus) -> Option<BoardColumn> {
|
||||
match status {
|
||||
RunStatus::Submitted | RunStatus::Queued => Some("queued"),
|
||||
RunStatus::Starting => Some("initializing"),
|
||||
RunStatus::Running | RunStatus::Paused { .. } => Some("running"),
|
||||
RunStatus::Blocked { .. } => Some("blocked"),
|
||||
RunStatus::Succeeded { .. } => Some("succeeded"),
|
||||
RunStatus::Failed { .. } | RunStatus::Dead => Some("failed"),
|
||||
RunStatus::Submitted | RunStatus::Queued => Some(BoardColumn::Queued),
|
||||
RunStatus::Starting => Some(BoardColumn::Initializing),
|
||||
RunStatus::Running | RunStatus::Paused { .. } => Some(BoardColumn::Running),
|
||||
RunStatus::Blocked { .. } => Some(BoardColumn::Blocked),
|
||||
RunStatus::Succeeded { .. } => Some(BoardColumn::Succeeded),
|
||||
RunStatus::Failed { .. } | RunStatus::Dead => Some(BoardColumn::Failed),
|
||||
RunStatus::Removing | RunStatus::Archived { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn board_columns() -> serde_json::Value {
|
||||
serde_json::json!([
|
||||
{"id": "queued", "name": "Queued"},
|
||||
{"id": "initializing", "name": "Initializing"},
|
||||
{"id": "running", "name": "Running"},
|
||||
{"id": "blocked", "name": "Blocked"},
|
||||
{"id": "succeeded", "name": "Succeeded"},
|
||||
{"id": "failed", "name": "Failed"},
|
||||
])
|
||||
pub(crate) fn board_columns() -> Vec<BoardColumnDefinition> {
|
||||
vec![
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Queued,
|
||||
name: "Queued".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Initializing,
|
||||
name: "Initializing".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Running,
|
||||
name: "Running".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Blocked,
|
||||
name: "Blocked".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Succeeded,
|
||||
name: "Succeeded".into(),
|
||||
},
|
||||
BoardColumnDefinition {
|
||||
id: BoardColumn::Failed,
|
||||
name: "Failed".into(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn board_run_metadata(
|
||||
|
|
|
|||
|
|
@ -6615,8 +6615,8 @@ async fn queue_position_reported_for_queued_runs() {
|
|||
let first_run_id = create_and_start_run(&app, MINIMAL_DOT).await;
|
||||
let second_run_id = create_and_start_run(&app, MINIMAL_DOT).await;
|
||||
|
||||
// Queued runs are excluded from the board, so verify queue positions
|
||||
// via the in-memory state directly.
|
||||
// Queue position is tracked in memory even when queued runs are also
|
||||
// visible on the board.
|
||||
let runs = state.runs.lock().expect("runs lock poisoned");
|
||||
let positions = compute_queue_positions(&runs);
|
||||
let first_id = first_run_id.parse::<RunId>().unwrap();
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { BoardColumn } from './board-column';
|
||||
|
||||
export interface BoardColumnDefinition {
|
||||
'id': string;
|
||||
'id': BoardColumn;
|
||||
'name': string;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue