@workflow/web-shared 4.1.0-beta.64 → 4.1.0-beta.66
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/event-list-view.d.ts +5 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +79 -22
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/hook-actions.d.ts.map +1 -1
- package/dist/components/hook-actions.js +2 -1
- package/dist/components/hook-actions.js.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts +3 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +55 -38
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +2 -1
- package/dist/components/sidebar/copyable-data-block.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +16 -20
- 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 +7 -5
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/stream-viewer.d.ts +3 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +23 -28
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/span-segments.js +54 -1
- package/dist/components/trace-viewer/components/span-segments.js.map +1 -1
- package/dist/components/ui/decrypt-button.d.ts +15 -0
- package/dist/components/ui/decrypt-button.d.ts.map +1 -0
- package/dist/components/ui/decrypt-button.js +12 -0
- package/dist/components/ui/decrypt-button.js.map +1 -0
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +2 -1
- package/dist/components/ui/error-stack-block.js.map +1 -1
- package/dist/components/ui/load-more-button.d.ts +13 -0
- package/dist/components/ui/load-more-button.d.ts.map +1 -0
- package/dist/components/ui/load-more-button.js +12 -0
- package/dist/components/ui/load-more-button.js.map +1 -0
- package/dist/components/ui/menu-dropdown.d.ts.map +1 -1
- package/dist/components/ui/menu-dropdown.js +2 -6
- package/dist/components/ui/menu-dropdown.js.map +1 -1
- package/dist/components/ui/spinner.d.ts +9 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +57 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/timestamp-tooltip.d.ts +6 -0
- package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -0
- package/dist/components/ui/timestamp-tooltip.js +200 -0
- package/dist/components/ui/timestamp-tooltip.js.map +1 -0
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +12 -4
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +10 -7
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/toast.d.ts +25 -0
- package/dist/lib/toast.d.ts.map +1 -0
- package/dist/lib/toast.js +24 -0
- package/dist/lib/toast.js.map +1 -0
- package/package.json +6 -4
- package/src/components/event-list-view.tsx +241 -111
- package/src/components/hook-actions.tsx +2 -1
- package/src/components/index.ts +3 -0
- package/src/components/sidebar/attribute-panel.tsx +60 -33
- package/src/components/sidebar/copyable-data-block.tsx +2 -1
- package/src/components/sidebar/entity-detail-panel.tsx +22 -30
- package/src/components/sidebar/events-list.tsx +14 -13
- package/src/components/stream-viewer.tsx +52 -63
- package/src/components/trace-viewer/components/span-segments.ts +70 -1
- package/src/components/ui/decrypt-button.tsx +69 -0
- package/src/components/ui/error-stack-block.tsx +2 -1
- package/src/components/ui/load-more-button.tsx +38 -0
- package/src/components/ui/menu-dropdown.tsx +3 -6
- package/src/components/ui/spinner.tsx +76 -0
- package/src/components/ui/timestamp-tooltip.tsx +326 -0
- package/src/components/workflow-trace-view.tsx +14 -20
- package/src/components/workflow-traces/trace-span-construction.ts +12 -7
- package/src/index.ts +2 -0
- package/src/lib/toast.tsx +42 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow/web-shared",
|
|
3
3
|
"description": "Shared components for Workflow Observability UI",
|
|
4
|
-
"version": "4.1.0-beta.
|
|
4
|
+
"version": "4.1.0-beta.66",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -52,23 +52,25 @@
|
|
|
52
52
|
"streamdown": "2.3.0",
|
|
53
53
|
"tailwind-merge": "3.5.0",
|
|
54
54
|
"tailwindcss": "4",
|
|
55
|
-
"@workflow/core": "4.2.0-beta.
|
|
55
|
+
"@workflow/core": "4.2.0-beta.71",
|
|
56
56
|
"@workflow/utils": "4.1.0-beta.13",
|
|
57
|
-
"@workflow/world": "4.1.0-beta.
|
|
57
|
+
"@workflow/world": "4.1.0-beta.13"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@biomejs/biome": "^2.4.4",
|
|
61
61
|
"@types/node": "22.19.0",
|
|
62
62
|
"@types/react": "19",
|
|
63
63
|
"@types/react-dom": "19",
|
|
64
|
-
"ai": "
|
|
64
|
+
"ai": "6.0.116",
|
|
65
65
|
"typescript": "^5.9.3",
|
|
66
|
+
"vitest": "^4.0.18",
|
|
66
67
|
"@workflow/tsconfig": "4.0.1-beta.0"
|
|
67
68
|
},
|
|
68
69
|
"scripts": {
|
|
69
70
|
"build": "tsc && cp -r src/components/trace-viewer/*.css dist/components/trace-viewer/ && cp src/styles.css dist/styles.css",
|
|
70
71
|
"dev": "tsc --watch",
|
|
71
72
|
"clean": "tsc --build --clean && rm -r dist ||:",
|
|
73
|
+
"test": "vitest run",
|
|
72
74
|
"typecheck": "tsc --noEmit",
|
|
73
75
|
"lint": "biome check",
|
|
74
76
|
"format": "biome format --write"
|
|
@@ -6,14 +6,18 @@ import { Check, ChevronRight, Copy } from 'lucide-react';
|
|
|
6
6
|
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
|
|
9
|
+
import { isEncryptedMarker } from '../lib/hydration';
|
|
10
|
+
import { DecryptButton } from './ui/decrypt-button';
|
|
9
11
|
import { formatDuration } from '../lib/utils';
|
|
10
12
|
import { DataInspector } from './ui/data-inspector';
|
|
11
13
|
import {
|
|
12
14
|
ErrorStackBlock,
|
|
13
15
|
isStructuredErrorWithStack,
|
|
14
16
|
} from './ui/error-stack-block';
|
|
17
|
+
import { LoadMoreButton } from './ui/load-more-button';
|
|
15
18
|
import { MenuDropdown } from './ui/menu-dropdown';
|
|
16
19
|
import { Skeleton } from './ui/skeleton';
|
|
20
|
+
import { TimestampTooltip } from './ui/timestamp-tooltip';
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Event types whose eventData contains an error field with a StructuredError.
|
|
@@ -208,6 +212,15 @@ function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
|
|
|
208
212
|
return durations;
|
|
209
213
|
}
|
|
210
214
|
|
|
215
|
+
/** Check if a loaded eventData object contains any encrypted marker values. */
|
|
216
|
+
function hasEncryptedValues(data: unknown): boolean {
|
|
217
|
+
if (!data || typeof data !== 'object') return false;
|
|
218
|
+
for (const val of Object.values(data as Record<string, unknown>)) {
|
|
219
|
+
if (isEncryptedMarker(val)) return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
211
224
|
function isRunLevel(eventType: string): boolean {
|
|
212
225
|
return (
|
|
213
226
|
eventType === 'run_created' ||
|
|
@@ -596,6 +609,76 @@ const SORT_OPTIONS = [
|
|
|
596
609
|
{ value: 'asc' as const, label: 'Oldest' },
|
|
597
610
|
];
|
|
598
611
|
|
|
612
|
+
function RowsSkeleton() {
|
|
613
|
+
return (
|
|
614
|
+
<div className="flex-1 overflow-hidden">
|
|
615
|
+
{Array.from({ length: 16 }, (_, i) => (
|
|
616
|
+
<div key={i} className="flex items-center gap-0" style={{ height: 40 }}>
|
|
617
|
+
{/* Gutter area */}
|
|
618
|
+
<div
|
|
619
|
+
className="relative flex-shrink-0 self-stretch flex items-center"
|
|
620
|
+
style={{ width: GUTTER_WIDTH }}
|
|
621
|
+
>
|
|
622
|
+
{/* Vertical line skeleton */}
|
|
623
|
+
<div
|
|
624
|
+
style={{
|
|
625
|
+
position: 'absolute',
|
|
626
|
+
left: 8,
|
|
627
|
+
top: i === 0 ? '50%' : 0,
|
|
628
|
+
bottom: 0,
|
|
629
|
+
width: 2,
|
|
630
|
+
}}
|
|
631
|
+
>
|
|
632
|
+
<Skeleton className="w-full h-full" style={{ borderRadius: 1 }} />
|
|
633
|
+
</div>
|
|
634
|
+
{/* Dot skeleton */}
|
|
635
|
+
<Skeleton
|
|
636
|
+
className="flex-shrink-0"
|
|
637
|
+
style={{
|
|
638
|
+
width: i % 4 === 0 ? 8 : 6,
|
|
639
|
+
height: i % 4 === 0 ? 8 : 6,
|
|
640
|
+
borderRadius: '50%',
|
|
641
|
+
marginLeft: i % 4 === 0 ? 5 : 6,
|
|
642
|
+
}}
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
{/* Chevron placeholder */}
|
|
646
|
+
<div className="w-5 flex-shrink-0 flex items-center justify-center">
|
|
647
|
+
<Skeleton className="w-5 h-5" style={{ borderRadius: 4 }} />
|
|
648
|
+
</div>
|
|
649
|
+
{/* Time */}
|
|
650
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
651
|
+
<Skeleton className="h-3" style={{ width: '70%' }} />
|
|
652
|
+
</div>
|
|
653
|
+
{/* Event Type */}
|
|
654
|
+
<div
|
|
655
|
+
className="min-w-0 px-4 flex items-center gap-1.5"
|
|
656
|
+
style={{ flex: '2 1 0%' }}
|
|
657
|
+
>
|
|
658
|
+
<Skeleton
|
|
659
|
+
className="flex-shrink-0"
|
|
660
|
+
style={{ width: 6, height: 6, borderRadius: '50%' }}
|
|
661
|
+
/>
|
|
662
|
+
<Skeleton className="h-3" style={{ width: '60%' }} />
|
|
663
|
+
</div>
|
|
664
|
+
{/* Name */}
|
|
665
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
666
|
+
<Skeleton className="h-3" style={{ width: '50%' }} />
|
|
667
|
+
</div>
|
|
668
|
+
{/* Correlation ID */}
|
|
669
|
+
<div className="min-w-0 px-4" style={{ flex: '3 1 0%' }}>
|
|
670
|
+
<Skeleton className="h-3" style={{ width: '75%' }} />
|
|
671
|
+
</div>
|
|
672
|
+
{/* Event ID */}
|
|
673
|
+
<div className="min-w-0 px-4" style={{ flex: '3 1 0%' }}>
|
|
674
|
+
<Skeleton className="h-3" style={{ width: '75%' }} />
|
|
675
|
+
</div>
|
|
676
|
+
</div>
|
|
677
|
+
))}
|
|
678
|
+
</div>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
599
682
|
// ──────────────────────────────────────────────────────────────────────────
|
|
600
683
|
// Event row
|
|
601
684
|
// ──────────────────────────────────────────────────────────────────────────
|
|
@@ -616,6 +699,10 @@ interface EventsListProps {
|
|
|
616
699
|
/** Called when the user changes sort order. When provided, the sort dropdown is shown
|
|
617
700
|
* and the parent is expected to refetch from the API with the new order. */
|
|
618
701
|
onSortOrderChange?: (order: 'asc' | 'desc') => void;
|
|
702
|
+
/** Called when the user clicks the Decrypt button. */
|
|
703
|
+
onDecrypt?: () => void;
|
|
704
|
+
/** Whether the encryption key is currently being fetched. */
|
|
705
|
+
isDecrypting?: boolean;
|
|
619
706
|
}
|
|
620
707
|
|
|
621
708
|
function EventRow({
|
|
@@ -637,6 +724,7 @@ function EventRow({
|
|
|
637
724
|
cachedEventData,
|
|
638
725
|
onCacheEventData,
|
|
639
726
|
encryptionKey,
|
|
727
|
+
onEncryptedDataDetected,
|
|
640
728
|
}: {
|
|
641
729
|
event: Event;
|
|
642
730
|
index: number;
|
|
@@ -656,6 +744,7 @@ function EventRow({
|
|
|
656
744
|
cachedEventData: unknown | null;
|
|
657
745
|
onCacheEventData: (eventId: string, data: unknown) => void;
|
|
658
746
|
encryptionKey?: Uint8Array;
|
|
747
|
+
onEncryptedDataDetected?: () => void;
|
|
659
748
|
}) {
|
|
660
749
|
const [isLoading, setIsLoading] = useState(false);
|
|
661
750
|
const [loadedEventData, setLoadedEventData] = useState<unknown | null>(
|
|
@@ -666,6 +755,18 @@ function EventRow({
|
|
|
666
755
|
cachedEventData !== null
|
|
667
756
|
);
|
|
668
757
|
|
|
758
|
+
// Notify parent if cached data has encrypted markers on mount
|
|
759
|
+
useEffect(() => {
|
|
760
|
+
if (
|
|
761
|
+
cachedEventData !== null &&
|
|
762
|
+
!encryptionKey &&
|
|
763
|
+
hasEncryptedValues(cachedEventData)
|
|
764
|
+
) {
|
|
765
|
+
onEncryptedDataDetected?.();
|
|
766
|
+
}
|
|
767
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
768
|
+
}, []);
|
|
769
|
+
|
|
669
770
|
const rowGroupKey = isRunLevel(event.eventType)
|
|
670
771
|
? '__run__'
|
|
671
772
|
: (event.correlationId ?? undefined);
|
|
@@ -714,6 +815,9 @@ function EventRow({
|
|
|
714
815
|
if (data !== null && data !== undefined) {
|
|
715
816
|
setLoadedEventData(data);
|
|
716
817
|
onCacheEventData(event.eventId, data);
|
|
818
|
+
if (!encryptionKey && hasEncryptedValues(data)) {
|
|
819
|
+
onEncryptedDataDetected?.();
|
|
820
|
+
}
|
|
717
821
|
}
|
|
718
822
|
} catch (err) {
|
|
719
823
|
setLoadError(
|
|
@@ -729,6 +833,8 @@ function EventRow({
|
|
|
729
833
|
hasExistingEventData,
|
|
730
834
|
onLoadEventData,
|
|
731
835
|
onCacheEventData,
|
|
836
|
+
encryptionKey,
|
|
837
|
+
onEncryptedDataDetected,
|
|
732
838
|
]);
|
|
733
839
|
|
|
734
840
|
// Auto-load event data when remounting in expanded state without cached data
|
|
@@ -846,7 +952,9 @@ function EventRow({
|
|
|
846
952
|
className="tabular-nums min-w-0 px-4"
|
|
847
953
|
style={{ color: 'var(--ds-gray-900)', flex: '2 1 0%' }}
|
|
848
954
|
>
|
|
849
|
-
{
|
|
955
|
+
<TimestampTooltip date={createdAt}>
|
|
956
|
+
<span>{formatEventTime(createdAt)}</span>
|
|
957
|
+
</TimestampTooltip>
|
|
850
958
|
</div>
|
|
851
959
|
|
|
852
960
|
{/* Event Type */}
|
|
@@ -1020,6 +1128,8 @@ export function EventListView({
|
|
|
1020
1128
|
isLoading = false,
|
|
1021
1129
|
sortOrder: sortOrderProp,
|
|
1022
1130
|
onSortOrderChange,
|
|
1131
|
+
onDecrypt,
|
|
1132
|
+
isDecrypting = false,
|
|
1023
1133
|
}: EventsListProps) {
|
|
1024
1134
|
const [internalSortOrder, setInternalSortOrder] = useState<'asc' | 'desc'>(
|
|
1025
1135
|
'asc'
|
|
@@ -1046,6 +1156,26 @@ export function EventListView({
|
|
|
1046
1156
|
);
|
|
1047
1157
|
}, [events, effectiveSortOrder]);
|
|
1048
1158
|
|
|
1159
|
+
// Detect encrypted fields across all loaded events (inline eventData).
|
|
1160
|
+
const hasEncryptedInlineData = useMemo(() => {
|
|
1161
|
+
if (!events) return false;
|
|
1162
|
+
for (const event of events) {
|
|
1163
|
+
const ed = (event as Record<string, unknown>).eventData;
|
|
1164
|
+
if (hasEncryptedValues(ed)) return true;
|
|
1165
|
+
}
|
|
1166
|
+
return false;
|
|
1167
|
+
}, [events]);
|
|
1168
|
+
|
|
1169
|
+
// Tracks whether any expanded row's lazy-loaded data contained encrypted markers.
|
|
1170
|
+
// Set to true by EventRow via onEncryptedDataDetected; never reset (sticky).
|
|
1171
|
+
const [foundEncryptedInLazyData, setFoundEncryptedInLazyData] =
|
|
1172
|
+
useState(false);
|
|
1173
|
+
const handleEncryptedDataDetected = useCallback(() => {
|
|
1174
|
+
setFoundEncryptedInLazyData(true);
|
|
1175
|
+
}, []);
|
|
1176
|
+
|
|
1177
|
+
const hasEncryptedData = hasEncryptedInlineData || foundEncryptedInLazyData;
|
|
1178
|
+
|
|
1049
1179
|
const { correlationNameMap, workflowName } = useMemo(
|
|
1050
1180
|
() => buildNameMaps(events ?? null, run ?? null),
|
|
1051
1181
|
[events, run]
|
|
@@ -1201,52 +1331,51 @@ export function EventListView({
|
|
|
1201
1331
|
}
|
|
1202
1332
|
}, [searchQuery, searchIndex]);
|
|
1203
1333
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1334
|
+
// Track whether we've ever had events to distinguish initial load from refetch
|
|
1335
|
+
const hasHadEventsRef = useRef(false);
|
|
1336
|
+
if (sortedEvents.length > 0) {
|
|
1337
|
+
hasHadEventsRef.current = true;
|
|
1338
|
+
}
|
|
1339
|
+
const isInitialLoad = isLoading && !hasHadEventsRef.current;
|
|
1340
|
+
const isRefetching =
|
|
1341
|
+
isLoading && hasHadEventsRef.current && sortedEvents.length === 0;
|
|
1342
|
+
|
|
1343
|
+
if (isInitialLoad) {
|
|
1344
|
+
return (
|
|
1345
|
+
<div className="h-full flex flex-col overflow-hidden">
|
|
1346
|
+
{/* Skeleton search bar */}
|
|
1347
|
+
<div style={{ padding: 6 }}>
|
|
1348
|
+
<Skeleton style={{ height: 40, borderRadius: 6 }} />
|
|
1349
|
+
</div>
|
|
1350
|
+
{/* Skeleton header */}
|
|
1351
|
+
<div
|
|
1352
|
+
className="flex items-center gap-0 h-10 border-b flex-shrink-0"
|
|
1353
|
+
style={{ borderColor: 'var(--ds-gray-alpha-200)' }}
|
|
1354
|
+
>
|
|
1355
|
+
<div className="flex-shrink-0" style={{ width: GUTTER_WIDTH }} />
|
|
1356
|
+
<div className="w-5 flex-shrink-0" />
|
|
1357
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1358
|
+
<Skeleton className="h-3" style={{ width: 40 }} />
|
|
1211
1359
|
</div>
|
|
1212
|
-
{
|
|
1213
|
-
|
|
1214
|
-
className="flex items-center gap-0 h-10 border-b flex-shrink-0 px-4"
|
|
1215
|
-
style={{ borderColor: 'var(--ds-gray-alpha-200)' }}
|
|
1216
|
-
>
|
|
1217
|
-
<Skeleton className="h-3" style={{ width: 60 }} />
|
|
1218
|
-
<div style={{ flex: 1 }} />
|
|
1219
|
-
<Skeleton className="h-3" style={{ width: 80 }} />
|
|
1220
|
-
<div style={{ flex: 1 }} />
|
|
1221
|
-
<Skeleton className="h-3" style={{ width: 50 }} />
|
|
1222
|
-
<div style={{ flex: 1 }} />
|
|
1223
|
-
<Skeleton className="h-3" style={{ width: 90 }} />
|
|
1224
|
-
<div style={{ flex: 1 }} />
|
|
1225
|
-
<Skeleton className="h-3" style={{ width: 70 }} />
|
|
1360
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1361
|
+
<Skeleton className="h-3" style={{ width: 72 }} />
|
|
1226
1362
|
</div>
|
|
1227
|
-
{
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
<Skeleton
|
|
1236
|
-
className="h-2 w-2 flex-shrink-0"
|
|
1237
|
-
style={{ borderRadius: '50%' }}
|
|
1238
|
-
/>
|
|
1239
|
-
<Skeleton className="h-3" style={{ width: 90 }} />
|
|
1240
|
-
<Skeleton className="h-3" style={{ width: 100 }} />
|
|
1241
|
-
<Skeleton className="h-3" style={{ width: 80 }} />
|
|
1242
|
-
<Skeleton className="h-3 flex-1" />
|
|
1243
|
-
<Skeleton className="h-3 flex-1" />
|
|
1244
|
-
</div>
|
|
1245
|
-
))}
|
|
1363
|
+
<div className="min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1364
|
+
<Skeleton className="h-3" style={{ width: 44 }} />
|
|
1365
|
+
</div>
|
|
1366
|
+
<div className="min-w-0 px-4" style={{ flex: '3 1 0%' }}>
|
|
1367
|
+
<Skeleton className="h-3" style={{ width: 92 }} />
|
|
1368
|
+
</div>
|
|
1369
|
+
<div className="min-w-0 px-4" style={{ flex: '3 1 0%' }}>
|
|
1370
|
+
<Skeleton className="h-3" style={{ width: 60 }} />
|
|
1246
1371
|
</div>
|
|
1247
1372
|
</div>
|
|
1248
|
-
|
|
1249
|
-
|
|
1373
|
+
<RowsSkeleton />
|
|
1374
|
+
</div>
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (!isLoading && (!events || events.length === 0)) {
|
|
1250
1379
|
return (
|
|
1251
1380
|
<div
|
|
1252
1381
|
className="flex items-center justify-center h-full text-sm"
|
|
@@ -1339,6 +1468,13 @@ export function EventListView({
|
|
|
1339
1468
|
value={effectiveSortOrder}
|
|
1340
1469
|
onChange={handleSortOrderChange}
|
|
1341
1470
|
/>
|
|
1471
|
+
{(hasEncryptedData || encryptionKey) && onDecrypt && (
|
|
1472
|
+
<DecryptButton
|
|
1473
|
+
decrypted={!!encryptionKey}
|
|
1474
|
+
loading={isDecrypting}
|
|
1475
|
+
onClick={onDecrypt}
|
|
1476
|
+
/>
|
|
1477
|
+
)}
|
|
1342
1478
|
</div>
|
|
1343
1479
|
|
|
1344
1480
|
{/* Header */}
|
|
@@ -1369,82 +1505,76 @@ export function EventListView({
|
|
|
1369
1505
|
</div>
|
|
1370
1506
|
</div>
|
|
1371
1507
|
|
|
1372
|
-
{/* Virtualized event rows */}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
disabled={isLoadingMoreEvents}
|
|
1419
|
-
className="h-8 px-3 text-xs rounded-md border transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
|
1420
|
-
style={{
|
|
1421
|
-
borderColor: 'var(--ds-gray-alpha-400)',
|
|
1422
|
-
color: 'var(--ds-gray-900)',
|
|
1423
|
-
backgroundColor: 'var(--ds-background-100)',
|
|
1424
|
-
}}
|
|
1425
|
-
>
|
|
1426
|
-
{isLoadingMoreEvents
|
|
1427
|
-
? 'Loading more events...'
|
|
1428
|
-
: 'Load more'}
|
|
1429
|
-
</button>
|
|
1430
|
-
</div>
|
|
1431
|
-
)
|
|
1432
|
-
: undefined,
|
|
1433
|
-
}}
|
|
1434
|
-
style={{ flex: 1, minHeight: 0 }}
|
|
1435
|
-
/>
|
|
1508
|
+
{/* Virtualized event rows or refetching skeleton */}
|
|
1509
|
+
{isRefetching ? (
|
|
1510
|
+
<RowsSkeleton />
|
|
1511
|
+
) : (
|
|
1512
|
+
<Virtuoso
|
|
1513
|
+
ref={virtuosoRef}
|
|
1514
|
+
totalCount={sortedEvents.length}
|
|
1515
|
+
overscan={20}
|
|
1516
|
+
defaultItemHeight={40}
|
|
1517
|
+
endReached={() => {
|
|
1518
|
+
if (!hasMoreEvents || isLoadingMoreEvents) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
void onLoadMoreEvents?.();
|
|
1522
|
+
}}
|
|
1523
|
+
itemContent={(index: number) => {
|
|
1524
|
+
const ev = sortedEvents[index];
|
|
1525
|
+
return (
|
|
1526
|
+
<EventRow
|
|
1527
|
+
event={ev}
|
|
1528
|
+
index={index}
|
|
1529
|
+
isFirst={index === 0}
|
|
1530
|
+
isLast={index === sortedEvents.length - 1}
|
|
1531
|
+
isExpanded={expandedEventIds.has(ev.eventId)}
|
|
1532
|
+
onToggleExpand={toggleEventExpanded}
|
|
1533
|
+
activeGroupKey={activeGroupKey}
|
|
1534
|
+
selectedGroupKey={selectedGroupKey}
|
|
1535
|
+
selectedGroupRange={selectedGroupRange}
|
|
1536
|
+
correlationNameMap={correlationNameMap}
|
|
1537
|
+
workflowName={workflowName}
|
|
1538
|
+
durationMap={durationMap}
|
|
1539
|
+
onSelectGroup={onSelectGroup}
|
|
1540
|
+
onHoverGroup={onHoverGroup}
|
|
1541
|
+
onLoadEventData={onLoadEventData}
|
|
1542
|
+
cachedEventData={
|
|
1543
|
+
eventDataCacheRef.current.get(ev.eventId) ?? null
|
|
1544
|
+
}
|
|
1545
|
+
onCacheEventData={cacheEventData}
|
|
1546
|
+
encryptionKey={encryptionKey}
|
|
1547
|
+
onEncryptedDataDetected={handleEncryptedDataDetected}
|
|
1548
|
+
/>
|
|
1549
|
+
);
|
|
1550
|
+
}}
|
|
1551
|
+
style={{ flex: 1, minHeight: 0 }}
|
|
1552
|
+
/>
|
|
1553
|
+
)}
|
|
1436
1554
|
|
|
1437
|
-
{/* Fixed footer —
|
|
1555
|
+
{/* Fixed footer — count + load more */}
|
|
1438
1556
|
<div
|
|
1439
|
-
className="flex-shrink-0 border-t
|
|
1557
|
+
className="relative flex-shrink-0 flex items-center h-10 border-t px-4 text-xs"
|
|
1440
1558
|
style={{
|
|
1441
1559
|
borderColor: 'var(--ds-gray-alpha-200)',
|
|
1442
1560
|
color: 'var(--ds-gray-900)',
|
|
1443
1561
|
backgroundColor: 'var(--ds-background-100)',
|
|
1444
1562
|
}}
|
|
1445
1563
|
>
|
|
1446
|
-
|
|
1447
|
-
|
|
1564
|
+
<span>
|
|
1565
|
+
{sortedEvents.length} event
|
|
1566
|
+
{sortedEvents.length !== 1 ? 's' : ''} loaded
|
|
1567
|
+
</span>
|
|
1568
|
+
{hasMoreEvents && (
|
|
1569
|
+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
|
1570
|
+
<div className="pointer-events-auto">
|
|
1571
|
+
<LoadMoreButton
|
|
1572
|
+
loading={isLoadingMoreEvents}
|
|
1573
|
+
onClick={() => void onLoadMoreEvents?.()}
|
|
1574
|
+
/>
|
|
1575
|
+
</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
)}
|
|
1448
1578
|
</div>
|
|
1449
1579
|
</div>
|
|
1450
1580
|
);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Hook, WorkflowRunStatus } from '@workflow/world';
|
|
4
4
|
import { Send } from 'lucide-react';
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import { useToast } from '../lib/toast';
|
|
7
7
|
import { ResolveHookModal } from './sidebar/resolve-hook-modal';
|
|
8
8
|
|
|
9
9
|
// ============================================================================
|
|
@@ -45,6 +45,7 @@ export function useHookActions({
|
|
|
45
45
|
onResolve,
|
|
46
46
|
callbacks,
|
|
47
47
|
}: UseHookActionsOptions): UseHookActionsReturn {
|
|
48
|
+
const toast = useToast();
|
|
48
49
|
const [isResolving, setIsResolving] = useState(false);
|
|
49
50
|
const [selectedHook, setSelectedHook] = useState<Hook | null>(null);
|
|
50
51
|
|
package/src/components/index.ts
CHANGED
|
@@ -22,5 +22,8 @@ export type {
|
|
|
22
22
|
export { type StreamChunk, StreamViewer } from './stream-viewer';
|
|
23
23
|
export type { Span, SpanEvent } from './trace-viewer/types';
|
|
24
24
|
export { DataInspector, type DataInspectorProps } from './ui/data-inspector';
|
|
25
|
+
export { DecryptButton } from './ui/decrypt-button';
|
|
26
|
+
export { LoadMoreButton } from './ui/load-more-button';
|
|
25
27
|
export { MenuDropdown, type MenuDropdownOption } from './ui/menu-dropdown';
|
|
28
|
+
export { Spinner } from './ui/spinner';
|
|
26
29
|
export { WorkflowTraceViewer } from './workflow-trace-view';
|