mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
refactor: simplify after stage status unification
Share ACTIVE_STAGE_STATES/SUCCEEDED_STAGE_STATES across stage-sidebar and run-overview, collapse the nested match in active_stage_state_from_events, and drop a few WHAT-comments that narrated the recent rename. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
29eca2921d
commit
118ce83966
6 changed files with 32 additions and 38 deletions
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@heroicons/react/24/solid";
|
||||
import { Bars3BottomLeftIcon, DocumentTextIcon, MapIcon } from "@heroicons/react/24/outline";
|
||||
import { formatDurationSecs } from "../lib/format";
|
||||
import { ACTIVE_STAGE_STATES } from "../lib/stage-sidebar";
|
||||
|
||||
export interface Stage {
|
||||
id: string;
|
||||
|
|
@ -20,8 +21,6 @@ export interface Stage {
|
|||
dotId?: string;
|
||||
}
|
||||
|
||||
const activeStageStates = new Set<StageState>(["running", "retrying"]);
|
||||
|
||||
export const statusConfig: Record<StageState, { icon: ComponentType<{ className?: string }>; color: string }> = {
|
||||
pending: { icon: PauseCircleIcon, color: "text-fg-muted" },
|
||||
running: { icon: ArrowPathIcon, color: "text-teal-500" },
|
||||
|
|
@ -48,7 +47,7 @@ export function StageSidebar({ stages, runId, selectedStageId, activeLink }: Sta
|
|||
// Track start times for running stages
|
||||
useEffect(() => {
|
||||
const running = new Set<string>(
|
||||
stages.filter((s) => activeStageStates.has(s.status)).map((s) => s.id),
|
||||
stages.filter((s) => ACTIVE_STAGE_STATES.has(s.status)).map((s) => s.id),
|
||||
);
|
||||
for (const stageId of running) {
|
||||
if (!runningStartRef.current.has(stageId)) {
|
||||
|
|
@ -64,13 +63,13 @@ export function StageSidebar({ stages, runId, selectedStageId, activeLink }: Sta
|
|||
|
||||
// Tick every second while any stage is running
|
||||
useEffect(() => {
|
||||
if (!stages.some((s) => activeStageStates.has(s.status))) return;
|
||||
if (!stages.some((s) => ACTIVE_STAGE_STATES.has(s.status))) return;
|
||||
const interval = setInterval(() => setTick((t) => t + 1), 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [stages]);
|
||||
|
||||
function stageDuration(stage: Stage): string {
|
||||
if (activeStageStates.has(stage.status)) {
|
||||
if (ACTIVE_STAGE_STATES.has(stage.status)) {
|
||||
const start = runningStartRef.current.get(stage.id);
|
||||
if (start) return formatDurationSecs(Math.floor((Date.now() - start) / 1000));
|
||||
return "0s";
|
||||
|
|
@ -100,7 +99,7 @@ export function StageSidebar({ stages, runId, selectedStageId, activeLink }: Sta
|
|||
: "text-fg-3 hover:bg-overlay hover:text-fg"
|
||||
}`}
|
||||
>
|
||||
<Icon className={`size-4 shrink-0 ${config.color} ${activeStageStates.has(stage.status) ? "animate-spin" : ""}`} />
|
||||
<Icon className={`size-4 shrink-0 ${config.color} ${ACTIVE_STAGE_STATES.has(stage.status) ? "animate-spin" : ""}`} />
|
||||
<span className="flex-1 truncate">{stage.name}</span>
|
||||
<span className="font-mono text-xs tabular-nums text-fg-muted">{stageDuration(stage)}</span>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import type { PaginatedRunStageList } from "@qltysh/fabro-api-client";
|
||||
import type { PaginatedRunStageList, StageState } from "@qltysh/fabro-api-client";
|
||||
|
||||
import type { Stage } from "../components/stage-sidebar";
|
||||
import { isVisibleStage } from "../data/runs";
|
||||
import { formatDurationSecs } from "./format";
|
||||
|
||||
export const ACTIVE_STAGE_STATES: ReadonlySet<StageState> = new Set(["running", "retrying"]);
|
||||
export const SUCCEEDED_STAGE_STATES: ReadonlySet<StageState> = new Set([
|
||||
"succeeded",
|
||||
"partially_succeeded",
|
||||
]);
|
||||
|
||||
export function mapRunStagesToSidebarStages(
|
||||
stagesResult: PaginatedRunStageList | null | undefined,
|
||||
): Stage[] {
|
||||
|
|
|
|||
|
|
@ -43,15 +43,9 @@ export function emptyStateCopy(kind: EmptyKind): string {
|
|||
}
|
||||
}
|
||||
|
||||
/// Derive the empty-state variant from the full loader context. `runStatus`
|
||||
/// comes from the parent run loader (`run.lifecycleStatus`); its absence
|
||||
/// collapses to the "unknown" catchall so the empty state never displays
|
||||
/// misleading copy.
|
||||
///
|
||||
/// The full RunStatus enum (per fabro-types/src/status.rs) is:
|
||||
/// submitted, queued, starting, running, blocked, paused, removing,
|
||||
/// succeeded, failed, dead, archived
|
||||
/// partially_succeeded is a stage outcome, not a run status.
|
||||
/// `runStatus` comes from the parent run loader (`run.lifecycleStatus`); its
|
||||
/// absence collapses to the "unknown" catchall so the empty state never
|
||||
/// displays misleading copy.
|
||||
export function deriveEmptyKind(args: {
|
||||
runStatus: string | undefined;
|
||||
totalChanged: number;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import {
|
|||
GraphToolbar,
|
||||
} from "../components/graph-toolbar";
|
||||
import { EmptyState } from "../components/state";
|
||||
import { mapRunStagesToSidebarStages } from "../lib/stage-sidebar";
|
||||
import {
|
||||
ACTIVE_STAGE_STATES,
|
||||
SUCCEEDED_STAGE_STATES,
|
||||
mapRunStagesToSidebarStages,
|
||||
} from "../lib/stage-sidebar";
|
||||
|
||||
export const handle = { wide: true };
|
||||
|
||||
|
|
@ -52,13 +56,13 @@ export default function RunOverview() {
|
|||
|
||||
const gt = graphTheme;
|
||||
const runningDotIds = new Set<string>(
|
||||
stages.filter((s: Stage) => s.status === "running" || s.status === "retrying").map((s: Stage) => s.dotId ?? s.id),
|
||||
stages.filter((s: Stage) => ACTIVE_STAGE_STATES.has(s.status)).map((s: Stage) => s.dotId ?? s.id),
|
||||
);
|
||||
const failedDotIds = new Set<string>(
|
||||
stages.filter((s: Stage) => s.status === "failed").map((s: Stage) => s.dotId ?? s.id),
|
||||
);
|
||||
const completedDotIds = new Set<string>(
|
||||
stages.filter((s: Stage) => s.status === "succeeded" || s.status === "partially_succeeded").map((s: Stage) => s.dotId ?? s.id),
|
||||
stages.filter((s: Stage) => SUCCEEDED_STAGE_STATES.has(s.status)).map((s: Stage) => s.dotId ?? s.id),
|
||||
);
|
||||
const dotIdToStageId = new Map<string, string>(
|
||||
stages.map((s: Stage) => [s.dotId ?? s.id, s.id]),
|
||||
|
|
|
|||
|
|
@ -95,9 +95,6 @@ function sampleSettings({
|
|||
};
|
||||
}
|
||||
|
||||
// Static sample data used by the `workflow-definition` index route for the
|
||||
// hardcoded showcase workflows. Shape mirrors the persisted
|
||||
// `WorkflowSettings` snapshot returned by `/api/v1/runs/:id/settings`.
|
||||
export const workflowData: Record<string, WorkflowEntry> = {
|
||||
fix_build: {
|
||||
name: "Fix Build",
|
||||
|
|
|
|||
|
|
@ -2150,23 +2150,18 @@ async fn openapi_spec() -> Response {
|
|||
}
|
||||
|
||||
fn active_stage_state_from_events(events: &[EventEnvelope], node_id: &str) -> StageState {
|
||||
let latest_stage_event = events.iter().rev().find_map(|envelope| {
|
||||
let event = &envelope.event;
|
||||
if event.node_id.as_deref() == Some(node_id) {
|
||||
match event.event_name() {
|
||||
"stage.retrying" | "stage.started" | "stage.completed" | "stage.failed" => {
|
||||
Some(event.event_name())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let latest = events.iter().rev().find(|envelope| {
|
||||
envelope.event.node_id.as_deref() == Some(node_id)
|
||||
&& matches!(
|
||||
envelope.event.event_name(),
|
||||
"stage.retrying" | "stage.started" | "stage.completed" | "stage.failed"
|
||||
)
|
||||
});
|
||||
|
||||
match latest_stage_event {
|
||||
Some("stage.retrying") => StageState::Retrying,
|
||||
_ => StageState::Running,
|
||||
if latest.is_some_and(|e| e.event.event_name() == "stage.retrying") {
|
||||
StageState::Retrying
|
||||
} else {
|
||||
StageState::Running
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2270,7 +2265,6 @@ async fn list_run_stages(
|
|||
.into_response();
|
||||
};
|
||||
|
||||
// Get durations and active retrying state from events.
|
||||
let events = match state.store.open_run_reader(&id).await {
|
||||
Ok(run_store) => run_store.list_events().await.unwrap_or_default(),
|
||||
Err(_) => Vec::new(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue