@workflow/web-shared 4.1.5 → 4.1.7
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 +16 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +171 -77
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/workflow-trace-view.js +1 -1
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/index.d.ts +1 -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/exact-event-search-id.d.ts +25 -0
- package/dist/lib/exact-event-search-id.d.ts.map +1 -0
- package/dist/lib/exact-event-search-id.js +39 -0
- package/dist/lib/exact-event-search-id.js.map +1 -0
- package/package.json +4 -4
- package/src/components/event-list-view.tsx +246 -95
- package/src/components/workflow-trace-view.tsx +1 -1
- package/src/components/workflow-traces/trace-span-construction.ts +1 -1
- package/src/index.ts +7 -0
- package/src/lib/exact-event-search-id.ts +67 -0
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { Event, WorkflowRun } from '@workflow/world';
|
|
2
|
+
import { type ExactIdSearchResult, type ExactWorkflowSearchIdKind } from '../lib/exact-event-search-id';
|
|
3
|
+
export interface DurationInfo {
|
|
4
|
+
/** Time from created → started (ms) */
|
|
5
|
+
queued?: number;
|
|
6
|
+
/** Time from started → completed/failed/cancelled (ms) */
|
|
7
|
+
ran?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build a map from correlationId → duration info by diffing
|
|
11
|
+
* created ↔ started (queued) and started ↔ completed/failed/cancelled (ran).
|
|
12
|
+
* Also computes run-level durations under the key '__run__'.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildDurationMap(events: Event[]): Map<string, DurationInfo>;
|
|
2
15
|
interface EventsListProps {
|
|
3
16
|
events: Event[] | null;
|
|
4
17
|
run?: WorkflowRun | null;
|
|
@@ -21,7 +34,9 @@ interface EventsListProps {
|
|
|
21
34
|
isDecrypting?: boolean;
|
|
22
35
|
/** Run-level hint: the run contains encrypted data (from probe). */
|
|
23
36
|
hasEncryptedData?: boolean;
|
|
37
|
+
/** Fetch events for an exact correlation or event ID. */
|
|
38
|
+
onExactIdSearch?: (id: string, kind: ExactWorkflowSearchIdKind, signal?: AbortSignal) => Promise<ExactIdSearchResult>;
|
|
24
39
|
}
|
|
25
|
-
export declare function EventListView({ events, run, onLoadEventData, hasMoreEvents, isLoadingMoreEvents, onLoadMoreEvents, encryptionKey, isLoading, sortOrder: sortOrderProp, onSortOrderChange, onDecrypt, isDecrypting, hasEncryptedData: hasEncryptedDataProp, }: EventsListProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function EventListView({ events, run, onLoadEventData, hasMoreEvents, isLoadingMoreEvents, onLoadMoreEvents, encryptionKey, isLoading, sortOrder: sortOrderProp, onSortOrderChange, onDecrypt, isDecrypting, hasEncryptedData: hasEncryptedDataProp, onExactIdSearch, }: EventsListProps): import("react/jsx-runtime").JSX.Element;
|
|
26
41
|
export {};
|
|
27
42
|
//# sourceMappingURL=event-list-view.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-list-view.d.ts","sourceRoot":"","sources":["../../src/components/event-list-view.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-list-view.d.ts","sourceRoot":"","sources":["../../src/components/event-list-view.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAU1D,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,8BAA8B,CAAC;AAyItC,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAwE3E;AAydD,UAAU,eAAe;IACvB,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACvB,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9C,6FAA6F;IAC7F,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gDAAgD;IAChD,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC3B;iFAC6E;IAC7E,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,KAAK,IAAI,CAAC;IACpD,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,eAAe,CAAC,EAAE,CAChB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,EAC/B,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACnC;AAuaD,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,GAAG,EACH,eAAe,EACf,aAAqB,EACrB,mBAA2B,EAC3B,gBAAgB,EAChB,aAAa,EACb,SAAiB,EACjB,SAAS,EAAE,aAAa,EACxB,iBAAiB,EACjB,SAAS,EACT,YAAoB,EACpB,gBAAgB,EAAE,oBAA4B,EAC9C,eAAe,GAChB,EAAE,eAAe,2CAujBjB"}
|
|
@@ -5,8 +5,10 @@ import { Check, ChevronRight, Copy } from 'lucide-react';
|
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { Virtuoso } from 'react-virtuoso';
|
|
7
7
|
import { isEncryptedMarker } from '../lib/hydration';
|
|
8
|
+
import { parseExactWorkflowSearchId, looksLikeWorkflowIdSearchInput, } from '../lib/exact-event-search-id';
|
|
8
9
|
import { DecryptButton } from './ui/decrypt-button';
|
|
9
10
|
import { formatDuration } from '../lib/utils';
|
|
11
|
+
import { useToast } from '../lib/toast';
|
|
10
12
|
import { DataInspector, DecryptClickContext } from './ui/data-inspector';
|
|
11
13
|
import { ErrorStackBlock, isStructuredErrorWithStack, } from './ui/error-stack-block';
|
|
12
14
|
import { LoadMoreButton } from './ui/load-more-button';
|
|
@@ -112,33 +114,46 @@ function buildNameMaps(events, run) {
|
|
|
112
114
|
* created ↔ started (queued) and started ↔ completed/failed/cancelled (ran).
|
|
113
115
|
* Also computes run-level durations under the key '__run__'.
|
|
114
116
|
*/
|
|
115
|
-
function buildDurationMap(events) {
|
|
117
|
+
export function buildDurationMap(events) {
|
|
118
|
+
// Process events in chronological order so the result doesn't depend on
|
|
119
|
+
// the caller's sort direction. Retried steps emit multiple `step_started`
|
|
120
|
+
// events for the same correlationId; the queued duration must be measured
|
|
121
|
+
// against the first one, not the last.
|
|
122
|
+
const chronological = [...events].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
116
123
|
const createdTimes = new Map();
|
|
124
|
+
const firstStartedTimes = new Map();
|
|
117
125
|
const startedTimes = new Map();
|
|
118
126
|
const durations = new Map();
|
|
119
|
-
for (const event of
|
|
127
|
+
for (const event of chronological) {
|
|
120
128
|
const ts = new Date(event.createdAt).getTime();
|
|
121
129
|
const key = event.correlationId ?? '__run__';
|
|
122
130
|
const type = event.eventType;
|
|
123
131
|
// Track created times (first event for each correlation)
|
|
124
132
|
if (type === 'step_created' || type === 'run_created') {
|
|
125
|
-
createdTimes.
|
|
133
|
+
if (!createdTimes.has(key)) {
|
|
134
|
+
createdTimes.set(key, ts);
|
|
135
|
+
}
|
|
126
136
|
}
|
|
127
137
|
// Track started times & compute queued duration
|
|
128
138
|
if (type === 'step_started' ||
|
|
129
139
|
type === 'run_started' ||
|
|
130
140
|
type === 'workflow_started') {
|
|
131
141
|
startedTimes.set(key, ts);
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
// The queued duration is anchored on the first start event only —
|
|
143
|
+
// subsequent step_started events come from retries.
|
|
144
|
+
if (!firstStartedTimes.has(key)) {
|
|
145
|
+
firstStartedTimes.set(key, ts);
|
|
146
|
+
// If no explicit created event was seen, use the started time as created
|
|
147
|
+
if (!createdTimes.has(key)) {
|
|
148
|
+
createdTimes.set(key, ts);
|
|
149
|
+
}
|
|
150
|
+
const createdAt = createdTimes.get(key);
|
|
151
|
+
const info = durations.get(key) ?? {};
|
|
152
|
+
if (createdAt !== undefined) {
|
|
153
|
+
info.queued = ts - createdAt;
|
|
154
|
+
}
|
|
155
|
+
durations.set(key, info);
|
|
140
156
|
}
|
|
141
|
-
durations.set(key, info);
|
|
142
157
|
}
|
|
143
158
|
// Compute ran duration on terminal events
|
|
144
159
|
if (type === 'step_completed' ||
|
|
@@ -390,7 +405,7 @@ function RowsSkeleton() {
|
|
|
390
405
|
marginLeft: i % 4 === 0 ? 5 : 6,
|
|
391
406
|
} })] }), _jsx("div", { className: "w-5 flex-shrink-0 flex items-center justify-center", children: _jsx(Skeleton, { className: "w-5 h-5", style: { borderRadius: 4 } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: '70%' } }) }), _jsxs("div", { className: "min-w-0 px-4 flex items-center gap-1.5", style: { flex: '2 1 0%' }, children: [_jsx(Skeleton, { className: "flex-shrink-0", style: { width: 6, height: 6, borderRadius: '50%' } }), _jsx(Skeleton, { className: "h-3", style: { width: '60%' } })] }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: '50%' } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: '75%' } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: '75%' } }) })] }, i))) }));
|
|
392
407
|
}
|
|
393
|
-
function EventRow({ event, index, isFirst, isLast, isExpanded, onToggleExpand, activeGroupKey, selectedGroupKey, selectedGroupRange, correlationNameMap, workflowName, durationMap, onSelectGroup, onHoverGroup, onLoadEventData, cachedEventData, onCacheEventData, encryptionKey, onEncryptedDataDetected, }) {
|
|
408
|
+
function EventRow({ event, index, isFirst, isLast, isExpanded, onToggleExpand, activeGroupKey, selectedGroupKey, selectedGroupRange, correlationNameMap, workflowName, durationMap, onSelectGroup, onHoverGroup, onLoadEventData, cachedEventData, onCacheEventData, encryptionKey, onEncryptedDataDetected, suppressGroupDimming = false, }) {
|
|
394
409
|
const [isLoading, setIsLoading] = useState(false);
|
|
395
410
|
const [loadedEventData, setLoadedEventData] = useState(cachedEventData);
|
|
396
411
|
const [loadError, setLoadError] = useState(null);
|
|
@@ -420,7 +435,7 @@ function EventRow({ event, index, isFirst, isLast, isExpanded, onToggleExpand, a
|
|
|
420
435
|
const durationInfo = durationKey ? durationMap.get(durationKey) : undefined;
|
|
421
436
|
const hasActive = activeGroupKey !== undefined;
|
|
422
437
|
const isRelated = rowGroupKey !== undefined && rowGroupKey === activeGroupKey;
|
|
423
|
-
const isDimmed = hasActive && !isRelated;
|
|
438
|
+
const isDimmed = hasActive && !isRelated && !suppressGroupDimming;
|
|
424
439
|
const isPulsing = hasActive && isRelated;
|
|
425
440
|
// Gutter state derived from selectedGroupRange
|
|
426
441
|
const showBranch = hasActive && isRelated && !isRun;
|
|
@@ -568,7 +583,8 @@ function EventRow({ event, index, isFirst, isLast, isExpanded, onToggleExpand, a
|
|
|
568
583
|
// ──────────────────────────────────────────────────────────────────────────
|
|
569
584
|
// Main component
|
|
570
585
|
// ──────────────────────────────────────────────────────────────────────────
|
|
571
|
-
export function EventListView({ events, run, onLoadEventData, hasMoreEvents = false, isLoadingMoreEvents = false, onLoadMoreEvents, encryptionKey, isLoading = false, sortOrder: sortOrderProp, onSortOrderChange, onDecrypt, isDecrypting = false, hasEncryptedData: hasEncryptedDataProp = false, }) {
|
|
586
|
+
export function EventListView({ events, run, onLoadEventData, hasMoreEvents = false, isLoadingMoreEvents = false, onLoadMoreEvents, encryptionKey, isLoading = false, sortOrder: sortOrderProp, onSortOrderChange, onDecrypt, isDecrypting = false, hasEncryptedData: hasEncryptedDataProp = false, onExactIdSearch, }) {
|
|
587
|
+
const toast = useToast();
|
|
572
588
|
const [internalSortOrder, setInternalSortOrder] = useState('asc');
|
|
573
589
|
const effectiveSortOrder = sortOrderProp ?? internalSortOrder;
|
|
574
590
|
const handleSortOrderChange = useCallback((order) => {
|
|
@@ -579,24 +595,36 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
579
595
|
setInternalSortOrder(order);
|
|
580
596
|
}
|
|
581
597
|
}, [onSortOrderChange]);
|
|
598
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
599
|
+
const [searchResults, setSearchResults] = useState(null);
|
|
600
|
+
const [searchResultsTruncated, setSearchResultsTruncated] = useState(false);
|
|
601
|
+
const [searchError, setSearchError] = useState(null);
|
|
602
|
+
const [searchLoading, setSearchLoading] = useState(false);
|
|
603
|
+
const [searchNotFound, setSearchNotFound] = useState(false);
|
|
604
|
+
const searchRequestRef = useRef(0);
|
|
605
|
+
const virtuosoRef = useRef(null);
|
|
606
|
+
const parsedSearchId = useMemo(() => parseExactWorkflowSearchId(searchQuery), [searchQuery]);
|
|
607
|
+
const isExactSearchActive = searchResults !== null;
|
|
582
608
|
const sortedEvents = useMemo(() => {
|
|
583
|
-
|
|
609
|
+
const sourceEvents = isExactSearchActive ? searchResults : (events ?? []);
|
|
610
|
+
if (sourceEvents.length === 0)
|
|
584
611
|
return [];
|
|
585
612
|
const dir = effectiveSortOrder === 'desc' ? -1 : 1;
|
|
586
|
-
return [...
|
|
613
|
+
return [...sourceEvents].sort((a, b) => dir *
|
|
587
614
|
(new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()));
|
|
588
|
-
}, [events, effectiveSortOrder]);
|
|
615
|
+
}, [events, effectiveSortOrder, isExactSearchActive, searchResults]);
|
|
589
616
|
// Detect encrypted fields across all loaded events (inline eventData).
|
|
590
617
|
const hasEncryptedInlineData = useMemo(() => {
|
|
591
|
-
|
|
618
|
+
const sourceEvents = isExactSearchActive ? searchResults : events;
|
|
619
|
+
if (!sourceEvents)
|
|
592
620
|
return false;
|
|
593
|
-
for (const event of
|
|
621
|
+
for (const event of sourceEvents) {
|
|
594
622
|
const ed = event.eventData;
|
|
595
623
|
if (hasEncryptedValues(ed))
|
|
596
624
|
return true;
|
|
597
625
|
}
|
|
598
626
|
return false;
|
|
599
|
-
}, [events]);
|
|
627
|
+
}, [events, isExactSearchActive, searchResults]);
|
|
600
628
|
// Tracks whether any expanded row's lazy-loaded data contained encrypted markers.
|
|
601
629
|
// Set to true by EventRow via onEncryptedDataDetected; never reset (sticky).
|
|
602
630
|
const [foundEncryptedInLazyData, setFoundEncryptedInLazyData] = useState(false);
|
|
@@ -604,7 +632,7 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
604
632
|
setFoundEncryptedInLazyData(true);
|
|
605
633
|
}, []);
|
|
606
634
|
const hasEncryptedData = hasEncryptedDataProp || hasEncryptedInlineData || foundEncryptedInLazyData;
|
|
607
|
-
const { correlationNameMap, workflowName } = useMemo(() => buildNameMaps(events ?? null, run ?? null), [events, run]);
|
|
635
|
+
const { correlationNameMap, workflowName } = useMemo(() => buildNameMaps(isExactSearchActive ? searchResults : (events ?? null), run ?? null), [events, isExactSearchActive, run, searchResults]);
|
|
608
636
|
const durationMap = useMemo(() => buildDurationMap(sortedEvents), [sortedEvents]);
|
|
609
637
|
const [selectedGroupKey, setSelectedGroupKey] = useState(undefined);
|
|
610
638
|
const [hoveredGroupKey, setHoveredGroupKey] = useState(undefined);
|
|
@@ -682,61 +710,112 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
682
710
|
}
|
|
683
711
|
return first >= 0 ? { first, last } : null;
|
|
684
712
|
}, [activeGroupKey, sortedEvents]);
|
|
685
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
686
|
-
const virtuosoRef = useRef(null);
|
|
687
|
-
const searchIndex = useMemo(() => {
|
|
688
|
-
const entries = [];
|
|
689
|
-
for (let i = 0; i < sortedEvents.length; i++) {
|
|
690
|
-
const ev = sortedEvents[i];
|
|
691
|
-
const isRun = isRunLevel(ev.eventType);
|
|
692
|
-
const name = isRun
|
|
693
|
-
? (workflowName ?? '')
|
|
694
|
-
: ev.correlationId
|
|
695
|
-
? (correlationNameMap.get(ev.correlationId) ?? '')
|
|
696
|
-
: '';
|
|
697
|
-
entries.push({
|
|
698
|
-
fields: [
|
|
699
|
-
ev.eventId,
|
|
700
|
-
ev.correlationId ?? '',
|
|
701
|
-
ev.eventType,
|
|
702
|
-
formatEventType(ev.eventType),
|
|
703
|
-
name,
|
|
704
|
-
].map((f) => f.toLowerCase()),
|
|
705
|
-
groupKey: ev.correlationId ?? (isRun ? '__run__' : undefined),
|
|
706
|
-
eventId: ev.eventId,
|
|
707
|
-
index: i,
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
return entries;
|
|
711
|
-
}, [sortedEvents, correlationNameMap, workflowName]);
|
|
712
713
|
useEffect(() => {
|
|
713
|
-
const
|
|
714
|
-
if (!
|
|
714
|
+
const trimmed = searchQuery.trim();
|
|
715
|
+
if (!trimmed) {
|
|
716
|
+
searchRequestRef.current += 1;
|
|
717
|
+
setSearchResults(null);
|
|
718
|
+
setSearchResultsTruncated(false);
|
|
719
|
+
setSearchError(null);
|
|
720
|
+
setSearchLoading(false);
|
|
721
|
+
setSearchNotFound(false);
|
|
715
722
|
setSelectedGroupKey(undefined);
|
|
716
723
|
return;
|
|
717
724
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
725
|
+
const parsed = parseExactWorkflowSearchId(trimmed);
|
|
726
|
+
if (!parsed || !onExactIdSearch) {
|
|
727
|
+
setSearchResults(null);
|
|
728
|
+
setSearchLoading(false);
|
|
729
|
+
setSearchNotFound(false);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const requestId = ++searchRequestRef.current;
|
|
733
|
+
setSearchLoading(true);
|
|
734
|
+
setSearchNotFound(false);
|
|
735
|
+
setSearchError(null);
|
|
736
|
+
const abortController = new AbortController();
|
|
737
|
+
const timer = setTimeout(() => {
|
|
738
|
+
void (async () => {
|
|
739
|
+
try {
|
|
740
|
+
const results = await onExactIdSearch(parsed.id, parsed.kind, abortController.signal);
|
|
741
|
+
if (abortController.signal.aborted ||
|
|
742
|
+
searchRequestRef.current !== requestId) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (results.status === 'error') {
|
|
746
|
+
setSearchResults([]);
|
|
747
|
+
setSearchResultsTruncated(false);
|
|
748
|
+
setSearchNotFound(false);
|
|
749
|
+
setSearchError(results.message);
|
|
750
|
+
setSelectedGroupKey(undefined);
|
|
751
|
+
return;
|
|
727
752
|
}
|
|
753
|
+
if (results.status === 'not_found' ||
|
|
754
|
+
(results.status === 'ok' && results.events.length === 0)) {
|
|
755
|
+
setSearchResults([]);
|
|
756
|
+
setSearchResultsTruncated(false);
|
|
757
|
+
setSearchNotFound(true);
|
|
758
|
+
setSearchError(null);
|
|
759
|
+
setSelectedGroupKey(undefined);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
setSearchResults(results.events);
|
|
763
|
+
setSearchResultsTruncated(Boolean(results.truncated));
|
|
764
|
+
setSearchNotFound(false);
|
|
765
|
+
setSearchError(null);
|
|
766
|
+
setSelectedGroupKey(parsed.kind === 'event'
|
|
767
|
+
? (() => {
|
|
768
|
+
const first = results.events[0];
|
|
769
|
+
if (!first)
|
|
770
|
+
return undefined;
|
|
771
|
+
return isRunLevel(first.eventType)
|
|
772
|
+
? '__run__'
|
|
773
|
+
: (first.correlationId ?? undefined);
|
|
774
|
+
})()
|
|
775
|
+
: parsed.id);
|
|
776
|
+
virtuosoRef.current?.scrollToIndex({
|
|
777
|
+
index: 0,
|
|
778
|
+
align: 'start',
|
|
779
|
+
behavior: 'smooth',
|
|
780
|
+
});
|
|
728
781
|
}
|
|
729
|
-
|
|
782
|
+
catch {
|
|
783
|
+
if (abortController.signal.aborted ||
|
|
784
|
+
searchRequestRef.current !== requestId) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
setSearchResults([]);
|
|
788
|
+
setSearchResultsTruncated(false);
|
|
789
|
+
setSearchNotFound(false);
|
|
790
|
+
setSearchError('Failed to search events. Try again.');
|
|
791
|
+
setSelectedGroupKey(undefined);
|
|
792
|
+
}
|
|
793
|
+
finally {
|
|
794
|
+
if (searchRequestRef.current === requestId &&
|
|
795
|
+
!abortController.signal.aborted) {
|
|
796
|
+
setSearchLoading(false);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
})();
|
|
800
|
+
}, 300);
|
|
801
|
+
return () => {
|
|
802
|
+
clearTimeout(timer);
|
|
803
|
+
abortController.abort();
|
|
804
|
+
};
|
|
805
|
+
}, [searchQuery, onExactIdSearch]);
|
|
806
|
+
const handleSearchKeyDown = useCallback((event) => {
|
|
807
|
+
if (event.key !== 'Enter') {
|
|
808
|
+
return;
|
|
730
809
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
});
|
|
810
|
+
const trimmed = searchQuery.trim();
|
|
811
|
+
if (!trimmed ||
|
|
812
|
+
parseExactWorkflowSearchId(trimmed) ||
|
|
813
|
+
!onExactIdSearch ||
|
|
814
|
+
!looksLikeWorkflowIdSearchInput(trimmed)) {
|
|
815
|
+
return;
|
|
738
816
|
}
|
|
739
|
-
|
|
817
|
+
toast.info('Enter a full step ID, wait ID, hook ID, or event ID');
|
|
818
|
+
}, [searchQuery, onExactIdSearch, toast]);
|
|
740
819
|
// Track whether we've ever had events to distinguish initial load from refetch
|
|
741
820
|
const hasHadEventsRef = useRef(false);
|
|
742
821
|
if (sortedEvents.length > 0) {
|
|
@@ -747,9 +826,6 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
747
826
|
if (isInitialLoad) {
|
|
748
827
|
return (_jsxs("div", { className: "h-full flex flex-col overflow-hidden", children: [_jsx("div", { style: { padding: 6 }, children: _jsx(Skeleton, { style: { height: 40, borderRadius: 6 } }) }), _jsxs("div", { className: "flex items-center gap-0 h-10 border-b flex-shrink-0", style: { borderColor: 'var(--ds-gray-alpha-200)' }, children: [_jsx("div", { className: "flex-shrink-0", style: { width: GUTTER_WIDTH } }), _jsx("div", { className: "w-5 flex-shrink-0" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: 40 } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: 72 } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: 44 } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: 92 } }) }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: _jsx(Skeleton, { className: "h-3", style: { width: 60 } }) })] }), _jsx(RowsSkeleton, {})] }));
|
|
749
828
|
}
|
|
750
|
-
if (!isLoading && (!events || events.length === 0)) {
|
|
751
|
-
return (_jsx("div", { className: "flex items-center justify-center h-full text-sm", style: { color: 'var(--ds-gray-700)' }, children: "No events found" }));
|
|
752
|
-
}
|
|
753
829
|
return (_jsx(DecryptClickContext.Provider, { value: onDecrypt ? { onDecrypt, isDecrypting } : undefined, children: _jsxs("div", { className: "h-full flex flex-col overflow-hidden", children: [_jsx("style", { children: `@keyframes workflow-dot-pulse{0%{transform:scale(1);opacity:.7}70%,100%{transform:scale(2.2);opacity:0}}` }), _jsxs("div", { style: {
|
|
754
830
|
padding: 6,
|
|
755
831
|
backgroundColor: 'var(--ds-background-100)',
|
|
@@ -773,7 +849,9 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
773
849
|
justifyContent: 'center',
|
|
774
850
|
color: 'var(--ds-gray-800)',
|
|
775
851
|
flexShrink: 0,
|
|
776
|
-
}, children: _jsxs("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "7", cy: "7", r: "4.5", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("path", { d: "M11.5 11.5L14 14", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }) }), _jsx("input", { type: "search", placeholder: "Search by
|
|
852
|
+
}, children: _jsxs("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "7", cy: "7", r: "4.5", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("path", { d: "M11.5 11.5L14 14", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }) }), _jsx("input", { type: "search", placeholder: "Search by step ID, wait ID, hook ID, or event ID\u2026", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onKeyDown: handleSearchKeyDown, disabled: !onExactIdSearch, title: onExactIdSearch
|
|
853
|
+
? undefined
|
|
854
|
+
: 'Exact ID search is unavailable in this view.', style: {
|
|
777
855
|
marginLeft: -16,
|
|
778
856
|
paddingInline: 12,
|
|
779
857
|
fontFamily: 'inherit',
|
|
@@ -783,22 +861,38 @@ export function EventListView({ events, run, onLoadEventData, hasMoreEvents = fa
|
|
|
783
861
|
outline: 'none',
|
|
784
862
|
height: 40,
|
|
785
863
|
width: '100%',
|
|
864
|
+
opacity: onExactIdSearch ? 1 : 0.5,
|
|
865
|
+
cursor: onExactIdSearch ? 'text' : 'not-allowed',
|
|
786
866
|
} })] }), _jsx(MenuDropdown, { options: SORT_OPTIONS, value: effectiveSortOrder, onChange: handleSortOrderChange }), (hasEncryptedData || encryptionKey) && onDecrypt && (_jsx(DecryptButton, { decrypted: !!encryptionKey, loading: isDecrypting, onClick: onDecrypt }))] }), _jsxs("div", { className: "flex items-center gap-0 text-[13px] font-medium h-10 border-b flex-shrink-0", style: {
|
|
787
867
|
borderColor: 'var(--ds-gray-alpha-200)',
|
|
788
868
|
color: 'var(--ds-gray-900)',
|
|
789
869
|
backgroundColor: 'var(--ds-background-100)',
|
|
790
|
-
}, children: [_jsx("div", { className: "flex-shrink-0", style: { width: GUTTER_WIDTH } }), _jsx("div", { className: "w-5 flex-shrink-0" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Time" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Event Type" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Name" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: "Correlation ID" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: "Event ID" })] }), isRefetching ? (_jsx(RowsSkeleton, {})) : (_jsx(
|
|
791
|
-
|
|
870
|
+
}, children: [_jsx("div", { className: "flex-shrink-0", style: { width: GUTTER_WIDTH } }), _jsx("div", { className: "w-5 flex-shrink-0" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Time" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Event Type" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '2 1 0%' }, children: "Name" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: "Correlation ID" }), _jsx("div", { className: "min-w-0 px-4", style: { flex: '3 1 0%' }, children: "Event ID" })] }), isRefetching || searchLoading ? (_jsx(RowsSkeleton, {})) : sortedEvents.length === 0 ? (_jsx("div", { className: "flex flex-1 items-center justify-center px-6 text-center text-sm", style: { color: 'var(--ds-gray-700)' }, children: searchNotFound && searchQuery.trim()
|
|
871
|
+
? `No events found for ${searchQuery.trim()}`
|
|
872
|
+
: searchError
|
|
873
|
+
? searchError
|
|
874
|
+
: parsedSearchId && searchQuery.trim() && !onExactIdSearch
|
|
875
|
+
? 'Exact ID search is unavailable in this view.'
|
|
876
|
+
: 'No events found' })) : (_jsx(Virtuoso, { ref: virtuosoRef, totalCount: sortedEvents.length, overscan: 20, defaultItemHeight: 40, endReached: () => {
|
|
877
|
+
if (isExactSearchActive ||
|
|
878
|
+
!hasMoreEvents ||
|
|
879
|
+
isLoadingMoreEvents) {
|
|
792
880
|
return;
|
|
793
881
|
}
|
|
794
882
|
void onLoadMoreEvents?.();
|
|
795
883
|
}, itemContent: (index) => {
|
|
796
884
|
const ev = sortedEvents[index];
|
|
797
|
-
return (_jsx(EventRow, { event: ev, index: index, isFirst: index === 0, isLast: index === sortedEvents.length - 1, isExpanded: expandedEventIds.has(ev.eventId), onToggleExpand: toggleEventExpanded, activeGroupKey: activeGroupKey, selectedGroupKey: selectedGroupKey, selectedGroupRange: selectedGroupRange, correlationNameMap: correlationNameMap, workflowName: workflowName, durationMap: durationMap, onSelectGroup: onSelectGroup, onHoverGroup: onHoverGroup, onLoadEventData: onLoadEventData, cachedEventData: eventDataCacheRef.current.get(ev.eventId) ?? null, onCacheEventData: cacheEventData, encryptionKey: encryptionKey, onEncryptedDataDetected: handleEncryptedDataDetected }));
|
|
885
|
+
return (_jsx(EventRow, { event: ev, index: index, isFirst: index === 0, isLast: index === sortedEvents.length - 1, isExpanded: expandedEventIds.has(ev.eventId), onToggleExpand: toggleEventExpanded, activeGroupKey: activeGroupKey, selectedGroupKey: selectedGroupKey, selectedGroupRange: selectedGroupRange, correlationNameMap: correlationNameMap, workflowName: workflowName, durationMap: durationMap, onSelectGroup: onSelectGroup, onHoverGroup: onHoverGroup, onLoadEventData: onLoadEventData, cachedEventData: eventDataCacheRef.current.get(ev.eventId) ?? null, onCacheEventData: cacheEventData, encryptionKey: encryptionKey, onEncryptedDataDetected: handleEncryptedDataDetected, suppressGroupDimming: isExactSearchActive }));
|
|
798
886
|
}, style: { flex: 1, minHeight: 0 } })), _jsxs("div", { className: "relative flex-shrink-0 flex items-center h-10 border-t px-4 text-xs", style: {
|
|
799
887
|
borderColor: 'var(--ds-gray-alpha-200)',
|
|
800
888
|
color: 'var(--ds-gray-900)',
|
|
801
889
|
backgroundColor: 'var(--ds-background-100)',
|
|
802
|
-
}, children: [
|
|
890
|
+
}, children: [_jsx("span", { children: isExactSearchActive
|
|
891
|
+
? searchError
|
|
892
|
+
? searchError
|
|
893
|
+
: searchNotFound
|
|
894
|
+
? `No events found for ${searchQuery.trim()}`
|
|
895
|
+
: `${sortedEvents.length} event${sortedEvents.length !== 1 ? 's' : ''} for ${searchQuery.trim()}${searchResultsTruncated ? ' (results may be truncated)' : ''}`
|
|
896
|
+
: `${sortedEvents.length} event${sortedEvents.length !== 1 ? 's' : ''} loaded` }), !isExactSearchActive && hasMoreEvents && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: _jsx("div", { className: "pointer-events-auto", children: _jsx(LoadMoreButton, { loading: isLoadingMoreEvents, onClick: () => void onLoadMoreEvents?.() }) }) }))] })] }) }));
|
|
803
897
|
}
|
|
804
898
|
//# sourceMappingURL=event-list-view.js.map
|