@workflow/web-shared 5.0.0-beta.24 → 5.0.0-beta.25
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/README.md +2 -10
- package/dist/components/event-list-view.d.ts +30 -0
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +27 -12
- package/dist/components/index.d.ts +4 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -3
- package/dist/components/new-trace-viewer/components/copy-button.js +2 -2
- package/dist/components/new-trace-viewer/components/event-list.js +2 -2
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +2 -2
- package/dist/components/new-trace-viewer/components/span-markers.js +3 -3
- package/dist/components/new-trace-viewer/components/split-pane.js +2 -2
- package/dist/components/new-trace-viewer/components/timeline.js +2 -2
- package/dist/components/new-trace-viewer/trace-viewer.js +4 -4
- package/dist/components/run-trace-view.d.ts +5 -7
- package/dist/components/run-trace-view.d.ts.map +1 -1
- package/dist/components/run-trace-view.js +3 -3
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +3 -5
- package/dist/components/sidebar/detail-card.js +2 -2
- package/dist/components/sidebar/entity-detail-panel.d.ts +6 -10
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +8 -60
- package/dist/components/sidebar/events-list.d.ts +3 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +31 -9
- package/dist/components/sidebar/sidebar-data-context.d.ts +5 -6
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.js +1 -1
- package/dist/components/sidebar/span-detail-merge.d.ts +15 -0
- package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -1
- package/dist/components/sidebar/span-detail-merge.js +40 -1
- package/dist/components/sidebar/use-selected-span-detail.d.ts +16 -0
- package/dist/components/sidebar/use-selected-span-detail.d.ts.map +1 -0
- package/dist/components/sidebar/use-selected-span-detail.js +93 -0
- package/dist/components/ui/alert.js +2 -2
- package/dist/components/ui/button.js +2 -2
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/context-card.js +2 -2
- package/dist/components/ui/error-card.js +2 -2
- package/dist/components/ui/icon-button.js +2 -2
- package/dist/components/ui/kbd.js +2 -2
- package/dist/components/ui/skeleton.js +2 -2
- package/dist/components/ui/tooltip.js +2 -2
- package/dist/components/workflow-trace-view.d.ts +6 -7
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +3 -6
- package/dist/components/workflow-traces/trace-span-construction.d.ts +4 -4
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +74 -53
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +83 -0
- package/dist/lib/event-materialization.d.ts +0 -3
- package/dist/lib/event-materialization.d.ts.map +1 -1
- package/dist/lib/event-materialization.js +28 -27
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +3 -3
- package/dist/lib/utils.d.ts +0 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +1 -6
- package/package.json +3 -3
- package/src/components/event-list-view.tsx +76 -13
- package/src/components/index.ts +10 -6
- package/src/components/new-trace-viewer/components/copy-button.tsx +1 -1
- package/src/components/new-trace-viewer/components/event-list.tsx +1 -1
- package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +1 -1
- package/src/components/new-trace-viewer/components/span-markers.tsx +2 -2
- package/src/components/new-trace-viewer/components/split-pane.tsx +1 -1
- package/src/components/new-trace-viewer/components/timeline.tsx +1 -1
- package/src/components/new-trace-viewer/trace-viewer.tsx +6 -6
- package/src/components/run-trace-view.tsx +10 -14
- package/src/components/sidebar/attribute-panel.tsx +2 -4
- package/src/components/sidebar/detail-card.tsx +1 -1
- package/src/components/sidebar/entity-detail-panel.tsx +23 -94
- package/src/components/sidebar/events-list.tsx +58 -8
- package/src/components/sidebar/sidebar-data-context.tsx +6 -7
- package/src/components/sidebar/span-detail-merge.ts +69 -0
- package/src/components/sidebar/use-selected-span-detail.ts +146 -0
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/button.tsx +1 -1
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/context-card.tsx +1 -1
- package/src/components/ui/error-card.tsx +1 -1
- package/src/components/ui/icon-button.tsx +1 -1
- package/src/components/ui/kbd.tsx +1 -1
- package/src/components/ui/skeleton.tsx +1 -1
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/components/workflow-trace-view.tsx +11 -21
- package/src/components/workflow-traces/trace-span-construction.test.ts +85 -0
- package/src/components/workflow-traces/trace-span-construction.ts +113 -89
- package/src/lib/cn.ts +93 -0
- package/src/lib/event-materialization.ts +56 -76
- package/src/lib/trace-builder.ts +2 -1
- package/src/lib/utils.ts +0 -6
|
@@ -29,25 +29,14 @@ const MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([
|
|
|
29
29
|
'attr_set',
|
|
30
30
|
]);
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const event = events.find((e) => e.eventType === eventType);
|
|
38
|
-
if (event) return event;
|
|
39
|
-
}
|
|
40
|
-
return undefined;
|
|
41
|
-
};
|
|
32
|
+
export const getEventTimestamp = (
|
|
33
|
+
event: Event | undefined
|
|
34
|
+
): Date | undefined => {
|
|
35
|
+
const value = event?.occurredAt ?? event?.createdAt;
|
|
36
|
+
if (!value) return undefined;
|
|
42
37
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
occurredAt: Event['occurredAt'] | undefined
|
|
46
|
-
): T & { occurredAt?: Date } => {
|
|
47
|
-
if (!occurredAt || (entity as { occurredAt?: unknown }).occurredAt != null) {
|
|
48
|
-
return entity;
|
|
49
|
-
}
|
|
50
|
-
return { ...entity, occurredAt };
|
|
38
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
39
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
51
40
|
};
|
|
52
41
|
|
|
53
42
|
/**
|
|
@@ -56,7 +45,8 @@ const withOccurredAt = <T extends object>(
|
|
|
56
45
|
*/
|
|
57
46
|
export function convertEventsToSpanEvents(
|
|
58
47
|
events: Event[],
|
|
59
|
-
filterTypes = true
|
|
48
|
+
filterTypes = true,
|
|
49
|
+
options: { preferOccurredAt?: boolean } = {}
|
|
60
50
|
): SpanEvent[] {
|
|
61
51
|
return events
|
|
62
52
|
.filter((event) =>
|
|
@@ -64,7 +54,11 @@ export function convertEventsToSpanEvents(
|
|
|
64
54
|
)
|
|
65
55
|
.map((event) => ({
|
|
66
56
|
name: event.eventType,
|
|
67
|
-
timestamp: dateToOtelTime(
|
|
57
|
+
timestamp: dateToOtelTime(
|
|
58
|
+
options.preferOccurredAt
|
|
59
|
+
? (getEventTimestamp(event) ?? event.createdAt)
|
|
60
|
+
: event.createdAt
|
|
61
|
+
),
|
|
68
62
|
attributes: {
|
|
69
63
|
eventId: event.eventId,
|
|
70
64
|
correlationId: event.correlationId,
|
|
@@ -81,7 +75,6 @@ export const waitEventsToWaitEntity = (
|
|
|
81
75
|
waitId: string;
|
|
82
76
|
runId: string;
|
|
83
77
|
createdAt: Date;
|
|
84
|
-
occurredAt?: Date;
|
|
85
78
|
resumeAt?: Date;
|
|
86
79
|
completedAt?: Date;
|
|
87
80
|
} | null => {
|
|
@@ -92,18 +85,15 @@ export const waitEventsToWaitEntity = (
|
|
|
92
85
|
const completedEvent = events.find(
|
|
93
86
|
(event) => event.eventType === 'wait_completed'
|
|
94
87
|
);
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
startEvent.occurredAt
|
|
106
|
-
);
|
|
88
|
+
return {
|
|
89
|
+
waitId: startEvent.correlationId,
|
|
90
|
+
runId: startEvent.runId,
|
|
91
|
+
createdAt: getEventTimestamp(startEvent) ?? startEvent.createdAt,
|
|
92
|
+
resumeAt: startEvent.eventData?.resumeAt
|
|
93
|
+
? new Date(startEvent.eventData.resumeAt)
|
|
94
|
+
: undefined,
|
|
95
|
+
completedAt: getEventTimestamp(completedEvent),
|
|
96
|
+
};
|
|
107
97
|
};
|
|
108
98
|
|
|
109
99
|
/**
|
|
@@ -166,7 +156,6 @@ export const stepEventsToStepEntity = (
|
|
|
166
156
|
attempt: number;
|
|
167
157
|
createdAt: Date;
|
|
168
158
|
updatedAt: Date;
|
|
169
|
-
occurredAt?: Date;
|
|
170
159
|
startedAt?: Date;
|
|
171
160
|
completedAt?: Date;
|
|
172
161
|
specVersion?: number;
|
|
@@ -195,16 +184,16 @@ export const stepEventsToStepEntity = (
|
|
|
195
184
|
case 'step_started':
|
|
196
185
|
status = 'running';
|
|
197
186
|
attempt += 1;
|
|
198
|
-
if (!startedAt) startedAt = e.createdAt;
|
|
187
|
+
if (!startedAt) startedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
199
188
|
completedAt = undefined;
|
|
200
189
|
break;
|
|
201
190
|
case 'step_completed':
|
|
202
191
|
status = 'completed';
|
|
203
|
-
completedAt = e.createdAt;
|
|
192
|
+
completedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
204
193
|
break;
|
|
205
194
|
case 'step_failed':
|
|
206
195
|
status = 'failed';
|
|
207
|
-
completedAt = e.createdAt;
|
|
196
|
+
completedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
208
197
|
break;
|
|
209
198
|
case 'step_retrying':
|
|
210
199
|
status = 'pending';
|
|
@@ -217,24 +206,22 @@ export const stepEventsToStepEntity = (
|
|
|
217
206
|
if (attempt === 0) attempt = 1;
|
|
218
207
|
|
|
219
208
|
const lastEvent = events[events.length - 1];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
createdAt
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
occurrenceEvent.occurredAt
|
|
237
|
-
);
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
stepId: anchorEvent.correlationId ?? '',
|
|
212
|
+
runId: anchorEvent.runId,
|
|
213
|
+
stepName: createdEvent?.eventData?.stepName ?? '',
|
|
214
|
+
status,
|
|
215
|
+
attempt,
|
|
216
|
+
createdAt: getEventTimestamp(anchorEvent) ?? anchorEvent.createdAt,
|
|
217
|
+
updatedAt:
|
|
218
|
+
getEventTimestamp(lastEvent) ??
|
|
219
|
+
lastEvent?.createdAt ??
|
|
220
|
+
anchorEvent.createdAt,
|
|
221
|
+
startedAt,
|
|
222
|
+
completedAt,
|
|
223
|
+
specVersion: anchorEvent.specVersion,
|
|
224
|
+
};
|
|
238
225
|
};
|
|
239
226
|
|
|
240
227
|
/**
|
|
@@ -253,25 +240,41 @@ export function stepToSpan(stepEvents: Event[], maxEndTime: Date): Span | null {
|
|
|
253
240
|
};
|
|
254
241
|
|
|
255
242
|
const resource = 'step';
|
|
256
|
-
const endTime = new Date(step.completedAt ?? maxEndTime);
|
|
257
243
|
|
|
258
244
|
// Include ALL correlated events on the span so the sidebar detail view
|
|
259
245
|
// can display them. The timeline uses the `showVerticalLine` flag to
|
|
260
246
|
// determine which events appear as markers.
|
|
261
|
-
const events = convertEventsToSpanEvents(stepEvents, false
|
|
247
|
+
const events = convertEventsToSpanEvents(stepEvents, false, {
|
|
248
|
+
preferOccurredAt: true,
|
|
249
|
+
});
|
|
262
250
|
|
|
263
|
-
// Use
|
|
264
|
-
//
|
|
265
|
-
const
|
|
251
|
+
// Use the event occurrence timestamp for step spans when it is available,
|
|
252
|
+
// falling back to the ingest timestamp used by older event streams.
|
|
253
|
+
const spanStartEvent =
|
|
254
|
+
stepEvents.find((event) => event.eventType === 'step_created') ??
|
|
255
|
+
stepEvents[0];
|
|
256
|
+
const spanStartTime =
|
|
257
|
+
getEventTimestamp(spanStartEvent) ?? new Date(step.createdAt);
|
|
266
258
|
let activeStartTime = step.startedAt ? new Date(step.startedAt) : undefined;
|
|
267
259
|
const firstStartEvent = stepEvents.find(
|
|
268
260
|
(event) => event.eventType === 'step_started'
|
|
269
261
|
);
|
|
270
262
|
if (firstStartEvent) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
263
|
+
activeStartTime =
|
|
264
|
+
getEventTimestamp(firstStartEvent) ?? new Date(firstStartEvent.createdAt);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let endTime = new Date(maxEndTime);
|
|
268
|
+
if (step.completedAt) {
|
|
269
|
+
const completedEvent = stepEvents
|
|
270
|
+
.slice()
|
|
271
|
+
.reverse()
|
|
272
|
+
.find(
|
|
273
|
+
(event) =>
|
|
274
|
+
event.eventType === 'step_completed' ||
|
|
275
|
+
event.eventType === 'step_failed'
|
|
276
|
+
);
|
|
277
|
+
endTime = getEventTimestamp(completedEvent) ?? new Date(step.completedAt);
|
|
275
278
|
}
|
|
276
279
|
|
|
277
280
|
return {
|
|
@@ -303,7 +306,6 @@ export const hookEventsToHookEntity = (
|
|
|
303
306
|
runId: string;
|
|
304
307
|
token?: string;
|
|
305
308
|
createdAt: Date;
|
|
306
|
-
occurredAt?: Date;
|
|
307
309
|
receivedCount: number;
|
|
308
310
|
lastReceivedAt?: Date;
|
|
309
311
|
disposedAt?: Date;
|
|
@@ -321,18 +323,15 @@ export const hookEventsToHookEntity = (
|
|
|
321
323
|
(event) => event.eventType === 'hook_disposed'
|
|
322
324
|
);
|
|
323
325
|
const lastReceivedEvent = receivedEvents.at(-1);
|
|
324
|
-
return
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
},
|
|
334
|
-
createdEvent.occurredAt
|
|
335
|
-
);
|
|
326
|
+
return {
|
|
327
|
+
hookId: createdEvent.correlationId,
|
|
328
|
+
runId: createdEvent.runId,
|
|
329
|
+
token: createdEvent.eventData?.token,
|
|
330
|
+
createdAt: getEventTimestamp(createdEvent) ?? createdEvent.createdAt,
|
|
331
|
+
receivedCount: receivedEvents.length,
|
|
332
|
+
lastReceivedAt: getEventTimestamp(lastReceivedEvent),
|
|
333
|
+
disposedAt: getEventTimestamp(disposedEvents.at(-1)),
|
|
334
|
+
};
|
|
336
335
|
};
|
|
337
336
|
|
|
338
337
|
/**
|
|
@@ -379,25 +378,50 @@ export function runToSpan(
|
|
|
379
378
|
): Span {
|
|
380
379
|
const now = nowTime ?? new Date();
|
|
381
380
|
|
|
381
|
+
// Prefer the event occurrence timestamp when available so the root span
|
|
382
|
+
// lines up with child spans that already use event occurrence time.
|
|
383
|
+
const runCreatedEvent = runEvents.find(
|
|
384
|
+
(event) => event.eventType === 'run_created'
|
|
385
|
+
);
|
|
386
|
+
const runStartedEvent = runEvents.find(
|
|
387
|
+
(event) => event.eventType === 'run_started'
|
|
388
|
+
);
|
|
389
|
+
const terminalEvent = runEvents
|
|
390
|
+
.slice()
|
|
391
|
+
.reverse()
|
|
392
|
+
.find(
|
|
393
|
+
(event) =>
|
|
394
|
+
event.eventType === 'run_completed' ||
|
|
395
|
+
event.eventType === 'run_failed' ||
|
|
396
|
+
event.eventType === 'run_cancelled'
|
|
397
|
+
);
|
|
398
|
+
const spanStartTime =
|
|
399
|
+
getEventTimestamp(runCreatedEvent) ?? new Date(run.createdAt);
|
|
400
|
+
const activeStartTime =
|
|
401
|
+
getEventTimestamp(runStartedEvent) ??
|
|
402
|
+
(run.startedAt ? new Date(run.startedAt) : undefined);
|
|
403
|
+
const completedAt =
|
|
404
|
+
getEventTimestamp(terminalEvent) ?? run.completedAt ?? undefined;
|
|
405
|
+
const endTime = completedAt ?? now;
|
|
406
|
+
|
|
382
407
|
// Only embed identification fields — not the full object with
|
|
383
|
-
// input/output/error which may contain non-cloneable types.
|
|
408
|
+
// input/output/error which may contain non-cloneable types. Lifecycle
|
|
409
|
+
// timestamps are event-derived so detail rows align with the span timeline.
|
|
384
410
|
const { input: _i, output: _o, error: _e, ...runIdentity } = run;
|
|
385
|
-
const occurrenceEvent = findEventByType(runEvents, [
|
|
386
|
-
'run_created',
|
|
387
|
-
'run_started',
|
|
388
|
-
]);
|
|
389
411
|
const attributes = {
|
|
390
412
|
resource: 'run' as const,
|
|
391
|
-
data:
|
|
413
|
+
data: {
|
|
414
|
+
...runIdentity,
|
|
415
|
+
createdAt: spanStartTime,
|
|
416
|
+
startedAt: activeStartTime,
|
|
417
|
+
completedAt,
|
|
418
|
+
},
|
|
392
419
|
};
|
|
393
420
|
|
|
394
|
-
// Use createdAt as span start time, with activeStartTime for when execution began
|
|
395
|
-
const spanStartTime = new Date(run.createdAt);
|
|
396
|
-
const activeStartTime = run.startedAt ? new Date(run.startedAt) : undefined;
|
|
397
|
-
const endTime = run.completedAt ?? now;
|
|
398
|
-
|
|
399
421
|
// Convert run-level events to span events
|
|
400
|
-
const events = convertEventsToSpanEvents(runEvents, false
|
|
422
|
+
const events = convertEventsToSpanEvents(runEvents, false, {
|
|
423
|
+
preferOccurredAt: true,
|
|
424
|
+
});
|
|
401
425
|
|
|
402
426
|
return {
|
|
403
427
|
spanId: String(run.runId),
|
package/src/lib/cn.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from 'clsx';
|
|
2
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
const twMergeConfig = {
|
|
5
|
+
extend: {
|
|
6
|
+
classGroups: {
|
|
7
|
+
'text-heading': [
|
|
8
|
+
{
|
|
9
|
+
'text-heading': [
|
|
10
|
+
'72',
|
|
11
|
+
'64',
|
|
12
|
+
'56',
|
|
13
|
+
'48',
|
|
14
|
+
'40',
|
|
15
|
+
'32',
|
|
16
|
+
'24',
|
|
17
|
+
'20',
|
|
18
|
+
'16',
|
|
19
|
+
'14',
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
'text-button': [
|
|
24
|
+
{
|
|
25
|
+
'text-button': ['16', '14', '12'],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
'text-label': [
|
|
29
|
+
{
|
|
30
|
+
'text-label': [
|
|
31
|
+
'20',
|
|
32
|
+
'18',
|
|
33
|
+
'16',
|
|
34
|
+
'14',
|
|
35
|
+
'14-mono',
|
|
36
|
+
'13',
|
|
37
|
+
'13-mono',
|
|
38
|
+
'12',
|
|
39
|
+
'12-mono',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
'text-copy': [
|
|
44
|
+
{
|
|
45
|
+
'text-copy': ['24', '20', '18', '16', '14', '13', '13-mono'],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
material: [
|
|
49
|
+
{
|
|
50
|
+
material: [
|
|
51
|
+
'base',
|
|
52
|
+
'small',
|
|
53
|
+
'medium',
|
|
54
|
+
'large',
|
|
55
|
+
'tooltip',
|
|
56
|
+
'menu',
|
|
57
|
+
'modal',
|
|
58
|
+
'fullscreen',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
// Make each category conflict with itself, so e.g. text-heading-32 overrides text-heading-24
|
|
64
|
+
conflictingClassGroups: {
|
|
65
|
+
'text-heading': ['text-heading'],
|
|
66
|
+
'text-button': ['text-button'],
|
|
67
|
+
'text-label': ['text-label'],
|
|
68
|
+
'text-copy': ['text-copy'],
|
|
69
|
+
material: ['material'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
} as const;
|
|
73
|
+
|
|
74
|
+
type CustomClassGroupIds =
|
|
75
|
+
| 'text-heading'
|
|
76
|
+
| 'text-button'
|
|
77
|
+
| 'text-label'
|
|
78
|
+
| 'text-copy'
|
|
79
|
+
| 'material';
|
|
80
|
+
|
|
81
|
+
const customTwMerge = extendTailwindMerge<CustomClassGroupIds>(twMergeConfig);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Merges Tailwind class names and resolves any conflicts using `clsx` with `tailwind-merge`.
|
|
85
|
+
*
|
|
86
|
+
* @param inputs - An array of class names to merge.
|
|
87
|
+
* @returns A string of merged and optimized class names.
|
|
88
|
+
*/
|
|
89
|
+
function cn(...classes: ClassValue[]) {
|
|
90
|
+
return customTwMerge(clsx(...classes));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { cn };
|
|
@@ -23,7 +23,6 @@ export interface MaterializedStep {
|
|
|
23
23
|
status: StepStatus;
|
|
24
24
|
attempt: number;
|
|
25
25
|
createdAt: Date;
|
|
26
|
-
occurredAt?: Date;
|
|
27
26
|
startedAt?: Date;
|
|
28
27
|
completedAt?: Date;
|
|
29
28
|
updatedAt: Date;
|
|
@@ -36,7 +35,6 @@ export interface MaterializedHook {
|
|
|
36
35
|
runId: string;
|
|
37
36
|
token?: string;
|
|
38
37
|
createdAt: Date;
|
|
39
|
-
occurredAt?: Date;
|
|
40
38
|
receivedCount: number;
|
|
41
39
|
lastReceivedAt?: Date;
|
|
42
40
|
disposedAt?: Date;
|
|
@@ -49,7 +47,6 @@ export interface MaterializedWait {
|
|
|
49
47
|
runId: string;
|
|
50
48
|
status: 'waiting' | 'completed';
|
|
51
49
|
createdAt: Date;
|
|
52
|
-
occurredAt?: Date;
|
|
53
50
|
resumeAt?: Date;
|
|
54
51
|
completedAt?: Date;
|
|
55
52
|
/** All events for this wait, in insertion order */
|
|
@@ -62,16 +59,6 @@ export interface MaterializedEntities {
|
|
|
62
59
|
waits: MaterializedWait[];
|
|
63
60
|
}
|
|
64
61
|
|
|
65
|
-
const withOccurredAt = <T extends object>(
|
|
66
|
-
entity: T,
|
|
67
|
-
occurredAt: Event['occurredAt'] | undefined
|
|
68
|
-
): T & { occurredAt?: Date } => {
|
|
69
|
-
if (!occurredAt || (entity as { occurredAt?: unknown }).occurredAt != null) {
|
|
70
|
-
return entity;
|
|
71
|
-
}
|
|
72
|
-
return { ...entity, occurredAt };
|
|
73
|
-
};
|
|
74
|
-
|
|
75
62
|
// ---------------------------------------------------------------------------
|
|
76
63
|
// Helper: group events by correlationId prefix
|
|
77
64
|
// ---------------------------------------------------------------------------
|
|
@@ -95,6 +82,14 @@ function groupByCorrelationId(
|
|
|
95
82
|
return groups;
|
|
96
83
|
}
|
|
97
84
|
|
|
85
|
+
function getEventTimestamp(event: Event | undefined): Date | undefined {
|
|
86
|
+
const value = event?.occurredAt ?? event?.createdAt;
|
|
87
|
+
if (!value) return undefined;
|
|
88
|
+
|
|
89
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
90
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
91
|
+
}
|
|
92
|
+
|
|
98
93
|
// ---------------------------------------------------------------------------
|
|
99
94
|
// materializeSteps
|
|
100
95
|
// ---------------------------------------------------------------------------
|
|
@@ -117,55 +112,50 @@ export function materializeSteps(events: Event[]): MaterializedStep[] {
|
|
|
117
112
|
let attempt = 0;
|
|
118
113
|
let startedAt: Date | undefined;
|
|
119
114
|
let completedAt: Date | undefined;
|
|
120
|
-
let updatedAt = created.createdAt;
|
|
115
|
+
let updatedAt = getEventTimestamp(created) ?? created.createdAt;
|
|
121
116
|
|
|
122
117
|
for (const e of stepEvents) {
|
|
123
118
|
switch (e.eventType) {
|
|
124
119
|
case 'step_started':
|
|
125
120
|
status = 'running';
|
|
126
121
|
attempt += 1;
|
|
127
|
-
if (!startedAt) startedAt = e.createdAt;
|
|
122
|
+
if (!startedAt) startedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
128
123
|
completedAt = undefined;
|
|
129
|
-
updatedAt = e.createdAt;
|
|
124
|
+
updatedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
130
125
|
break;
|
|
131
126
|
case 'step_completed':
|
|
132
127
|
status = 'completed';
|
|
133
|
-
completedAt = e.createdAt;
|
|
134
|
-
updatedAt = e.createdAt;
|
|
128
|
+
completedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
129
|
+
updatedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
135
130
|
break;
|
|
136
131
|
case 'step_failed':
|
|
137
132
|
status = 'failed';
|
|
138
|
-
completedAt = e.createdAt;
|
|
139
|
-
updatedAt = e.createdAt;
|
|
133
|
+
completedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
134
|
+
updatedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
140
135
|
break;
|
|
141
136
|
case 'step_retrying':
|
|
142
137
|
status = 'pending';
|
|
143
138
|
completedAt = undefined;
|
|
144
|
-
updatedAt = e.createdAt;
|
|
139
|
+
updatedAt = getEventTimestamp(e) ?? e.createdAt;
|
|
145
140
|
break;
|
|
146
141
|
}
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
steps.push(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
stepName
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
events: stepEvents,
|
|
165
|
-
},
|
|
166
|
-
created.occurredAt
|
|
167
|
-
)
|
|
168
|
-
);
|
|
144
|
+
steps.push({
|
|
145
|
+
stepId: correlationId,
|
|
146
|
+
runId: created.runId,
|
|
147
|
+
stepName:
|
|
148
|
+
created.eventType === 'step_created'
|
|
149
|
+
? (created.eventData?.stepName ?? correlationId)
|
|
150
|
+
: correlationId,
|
|
151
|
+
status,
|
|
152
|
+
attempt,
|
|
153
|
+
createdAt: getEventTimestamp(created) ?? created.createdAt,
|
|
154
|
+
startedAt,
|
|
155
|
+
completedAt,
|
|
156
|
+
updatedAt,
|
|
157
|
+
events: stepEvents,
|
|
158
|
+
});
|
|
169
159
|
}
|
|
170
160
|
|
|
171
161
|
return steps;
|
|
@@ -192,24 +182,19 @@ export function materializeHooks(events: Event[]): MaterializedHook[] {
|
|
|
192
182
|
const disposed = hookEvents.find((e) => e.eventType === 'hook_disposed');
|
|
193
183
|
const lastReceived = receivedEvents.at(-1);
|
|
194
184
|
|
|
195
|
-
hooks.push(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
token
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
events: hookEvents,
|
|
209
|
-
},
|
|
210
|
-
created.occurredAt
|
|
211
|
-
)
|
|
212
|
-
);
|
|
185
|
+
hooks.push({
|
|
186
|
+
hookId: correlationId,
|
|
187
|
+
runId: created.runId,
|
|
188
|
+
token:
|
|
189
|
+
created.eventType === 'hook_created'
|
|
190
|
+
? created.eventData?.token
|
|
191
|
+
: undefined,
|
|
192
|
+
createdAt: getEventTimestamp(created) ?? created.createdAt,
|
|
193
|
+
receivedCount: receivedEvents.length,
|
|
194
|
+
lastReceivedAt: getEventTimestamp(lastReceived),
|
|
195
|
+
disposedAt: getEventTimestamp(disposed),
|
|
196
|
+
events: hookEvents,
|
|
197
|
+
});
|
|
213
198
|
}
|
|
214
199
|
|
|
215
200
|
return hooks;
|
|
@@ -232,23 +217,18 @@ export function materializeWaits(events: Event[]): MaterializedWait[] {
|
|
|
232
217
|
|
|
233
218
|
const completed = waitEvents.find((e) => e.eventType === 'wait_completed');
|
|
234
219
|
|
|
235
|
-
waits.push(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
resumeAt
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
events: waitEvents,
|
|
248
|
-
},
|
|
249
|
-
created.occurredAt
|
|
250
|
-
)
|
|
251
|
-
);
|
|
220
|
+
waits.push({
|
|
221
|
+
waitId: correlationId,
|
|
222
|
+
runId: created.runId,
|
|
223
|
+
status: completed ? 'completed' : 'waiting',
|
|
224
|
+
createdAt: getEventTimestamp(created) ?? created.createdAt,
|
|
225
|
+
resumeAt:
|
|
226
|
+
created.eventType === 'wait_created'
|
|
227
|
+
? created.eventData?.resumeAt
|
|
228
|
+
: undefined,
|
|
229
|
+
completedAt: getEventTimestamp(completed),
|
|
230
|
+
events: waitEvents,
|
|
231
|
+
});
|
|
252
232
|
}
|
|
253
233
|
|
|
254
234
|
return waits;
|
package/src/lib/trace-builder.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { Event, WorkflowRun } from '@workflow/world';
|
|
10
10
|
import type { Span } from '../components/trace-viewer/types';
|
|
11
11
|
import {
|
|
12
|
+
getEventTimestamp,
|
|
12
13
|
hookToSpan,
|
|
13
14
|
runToSpan,
|
|
14
15
|
stepToSpan,
|
|
@@ -132,7 +133,7 @@ export function groupEventsByCorrelation(events: Event[]): GroupedEvents {
|
|
|
132
133
|
function computeLatestKnownTime(events: Event[], run: WorkflowRun): Date {
|
|
133
134
|
let latest = new Date(run.createdAt).getTime();
|
|
134
135
|
for (const event of events) {
|
|
135
|
-
const t = new Date(event.createdAt).getTime();
|
|
136
|
+
const t = (getEventTimestamp(event) ?? new Date(event.createdAt)).getTime();
|
|
136
137
|
if (t > latest) latest = t;
|
|
137
138
|
}
|
|
138
139
|
return new Date(latest);
|
package/src/lib/utils.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { Step } from '@workflow/world';
|
|
2
2
|
import type { ModelMessage } from 'ai';
|
|
3
|
-
import { type ClassValue, clsx } from 'clsx';
|
|
4
|
-
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
|
|
6
|
-
export function cn(...inputs: ClassValue[]) {
|
|
7
|
-
return twMerge(clsx(inputs));
|
|
8
|
-
}
|
|
9
3
|
|
|
10
4
|
const MS_IN_SECOND = 1000;
|
|
11
5
|
const MS_IN_MINUTE = 60 * MS_IN_SECOND;
|