@workflow/web-shared 4.1.0-beta.51 → 4.1.0-beta.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +15 -20
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +17 -31
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/event-list-view.d.ts +7 -6
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +492 -109
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/run-trace-view.d.ts +2 -1
- package/dist/components/run-trace-view.d.ts.map +1 -1
- package/dist/components/run-trace-view.js +2 -2
- package/dist/components/run-trace-view.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts +2 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +53 -142
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/conversation-view.d.ts.map +1 -1
- package/dist/components/sidebar/conversation-view.js +3 -17
- package/dist/components/sidebar/conversation-view.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +63 -10
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +4 -8
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/sidebar/resolve-hook-modal.d.ts +3 -0
- package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -1
- package/dist/components/sidebar/resolve-hook-modal.js +152 -3
- package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
- package/dist/components/stream-viewer.d.ts +7 -5
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +54 -22
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/components/markers.d.ts +2 -1
- package/dist/components/trace-viewer/components/markers.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/markers.js +59 -20
- package/dist/components/trace-viewer/components/markers.js.map +1 -1
- package/dist/components/trace-viewer/components/node.d.ts +5 -1
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/node.js +250 -68
- package/dist/components/trace-viewer/components/node.js.map +1 -1
- package/dist/components/trace-viewer/components/span-content.d.ts +19 -0
- package/dist/components/trace-viewer/components/span-content.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-content.js +137 -0
- package/dist/components/trace-viewer/components/span-content.js.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +3 -2
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
- package/dist/components/trace-viewer/components/span-segments.d.ts +50 -0
- package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-segments.js +392 -0
- package/dist/components/trace-viewer/components/span-segments.js.map +1 -0
- package/dist/components/trace-viewer/components/span-strategies.d.ts +46 -0
- package/dist/components/trace-viewer/components/span-strategies.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-strategies.js +108 -0
- package/dist/components/trace-viewer/components/span-strategies.js.map +1 -0
- package/dist/components/trace-viewer/context.d.ts +7 -6
- package/dist/components/trace-viewer/context.d.ts.map +1 -1
- package/dist/components/trace-viewer/context.js +47 -18
- package/dist/components/trace-viewer/context.js.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.d.ts +5 -1
- package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +87 -11
- package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.module.css +179 -6
- package/dist/components/trace-viewer/util/timing.d.ts +5 -0
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +12 -0
- package/dist/components/trace-viewer/util/timing.js.map +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +29 -17
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
- package/dist/components/trace-viewer/worker.js +3 -1
- package/dist/components/trace-viewer/worker.js.map +1 -1
- package/dist/components/ui/alert.js +3 -3
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/data-inspector.d.ts +17 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -0
- package/dist/components/ui/data-inspector.js +184 -0
- package/dist/components/ui/data-inspector.js.map +1 -0
- package/dist/components/ui/error-card.d.ts.map +1 -1
- package/dist/components/ui/error-card.js +4 -1
- package/dist/components/ui/error-card.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +39 -24
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +90 -38
- package/dist/components/ui/inspector-theme.js.map +1 -1
- package/dist/components/ui/skeleton.d.ts +1 -1
- package/dist/components/ui/skeleton.d.ts.map +1 -1
- package/dist/components/ui/skeleton.js +2 -2
- package/dist/components/ui/skeleton.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +435 -21
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +2 -2
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +17 -3
- package/dist/lib/hydration.js.map +1 -1
- package/dist/styles.css +186 -0
- package/package.json +8 -7
- package/src/components/error-boundary.tsx +29 -40
- package/src/components/event-list-view.tsx +1000 -287
- package/src/components/index.ts +1 -0
- package/src/components/run-trace-view.tsx +3 -0
- package/src/components/sidebar/attribute-panel.tsx +58 -258
- package/src/components/sidebar/conversation-view.tsx +30 -27
- package/src/components/sidebar/entity-detail-panel.tsx +86 -20
- package/src/components/sidebar/events-list.tsx +4 -11
- package/src/components/sidebar/resolve-hook-modal.tsx +206 -47
- package/src/components/stream-viewer.tsx +138 -61
- package/src/components/trace-viewer/components/markers.tsx +69 -21
- package/src/components/trace-viewer/components/node.tsx +346 -100
- package/src/components/trace-viewer/components/span-content.tsx +247 -0
- package/src/components/trace-viewer/components/span-detail-panel.tsx +7 -2
- package/src/components/trace-viewer/components/span-segments.ts +516 -0
- package/src/components/trace-viewer/components/span-strategies.ts +205 -0
- package/src/components/trace-viewer/context.tsx +92 -40
- package/src/components/trace-viewer/trace-viewer.module.css +179 -6
- package/src/components/trace-viewer/trace-viewer.tsx +115 -11
- package/src/components/trace-viewer/util/timing.ts +13 -0
- package/src/components/trace-viewer/util/use-streaming-spans.ts +28 -17
- package/src/components/trace-viewer/worker.ts +4 -1
- package/src/components/ui/alert.tsx +3 -3
- package/src/components/ui/card.tsx +3 -5
- package/src/components/ui/data-inspector.tsx +318 -0
- package/src/components/ui/error-card.tsx +17 -6
- package/src/components/ui/inspector-theme.ts +127 -39
- package/src/components/ui/skeleton.tsx +3 -1
- package/src/components/workflow-trace-view.tsx +625 -26
- package/src/components/workflow-traces/trace-span-construction.ts +3 -2
- package/src/lib/hydration.ts +17 -8
- package/src/styles.css +186 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import type { SpanNode, SpanNodeEvent } from '../types';
|
|
2
|
+
import type { ResourceType } from './span-strategies';
|
|
3
|
+
|
|
4
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Segment types
|
|
6
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Visual status of a segment within a span bar.
|
|
10
|
+
*
|
|
11
|
+
* - queued: Waiting to start (hatched pattern)
|
|
12
|
+
* - running: Actively executing (solid blue)
|
|
13
|
+
* - failed: Attempt that ended in failure (soft red)
|
|
14
|
+
* - retrying: Waiting between retry attempts (hatched pattern)
|
|
15
|
+
* - succeeded: Final successful attempt (soft green)
|
|
16
|
+
* - waiting: Passive waiting, e.g. hook waiting for payload (hatched pattern)
|
|
17
|
+
* - sleeping: Sleep in progress (soft amber)
|
|
18
|
+
* - received: Hook received a payload (soft blue)
|
|
19
|
+
*/
|
|
20
|
+
export type SegmentStatus =
|
|
21
|
+
| 'queued'
|
|
22
|
+
| 'running'
|
|
23
|
+
| 'failed'
|
|
24
|
+
| 'retrying'
|
|
25
|
+
| 'succeeded'
|
|
26
|
+
| 'waiting'
|
|
27
|
+
| 'sleeping'
|
|
28
|
+
| 'received';
|
|
29
|
+
|
|
30
|
+
export interface Segment {
|
|
31
|
+
/** Fraction of span width where this segment starts (0–1) */
|
|
32
|
+
startFraction: number;
|
|
33
|
+
/** Fraction of span width where this segment ends (0–1) */
|
|
34
|
+
endFraction: number;
|
|
35
|
+
/** Visual status controlling color/pattern */
|
|
36
|
+
status: SegmentStatus;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A boundary between two segments, representing an event that caused
|
|
41
|
+
* a transition. Rendered as a hoverable divider with a tooltip.
|
|
42
|
+
*/
|
|
43
|
+
export interface SegmentBoundary {
|
|
44
|
+
/** Fractional position along the span (0–1) */
|
|
45
|
+
fraction: number;
|
|
46
|
+
/** Display label for the tooltip (e.g. "step_started") */
|
|
47
|
+
label: string;
|
|
48
|
+
/** Formatted timestamp relative to span start */
|
|
49
|
+
offsetMs: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
53
|
+
// CSS class mapping (segment status → module CSS class name)
|
|
54
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Maps a SegmentStatus to the CSS module class name to use.
|
|
58
|
+
* These class names must exist in trace-viewer.module.css.
|
|
59
|
+
*/
|
|
60
|
+
export const SEGMENT_CLASS_MAP: Record<SegmentStatus, string> = {
|
|
61
|
+
queued: 'segQueued',
|
|
62
|
+
running: 'segRunning',
|
|
63
|
+
failed: 'segFailed',
|
|
64
|
+
retrying: 'segRetrying',
|
|
65
|
+
succeeded: 'segSucceeded',
|
|
66
|
+
waiting: 'segWaiting',
|
|
67
|
+
sleeping: 'segSleeping',
|
|
68
|
+
received: 'segReceived',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
72
|
+
// Helpers
|
|
73
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
function clampFraction(value: number): number {
|
|
76
|
+
return Math.max(0, Math.min(1, value));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function timeToFraction(
|
|
80
|
+
time: number,
|
|
81
|
+
spanStart: number,
|
|
82
|
+
spanDuration: number
|
|
83
|
+
): number {
|
|
84
|
+
if (spanDuration <= 0) return 0;
|
|
85
|
+
return clampFraction((time - spanStart) / spanDuration);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Sort events by timestamp ascending.
|
|
90
|
+
*/
|
|
91
|
+
function sortedEvents(events: SpanNodeEvent[]): SpanNodeEvent[] {
|
|
92
|
+
return [...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// Step segments
|
|
97
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Compute segments for a step span.
|
|
101
|
+
*
|
|
102
|
+
* Timeline: [queued] → [attempt₁ (fail)] → [retry wait] → [attempt₂ (fail)] → ... → [attemptₙ (success)]
|
|
103
|
+
*
|
|
104
|
+
* Events used: step_started, step_retrying, step_failed, step_completed
|
|
105
|
+
* The final segment is 'succeeded' if no trailing step_retrying/step_failed.
|
|
106
|
+
*/
|
|
107
|
+
function computeStepSegments(node: SpanNode): Segment[] {
|
|
108
|
+
const events = node.events ? sortedEvents(node.events) : [];
|
|
109
|
+
const { startTime, duration } = node;
|
|
110
|
+
const segments: Segment[] = [];
|
|
111
|
+
|
|
112
|
+
if (duration <= 0) return segments;
|
|
113
|
+
|
|
114
|
+
// Build a timeline of event boundaries
|
|
115
|
+
interface EventMark {
|
|
116
|
+
time: number;
|
|
117
|
+
type: string;
|
|
118
|
+
}
|
|
119
|
+
const marks: EventMark[] = events
|
|
120
|
+
.filter((e) =>
|
|
121
|
+
[
|
|
122
|
+
'step_started',
|
|
123
|
+
'step_retrying',
|
|
124
|
+
'step_failed',
|
|
125
|
+
'step_completed',
|
|
126
|
+
].includes(e.event.name)
|
|
127
|
+
)
|
|
128
|
+
.map((e) => ({ time: e.timestamp, type: e.event.name }));
|
|
129
|
+
|
|
130
|
+
if (marks.length === 0) {
|
|
131
|
+
// No events — show entire span as running
|
|
132
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'running' });
|
|
133
|
+
return segments;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Queued period: from span start to first event
|
|
137
|
+
const firstMark = marks[0];
|
|
138
|
+
const firstFraction = timeToFraction(firstMark.time, startTime, duration);
|
|
139
|
+
if (firstFraction > 0.001) {
|
|
140
|
+
segments.push({
|
|
141
|
+
startFraction: 0,
|
|
142
|
+
endFraction: firstFraction,
|
|
143
|
+
status: 'queued',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Walk through events to build attempt/retry segments
|
|
148
|
+
for (let i = 0; i < marks.length; i++) {
|
|
149
|
+
const mark = marks[i];
|
|
150
|
+
const markFraction = timeToFraction(mark.time, startTime, duration);
|
|
151
|
+
const nextMark = marks[i + 1];
|
|
152
|
+
const nextFraction = nextMark
|
|
153
|
+
? timeToFraction(nextMark.time, startTime, duration)
|
|
154
|
+
: 1;
|
|
155
|
+
|
|
156
|
+
if (mark.type === 'step_started') {
|
|
157
|
+
// This is the start of an execution attempt.
|
|
158
|
+
// It runs until the next event (step_retrying or step_failed) or end of span.
|
|
159
|
+
const isLastMark = i === marks.length - 1;
|
|
160
|
+
if (isLastMark) {
|
|
161
|
+
// Last event is step_started — this attempt is either still running or succeeded
|
|
162
|
+
segments.push({
|
|
163
|
+
startFraction: markFraction,
|
|
164
|
+
endFraction: 1,
|
|
165
|
+
status: 'succeeded',
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
// Attempt runs until the next event
|
|
169
|
+
const nextType = nextMark.type;
|
|
170
|
+
const attemptStatus: SegmentStatus =
|
|
171
|
+
nextType === 'step_retrying' || nextType === 'step_failed'
|
|
172
|
+
? 'failed'
|
|
173
|
+
: nextType === 'step_completed'
|
|
174
|
+
? 'succeeded'
|
|
175
|
+
: 'running';
|
|
176
|
+
segments.push({
|
|
177
|
+
startFraction: markFraction,
|
|
178
|
+
endFraction: nextFraction,
|
|
179
|
+
status: attemptStatus,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
} else if (mark.type === 'step_retrying') {
|
|
183
|
+
// Retry wait: from this event to the next step_started (or end)
|
|
184
|
+
segments.push({
|
|
185
|
+
startFraction: markFraction,
|
|
186
|
+
endFraction: nextFraction,
|
|
187
|
+
status: 'retrying',
|
|
188
|
+
});
|
|
189
|
+
} else if (mark.type === 'step_failed') {
|
|
190
|
+
// Terminal failure: from this event to the end
|
|
191
|
+
if (markFraction < 0.999) {
|
|
192
|
+
segments.push({
|
|
193
|
+
startFraction: markFraction,
|
|
194
|
+
endFraction: 1,
|
|
195
|
+
status: 'failed',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
} else if (mark.type === 'step_completed') {
|
|
199
|
+
// Terminal success: do nothing here since the preceding step_started
|
|
200
|
+
// segment already terminates at this marker.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return segments;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
208
|
+
// Hook segments
|
|
209
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Compute segments for a hook span.
|
|
213
|
+
*
|
|
214
|
+
* Timeline: [waiting] → [received] → [disposed]
|
|
215
|
+
*/
|
|
216
|
+
function computeHookSegments(node: SpanNode): Segment[] {
|
|
217
|
+
const events = node.events ? sortedEvents(node.events) : [];
|
|
218
|
+
const { startTime, duration } = node;
|
|
219
|
+
const segments: Segment[] = [];
|
|
220
|
+
|
|
221
|
+
if (duration <= 0) return segments;
|
|
222
|
+
|
|
223
|
+
const receivedEvent = events.find((e) => e.event.name === 'hook_received');
|
|
224
|
+
const disposedEvent = events.find((e) => e.event.name === 'hook_disposed');
|
|
225
|
+
|
|
226
|
+
if (!receivedEvent && !disposedEvent) {
|
|
227
|
+
// Still waiting
|
|
228
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'waiting' });
|
|
229
|
+
return segments;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const receivedFraction = receivedEvent
|
|
233
|
+
? timeToFraction(receivedEvent.timestamp, startTime, duration)
|
|
234
|
+
: null;
|
|
235
|
+
const disposedFraction = disposedEvent
|
|
236
|
+
? timeToFraction(disposedEvent.timestamp, startTime, duration)
|
|
237
|
+
: null;
|
|
238
|
+
|
|
239
|
+
// Waiting period (before received)
|
|
240
|
+
if (receivedFraction !== null && receivedFraction > 0.001) {
|
|
241
|
+
segments.push({
|
|
242
|
+
startFraction: 0,
|
|
243
|
+
endFraction: receivedFraction,
|
|
244
|
+
status: 'waiting',
|
|
245
|
+
});
|
|
246
|
+
} else if (receivedFraction === null && disposedFraction !== null) {
|
|
247
|
+
// Disposed without receiving — waiting the whole time
|
|
248
|
+
segments.push({
|
|
249
|
+
startFraction: 0,
|
|
250
|
+
endFraction: disposedFraction,
|
|
251
|
+
status: 'waiting',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Received period
|
|
256
|
+
if (receivedFraction !== null) {
|
|
257
|
+
const end = disposedFraction ?? 1;
|
|
258
|
+
segments.push({
|
|
259
|
+
startFraction: receivedFraction,
|
|
260
|
+
endFraction: end,
|
|
261
|
+
status: 'received',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Post-disposed (if there's remaining span after disposal)
|
|
266
|
+
if (disposedFraction !== null && disposedFraction < 0.999) {
|
|
267
|
+
segments.push({
|
|
268
|
+
startFraction: disposedFraction,
|
|
269
|
+
endFraction: 1,
|
|
270
|
+
status: 'succeeded',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return segments;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
278
|
+
// Sleep segments
|
|
279
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Compute segments for a sleep span.
|
|
283
|
+
*
|
|
284
|
+
* Timeline: [sleeping] → [completed]
|
|
285
|
+
*/
|
|
286
|
+
function computeSleepSegments(node: SpanNode): Segment[] {
|
|
287
|
+
const events = node.events ? sortedEvents(node.events) : [];
|
|
288
|
+
const { startTime, duration } = node;
|
|
289
|
+
const segments: Segment[] = [];
|
|
290
|
+
|
|
291
|
+
if (duration <= 0) return segments;
|
|
292
|
+
|
|
293
|
+
const completedEvent = events.find((e) => e.event.name === 'wait_completed');
|
|
294
|
+
|
|
295
|
+
if (!completedEvent) {
|
|
296
|
+
// Still sleeping
|
|
297
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'sleeping' });
|
|
298
|
+
return segments;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const completedFraction = timeToFraction(
|
|
302
|
+
completedEvent.timestamp,
|
|
303
|
+
startTime,
|
|
304
|
+
duration
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (completedFraction > 0.001) {
|
|
308
|
+
segments.push({
|
|
309
|
+
startFraction: 0,
|
|
310
|
+
endFraction: completedFraction,
|
|
311
|
+
status: 'sleeping',
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
segments.push({
|
|
316
|
+
startFraction: completedFraction,
|
|
317
|
+
endFraction: 1,
|
|
318
|
+
status: 'succeeded',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
return segments;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
325
|
+
// Run segments
|
|
326
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Compute segments for a run span.
|
|
330
|
+
*
|
|
331
|
+
* Timeline: [queued] → [running] → [completed/failed]
|
|
332
|
+
*
|
|
333
|
+
* The queued period is derived from activeStartTime (run.startedAt).
|
|
334
|
+
* A synthetic run_started event is injected at that timestamp so
|
|
335
|
+
* the boundary is hoverable in the trace viewer.
|
|
336
|
+
*/
|
|
337
|
+
function computeRunSegments(node: SpanNode): Segment[] {
|
|
338
|
+
const events = node.events ? sortedEvents(node.events) : [];
|
|
339
|
+
const { startTime, duration, activeStartTime } = node;
|
|
340
|
+
const segments: Segment[] = [];
|
|
341
|
+
|
|
342
|
+
if (duration <= 0) return segments;
|
|
343
|
+
|
|
344
|
+
const failedEvent = events.find((e) => e.event.name === 'run_failed');
|
|
345
|
+
const completedEvent = events.find((e) => e.event.name === 'run_completed');
|
|
346
|
+
|
|
347
|
+
// Queued period (from span start to activeStartTime)
|
|
348
|
+
let cursor = 0;
|
|
349
|
+
if (activeStartTime && activeStartTime > startTime) {
|
|
350
|
+
const queuedFraction = timeToFraction(activeStartTime, startTime, duration);
|
|
351
|
+
if (queuedFraction > 0.001) {
|
|
352
|
+
segments.push({
|
|
353
|
+
startFraction: 0,
|
|
354
|
+
endFraction: queuedFraction,
|
|
355
|
+
status: 'queued',
|
|
356
|
+
});
|
|
357
|
+
cursor = queuedFraction;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (failedEvent) {
|
|
362
|
+
const failedFraction = timeToFraction(
|
|
363
|
+
failedEvent.timestamp,
|
|
364
|
+
startTime,
|
|
365
|
+
duration
|
|
366
|
+
);
|
|
367
|
+
// Running until failure
|
|
368
|
+
if (failedFraction > cursor + 0.001) {
|
|
369
|
+
segments.push({
|
|
370
|
+
startFraction: cursor,
|
|
371
|
+
endFraction: failedFraction,
|
|
372
|
+
status: 'running',
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
segments.push({
|
|
376
|
+
startFraction: failedFraction,
|
|
377
|
+
endFraction: 1,
|
|
378
|
+
status: 'failed',
|
|
379
|
+
});
|
|
380
|
+
} else if (completedEvent) {
|
|
381
|
+
const completedFraction = timeToFraction(
|
|
382
|
+
completedEvent.timestamp,
|
|
383
|
+
startTime,
|
|
384
|
+
duration
|
|
385
|
+
);
|
|
386
|
+
if (completedFraction > cursor + 0.001) {
|
|
387
|
+
segments.push({
|
|
388
|
+
startFraction: cursor,
|
|
389
|
+
endFraction: completedFraction,
|
|
390
|
+
status: 'running',
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
segments.push({
|
|
394
|
+
startFraction: completedFraction,
|
|
395
|
+
endFraction: 1,
|
|
396
|
+
status: 'succeeded',
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
// Running to completion
|
|
400
|
+
segments.push({
|
|
401
|
+
startFraction: cursor,
|
|
402
|
+
endFraction: 1,
|
|
403
|
+
status: 'running',
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return segments;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
411
|
+
// Dispatcher
|
|
412
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
export interface SegmentResult {
|
|
415
|
+
segments: Segment[];
|
|
416
|
+
boundaries: SegmentBoundary[];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
420
|
+
// Boundary computation
|
|
421
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
/** Event names to human-readable labels */
|
|
424
|
+
const EVENT_LABELS: Record<string, string> = {
|
|
425
|
+
step_started: 'Started',
|
|
426
|
+
step_retrying: 'Retrying',
|
|
427
|
+
step_failed: 'Failed',
|
|
428
|
+
hook_created: 'Created',
|
|
429
|
+
hook_received: 'Received',
|
|
430
|
+
hook_disposed: 'Resolved',
|
|
431
|
+
wait_created: 'Sleep started',
|
|
432
|
+
wait_completed: 'Sleep completed',
|
|
433
|
+
run_completed: 'Completed',
|
|
434
|
+
run_failed: 'Run failed',
|
|
435
|
+
step_completed: 'Completed',
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Extract boundaries from a span's events. Each boundary sits at the
|
|
440
|
+
* fractional position of a relevant event within the span duration.
|
|
441
|
+
* Only includes events that are used as segment boundaries (not at the
|
|
442
|
+
* very start or very end of the span, to avoid edge clutter).
|
|
443
|
+
*/
|
|
444
|
+
function computeBoundaries(
|
|
445
|
+
node: SpanNode,
|
|
446
|
+
relevantEventNames: string[]
|
|
447
|
+
): SegmentBoundary[] {
|
|
448
|
+
const events = node.events ? sortedEvents(node.events) : [];
|
|
449
|
+
const { startTime, duration } = node;
|
|
450
|
+
|
|
451
|
+
if (duration <= 0) return [];
|
|
452
|
+
|
|
453
|
+
const boundaries: SegmentBoundary[] = [];
|
|
454
|
+
for (const e of events) {
|
|
455
|
+
if (!relevantEventNames.includes(e.event.name)) continue;
|
|
456
|
+
|
|
457
|
+
const fraction = timeToFraction(e.timestamp, startTime, duration);
|
|
458
|
+
// Skip boundaries at the very edges (< 1% or > 99%) to avoid clutter
|
|
459
|
+
if (fraction < 0.01 || fraction > 0.99) continue;
|
|
460
|
+
|
|
461
|
+
boundaries.push({
|
|
462
|
+
fraction,
|
|
463
|
+
label: EVENT_LABELS[e.event.name] ?? e.event.name,
|
|
464
|
+
offsetMs: e.timestamp - startTime,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return boundaries;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
472
|
+
// Dispatcher
|
|
473
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Compute the event-based segments and boundaries for a span based on its resource type.
|
|
477
|
+
* Returns empty arrays for 'default' (generic OTEL) spans.
|
|
478
|
+
*/
|
|
479
|
+
export function computeSegments(
|
|
480
|
+
resourceType: ResourceType,
|
|
481
|
+
node: SpanNode
|
|
482
|
+
): SegmentResult {
|
|
483
|
+
switch (resourceType) {
|
|
484
|
+
case 'step':
|
|
485
|
+
return {
|
|
486
|
+
segments: computeStepSegments(node),
|
|
487
|
+
boundaries: computeBoundaries(node, [
|
|
488
|
+
'step_started',
|
|
489
|
+
'step_retrying',
|
|
490
|
+
'step_failed',
|
|
491
|
+
'step_completed',
|
|
492
|
+
]),
|
|
493
|
+
};
|
|
494
|
+
case 'hook':
|
|
495
|
+
return {
|
|
496
|
+
segments: computeHookSegments(node),
|
|
497
|
+
boundaries: computeBoundaries(node, [
|
|
498
|
+
'hook_created',
|
|
499
|
+
'hook_received',
|
|
500
|
+
'hook_disposed',
|
|
501
|
+
]),
|
|
502
|
+
};
|
|
503
|
+
case 'sleep':
|
|
504
|
+
return {
|
|
505
|
+
segments: computeSleepSegments(node),
|
|
506
|
+
boundaries: computeBoundaries(node, ['wait_created', 'wait_completed']),
|
|
507
|
+
};
|
|
508
|
+
case 'run':
|
|
509
|
+
return {
|
|
510
|
+
segments: computeRunSegments(node),
|
|
511
|
+
boundaries: computeBoundaries(node, ['run_completed', 'run_failed']),
|
|
512
|
+
};
|
|
513
|
+
default:
|
|
514
|
+
return { segments: [], boundaries: [] };
|
|
515
|
+
}
|
|
516
|
+
}
|