@workflow/web-shared 5.0.0-beta.24 → 5.0.0-beta.26
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
|
@@ -69,6 +69,25 @@ function formatEventTime(date: Date): string {
|
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
function parseEventDate(value: unknown): Date | null {
|
|
73
|
+
if (value == null) return null;
|
|
74
|
+
|
|
75
|
+
const date = value instanceof Date ? value : new Date(String(value));
|
|
76
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getEffectiveEventDate(
|
|
80
|
+
event: Pick<Event, 'createdAt' | 'occurredAt'>
|
|
81
|
+
): Date {
|
|
82
|
+
return parseEventDate(event.occurredAt) ?? new Date(event.createdAt);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getEffectiveEventTime(
|
|
86
|
+
event: Pick<Event, 'createdAt' | 'occurredAt'>
|
|
87
|
+
): number {
|
|
88
|
+
return getEffectiveEventDate(event).getTime();
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
function formatEventType(eventType: Event['eventType']): string {
|
|
73
92
|
return eventType
|
|
74
93
|
.split('_')
|
|
@@ -178,7 +197,7 @@ export function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
|
|
|
178
197
|
// events for the same correlationId; the queued duration must be measured
|
|
179
198
|
// against the first one, not the last.
|
|
180
199
|
const chronological = [...events].sort(
|
|
181
|
-
(a, b) =>
|
|
200
|
+
(a, b) => getEffectiveEventTime(a) - getEffectiveEventTime(b)
|
|
182
201
|
);
|
|
183
202
|
|
|
184
203
|
const createdTimes = new Map<string, number>();
|
|
@@ -187,7 +206,7 @@ export function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
|
|
|
187
206
|
const durations = new Map<string, DurationInfo>();
|
|
188
207
|
|
|
189
208
|
for (const event of chronological) {
|
|
190
|
-
const ts =
|
|
209
|
+
const ts = getEffectiveEventTime(event);
|
|
191
210
|
const key = event.correlationId ?? '__run__';
|
|
192
211
|
const type: string = event.eventType;
|
|
193
212
|
|
|
@@ -652,7 +671,11 @@ const SORT_OPTIONS = [
|
|
|
652
671
|
{ value: 'asc' as const, label: 'Oldest' },
|
|
653
672
|
];
|
|
654
673
|
|
|
655
|
-
function RowsSkeleton(
|
|
674
|
+
function RowsSkeleton({
|
|
675
|
+
showSeparateEventOccurrenceTimestamps = false,
|
|
676
|
+
}: {
|
|
677
|
+
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
678
|
+
}) {
|
|
656
679
|
return (
|
|
657
680
|
<div className="flex-1 overflow-hidden">
|
|
658
681
|
{Array.from({ length: 16 }, (_, i) => (
|
|
@@ -689,7 +712,12 @@ function RowsSkeleton() {
|
|
|
689
712
|
<div className="w-5 flex-shrink-0 flex items-center justify-center">
|
|
690
713
|
<Skeleton className="w-5 h-5" style={{ borderRadius: 4 }} />
|
|
691
714
|
</div>
|
|
692
|
-
{
|
|
715
|
+
{showSeparateEventOccurrenceTimestamps && (
|
|
716
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
717
|
+
<Skeleton className="h-3" style={{ width: '70%' }} />
|
|
718
|
+
</div>
|
|
719
|
+
)}
|
|
720
|
+
{/* Created */}
|
|
693
721
|
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
694
722
|
<Skeleton className="h-3" style={{ width: '70%' }} />
|
|
695
723
|
</div>
|
|
@@ -754,9 +782,11 @@ interface EventsListProps {
|
|
|
754
782
|
kind: ExactWorkflowSearchIdKind,
|
|
755
783
|
signal?: AbortSignal
|
|
756
784
|
) => Promise<ExactIdSearchResult>;
|
|
785
|
+
/** Show occurredAt separately instead of folding it into the Created timestamp. */
|
|
786
|
+
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
757
787
|
}
|
|
758
788
|
|
|
759
|
-
function EventRow({
|
|
789
|
+
export function EventRow({
|
|
760
790
|
event,
|
|
761
791
|
index,
|
|
762
792
|
isFirst,
|
|
@@ -777,6 +807,7 @@ function EventRow({
|
|
|
777
807
|
encryptionKey,
|
|
778
808
|
onEncryptedDataDetected,
|
|
779
809
|
suppressGroupDimming = false,
|
|
810
|
+
showSeparateEventOccurrenceTimestamps = false,
|
|
780
811
|
}: {
|
|
781
812
|
event: Event;
|
|
782
813
|
index: number;
|
|
@@ -799,6 +830,8 @@ function EventRow({
|
|
|
799
830
|
onEncryptedDataDetected?: () => void;
|
|
800
831
|
/** Exact-ID search results should not dim unrelated rows. */
|
|
801
832
|
suppressGroupDimming?: boolean;
|
|
833
|
+
/** Show occurredAt separately instead of folding it into the Created timestamp. */
|
|
834
|
+
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
802
835
|
}) {
|
|
803
836
|
const [isLoading, setIsLoading] = useState(false);
|
|
804
837
|
const [loadedEventData, setLoadedEventData] = useState<unknown | null>(
|
|
@@ -827,6 +860,10 @@ function EventRow({
|
|
|
827
860
|
|
|
828
861
|
const statusDotColor = getStatusDotColor(event.eventType);
|
|
829
862
|
const createdAt = new Date(event.createdAt);
|
|
863
|
+
const occurredAt = parseEventDate(event.occurredAt);
|
|
864
|
+
const displayedCreatedAt = showSeparateEventOccurrenceTimestamps
|
|
865
|
+
? createdAt
|
|
866
|
+
: getEffectiveEventDate(event);
|
|
830
867
|
const hasExistingEventData = 'eventData' in event && event.eventData != null;
|
|
831
868
|
const isRun = isRunLevel(event.eventType);
|
|
832
869
|
const eventName = isRun
|
|
@@ -1007,13 +1044,28 @@ function EventRow({
|
|
|
1007
1044
|
/>
|
|
1008
1045
|
</div>
|
|
1009
1046
|
|
|
1010
|
-
{
|
|
1047
|
+
{showSeparateEventOccurrenceTimestamps && (
|
|
1048
|
+
<div
|
|
1049
|
+
className="tabular-nums min-w-0 px-4"
|
|
1050
|
+
style={{ color: 'var(--ds-gray-900)', flex: '2 1 0%' }}
|
|
1051
|
+
>
|
|
1052
|
+
{occurredAt ? (
|
|
1053
|
+
<TimestampTooltip date={occurredAt}>
|
|
1054
|
+
<span>{formatEventTime(occurredAt)}</span>
|
|
1055
|
+
</TimestampTooltip>
|
|
1056
|
+
) : (
|
|
1057
|
+
'-'
|
|
1058
|
+
)}
|
|
1059
|
+
</div>
|
|
1060
|
+
)}
|
|
1061
|
+
|
|
1062
|
+
{/* Created */}
|
|
1011
1063
|
<div
|
|
1012
1064
|
className="tabular-nums min-w-0 px-4"
|
|
1013
1065
|
style={{ color: 'var(--ds-gray-900)', flex: '2 1 0%' }}
|
|
1014
1066
|
>
|
|
1015
|
-
<TimestampTooltip date={
|
|
1016
|
-
<span>{formatEventTime(
|
|
1067
|
+
<TimestampTooltip date={displayedCreatedAt}>
|
|
1068
|
+
<span>{formatEventTime(displayedCreatedAt)}</span>
|
|
1017
1069
|
</TimestampTooltip>
|
|
1018
1070
|
</div>
|
|
1019
1071
|
|
|
@@ -1192,6 +1244,7 @@ function EventListViewInner({
|
|
|
1192
1244
|
isDecrypting = false,
|
|
1193
1245
|
hasEncryptedData: hasEncryptedDataProp = false,
|
|
1194
1246
|
onExactIdSearch,
|
|
1247
|
+
showSeparateEventOccurrenceTimestamps = false,
|
|
1195
1248
|
}: EventsListProps) {
|
|
1196
1249
|
const toast = useToast();
|
|
1197
1250
|
const [internalSortOrder, setInternalSortOrder] = useState<'asc' | 'desc'>(
|
|
@@ -1229,9 +1282,7 @@ function EventListViewInner({
|
|
|
1229
1282
|
if (sourceEvents.length === 0) return [];
|
|
1230
1283
|
const dir = effectiveSortOrder === 'desc' ? -1 : 1;
|
|
1231
1284
|
return [...sourceEvents].sort(
|
|
1232
|
-
(a, b) =>
|
|
1233
|
-
dir *
|
|
1234
|
-
(new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
|
|
1285
|
+
(a, b) => dir * (getEffectiveEventTime(a) - getEffectiveEventTime(b))
|
|
1235
1286
|
);
|
|
1236
1287
|
}, [events, effectiveSortOrder, isExactSearchActive, searchResults]);
|
|
1237
1288
|
|
|
@@ -1645,8 +1696,13 @@ function EventListViewInner({
|
|
|
1645
1696
|
>
|
|
1646
1697
|
<div className="flex-shrink-0" style={{ width: GUTTER_WIDTH }} />
|
|
1647
1698
|
<div className="w-5 flex-shrink-0" />
|
|
1699
|
+
{showSeparateEventOccurrenceTimestamps && (
|
|
1700
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1701
|
+
Occurred
|
|
1702
|
+
</div>
|
|
1703
|
+
)}
|
|
1648
1704
|
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1649
|
-
|
|
1705
|
+
Created
|
|
1650
1706
|
</div>
|
|
1651
1707
|
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1652
1708
|
Event Type
|
|
@@ -1664,7 +1720,11 @@ function EventListViewInner({
|
|
|
1664
1720
|
|
|
1665
1721
|
{/* Virtualized event rows or refetching skeleton */}
|
|
1666
1722
|
{isRefetching || searchLoading ? (
|
|
1667
|
-
<RowsSkeleton
|
|
1723
|
+
<RowsSkeleton
|
|
1724
|
+
showSeparateEventOccurrenceTimestamps={
|
|
1725
|
+
showSeparateEventOccurrenceTimestamps
|
|
1726
|
+
}
|
|
1727
|
+
/>
|
|
1668
1728
|
) : sortedEvents.length === 0 ? (
|
|
1669
1729
|
<div
|
|
1670
1730
|
className="flex flex-1 items-center justify-center px-6 text-center text-sm"
|
|
@@ -1720,6 +1780,9 @@ function EventListViewInner({
|
|
|
1720
1780
|
encryptionKey={encryptionKey}
|
|
1721
1781
|
onEncryptedDataDetected={handleEncryptedDataDetected}
|
|
1722
1782
|
suppressGroupDimming={isExactSearchActive}
|
|
1783
|
+
showSeparateEventOccurrenceTimestamps={
|
|
1784
|
+
showSeparateEventOccurrenceTimestamps
|
|
1785
|
+
}
|
|
1723
1786
|
/>
|
|
1724
1787
|
);
|
|
1725
1788
|
}}
|
package/src/components/index.ts
CHANGED
|
@@ -13,18 +13,24 @@ export {
|
|
|
13
13
|
ResolveHookModal,
|
|
14
14
|
useHookActions,
|
|
15
15
|
} from './hook-actions';
|
|
16
|
+
export { TraceViewerSkeleton } from './new-trace-viewer/components/trace-viewer-skeleton';
|
|
16
17
|
export { RunTraceView } from './run-trace-view';
|
|
17
18
|
export { ConversationView } from './sidebar/conversation-view';
|
|
18
|
-
export {
|
|
19
|
-
SidebarDataProvider,
|
|
20
|
-
type SidebarDataContextValue,
|
|
21
|
-
} from './sidebar/sidebar-data-context';
|
|
22
19
|
export type {
|
|
23
20
|
SelectedSpanInfo,
|
|
24
21
|
SpanSelectionInfo,
|
|
25
22
|
} from './sidebar/entity-detail-panel';
|
|
23
|
+
export {
|
|
24
|
+
type SidebarDataContextValue,
|
|
25
|
+
SidebarDataProvider,
|
|
26
|
+
} from './sidebar/sidebar-data-context';
|
|
27
|
+
export type {
|
|
28
|
+
DetailResource,
|
|
29
|
+
FetchSpanDetail,
|
|
30
|
+
} from './sidebar/use-selected-span-detail';
|
|
26
31
|
export { type StreamChunk, StreamViewer } from './stream-viewer';
|
|
27
32
|
export type { Span, SpanEvent } from './trace-viewer/types';
|
|
33
|
+
export { NewTraceViewer } from './trace-viewer-new';
|
|
28
34
|
export {
|
|
29
35
|
DataInspector,
|
|
30
36
|
type DataInspectorProps,
|
|
@@ -38,5 +44,3 @@ export { LoadMoreButton } from './ui/load-more-button';
|
|
|
38
44
|
export { MenuDropdown, type MenuDropdownOption } from './ui/menu-dropdown';
|
|
39
45
|
export { Spinner } from './ui/spinner';
|
|
40
46
|
export { WorkflowTraceViewer } from './workflow-trace-view';
|
|
41
|
-
export { NewTraceViewer } from './trace-viewer-new';
|
|
42
|
-
export { TraceViewerSkeleton } from './new-trace-viewer/components/trace-viewer-skeleton';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Circle } from 'lucide-react';
|
|
2
2
|
import { useRef } from 'react';
|
|
3
|
-
import { cn } from '../../../lib/
|
|
3
|
+
import { cn } from '../../../lib/cn';
|
|
4
4
|
import type { Span } from '../types';
|
|
5
5
|
import { formatDurationPrecise } from '../../trace-viewer/util/timing';
|
|
6
6
|
import {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useRef } from 'react';
|
|
4
4
|
import type { ClipboardEvent, ComponentPropsWithoutRef, JSX } from 'react';
|
|
5
|
-
import { cn } from '../../../../lib/
|
|
5
|
+
import { cn } from '../../../../lib/cn';
|
|
6
6
|
import {
|
|
7
7
|
getMiddleTruncateCopyText,
|
|
8
8
|
getMiddleTruncateCopyTextFromSelectionText,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
4
4
|
import type { ReactNode } from 'react';
|
|
5
|
-
import { cn } from '../../../lib/
|
|
5
|
+
import { cn } from '../../../lib/cn';
|
|
6
6
|
import { TimestampTooltip } from '../../ui/timestamp-tooltip';
|
|
7
7
|
import type { SpanMarker } from '../utils';
|
|
8
8
|
|
|
@@ -153,7 +153,7 @@ export function OffscreenMarkerIndicator({
|
|
|
153
153
|
onReveal?.(targetMs);
|
|
154
154
|
}}
|
|
155
155
|
className={cn(
|
|
156
|
-
'pointer-events-auto absolute top-1/2 z-20 flex h-6 -translate-y-1/2 cursor-pointer items-center rounded-[0.25rem] border border-gray-alpha-400 bg-background-100
|
|
156
|
+
'pointer-events-auto absolute top-1/2 z-20 flex h-6 -translate-y-1/2 cursor-pointer items-center rounded-[0.25rem] border border-gray-alpha-400 bg-background-100 hover:bg-gray-100',
|
|
157
157
|
direction === 'right' ? 'right-0 pl-1.5' : 'left-0 pr-1.5'
|
|
158
158
|
)}
|
|
159
159
|
>
|
|
@@ -39,14 +39,14 @@ import {
|
|
|
39
39
|
TooltipTrigger,
|
|
40
40
|
} from '../ui/tooltip';
|
|
41
41
|
import EventList from './components/event-list';
|
|
42
|
-
import { TraceShortcutHelper } from './components/trace-shortcut-helper';
|
|
43
|
-
import { ROW_HEIGHT_PX, scrollRowIntoView } from './components/use-row-window';
|
|
44
42
|
import { SplitPane } from './components/split-pane';
|
|
45
43
|
import {
|
|
46
44
|
TIMELINE_PADDING_PX,
|
|
47
45
|
Timeline,
|
|
48
46
|
TimelineHeader,
|
|
49
47
|
} from './components/timeline';
|
|
48
|
+
import { TraceShortcutHelper } from './components/trace-shortcut-helper';
|
|
49
|
+
import { ROW_HEIGHT_PX, scrollRowIntoView } from './components/use-row-window';
|
|
50
50
|
import { ActiveSpanProvider, useActiveSpan } from './context';
|
|
51
51
|
import { searchSpans } from './search';
|
|
52
52
|
import type { TraceWithMeta } from './types';
|
|
@@ -822,10 +822,7 @@ function NewTraceViewerContent({
|
|
|
822
822
|
run={sidebar.run}
|
|
823
823
|
onStreamClick={sidebar.onStreamClick}
|
|
824
824
|
onRunClick={sidebar.onRunClick}
|
|
825
|
-
|
|
826
|
-
spanDetailError={sidebar.spanDetailError}
|
|
827
|
-
spanDetailLoading={sidebar.spanDetailLoading}
|
|
828
|
-
onSpanSelect={sidebar.onSpanSelect}
|
|
825
|
+
fetchSpanDetail={sidebar.fetchSpanDetail}
|
|
829
826
|
onWakeUpSleep={sidebar.onWakeUpSleep}
|
|
830
827
|
onLoadEventData={sidebar.onLoadEventData}
|
|
831
828
|
onResolveHook={sidebar.onResolveHook}
|
|
@@ -833,6 +830,9 @@ function NewTraceViewerContent({
|
|
|
833
830
|
onDecrypt={sidebar.onDecrypt}
|
|
834
831
|
isDecrypting={sidebar.isDecrypting}
|
|
835
832
|
selectedSpan={selectedSpan}
|
|
833
|
+
showSeparateEventOccurrenceTimestamps={
|
|
834
|
+
sidebar.showSeparateEventOccurrenceTimestamps
|
|
835
|
+
}
|
|
836
836
|
/>
|
|
837
837
|
</ErrorBoundary>
|
|
838
838
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type { Event, Hook,
|
|
3
|
+
import type { Event, Hook, WorkflowRun } from '@workflow/world';
|
|
4
4
|
import { AlertCircle } from 'lucide-react';
|
|
5
|
-
import type {
|
|
5
|
+
import type { FetchSpanDetail } from './sidebar/use-selected-span-detail';
|
|
6
6
|
import { WorkflowTraceViewer } from './workflow-trace-view';
|
|
7
7
|
|
|
8
8
|
interface RunTraceViewProps {
|
|
@@ -10,9 +10,7 @@ interface RunTraceViewProps {
|
|
|
10
10
|
events: Event[];
|
|
11
11
|
isLoading?: boolean;
|
|
12
12
|
error?: Error | null;
|
|
13
|
-
|
|
14
|
-
spanDetailLoading?: boolean;
|
|
15
|
-
spanDetailError?: Error | null;
|
|
13
|
+
fetchSpanDetail: FetchSpanDetail;
|
|
16
14
|
onWakeUpSleep?: (
|
|
17
15
|
runId: string,
|
|
18
16
|
correlationId: string
|
|
@@ -25,10 +23,10 @@ interface RunTraceViewProps {
|
|
|
25
23
|
onCancelRun?: (runId: string) => Promise<void>;
|
|
26
24
|
onStreamClick?: (streamId: string) => void;
|
|
27
25
|
onRunClick?: (runId: string) => void;
|
|
28
|
-
onSpanSelect?: (info: SpanSelectionInfo) => void;
|
|
29
26
|
onLoadMoreSpans?: () => void | Promise<void>;
|
|
30
27
|
hasMoreSpans?: boolean;
|
|
31
28
|
isLoadingMoreSpans?: boolean;
|
|
29
|
+
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
export function RunTraceView({
|
|
@@ -36,18 +34,16 @@ export function RunTraceView({
|
|
|
36
34
|
events,
|
|
37
35
|
isLoading,
|
|
38
36
|
error,
|
|
39
|
-
|
|
40
|
-
spanDetailLoading,
|
|
41
|
-
spanDetailError,
|
|
37
|
+
fetchSpanDetail,
|
|
42
38
|
onWakeUpSleep,
|
|
43
39
|
onResolveHook,
|
|
44
40
|
onCancelRun,
|
|
45
41
|
onStreamClick,
|
|
46
42
|
onRunClick,
|
|
47
|
-
onSpanSelect,
|
|
48
43
|
onLoadMoreSpans,
|
|
49
44
|
hasMoreSpans,
|
|
50
45
|
isLoadingMoreSpans,
|
|
46
|
+
showSeparateEventOccurrenceTimestamps,
|
|
51
47
|
}: RunTraceViewProps) {
|
|
52
48
|
if (error && !run) {
|
|
53
49
|
return (
|
|
@@ -66,18 +62,18 @@ export function RunTraceView({
|
|
|
66
62
|
events={events}
|
|
67
63
|
run={run}
|
|
68
64
|
isLoading={isLoading}
|
|
69
|
-
|
|
70
|
-
spanDetailLoading={spanDetailLoading}
|
|
71
|
-
spanDetailError={spanDetailError}
|
|
65
|
+
fetchSpanDetail={fetchSpanDetail}
|
|
72
66
|
onWakeUpSleep={onWakeUpSleep}
|
|
73
67
|
onResolveHook={onResolveHook}
|
|
74
68
|
onCancelRun={onCancelRun}
|
|
75
69
|
onStreamClick={onStreamClick}
|
|
76
70
|
onRunClick={onRunClick}
|
|
77
|
-
onSpanSelect={onSpanSelect}
|
|
78
71
|
onLoadMoreSpans={onLoadMoreSpans}
|
|
79
72
|
hasMoreSpans={hasMoreSpans}
|
|
80
73
|
isLoadingMoreSpans={isLoadingMoreSpans}
|
|
74
|
+
showSeparateEventOccurrenceTimestamps={
|
|
75
|
+
showSeparateEventOccurrenceTimestamps
|
|
76
|
+
}
|
|
81
77
|
/>
|
|
82
78
|
</div>
|
|
83
79
|
);
|
|
@@ -768,10 +768,8 @@ export const AttributePanel = ({
|
|
|
768
768
|
|
|
769
769
|
if (resource === 'sleep') return present;
|
|
770
770
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const loadingDefaults = ['input', 'output'];
|
|
774
|
-
for (const key of loadingDefaults) {
|
|
771
|
+
const loadingPlaceholders = ['input', 'output'];
|
|
772
|
+
for (const key of loadingPlaceholders) {
|
|
775
773
|
if (!present.includes(key)) {
|
|
776
774
|
present.push(key);
|
|
777
775
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type { Event, Hook,
|
|
3
|
+
import type { Event, Hook, WorkflowRun } from '@workflow/world';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { Send, Zap } from 'lucide-react';
|
|
6
|
-
import { useCallback, useEffect, useMemo,
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
import { useToast } from '../../lib/toast';
|
|
8
8
|
import { DecryptClickContext } from '../ui/data-inspector';
|
|
9
9
|
import { AttributePanel } from './attribute-panel';
|
|
@@ -11,19 +11,11 @@ import { EventsList } from './events-list';
|
|
|
11
11
|
import { ResolveHookModal } from './resolve-hook-modal';
|
|
12
12
|
import { useSidebarDataOptional } from './sidebar-data-context';
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from './span-detail
|
|
17
|
-
|
|
18
|
-
// Type guards for runtime validation of span attribute data
|
|
19
|
-
function isStep(data: unknown): data is Step {
|
|
20
|
-
return data !== null && typeof data === 'object' && 'stepId' in data;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isWorkflowRun(data: unknown): data is WorkflowRun {
|
|
24
|
-
return data !== null && typeof data === 'object' && 'runId' in data;
|
|
25
|
-
}
|
|
14
|
+
type FetchSpanDetail,
|
|
15
|
+
useSelectedSpanDetail,
|
|
16
|
+
} from './use-selected-span-detail';
|
|
26
17
|
|
|
18
|
+
// Type guard for runtime validation of span attribute data
|
|
27
19
|
function isHook(data: unknown): data is Hook {
|
|
28
20
|
return data !== null && typeof data === 'object' && 'hookId' in data;
|
|
29
21
|
}
|
|
@@ -61,10 +53,7 @@ export function EntityDetailPanel({
|
|
|
61
53
|
run,
|
|
62
54
|
onStreamClick,
|
|
63
55
|
onRunClick,
|
|
64
|
-
|
|
65
|
-
spanDetailError,
|
|
66
|
-
spanDetailLoading,
|
|
67
|
-
onSpanSelect,
|
|
56
|
+
fetchSpanDetail,
|
|
68
57
|
onWakeUpSleep,
|
|
69
58
|
onLoadEventData,
|
|
70
59
|
onResolveHook,
|
|
@@ -72,20 +61,14 @@ export function EntityDetailPanel({
|
|
|
72
61
|
onDecrypt,
|
|
73
62
|
isDecrypting = false,
|
|
74
63
|
selectedSpan,
|
|
64
|
+
showSeparateEventOccurrenceTimestamps = false,
|
|
75
65
|
}: {
|
|
76
66
|
run: WorkflowRun;
|
|
77
67
|
/** Callback when a stream reference is clicked */
|
|
78
68
|
onStreamClick?: (streamId: string) => void;
|
|
79
69
|
/** Callback when a run reference is clicked */
|
|
80
70
|
onRunClick?: (runId: string) => void;
|
|
81
|
-
|
|
82
|
-
spanDetailData: WorkflowRun | Step | Hook | Event | null;
|
|
83
|
-
/** Error from external span detail fetch. */
|
|
84
|
-
spanDetailError?: Error | null;
|
|
85
|
-
/** Loading state from external span detail fetch. */
|
|
86
|
-
spanDetailLoading?: boolean;
|
|
87
|
-
/** Callback when a span is selected. Use this to fetch data externally and pass via spanDetailData. */
|
|
88
|
-
onSpanSelect: (info: SpanSelectionInfo) => void;
|
|
71
|
+
fetchSpanDetail: FetchSpanDetail;
|
|
89
72
|
/** Callback to wake up a pending sleep call. */
|
|
90
73
|
onWakeUpSleep?: (
|
|
91
74
|
runId: string,
|
|
@@ -110,6 +93,8 @@ export function EntityDetailPanel({
|
|
|
110
93
|
isDecrypting?: boolean;
|
|
111
94
|
/** Info about the currently selected span from the trace viewer */
|
|
112
95
|
selectedSpan: SelectedSpanInfo | null;
|
|
96
|
+
/** Show occurredAt separately instead of folding it into the Created timestamp. */
|
|
97
|
+
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
113
98
|
}): React.JSX.Element | null {
|
|
114
99
|
const toast = useToast();
|
|
115
100
|
const [stoppingSleep, setStoppingSleep] = useState(false);
|
|
@@ -128,53 +113,15 @@ export function EntityDetailPanel({
|
|
|
128
113
|
const rawEvents = selectedSpan?.rawEvents;
|
|
129
114
|
const rawEventsLength = rawEvents?.length ?? 0;
|
|
130
115
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (res === 'run' && isWorkflowRun(data)) {
|
|
142
|
-
return { resource: 'run', resourceId: data.runId, runId: undefined };
|
|
143
|
-
}
|
|
144
|
-
if (res === 'hook' && isHook(data)) {
|
|
145
|
-
return { resource: 'hook', resourceId: data.hookId, runId: undefined };
|
|
146
|
-
}
|
|
147
|
-
if (res === 'sleep') {
|
|
148
|
-
const waitData = data as { runId?: string } | undefined;
|
|
149
|
-
return {
|
|
150
|
-
resource: 'sleep',
|
|
151
|
-
resourceId: selectedSpan.spanId,
|
|
152
|
-
runId: waitData?.runId,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
return { resource: undefined, resourceId: undefined, runId: undefined };
|
|
156
|
-
}, [selectedSpan, data]);
|
|
157
|
-
|
|
158
|
-
// Notify parent when span selection changes.
|
|
159
|
-
// Use a ref for the callback so the effect only fires when the actual
|
|
160
|
-
// selection values change, not when the callback identity changes due to
|
|
161
|
-
// parent re-renders from polling.
|
|
162
|
-
const onSpanSelectRef = useRef(onSpanSelect);
|
|
163
|
-
onSpanSelectRef.current = onSpanSelect;
|
|
164
|
-
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
if (
|
|
167
|
-
resource &&
|
|
168
|
-
resourceId &&
|
|
169
|
-
['run', 'step', 'hook', 'sleep'].includes(resource)
|
|
170
|
-
) {
|
|
171
|
-
onSpanSelectRef.current({
|
|
172
|
-
resource: resource as 'run' | 'step' | 'hook' | 'sleep',
|
|
173
|
-
resourceId,
|
|
174
|
-
runId,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}, [resource, resourceId, runId]);
|
|
116
|
+
const {
|
|
117
|
+
status,
|
|
118
|
+
resource,
|
|
119
|
+
resourceId,
|
|
120
|
+
displayData,
|
|
121
|
+
detail: matchedSpanDetailData,
|
|
122
|
+
error,
|
|
123
|
+
} = useSelectedSpanDetail(selectedSpan, fetchSpanDetail);
|
|
124
|
+
const loading = status === 'loading';
|
|
178
125
|
|
|
179
126
|
// Check if this sleep is still pending and can be woken up
|
|
180
127
|
const canWakeUp = useMemo(() => {
|
|
@@ -216,17 +163,6 @@ export function EntityDetailPanel({
|
|
|
216
163
|
resolvedHookIds,
|
|
217
164
|
]);
|
|
218
165
|
|
|
219
|
-
const error = spanDetailError ?? undefined;
|
|
220
|
-
const loading = spanDetailLoading ?? false;
|
|
221
|
-
|
|
222
|
-
const matchedSpanDetailData = useMemo(
|
|
223
|
-
() =>
|
|
224
|
-
spanDetailMatchesSelection(spanDetailData, resource, resourceId)
|
|
225
|
-
? spanDetailData
|
|
226
|
-
: null,
|
|
227
|
-
[spanDetailData, resource, resourceId]
|
|
228
|
-
);
|
|
229
|
-
|
|
230
166
|
// Get the hook token for resolving (prefer fetched data, then hooks array fallback)
|
|
231
167
|
const hookToken = useMemo(() => {
|
|
232
168
|
if (resource !== 'hook' || !resourceId) return undefined;
|
|
@@ -325,16 +261,6 @@ export function EntityDetailPanel({
|
|
|
325
261
|
[onResolveHook, hookToken, resolvingHook, matchedSpanDetailData, data]
|
|
326
262
|
);
|
|
327
263
|
|
|
328
|
-
const displayData = useMemo(
|
|
329
|
-
() =>
|
|
330
|
-
mergeSpanDetail(data, matchedSpanDetailData) as
|
|
331
|
-
| WorkflowRun
|
|
332
|
-
| Step
|
|
333
|
-
| Hook
|
|
334
|
-
| Event,
|
|
335
|
-
[data, matchedSpanDetailData]
|
|
336
|
-
);
|
|
337
|
-
|
|
338
264
|
const moduleSpecifier = useMemo(() => {
|
|
339
265
|
const displayRecord = displayData as Record<string, unknown>;
|
|
340
266
|
const displayStepName = displayRecord.stepName;
|
|
@@ -451,6 +377,9 @@ export function EntityDetailPanel({
|
|
|
451
377
|
onStreamClick={onStreamClick}
|
|
452
378
|
onRunClick={onRunClick}
|
|
453
379
|
encryptionKey={encryptionKey}
|
|
380
|
+
showSeparateEventOccurrenceTimestamps={
|
|
381
|
+
showSeparateEventOccurrenceTimestamps
|
|
382
|
+
}
|
|
454
383
|
/>
|
|
455
384
|
)}
|
|
456
385
|
</div>
|