@workflow/web-shared 5.0.0-beta.2 → 5.0.0-beta.3
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/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
- package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.js +35 -0
- package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.js +100 -0
- package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts +32 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.js +134 -0
- package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
- package/dist/components/new-trace-viewer/context.d.ts +15 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/context.js +49 -0
- package/dist/components/new-trace-viewer/context.js.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.js +11 -0
- package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
- package/dist/components/new-trace-viewer/icons.d.ts +6 -0
- package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/icons.js +15 -0
- package/dist/components/new-trace-viewer/icons.js.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.js +329 -0
- package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
- package/dist/components/new-trace-viewer/types.d.ts +1 -0
- package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/types.js +2 -0
- package/dist/components/new-trace-viewer/types.js.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.js +2 -0
- package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
- package/dist/components/new-trace-viewer/utils.d.ts +47 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/utils.js +358 -0
- package/dist/components/new-trace-viewer/utils.js.map +1 -0
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +42 -23
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +1 -1
- package/dist/components/sidebar/copyable-data-block.js.map +1 -1
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +1 -1
- package/dist/components/sidebar/detail-card.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +3 -25
- 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 +2 -2
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar-data-context.js +19 -0
- package/dist/components/sidebar/sidebar-data-context.js.map +1 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +2 -26
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer-new.d.ts +10 -0
- package/dist/components/trace-viewer-new.d.ts.map +1 -0
- package/dist/components/trace-viewer-new.js +23 -0
- package/dist/components/trace-viewer-new.js.map +1 -0
- package/dist/components/ui/data-inspector.d.ts +21 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +99 -4
- package/dist/components/ui/data-inspector.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +9 -10
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +38 -71
- package/dist/components/ui/inspector-theme.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +1 -3
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +4 -4
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/stream-display.d.ts +18 -0
- package/dist/lib/stream-display.d.ts.map +1 -0
- package/dist/lib/stream-display.js +30 -0
- package/dist/lib/stream-display.js.map +1 -0
- package/dist/lib/utils.d.ts +7 -9
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +17 -22
- package/dist/lib/utils.js.map +1 -1
- package/dist/styles.css +470 -1
- package/package.json +3 -2
- package/src/components/index.ts +5 -0
- package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +109 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +186 -0
- package/src/components/new-trace-viewer/components/timeline.tsx +378 -0
- package/src/components/new-trace-viewer/context.tsx +94 -0
- package/src/components/new-trace-viewer/detail-panel.tsx +57 -0
- package/src/components/new-trace-viewer/icons.tsx +79 -0
- package/src/components/new-trace-viewer/trace-viewer.tsx +585 -0
- package/src/components/new-trace-viewer/types.ts +0 -0
- package/src/components/new-trace-viewer/util/constants.ts +0 -0
- package/src/components/new-trace-viewer/utils.ts +524 -0
- package/src/components/sidebar/attribute-panel.tsx +43 -32
- package/src/components/sidebar/copyable-data-block.tsx +1 -1
- package/src/components/sidebar/detail-card.tsx +1 -19
- package/src/components/sidebar/entity-detail-panel.tsx +0 -71
- package/src/components/sidebar/events-list.tsx +9 -6
- package/src/components/sidebar/sidebar-data-context.tsx +62 -0
- package/src/components/stream-viewer.tsx +18 -40
- package/src/components/trace-viewer-new.tsx +49 -0
- package/src/components/ui/data-inspector.tsx +235 -4
- package/src/components/ui/inspector-theme.ts +39 -81
- package/src/components/workflow-trace-view.tsx +2 -6
- package/src/index.ts +1 -0
- package/src/lib/stream-display.ts +48 -0
- package/src/lib/utils.ts +18 -23
- package/src/styles.css +470 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import type { Span, SpanEvent } from '../trace-viewer/types';
|
|
2
|
+
import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Root bounds
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export interface RootBounds {
|
|
9
|
+
startTime: number;
|
|
10
|
+
endTime: number;
|
|
11
|
+
duration: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function computeRootBounds(spans: Span[]): RootBounds {
|
|
15
|
+
let minStart = Number.POSITIVE_INFINITY;
|
|
16
|
+
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
17
|
+
|
|
18
|
+
for (const span of spans) {
|
|
19
|
+
const s = getHighResInMs(span.startTime);
|
|
20
|
+
const e = getHighResInMs(span.endTime);
|
|
21
|
+
if (s < minStart) minStart = s;
|
|
22
|
+
if (e > maxEnd) maxEnd = e;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) {
|
|
26
|
+
return { startTime: 0, endTime: 1, duration: 1 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const duration = Math.max(maxEnd - minStart, 1);
|
|
30
|
+
return { startTime: minStart, endTime: maxEnd, duration };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Time compression
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export interface TimeCompression {
|
|
38
|
+
toVisual(time: number): number;
|
|
39
|
+
fromVisual(fraction: number): number;
|
|
40
|
+
isCompressed: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildTimeCompression(
|
|
44
|
+
_spans: Span[],
|
|
45
|
+
viewStart: number,
|
|
46
|
+
viewEnd: number
|
|
47
|
+
): TimeCompression {
|
|
48
|
+
const range = viewEnd - viewStart;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isCompressed: false,
|
|
52
|
+
toVisual(time: number): number {
|
|
53
|
+
if (range <= 0) return 0;
|
|
54
|
+
return Math.min(Math.max((time - viewStart) / range, 0), 1);
|
|
55
|
+
},
|
|
56
|
+
fromVisual(fraction: number): number {
|
|
57
|
+
return viewStart + fraction * range;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Time markers
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export interface TimeMarker {
|
|
67
|
+
position: number;
|
|
68
|
+
label: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const NICE_INTERVALS = [
|
|
72
|
+
1, 2, 5, 10, 20, 50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000,
|
|
73
|
+
50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000,
|
|
74
|
+
10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000,
|
|
75
|
+
1_000_000_000, 2_000_000_000, 5_000_000_000,
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const MAX_MARKERS = 8;
|
|
79
|
+
|
|
80
|
+
function pickInterval(viewDuration: number, maxTicks: number): number {
|
|
81
|
+
for (const interval of NICE_INTERVALS) {
|
|
82
|
+
if (viewDuration / interval <= maxTicks) return interval;
|
|
83
|
+
}
|
|
84
|
+
return NICE_INTERVALS[NICE_INTERVALS.length - 1];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function computeTimeMarkers(
|
|
88
|
+
viewDuration: number,
|
|
89
|
+
offset: number
|
|
90
|
+
): TimeMarker[] {
|
|
91
|
+
if (viewDuration <= 0) return [];
|
|
92
|
+
|
|
93
|
+
const maxTicks = 6;
|
|
94
|
+
const interval = pickInterval(viewDuration, maxTicks);
|
|
95
|
+
|
|
96
|
+
const firstTick = Math.ceil(offset / interval) * interval;
|
|
97
|
+
const markers: TimeMarker[] = [];
|
|
98
|
+
|
|
99
|
+
for (let t = firstTick; t <= offset + viewDuration; t += interval) {
|
|
100
|
+
const position = (t - offset) / viewDuration;
|
|
101
|
+
if (position < -0.01 || position > 1.01) continue;
|
|
102
|
+
markers.push({
|
|
103
|
+
position: Math.min(Math.max(position, 0), 1),
|
|
104
|
+
label: formatDuration(Math.abs(t), true),
|
|
105
|
+
});
|
|
106
|
+
if (markers.length >= MAX_MARKERS) break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return markers;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function computeCompressedTimeMarkers(
|
|
113
|
+
compression: TimeCompression,
|
|
114
|
+
viewStart: number,
|
|
115
|
+
viewEnd: number,
|
|
116
|
+
rootStart: number
|
|
117
|
+
): TimeMarker[] {
|
|
118
|
+
const viewDuration = viewEnd - viewStart;
|
|
119
|
+
if (viewDuration <= 0) return [];
|
|
120
|
+
|
|
121
|
+
const maxTicks = 6;
|
|
122
|
+
const interval = pickInterval(viewDuration, maxTicks);
|
|
123
|
+
const offset = viewStart - rootStart;
|
|
124
|
+
|
|
125
|
+
const firstTick = Math.ceil(offset / interval) * interval;
|
|
126
|
+
const markers: TimeMarker[] = [];
|
|
127
|
+
|
|
128
|
+
for (let t = firstTick; t <= offset + viewDuration; t += interval) {
|
|
129
|
+
const absTime = rootStart + t;
|
|
130
|
+
const position = compression.toVisual(absTime);
|
|
131
|
+
if (position < -0.01 || position > 1.01) continue;
|
|
132
|
+
markers.push({
|
|
133
|
+
position: Math.min(Math.max(position, 0), 1),
|
|
134
|
+
label: formatDuration(Math.abs(t), true),
|
|
135
|
+
});
|
|
136
|
+
if (markers.length >= MAX_MARKERS) break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return markers;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Span gaps — time deltas between consecutive spans (Alt-key overlay)
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
export interface SpanGap {
|
|
147
|
+
gapMs: number;
|
|
148
|
+
leftFrac: number;
|
|
149
|
+
rightFrac: number;
|
|
150
|
+
rowIndex: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function computeSpanGaps(
|
|
154
|
+
spans: Span[],
|
|
155
|
+
compression: TimeCompression
|
|
156
|
+
): SpanGap[] {
|
|
157
|
+
const gaps: SpanGap[] = [];
|
|
158
|
+
for (let i = 0; i < spans.length - 1; i++) {
|
|
159
|
+
const endTime = getHighResInMs(spans[i].endTime);
|
|
160
|
+
const startTime = getHighResInMs(spans[i + 1].startTime);
|
|
161
|
+
const gapMs = startTime - endTime;
|
|
162
|
+
if (gapMs <= 0) continue;
|
|
163
|
+
|
|
164
|
+
const leftFrac = compression.toVisual(endTime);
|
|
165
|
+
const rightFrac = compression.toVisual(startTime);
|
|
166
|
+
if (rightFrac - leftFrac < 0.001) continue;
|
|
167
|
+
|
|
168
|
+
gaps.push({ gapMs, leftFrac, rightFrac, rowIndex: i });
|
|
169
|
+
}
|
|
170
|
+
return gaps;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Resource colors
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
export const RESOURCE_COLORS: Record<
|
|
178
|
+
string,
|
|
179
|
+
{ bar: string; errorBar?: string }
|
|
180
|
+
> = {
|
|
181
|
+
run: { bar: 'var(--ds-blue-700)', errorBar: 'var(--ds-red-700)' },
|
|
182
|
+
step: { bar: 'var(--ds-green-700)', errorBar: 'var(--ds-red-700)' },
|
|
183
|
+
hook: { bar: 'var(--ds-amber-700)', errorBar: 'var(--ds-red-700)' },
|
|
184
|
+
sleep: { bar: 'var(--ds-purple-700)', errorBar: 'var(--ds-red-700)' },
|
|
185
|
+
default: { bar: 'var(--ds-gray-500)', errorBar: 'var(--ds-red-700)' },
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export function getResourceColor(resource: string): {
|
|
189
|
+
bar: string;
|
|
190
|
+
errorBar?: string;
|
|
191
|
+
} {
|
|
192
|
+
return RESOURCE_COLORS[resource] ?? RESOURCE_COLORS.default;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Span segments — split a timeline bar into colored sections by event state
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export type SegmentStatus =
|
|
200
|
+
| 'queued'
|
|
201
|
+
| 'running'
|
|
202
|
+
| 'failed'
|
|
203
|
+
| 'retrying'
|
|
204
|
+
| 'succeeded'
|
|
205
|
+
| 'waiting'
|
|
206
|
+
| 'sleeping'
|
|
207
|
+
| 'received';
|
|
208
|
+
|
|
209
|
+
export interface Segment {
|
|
210
|
+
startFraction: number;
|
|
211
|
+
endFraction: number;
|
|
212
|
+
status: SegmentStatus;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function clampFraction(value: number): number {
|
|
216
|
+
return Math.max(0, Math.min(1, value));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function timeToFraction(
|
|
220
|
+
time: number,
|
|
221
|
+
spanStart: number,
|
|
222
|
+
spanDuration: number
|
|
223
|
+
): number {
|
|
224
|
+
if (spanDuration <= 0) return 0;
|
|
225
|
+
return clampFraction((time - spanStart) / spanDuration);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface EventMark {
|
|
229
|
+
time: number;
|
|
230
|
+
type: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function sortedEventMarks(
|
|
234
|
+
events: SpanEvent[],
|
|
235
|
+
relevantNames: string[]
|
|
236
|
+
): EventMark[] {
|
|
237
|
+
return events
|
|
238
|
+
.filter((e) => relevantNames.includes(e.name))
|
|
239
|
+
.map((e) => ({ time: getHighResInMs(e.timestamp), type: e.name }))
|
|
240
|
+
.sort((a, b) => a.time - b.time);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function computeStepSegmentsFromSpan(
|
|
244
|
+
startMs: number,
|
|
245
|
+
duration: number,
|
|
246
|
+
events: SpanEvent[]
|
|
247
|
+
): Segment[] {
|
|
248
|
+
const segments: Segment[] = [];
|
|
249
|
+
if (duration <= 0) return segments;
|
|
250
|
+
|
|
251
|
+
const marks = sortedEventMarks(events, [
|
|
252
|
+
'step_started',
|
|
253
|
+
'step_retrying',
|
|
254
|
+
'step_failed',
|
|
255
|
+
'step_completed',
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
if (marks.length === 0) {
|
|
259
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'running' });
|
|
260
|
+
return segments;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const firstFraction = timeToFraction(marks[0].time, startMs, duration);
|
|
264
|
+
if (firstFraction > 0.001) {
|
|
265
|
+
segments.push({
|
|
266
|
+
startFraction: 0,
|
|
267
|
+
endFraction: firstFraction,
|
|
268
|
+
status: 'queued',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < marks.length; i++) {
|
|
273
|
+
const mark = marks[i];
|
|
274
|
+
const markFrac = timeToFraction(mark.time, startMs, duration);
|
|
275
|
+
const nextMark = marks[i + 1];
|
|
276
|
+
const nextFrac = nextMark
|
|
277
|
+
? timeToFraction(nextMark.time, startMs, duration)
|
|
278
|
+
: 1;
|
|
279
|
+
|
|
280
|
+
if (mark.type === 'step_started') {
|
|
281
|
+
if (i === marks.length - 1) {
|
|
282
|
+
segments.push({
|
|
283
|
+
startFraction: markFrac,
|
|
284
|
+
endFraction: 1,
|
|
285
|
+
status: 'succeeded',
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
const nextType = nextMark.type;
|
|
289
|
+
const attemptStatus: SegmentStatus =
|
|
290
|
+
nextType === 'step_retrying' || nextType === 'step_failed'
|
|
291
|
+
? 'failed'
|
|
292
|
+
: nextType === 'step_completed'
|
|
293
|
+
? 'succeeded'
|
|
294
|
+
: 'running';
|
|
295
|
+
segments.push({
|
|
296
|
+
startFraction: markFrac,
|
|
297
|
+
endFraction: nextFrac,
|
|
298
|
+
status: attemptStatus,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
} else if (mark.type === 'step_retrying') {
|
|
302
|
+
segments.push({
|
|
303
|
+
startFraction: markFrac,
|
|
304
|
+
endFraction: nextFrac,
|
|
305
|
+
status: 'retrying',
|
|
306
|
+
});
|
|
307
|
+
} else if (mark.type === 'step_failed') {
|
|
308
|
+
if (markFrac < 0.999) {
|
|
309
|
+
segments.push({
|
|
310
|
+
startFraction: markFrac,
|
|
311
|
+
endFraction: 1,
|
|
312
|
+
status: 'failed',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return segments;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function computeHookSegmentsFromSpan(
|
|
322
|
+
startMs: number,
|
|
323
|
+
duration: number,
|
|
324
|
+
events: SpanEvent[]
|
|
325
|
+
): Segment[] {
|
|
326
|
+
const segments: Segment[] = [];
|
|
327
|
+
if (duration <= 0) return segments;
|
|
328
|
+
|
|
329
|
+
const sorted = [...events]
|
|
330
|
+
.map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
|
|
331
|
+
.sort((a, b) => a.time - b.time);
|
|
332
|
+
|
|
333
|
+
const received = sorted.find((e) => e.name === 'hook_received');
|
|
334
|
+
const disposed = sorted.find((e) => e.name === 'hook_disposed');
|
|
335
|
+
|
|
336
|
+
if (!received && !disposed) {
|
|
337
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'waiting' });
|
|
338
|
+
return segments;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const receivedFrac = received
|
|
342
|
+
? timeToFraction(received.time, startMs, duration)
|
|
343
|
+
: null;
|
|
344
|
+
const disposedFrac = disposed
|
|
345
|
+
? timeToFraction(disposed.time, startMs, duration)
|
|
346
|
+
: null;
|
|
347
|
+
|
|
348
|
+
if (receivedFrac !== null && receivedFrac > 0.001) {
|
|
349
|
+
segments.push({
|
|
350
|
+
startFraction: 0,
|
|
351
|
+
endFraction: receivedFrac,
|
|
352
|
+
status: 'waiting',
|
|
353
|
+
});
|
|
354
|
+
} else if (receivedFrac === null && disposedFrac !== null) {
|
|
355
|
+
segments.push({
|
|
356
|
+
startFraction: 0,
|
|
357
|
+
endFraction: disposedFrac,
|
|
358
|
+
status: 'waiting',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (receivedFrac !== null) {
|
|
363
|
+
segments.push({
|
|
364
|
+
startFraction: receivedFrac,
|
|
365
|
+
endFraction: disposedFrac ?? 1,
|
|
366
|
+
status: 'received',
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (disposedFrac !== null && disposedFrac < 0.999) {
|
|
371
|
+
segments.push({
|
|
372
|
+
startFraction: disposedFrac,
|
|
373
|
+
endFraction: 1,
|
|
374
|
+
status: 'succeeded',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return segments;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function computeSleepSegmentsFromSpan(
|
|
382
|
+
startMs: number,
|
|
383
|
+
duration: number,
|
|
384
|
+
events: SpanEvent[]
|
|
385
|
+
): Segment[] {
|
|
386
|
+
const segments: Segment[] = [];
|
|
387
|
+
if (duration <= 0) return segments;
|
|
388
|
+
|
|
389
|
+
const completedTime = events
|
|
390
|
+
.filter((e) => e.name === 'wait_completed')
|
|
391
|
+
.map((e) => getHighResInMs(e.timestamp))
|
|
392
|
+
.sort((a, b) => a - b)[0];
|
|
393
|
+
|
|
394
|
+
if (completedTime === undefined) {
|
|
395
|
+
segments.push({ startFraction: 0, endFraction: 1, status: 'sleeping' });
|
|
396
|
+
return segments;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const completedFrac = timeToFraction(completedTime, startMs, duration);
|
|
400
|
+
if (completedFrac > 0.001) {
|
|
401
|
+
segments.push({
|
|
402
|
+
startFraction: 0,
|
|
403
|
+
endFraction: completedFrac,
|
|
404
|
+
status: 'sleeping',
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
segments.push({
|
|
409
|
+
startFraction: completedFrac,
|
|
410
|
+
endFraction: 1,
|
|
411
|
+
status: 'succeeded',
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return segments;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function computeRunSegmentsFromSpan(
|
|
418
|
+
startMs: number,
|
|
419
|
+
duration: number,
|
|
420
|
+
activeStartMs: number | undefined,
|
|
421
|
+
events: SpanEvent[],
|
|
422
|
+
attributes: Record<string, unknown>
|
|
423
|
+
): Segment[] {
|
|
424
|
+
const segments: Segment[] = [];
|
|
425
|
+
if (duration <= 0) return segments;
|
|
426
|
+
|
|
427
|
+
const sorted = [...events]
|
|
428
|
+
.map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
|
|
429
|
+
.sort((a, b) => a.time - b.time);
|
|
430
|
+
|
|
431
|
+
const hasRunCreated = sorted.some((e) => e.name === 'run_created');
|
|
432
|
+
|
|
433
|
+
if (!hasRunCreated) {
|
|
434
|
+
const runData = attributes?.data as Record<string, unknown> | undefined;
|
|
435
|
+
const runStatus = runData?.status as string | undefined;
|
|
436
|
+
return computeV1RunSegments(startMs, duration, activeStartMs, runStatus);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const failedEvent = sorted.find((e) => e.name === 'run_failed');
|
|
440
|
+
|
|
441
|
+
let cursor = 0;
|
|
442
|
+
if (activeStartMs !== undefined && activeStartMs > startMs) {
|
|
443
|
+
const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
|
|
444
|
+
if (queuedFrac > 0.001) {
|
|
445
|
+
segments.push({
|
|
446
|
+
startFraction: 0,
|
|
447
|
+
endFraction: queuedFrac,
|
|
448
|
+
status: 'queued',
|
|
449
|
+
});
|
|
450
|
+
cursor = queuedFrac;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
segments.push({
|
|
455
|
+
startFraction: cursor,
|
|
456
|
+
endFraction: 1,
|
|
457
|
+
status: failedEvent ? 'failed' : 'running',
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return segments;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function computeV1RunSegments(
|
|
464
|
+
startMs: number,
|
|
465
|
+
duration: number,
|
|
466
|
+
activeStartMs: number | undefined,
|
|
467
|
+
runStatus: string | undefined
|
|
468
|
+
): Segment[] {
|
|
469
|
+
const segments: Segment[] = [];
|
|
470
|
+
|
|
471
|
+
let cursor = 0;
|
|
472
|
+
if (activeStartMs !== undefined && activeStartMs > startMs) {
|
|
473
|
+
const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
|
|
474
|
+
if (queuedFrac > 0.001) {
|
|
475
|
+
segments.push({
|
|
476
|
+
startFraction: 0,
|
|
477
|
+
endFraction: queuedFrac,
|
|
478
|
+
status: 'queued',
|
|
479
|
+
});
|
|
480
|
+
cursor = queuedFrac;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
segments.push({
|
|
485
|
+
startFraction: cursor,
|
|
486
|
+
endFraction: 1,
|
|
487
|
+
status: runStatus === 'failed' ? 'failed' : 'running',
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return segments;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Compute event-based segments for a span. Dispatches on `span.resource`
|
|
495
|
+
* to the appropriate resource-type-specific segment builder.
|
|
496
|
+
* Returns an empty array for generic (non-workflow) spans.
|
|
497
|
+
*/
|
|
498
|
+
export function computeSpanSegments(span: Span): Segment[] {
|
|
499
|
+
const startMs = getHighResInMs(span.startTime);
|
|
500
|
+
const endMs = getHighResInMs(span.endTime);
|
|
501
|
+
const duration = endMs - startMs;
|
|
502
|
+
const activeStartMs = span.activeStartTime
|
|
503
|
+
? getHighResInMs(span.activeStartTime)
|
|
504
|
+
: undefined;
|
|
505
|
+
|
|
506
|
+
switch (span.resource) {
|
|
507
|
+
case 'step':
|
|
508
|
+
return computeStepSegmentsFromSpan(startMs, duration, span.events);
|
|
509
|
+
case 'hook':
|
|
510
|
+
return computeHookSegmentsFromSpan(startMs, duration, span.events);
|
|
511
|
+
case 'sleep':
|
|
512
|
+
return computeSleepSegmentsFromSpan(startMs, duration, span.events);
|
|
513
|
+
case 'run':
|
|
514
|
+
return computeRunSegmentsFromSpan(
|
|
515
|
+
startMs,
|
|
516
|
+
duration,
|
|
517
|
+
activeStartMs,
|
|
518
|
+
span.events,
|
|
519
|
+
span.attributes
|
|
520
|
+
);
|
|
521
|
+
default:
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
@@ -318,8 +318,29 @@ const sortByAttributeOrder = (a: string, b: string): number => {
|
|
|
318
318
|
* Display names for attributes that should render differently from their key.
|
|
319
319
|
*/
|
|
320
320
|
const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
|
|
321
|
+
moduleSpecifier: 'Module',
|
|
322
|
+
workflowName: 'Workflow Name',
|
|
323
|
+
stepName: 'Step Name',
|
|
324
|
+
stepId: 'Step ID',
|
|
325
|
+
hookId: 'Hook ID',
|
|
326
|
+
attempt: 'Attempts',
|
|
327
|
+
eventId: 'Event ID',
|
|
328
|
+
runId: 'Run ID',
|
|
329
|
+
eventType: 'Event Type',
|
|
330
|
+
correlationId: 'Correlation ID',
|
|
331
|
+
deploymentId: 'Deployment ID',
|
|
332
|
+
specVersion: 'Spec Version',
|
|
321
333
|
workflowCoreVersion: '@workflow/core version',
|
|
322
|
-
|
|
334
|
+
createdAt: 'Created',
|
|
335
|
+
startedAt: 'Started',
|
|
336
|
+
updatedAt: 'Updated',
|
|
337
|
+
completedAt: 'Completed',
|
|
338
|
+
expiredAt: 'Expired',
|
|
339
|
+
retryAfter: 'Retry After',
|
|
340
|
+
resumeAt: 'Resume',
|
|
341
|
+
lastReceivedAt: 'Last Received',
|
|
342
|
+
disposedAt: 'Disposed',
|
|
343
|
+
receivedCount: 'Times Resolved',
|
|
323
344
|
};
|
|
324
345
|
|
|
325
346
|
/**
|
|
@@ -406,18 +427,16 @@ const attributeToDisplayFn: Record<
|
|
|
406
427
|
(value: unknown, context?: DisplayContext) => null | string | ReactNode
|
|
407
428
|
> = {
|
|
408
429
|
// Names that need pretty-printing
|
|
409
|
-
workflowName: (
|
|
410
|
-
parseWorkflowName(String(value))?.shortName ?? '?',
|
|
430
|
+
workflowName: (_value: unknown) => null,
|
|
411
431
|
moduleSpecifier: (value: unknown) => getModuleSpecifierFromName(value),
|
|
412
|
-
stepName: (
|
|
413
|
-
parseStepName(String(value))?.shortName ?? String(value),
|
|
432
|
+
stepName: (_value: unknown) => null,
|
|
414
433
|
// IDs
|
|
415
|
-
runId: (
|
|
416
|
-
stepId: (
|
|
434
|
+
runId: (_value: unknown) => null,
|
|
435
|
+
stepId: (_value: unknown) => null,
|
|
417
436
|
hookId: (value: unknown) => String(value),
|
|
418
437
|
eventId: (value: unknown) => String(value),
|
|
419
438
|
// Run/step details
|
|
420
|
-
status: (
|
|
439
|
+
status: (_value: unknown) => null,
|
|
421
440
|
attempt: (value: unknown) => String(value),
|
|
422
441
|
// Hook details
|
|
423
442
|
token: (value: unknown) => String(value),
|
|
@@ -440,9 +459,9 @@ const attributeToDisplayFn: Record<
|
|
|
440
459
|
// Dates — wrapped with TimestampTooltip showing UTC/local + relative time
|
|
441
460
|
createdAt: timestampWithTooltipOrNull,
|
|
442
461
|
startedAt: timestampWithTooltipOrNull,
|
|
443
|
-
updatedAt:
|
|
462
|
+
updatedAt: (_value: unknown) => null,
|
|
444
463
|
completedAt: timestampWithTooltipOrNull,
|
|
445
|
-
expiredAt:
|
|
464
|
+
expiredAt: (_value: unknown) => null,
|
|
446
465
|
retryAfter: timestampWithTooltipOrNull,
|
|
447
466
|
resumeAt: timestampWithTooltipOrNull,
|
|
448
467
|
// Resolved attributes, won't actually use this function
|
|
@@ -496,7 +515,7 @@ const attributeToDisplayFn: Record<
|
|
|
496
515
|
<DetailCard
|
|
497
516
|
summary="Input (no data)"
|
|
498
517
|
disabled
|
|
499
|
-
summaryClassName="text-
|
|
518
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
500
519
|
/>
|
|
501
520
|
);
|
|
502
521
|
}
|
|
@@ -505,7 +524,7 @@ const attributeToDisplayFn: Record<
|
|
|
505
524
|
<>
|
|
506
525
|
<DetailCard
|
|
507
526
|
summary={`Input (${argCount} ${argLabel})`}
|
|
508
|
-
summaryClassName="text-
|
|
527
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
509
528
|
contentClassName="mt-0"
|
|
510
529
|
>
|
|
511
530
|
{Array.isArray(args)
|
|
@@ -536,14 +555,14 @@ const attributeToDisplayFn: Record<
|
|
|
536
555
|
<DetailCard
|
|
537
556
|
summary="Input (no data)"
|
|
538
557
|
disabled
|
|
539
|
-
summaryClassName="text-
|
|
558
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
540
559
|
/>
|
|
541
560
|
);
|
|
542
561
|
}
|
|
543
562
|
return (
|
|
544
563
|
<DetailCard
|
|
545
564
|
summary={`Input (${argCount} ${argLabel})`}
|
|
546
|
-
summaryClassName="text-
|
|
565
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
547
566
|
contentClassName="mt-0"
|
|
548
567
|
>
|
|
549
568
|
{Array.isArray(value)
|
|
@@ -563,7 +582,7 @@ const attributeToDisplayFn: Record<
|
|
|
563
582
|
return (
|
|
564
583
|
<DetailCard
|
|
565
584
|
summary="Output"
|
|
566
|
-
summaryClassName="text-
|
|
585
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
567
586
|
contentClassName="mt-0"
|
|
568
587
|
>
|
|
569
588
|
{JsonBlock(value)}
|
|
@@ -581,7 +600,7 @@ const attributeToDisplayFn: Record<
|
|
|
581
600
|
return (
|
|
582
601
|
<DetailCard
|
|
583
602
|
summary="Error"
|
|
584
|
-
summaryClassName="text-
|
|
603
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
585
604
|
contentClassName="mt-0"
|
|
586
605
|
>
|
|
587
606
|
<ErrorStackBlock value={value} />
|
|
@@ -592,7 +611,7 @@ const attributeToDisplayFn: Record<
|
|
|
592
611
|
return (
|
|
593
612
|
<DetailCard
|
|
594
613
|
summary="Error"
|
|
595
|
-
summaryClassName="text-
|
|
614
|
+
summaryClassName="text-label-14 font-medium py-2"
|
|
596
615
|
contentClassName="mt-0"
|
|
597
616
|
>
|
|
598
617
|
{JsonBlock(value)}
|
|
@@ -642,18 +661,13 @@ export const AttributeBlock = ({
|
|
|
642
661
|
context?: DisplayContext;
|
|
643
662
|
}) => {
|
|
644
663
|
const isExpandableLoadingTarget =
|
|
645
|
-
attribute === 'input' ||
|
|
646
|
-
attribute === 'output' ||
|
|
647
|
-
attribute === 'eventData';
|
|
664
|
+
attribute === 'input' || attribute === 'eventData';
|
|
648
665
|
if (isLoading && isExpandableLoadingTarget && !hasDisplayContent(value)) {
|
|
649
666
|
return (
|
|
650
667
|
<div
|
|
651
|
-
className={`my-2 flex flex-col ${attribute === 'input'
|
|
668
|
+
className={`my-2 flex flex-col ${attribute === 'input' ? 'gap-2 my-3.5' : 'gap-0'}`}
|
|
652
669
|
>
|
|
653
|
-
<span
|
|
654
|
-
className={`${attribute === 'input' || attribute === 'output' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
|
|
655
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
656
|
-
>
|
|
670
|
+
<span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
|
|
657
671
|
{attribute}
|
|
658
672
|
</span>
|
|
659
673
|
<Skeleton className="h-9 w-full rounded-md" />
|
|
@@ -701,10 +715,7 @@ export const AttributeBlock = ({
|
|
|
701
715
|
key={attribute}
|
|
702
716
|
className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'gap-2 my-3.5' : 'gap-0'}`}
|
|
703
717
|
>
|
|
704
|
-
<span
|
|
705
|
-
className={`${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
|
|
706
|
-
style={{ color: 'var(--ds-gray-700)' }}
|
|
707
|
-
>
|
|
718
|
+
<span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
|
|
708
719
|
{attribute}
|
|
709
720
|
</span>
|
|
710
721
|
<span className="text-xs" style={{ color: 'var(--ds-gray-1000)' }}>
|
|
@@ -773,9 +784,9 @@ export const AttributePanel = ({
|
|
|
773
784
|
|
|
774
785
|
if (!isLoading) return present;
|
|
775
786
|
|
|
776
|
-
// During loading, ensure input
|
|
787
|
+
// During loading, ensure input appears so its skeleton renders
|
|
777
788
|
// in the correct position (above the events section).
|
|
778
|
-
const loadingDefaults = ['input'
|
|
789
|
+
const loadingDefaults = ['input'];
|
|
779
790
|
for (const key of loadingDefaults) {
|
|
780
791
|
if (!present.includes(key)) {
|
|
781
792
|
present.push(key);
|
|
@@ -848,7 +859,7 @@ export const AttributePanel = ({
|
|
|
848
859
|
{/* Basic attributes in a vertical layout with border */}
|
|
849
860
|
{visibleBasicAttributes.length > 0 && (
|
|
850
861
|
<div
|
|
851
|
-
className="mb-3 flex flex-col overflow-hidden rounded-
|
|
862
|
+
className="mb-3 flex flex-col overflow-hidden rounded-md border"
|
|
852
863
|
style={{
|
|
853
864
|
borderColor: 'var(--ds-gray-300)',
|
|
854
865
|
}}
|
|
@@ -24,7 +24,7 @@ export function CopyableDataBlock({ data }: { data: unknown }) {
|
|
|
24
24
|
const toast = useToast();
|
|
25
25
|
return (
|
|
26
26
|
<div
|
|
27
|
-
className="relative overflow-x-auto rounded-md border p-3
|
|
27
|
+
className="relative overflow-x-auto rounded-md border p-3"
|
|
28
28
|
style={{ borderColor: 'var(--ds-gray-300)' }}
|
|
29
29
|
>
|
|
30
30
|
<button
|