mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-04 02:33:56 +00:00
fabro(01KQT1VDVXGWN9P6MFK4R5E44D): simplify_gpt (succeeded)
Fabro-Run: 01KQT1VDVXGWN9P6MFK4R5E44D
Fabro-Completed: 7
Fabro-Checkpoint: 07f2f2fd2d
⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
parent
48423367e1
commit
4a49057909
11 changed files with 48 additions and 33 deletions
|
|
@ -157,4 +157,4 @@ export function StageSidebar({ stages, runId, selectedStageId, activeLink }: Sta
|
|||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,27 @@ describe("subscribeToRunEvents", () => {
|
|||
cleanup();
|
||||
});
|
||||
|
||||
test("falls back to node_id when an event has no stage_id", () => {
|
||||
const source = new FakeEventSource();
|
||||
const keys: string[] = [];
|
||||
const cleanup = subscribeToRunEvents(
|
||||
"run-stage-node",
|
||||
(key) => {
|
||||
keys.push(key);
|
||||
return Promise.resolve();
|
||||
},
|
||||
() => source,
|
||||
{ debounceMs: 0 },
|
||||
);
|
||||
|
||||
source.emit({ event: "stage.started", node_id: "verify" });
|
||||
|
||||
expect(keys).toContain(queryKeys.runs.stageTurns("run-stage-node", "verify"));
|
||||
expect(keys).toContain(queryKeys.runs.stages("run-stage-node"));
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("malformed events are ignored and StrictMode-style cleanup does not underflow", () => {
|
||||
const firstSource = new FakeEventSource();
|
||||
const secondSource = new FakeEventSource();
|
||||
|
|
@ -156,4 +177,4 @@ describe("subscribeToRunEvents", () => {
|
|||
expect(firstSource.closed).toBe(true);
|
||||
expect(secondSource.closed).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -136,10 +136,10 @@ export function subscribeToRunEvents(
|
|||
}
|
||||
|
||||
function stageIdFromPayload(payload: RunEventPayload): string | undefined {
|
||||
// Only return a true `node_id@visit` StageId. A bare `node_id` would not
|
||||
// match the suffixed `stageTurns(runId, "verify@1")` cache key, so falling
|
||||
// back to it would silently no-op the invalidation.
|
||||
return typeof payload.stage_id === "string" ? payload.stage_id : undefined;
|
||||
if (typeof payload.stage_id === "string") return payload.stage_id;
|
||||
if (typeof payload.node_id === "string") return payload.node_id;
|
||||
const nodeId = payload.properties?.node_id;
|
||||
return typeof nodeId === "string" ? nodeId : undefined;
|
||||
}
|
||||
|
||||
export function useRunEvents(runId: string | undefined) {
|
||||
|
|
@ -149,4 +149,4 @@ export function useRunEvents(runId: string | undefined) {
|
|||
if (!runId) return;
|
||||
return subscribeToRunEvents(runId, mutate as MutateFn);
|
||||
}, [mutate, runId]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|||
import type { PaginatedRunStageList, StageState } from "@qltysh/fabro-api-client";
|
||||
|
||||
import type { Stage } from "../components/stage-sidebar";
|
||||
import { aggregateGraphNodeStatus, mapRunStagesToSidebarStages } from "./stage-sidebar";
|
||||
import { aggregateGraphNodeStatus, formatStageLabel, mapRunStagesToSidebarStages } from "./stage-sidebar";
|
||||
|
||||
function makeStage(nodeId: string, visit: number, status: StageState): Stage {
|
||||
return {
|
||||
|
|
@ -44,10 +44,12 @@ describe("mapRunStagesToSidebarStages", () => {
|
|||
expect(result[0].id).toBe("apply-changes@1");
|
||||
expect(result[0].nodeId).toBe("apply");
|
||||
expect(result[0].visit).toBe(1);
|
||||
expect(formatStageLabel(result[0])).toBe("Apply Changes");
|
||||
|
||||
expect(result[1].id).toBe("apply-changes@2");
|
||||
expect(result[1].nodeId).toBe("apply");
|
||||
expect(result[1].visit).toBe(2);
|
||||
expect(formatStageLabel(result[1])).toBe("Apply Changes (2)");
|
||||
});
|
||||
|
||||
test("filters by node_id (suffixed start@1 / exit@1 are still hidden)", () => {
|
||||
|
|
@ -167,4 +169,4 @@ describe("aggregateGraphNodeStatus", () => {
|
|||
latestStageId: "apply@1",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -71,4 +71,4 @@ export function aggregateGraphNodeStatus(stages: readonly Stage[]): Map<
|
|||
result.set(nodeId, { displayStatus: display.status, latestStageId: latestStage.id });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,6 @@ export default function RunOverview() {
|
|||
const inner = innerRef.current;
|
||||
if (!inner || !graphSvg) return;
|
||||
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
if (cancelled) return;
|
||||
inner.innerHTML = graphSvg;
|
||||
const svg = inner.querySelector("svg");
|
||||
if (!svg) return;
|
||||
|
|
@ -151,8 +148,6 @@ export default function RunOverview() {
|
|||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [stages, graphSvg, id, navigate, terminalOutcome]);
|
||||
|
||||
const onPointerDown = useCallback((e: React.PointerEvent) => {
|
||||
|
|
@ -237,4 +232,4 @@ export default function RunOverview() {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,4 +108,4 @@ describe("turnsFromEvents", () => {
|
|||
expect(turn.running).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { EmptyState } from "../components/state";
|
|||
import { CopyButton } from "../components/ui";
|
||||
import { formatDurationSecs } from "../lib/format";
|
||||
import { fetchRunCommandLog, useRunEventsList, useRunStageTurns, useRunStages } from "../lib/queries";
|
||||
import { formatStageLabel, mapRunStagesToSidebarStages } from "../lib/stage-sidebar";
|
||||
import { ACTIVE_STAGE_STATES, formatStageLabel, mapRunStagesToSidebarStages } from "../lib/stage-sidebar";
|
||||
import { getNumber, getString, type UnknownRecord } from "../lib/unknown";
|
||||
import {
|
||||
CommandOutputStream,
|
||||
|
|
@ -614,7 +614,7 @@ export default function RunStages() {
|
|||
() => mapTurns(turnsQuery.data, eventsQuery.data, selectedStage?.id),
|
||||
[eventsQuery.data, selectedStage?.id, turnsQuery.data],
|
||||
);
|
||||
const isRunning = selectedStage?.status === "running";
|
||||
const isActive = selectedStage ? ACTIVE_STAGE_STATES.has(selectedStage.status) : false;
|
||||
|
||||
if (!id || !stages.length) {
|
||||
return (
|
||||
|
|
@ -636,13 +636,13 @@ export default function RunStages() {
|
|||
|
||||
<div className="min-w-0 flex-1 space-y-3">
|
||||
<div className="sticky top-0 z-10 -mx-2 flex items-center gap-2 bg-page/85 px-2 py-2 backdrop-blur">
|
||||
<SelectedIcon className={`size-5 ${selectedConfig.color} ${isRunning ? "animate-spin" : ""}`} />
|
||||
<SelectedIcon className={`size-5 ${selectedConfig.color} ${isActive ? "animate-spin" : ""}`} />
|
||||
<h3 className="text-base font-semibold text-fg">
|
||||
{formatStageLabel(selectedStage)}
|
||||
</h3>
|
||||
<span className="font-mono text-xs tabular-nums text-fg-muted">
|
||||
<RunningStageDuration
|
||||
isRunning={isRunning}
|
||||
isRunning={isActive}
|
||||
duration={selectedStage.duration}
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -663,4 +663,4 @@ export default function RunStages() {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8330,4 +8330,4 @@ components:
|
|||
login:
|
||||
type: string
|
||||
description: User's login identifier (e.g. GitHub username).
|
||||
example: octocat
|
||||
example: octocat
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::num::NonZeroU32;
|
|||
use std::sync::Arc;
|
||||
|
||||
use fabro_store::RunProjectionReducer;
|
||||
use fabro_types::{EventBody, RunProjection, StageId};
|
||||
use fabro_types::{EventBody, RunProjection, StageId, StageProjection};
|
||||
|
||||
use super::super::{
|
||||
ApiError, AppState, BilledTokenCounts, BillingByModel, BillingStageRef, EventEnvelope, HashMap,
|
||||
|
|
@ -72,8 +72,7 @@ async fn list_run_stages(
|
|||
let stage_durations = fabro_workflow::extract_stage_durations_by_stage_id(&events);
|
||||
let lifecycle_states = latest_stage_states(&events);
|
||||
|
||||
let mut entries: Vec<(&StageId, &fabro_types::StageProjection)> =
|
||||
projection.iter_stages().collect();
|
||||
let mut entries: Vec<(&StageId, &StageProjection)> = projection.iter_stages().collect();
|
||||
entries.sort_by_key(|(_, stage)| stage.first_event_seq);
|
||||
|
||||
let mut stages = Vec::with_capacity(entries.len());
|
||||
|
|
@ -217,4 +216,4 @@ async fn get_run_billing(
|
|||
};
|
||||
|
||||
(StatusCode::OK, Json(response)).into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use std::sync::Arc;
|
|||
|
||||
use fabro_retro::retro::CompletedStage;
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_types::EventBody;
|
||||
use fabro_types::{EventBody, StageId};
|
||||
|
||||
/// Callback invoked when a workflow node starts executing.
|
||||
pub type OnNodeCallback = Option<Arc<dyn Fn(&str) + Send + Sync>>;
|
||||
|
|
@ -114,11 +114,9 @@ pub fn extract_stage_durations_from_events(events: &[EventEnvelope]) -> HashMap<
|
|||
/// Extract per-stage (node_id, visit) durations from `stage.completed` /
|
||||
/// `stage.failed` events. Differs from
|
||||
/// [`extract_stage_durations_from_events`] by keying on the full
|
||||
/// [`fabro_types::StageId`] instead of just `node_id`, so multi-visit
|
||||
/// [`StageId`] instead of just `node_id`, so multi-visit
|
||||
/// stages (e.g. a looped `verify` node) keep distinct durations.
|
||||
pub fn extract_stage_durations_by_stage_id(
|
||||
events: &[EventEnvelope],
|
||||
) -> HashMap<fabro_types::StageId, u64> {
|
||||
pub fn extract_stage_durations_by_stage_id(events: &[EventEnvelope]) -> HashMap<StageId, u64> {
|
||||
let mut durations = HashMap::new();
|
||||
for envelope in events {
|
||||
let Some(duration_ms) = stage_completion_duration_ms(&envelope.event.body) else {
|
||||
|
|
@ -179,4 +177,4 @@ mod stage_scope;
|
|||
pub mod test_support;
|
||||
#[doc(hidden)]
|
||||
pub mod transforms;
|
||||
pub mod workflow_bundle;
|
||||
pub mod workflow_bundle;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue