Files
2026-07-13 13:32:57 +08:00

76 lines
3.0 KiB
TypeScript

import type { SyntheticRun } from "./readFallback.server";
// Synthesise the run-detail page's `run` header shape (the NavBar +
// status badge + Cancel-button gate) from a buffered run snapshot. The
// shape matches `RunPresenter.getRun`'s `runData` — keep this in sync
// when fields are added there.
//
// CANCELED and FAILED state is reflected back from
// `SyntheticRun.cancelledAt` / `status` so terminal buffered runs show
// the correct status in the NavBar + isFinished:true (which collapses
// the Cancel button on the page header) before the drainer materialises
// the PG row. This mirrors what `buildSyntheticSpanRun` does for the
// right-side details panel — the SyntheticRun.cancelledAt contract
// comment in readFallback.server.ts names this exact UI surface.
//
// FAILED status maps to `SYSTEM_FAILURE` to match the drainer's
// non-retryable terminal path, which is what `buildSyntheticSpanRun`
// uses too. Symmetric across the header + span-detail panel so an
// admin doesn't see "Pending" + "FAILED" simultaneously on the same
// run.
export function buildSyntheticRunHeader(args: {
run: SyntheticRun;
environment: {
id: string;
organizationId: string;
type: "PRODUCTION" | "DEVELOPMENT" | "STAGING" | "PREVIEW";
slug: string;
};
}) {
const { run, environment } = args;
const isCancelled = run.status === "CANCELED";
const isFailed = run.status === "FAILED";
return {
// `id` mirrors RunPresenter.getRun's runData (the PG path), which
// is the internal cuid — not the friendlyId. SyntheticRun.id is
// already the cuid (RunId.fromFriendlyId(entry.runId) in
// readFallback.server.ts) so the admin debug tooltip on the run
// detail page shows the same format for buffered + materialised
// runs.
id: run.id,
number: 1,
friendlyId: run.friendlyId,
traceId: run.traceId ?? "",
spanId: run.spanId ?? "",
status: isCancelled
? ("CANCELED" as const)
: isFailed
? ("SYSTEM_FAILURE" as const)
: ("PENDING" as const),
isFinished: isCancelled || isFailed,
startedAt: null,
// Symmetric with `buildSyntheticSpanRun` and the
// `ApiRetrieveRunPresenter` synth path. The run-detail route
// derives `isCompleted` from `completedAt !== null` and gates SSE
// live-reloading on it (`route.tsx:459`, `:551`); leaving
// `completedAt` null for FAILED would keep a terminal buffered run
// live-reloading forever. PG-resident SYSTEM_FAILURE rows always
// have completedAt set, so fall back to createdAt (the buffer
// entry has no separate failedAt — closest proxy for when the
// terminal state landed).
completedAt: run.cancelledAt ?? (isFailed ? run.createdAt : null),
logsDeletedAt: null,
rootTaskRun: null,
parentTaskRun: null,
environment: {
id: environment.id,
organizationId: environment.organizationId,
type: environment.type,
slug: environment.slug,
userId: undefined,
userName: undefined,
},
};
}