91 lines
3.6 KiB
TypeScript
91 lines
3.6 KiB
TypeScript
import { millisecondsToNanoseconds } from "@trigger.dev/core/v3";
|
|
import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView";
|
|
import { createTimelineSpanEventsFromSpanEvents } from "~/utils/timelineSpanEvents";
|
|
import type { SpanSummary } from "~/v3/eventRepository/eventRepository.types";
|
|
import type { SyntheticRun } from "./readFallback.server";
|
|
|
|
// Build a single-span trace for a buffered run so the run-detail page
|
|
// renders a meaningful timeline before the drainer materialises the
|
|
// row. Mirrors the shape produced by `RunPresenter` when its trace
|
|
// store lookup returns no spans, so the dashboard consumer treats the
|
|
// buffered run identically to a freshly enqueued PG run that hasn't
|
|
// emitted any events yet.
|
|
export function buildSyntheticTraceForBufferedRun(run: SyntheticRun) {
|
|
const spanId = run.spanId ?? "";
|
|
const isCancelled = run.status === "CANCELED";
|
|
const isFailed = run.status === "FAILED";
|
|
const span: SpanSummary = {
|
|
id: spanId,
|
|
parentId: run.parentSpanId,
|
|
runId: run.friendlyId,
|
|
data: {
|
|
message: run.taskIdentifier ?? "Task",
|
|
style: { icon: "task", variant: "primary" },
|
|
events: [],
|
|
startTime: run.createdAt,
|
|
duration: 0,
|
|
isError: isFailed,
|
|
// CANCELED and FAILED are terminal; only a still-queued buffered run
|
|
// is partial. A partial failed span would otherwise render as
|
|
// "executing" forever in the timeline.
|
|
isPartial: !isCancelled && !isFailed,
|
|
isCancelled,
|
|
isDebug: false,
|
|
level: "TRACE",
|
|
},
|
|
};
|
|
|
|
const tree = createTreeFromFlatItems([span], spanId);
|
|
const treeRootStartTimeMs = tree?.data.startTime.getTime() ?? 0;
|
|
const totalDuration = Math.max(tree?.data.duration ?? 0, millisecondsToNanoseconds(1));
|
|
|
|
const events = tree
|
|
? flattenTree(tree).map((n) => {
|
|
const offset = millisecondsToNanoseconds(n.data.startTime.getTime() - treeRootStartTimeMs);
|
|
// Mirror RunPresenter: raw span events stay server-side, only
|
|
// timelineEvents ship to the client.
|
|
const { events: spanEvents, ...data } = n.data;
|
|
return {
|
|
...n,
|
|
data: {
|
|
...data,
|
|
timelineEvents: createTimelineSpanEventsFromSpanEvents(
|
|
spanEvents,
|
|
false,
|
|
treeRootStartTimeMs
|
|
),
|
|
duration: n.data.isPartial ? null : n.data.duration,
|
|
offset,
|
|
isRoot: n.id === spanId,
|
|
// Synthetic traces represent buffered/queued/canceled runs that
|
|
// haven't executed yet — they can't be agent runs. Keeping this
|
|
// field present (instead of omitting it) keeps the trace shape
|
|
// structurally identical to `RunPresenter`'s, which downstream
|
|
// consumers like the agent-icon check in runs/$runParam/route
|
|
// require to typecheck against the JsonifyObject union.
|
|
isAgentRun: false,
|
|
},
|
|
};
|
|
})
|
|
: [];
|
|
|
|
return {
|
|
// Matches RunPresenter's derivation: failed root span -> "failed",
|
|
// otherwise a terminal (non-partial) span -> "completed", else
|
|
// "executing". CANCELED is terminal-but-not-error, so "completed".
|
|
rootSpanStatus: (isFailed ? "failed" : isCancelled ? "completed" : "executing") as
|
|
| "executing"
|
|
| "completed"
|
|
| "failed",
|
|
events,
|
|
duration: totalDuration,
|
|
rootStartedAt: tree?.data.startTime,
|
|
startedAt: null,
|
|
queuedDuration: undefined,
|
|
overridesBySpanId: undefined,
|
|
linkedRunIdBySpanId: {} as Record<string, string>,
|
|
isTruncated: false,
|
|
missingAnchor: false,
|
|
};
|
|
}
|