@workflow/web-shared 4.1.0-beta.50 → 4.1.0-beta.52
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.map +1 -1
- package/dist/components/event-list-view.js +8 -5
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/index.d.ts +3 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +23 -91
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/detail-card.d.ts +3 -1
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +2 -2
- package/dist/components/sidebar/detail-card.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +22 -2
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +38 -49
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/sidebar/events-list.d.ts +4 -4
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +77 -12
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/stream-viewer.d.ts +8 -7
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +12 -9
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/context.d.ts +3 -2
- package/dist/components/trace-viewer/context.d.ts.map +1 -1
- package/dist/components/trace-viewer/context.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +81 -0
- package/dist/components/ui/inspector-theme.d.ts.map +1 -0
- package/dist/components/ui/inspector-theme.js +63 -0
- package/dist/components/ui/inspector-theme.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 +111 -10
- 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 +17 -12
- 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/hydration.d.ts +26 -0
- package/dist/lib/hydration.d.ts.map +1 -0
- package/dist/lib/hydration.js +98 -0
- package/dist/lib/hydration.js.map +1 -0
- package/package.json +3 -2
- package/src/components/event-list-view.tsx +15 -9
- package/src/components/index.ts +6 -5
- package/src/components/sidebar/attribute-panel.tsx +30 -129
- package/src/components/sidebar/detail-card.tsx +7 -1
- package/src/components/sidebar/entity-detail-panel.tsx +71 -57
- package/src/components/sidebar/events-list.tsx +204 -88
- package/src/components/stream-viewer.tsx +37 -21
- package/src/components/trace-viewer/context.tsx +3 -2
- package/src/components/ui/inspector-theme.ts +64 -0
- package/src/components/workflow-trace-view.tsx +210 -32
- package/src/components/workflow-traces/trace-span-construction.ts +17 -12
- package/src/index.ts +13 -0
- package/src/lib/hydration.ts +137 -0
|
@@ -5,9 +5,10 @@ import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
|
|
|
5
5
|
import type { ModelMessage } from 'ai';
|
|
6
6
|
import type { ReactNode } from 'react';
|
|
7
7
|
import { createContext, useContext, useMemo, useState } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { ObjectInspector } from 'react-inspector';
|
|
9
9
|
import { useDarkMode } from '../../hooks/use-dark-mode';
|
|
10
10
|
import { extractConversation, isDoStreamStep } from '../../lib/utils';
|
|
11
|
+
import { ErrorCard } from '../ui/error-card';
|
|
11
12
|
import { ConversationView } from './conversation-view';
|
|
12
13
|
import { DetailCard } from './detail-card';
|
|
13
14
|
|
|
@@ -334,145 +335,45 @@ const StreamRefDisplay = ({ streamRef }: { streamRef: StreamRef }) => {
|
|
|
334
335
|
};
|
|
335
336
|
|
|
336
337
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* and
|
|
338
|
+
* Renders a value using react-inspector's ObjectInspector for proper
|
|
339
|
+
* display of Map, Set, URLSearchParams, Date, Error, RegExp, typed
|
|
340
|
+
* arrays, and other non-plain-object types.
|
|
341
|
+
*
|
|
342
|
+
* StreamRef and ClassInstanceRef objects are rendered inline as
|
|
343
|
+
* custom components (clickable stream links and class cards).
|
|
340
344
|
*/
|
|
341
|
-
const transformValueForDisplay = (
|
|
342
|
-
value: unknown
|
|
343
|
-
): {
|
|
344
|
-
json: string;
|
|
345
|
-
streamRefs: Map<string, StreamRef>;
|
|
346
|
-
classInstanceRefs: Map<string, ClassInstanceRef>;
|
|
347
|
-
} => {
|
|
348
|
-
const streamRefs = new Map<string, StreamRef>();
|
|
349
|
-
const classInstanceRefs = new Map<string, ClassInstanceRef>();
|
|
350
|
-
let counter = 0;
|
|
351
|
-
|
|
352
|
-
const transform = (v: unknown): unknown => {
|
|
353
|
-
if (isStreamRef(v)) {
|
|
354
|
-
const placeholder = `__STREAM_REF_${counter++}__`;
|
|
355
|
-
streamRefs.set(placeholder, v);
|
|
356
|
-
return placeholder;
|
|
357
|
-
}
|
|
358
|
-
if (isClassInstanceRef(v)) {
|
|
359
|
-
const placeholder = `__CLASS_INSTANCE_REF_${counter++}__`;
|
|
360
|
-
classInstanceRefs.set(placeholder, v);
|
|
361
|
-
return placeholder;
|
|
362
|
-
}
|
|
363
|
-
if (Array.isArray(v)) {
|
|
364
|
-
return v.map(transform);
|
|
365
|
-
}
|
|
366
|
-
if (v !== null && typeof v === 'object') {
|
|
367
|
-
const result: Record<string, unknown> = {};
|
|
368
|
-
for (const [key, val] of Object.entries(v)) {
|
|
369
|
-
result[key] = transform(val);
|
|
370
|
-
}
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
return v;
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const transformed = transform(value);
|
|
377
|
-
return {
|
|
378
|
-
json: JSON.stringify(transformed, null, 2),
|
|
379
|
-
streamRefs,
|
|
380
|
-
classInstanceRefs,
|
|
381
|
-
};
|
|
382
|
-
};
|
|
383
|
-
|
|
384
345
|
const JsonBlock = (value: unknown) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// If no special refs, just render plain JSON
|
|
389
|
-
if (streamRefs.size === 0 && classInstanceRefs.size === 0) {
|
|
390
|
-
return (
|
|
391
|
-
<pre
|
|
392
|
-
className="text-[11px] overflow-x-auto rounded-md border p-3"
|
|
393
|
-
style={{
|
|
394
|
-
borderColor: 'var(--ds-gray-300)',
|
|
395
|
-
backgroundColor: 'var(--ds-gray-100)',
|
|
396
|
-
color: 'var(--ds-gray-1000)',
|
|
397
|
-
}}
|
|
398
|
-
>
|
|
399
|
-
<code>{json}</code>
|
|
400
|
-
</pre>
|
|
401
|
-
);
|
|
402
|
-
}
|
|
346
|
+
return <DataInspector data={value} />;
|
|
347
|
+
};
|
|
403
348
|
|
|
404
|
-
|
|
405
|
-
const placeholderComponents = new Map<string, ReactNode>();
|
|
406
|
-
let keyIndex = 0;
|
|
349
|
+
import { inspectorThemeDark, inspectorThemeLight } from '../ui/inspector-theme';
|
|
407
350
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
placeholder,
|
|
411
|
-
<StreamRefDisplay key={keyIndex++} streamRef={streamRef} />
|
|
412
|
-
);
|
|
413
|
-
}
|
|
351
|
+
function DataInspector({ data }: { data: unknown }) {
|
|
352
|
+
const isDark = useDarkMode();
|
|
414
353
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
<ClassInstanceRefDisplay
|
|
419
|
-
key={keyIndex++}
|
|
420
|
-
classInstanceRef={classInstanceRef}
|
|
421
|
-
/>
|
|
422
|
-
);
|
|
354
|
+
// Render top-level StreamRef/ClassInstanceRef as full custom components
|
|
355
|
+
if (isStreamRef(data)) {
|
|
356
|
+
return <StreamRefDisplay streamRef={data} />;
|
|
423
357
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const parts: ReactNode[] = [];
|
|
427
|
-
let remaining = json;
|
|
428
|
-
|
|
429
|
-
// Process placeholders in order of their appearance in the string
|
|
430
|
-
while (remaining.length > 0) {
|
|
431
|
-
let earliestIndex = -1;
|
|
432
|
-
let earliestPlaceholder = '';
|
|
433
|
-
let earliestComponent: ReactNode = null;
|
|
434
|
-
|
|
435
|
-
// Find the earliest placeholder in the remaining string
|
|
436
|
-
for (const [placeholder, component] of placeholderComponents) {
|
|
437
|
-
const index = remaining.indexOf(`"${placeholder}"`);
|
|
438
|
-
if (index !== -1 && (earliestIndex === -1 || index < earliestIndex)) {
|
|
439
|
-
earliestIndex = index;
|
|
440
|
-
earliestPlaceholder = placeholder;
|
|
441
|
-
earliestComponent = component;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (earliestIndex === -1) {
|
|
446
|
-
// No more placeholders found, add the rest
|
|
447
|
-
parts.push(remaining);
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Add text before the placeholder
|
|
452
|
-
if (earliestIndex > 0) {
|
|
453
|
-
parts.push(remaining.slice(0, earliestIndex));
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Add the component
|
|
457
|
-
parts.push(earliestComponent);
|
|
458
|
-
|
|
459
|
-
// Move past the placeholder
|
|
460
|
-
remaining = remaining.slice(earliestIndex + earliestPlaceholder.length + 2); // +2 for quotes
|
|
358
|
+
if (isClassInstanceRef(data)) {
|
|
359
|
+
return <ClassInstanceRefDisplay classInstanceRef={data} />;
|
|
461
360
|
}
|
|
462
361
|
|
|
463
362
|
return (
|
|
464
|
-
<
|
|
465
|
-
className="
|
|
466
|
-
style={{
|
|
467
|
-
borderColor: 'var(--ds-gray-300)',
|
|
468
|
-
backgroundColor: 'var(--ds-gray-100)',
|
|
469
|
-
color: 'var(--ds-gray-1000)',
|
|
470
|
-
}}
|
|
363
|
+
<div
|
|
364
|
+
className="overflow-x-auto rounded-md border p-3"
|
|
365
|
+
style={{ borderColor: 'var(--ds-gray-300)' }}
|
|
471
366
|
>
|
|
472
|
-
<
|
|
473
|
-
|
|
367
|
+
<ObjectInspector
|
|
368
|
+
data={data}
|
|
369
|
+
// @ts-expect-error react-inspector accepts theme objects at runtime despite
|
|
370
|
+
// types declaring string only — see https://github.com/storybookjs/react-inspector/blob/main/README.md#theme
|
|
371
|
+
theme={isDark ? inspectorThemeDark : inspectorThemeLight}
|
|
372
|
+
expandLevel={2}
|
|
373
|
+
/>
|
|
374
|
+
</div>
|
|
474
375
|
);
|
|
475
|
-
}
|
|
376
|
+
}
|
|
476
377
|
|
|
477
378
|
type AttributeKey =
|
|
478
379
|
| keyof Step
|
|
@@ -3,12 +3,18 @@ import type { ReactNode } from 'react';
|
|
|
3
3
|
export function DetailCard({
|
|
4
4
|
summary,
|
|
5
5
|
children,
|
|
6
|
+
onToggle,
|
|
6
7
|
}: {
|
|
7
8
|
summary: ReactNode;
|
|
8
9
|
children?: ReactNode;
|
|
10
|
+
/** Called when the detail card is expanded/collapsed */
|
|
11
|
+
onToggle?: (open: boolean) => void;
|
|
9
12
|
}) {
|
|
10
13
|
return (
|
|
11
|
-
<details
|
|
14
|
+
<details
|
|
15
|
+
className="group"
|
|
16
|
+
onToggle={(e) => onToggle?.((e.target as HTMLDetailsElement).open)}
|
|
17
|
+
>
|
|
12
18
|
<summary
|
|
13
19
|
className="cursor-pointer rounded-md border px-2.5 py-1.5 text-xs hover:brightness-95"
|
|
14
20
|
style={{
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
import type { Event, Hook, Step, 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 { toast } from 'sonner';
|
|
8
|
-
import { useTraceViewer } from '../trace-viewer';
|
|
9
8
|
import { AttributePanel } from './attribute-panel';
|
|
10
9
|
import { EventsList } from './events-list';
|
|
11
10
|
import { ResolveHookModal } from './resolve-hook-modal';
|
|
@@ -33,7 +32,24 @@ export type SpanSelectionInfo = {
|
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
|
-
*
|
|
35
|
+
* Info about the selected span from the trace viewer.
|
|
36
|
+
*/
|
|
37
|
+
export interface SelectedSpanInfo {
|
|
38
|
+
/** The raw data from the span attributes (step/run/hook object from the trace) */
|
|
39
|
+
data?: unknown;
|
|
40
|
+
/** The span resource type (from span attributes) */
|
|
41
|
+
resource?: string;
|
|
42
|
+
/** The span ID (correlationId for filtering events) */
|
|
43
|
+
spanId?: string;
|
|
44
|
+
/** Raw correlated events from the store (NOT from the trace worker pipeline) */
|
|
45
|
+
rawEvents?: Event[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Panel component for workflow traces that displays entity details.
|
|
50
|
+
*
|
|
51
|
+
* This component is rendered OUTSIDE the trace viewer context — it
|
|
52
|
+
* receives all data via props rather than reading from context.
|
|
37
53
|
*/
|
|
38
54
|
export function EntityDetailPanel({
|
|
39
55
|
run,
|
|
@@ -43,7 +59,9 @@ export function EntityDetailPanel({
|
|
|
43
59
|
spanDetailLoading,
|
|
44
60
|
onSpanSelect,
|
|
45
61
|
onWakeUpSleep,
|
|
62
|
+
onLoadEventData,
|
|
46
63
|
onResolveHook,
|
|
64
|
+
selectedSpan,
|
|
47
65
|
}: {
|
|
48
66
|
run: WorkflowRun;
|
|
49
67
|
/** Callback when a stream reference is clicked */
|
|
@@ -61,47 +79,53 @@ export function EntityDetailPanel({
|
|
|
61
79
|
runId: string,
|
|
62
80
|
correlationId: string
|
|
63
81
|
) => Promise<{ stoppedCount: number }>;
|
|
82
|
+
/** Callback to load event data for a specific event (lazy loading) */
|
|
83
|
+
onLoadEventData?: (
|
|
84
|
+
correlationId: string,
|
|
85
|
+
eventId: string
|
|
86
|
+
) => Promise<unknown | null>;
|
|
64
87
|
/** Callback to resolve a hook with a payload. */
|
|
65
88
|
onResolveHook?: (
|
|
66
89
|
hookToken: string,
|
|
67
90
|
payload: unknown,
|
|
68
91
|
hook?: Hook
|
|
69
92
|
) => Promise<void>;
|
|
93
|
+
/** Info about the currently selected span from the trace viewer */
|
|
94
|
+
selectedSpan: SelectedSpanInfo | null;
|
|
70
95
|
}): React.JSX.Element | null {
|
|
71
|
-
const { state } = useTraceViewer();
|
|
72
|
-
const { selected } = state;
|
|
73
96
|
const [stoppingSleep, setStoppingSleep] = useState(false);
|
|
74
97
|
const [showResolveHookModal, setShowResolveHookModal] = useState(false);
|
|
75
98
|
const [resolvingHook, setResolvingHook] = useState(false);
|
|
76
99
|
|
|
77
|
-
const data =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// doesn't memoize the callback with useCallback.
|
|
81
|
-
const onSpanSelectRef = useRef(onSpanSelect);
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
onSpanSelectRef.current = onSpanSelect;
|
|
84
|
-
});
|
|
100
|
+
const data = selectedSpan?.data;
|
|
101
|
+
const rawEvents = selectedSpan?.rawEvents;
|
|
102
|
+
const rawEventsLength = rawEvents?.length ?? 0;
|
|
85
103
|
|
|
86
|
-
// Determine resource ID and runId
|
|
87
|
-
// Uses type guards to validate the data shape matches the expected resource type
|
|
104
|
+
// Determine resource type, ID, and runId from the selected span
|
|
88
105
|
const { resource, resourceId, runId } = useMemo(() => {
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
if (!selectedSpan) {
|
|
107
|
+
return { resource: undefined, resourceId: undefined, runId: undefined };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const res = selectedSpan.resource;
|
|
111
|
+
if (res === 'step' && isStep(data)) {
|
|
91
112
|
return { resource: 'step', resourceId: data.stepId, runId: data.runId };
|
|
92
|
-
}
|
|
113
|
+
}
|
|
114
|
+
if (res === 'run' && isWorkflowRun(data)) {
|
|
93
115
|
return { resource: 'run', resourceId: data.runId, runId: undefined };
|
|
94
|
-
}
|
|
116
|
+
}
|
|
117
|
+
if (res === 'hook' && isHook(data)) {
|
|
95
118
|
return { resource: 'hook', resourceId: data.hookId, runId: undefined };
|
|
96
|
-
}
|
|
119
|
+
}
|
|
120
|
+
if (res === 'sleep') {
|
|
97
121
|
return {
|
|
98
122
|
resource: 'sleep',
|
|
99
|
-
resourceId:
|
|
123
|
+
resourceId: selectedSpan.spanId,
|
|
100
124
|
runId: undefined,
|
|
101
125
|
};
|
|
102
126
|
}
|
|
103
127
|
return { resource: undefined, resourceId: undefined, runId: undefined };
|
|
104
|
-
}, [
|
|
128
|
+
}, [selectedSpan, data]);
|
|
105
129
|
|
|
106
130
|
// Notify parent when span selection changes
|
|
107
131
|
useEffect(() => {
|
|
@@ -110,65 +134,53 @@ export function EntityDetailPanel({
|
|
|
110
134
|
resourceId &&
|
|
111
135
|
['run', 'step', 'hook', 'sleep'].includes(resource)
|
|
112
136
|
) {
|
|
113
|
-
|
|
137
|
+
onSpanSelect({
|
|
114
138
|
resource: resource as 'run' | 'step' | 'hook' | 'sleep',
|
|
115
139
|
resourceId,
|
|
116
140
|
runId,
|
|
117
141
|
});
|
|
118
142
|
}
|
|
119
|
-
}, [resource, resourceId, runId]);
|
|
143
|
+
}, [resource, resourceId, runId, onSpanSelect]);
|
|
120
144
|
|
|
121
145
|
// Check if this sleep is still pending and can be woken up
|
|
122
|
-
// Requirements: no wait_completed event, resumeAt is in the future, run is not terminal
|
|
123
|
-
const spanEvents = selected?.span.events;
|
|
124
|
-
const spanEventsLength = spanEvents?.length ?? 0;
|
|
125
146
|
const canWakeUp = useMemo(() => {
|
|
126
|
-
void
|
|
127
|
-
if (resource !== 'sleep' || !
|
|
128
|
-
|
|
129
|
-
// Check run is not in a terminal state
|
|
147
|
+
void rawEventsLength;
|
|
148
|
+
if (resource !== 'sleep' || !rawEvents) return false;
|
|
130
149
|
const terminalStates = ['completed', 'failed', 'cancelled'];
|
|
131
150
|
if (terminalStates.includes(run.status)) return false;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const hasWaitCompleted = spanEvents.some(
|
|
135
|
-
(e) => e.name === 'wait_completed'
|
|
151
|
+
const hasWaitCompleted = rawEvents.some(
|
|
152
|
+
(e) => e.eventType === 'wait_completed'
|
|
136
153
|
);
|
|
137
154
|
if (hasWaitCompleted) return false;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const eventData = waitCreatedEvent?.
|
|
155
|
+
const waitCreatedEvent = rawEvents.find(
|
|
156
|
+
(e) => e.eventType === 'wait_created'
|
|
157
|
+
);
|
|
158
|
+
const eventData = (waitCreatedEvent as any)?.eventData as
|
|
142
159
|
| { resumeAt?: string | Date }
|
|
143
160
|
| undefined;
|
|
144
161
|
const resumeAt = eventData?.resumeAt;
|
|
145
162
|
if (!resumeAt) return false;
|
|
146
|
-
|
|
147
163
|
const resumeAtDate = new Date(resumeAt);
|
|
148
164
|
return resumeAtDate.getTime() > Date.now();
|
|
149
|
-
}, [resource,
|
|
165
|
+
}, [resource, rawEvents, rawEventsLength, run.status]);
|
|
150
166
|
|
|
151
|
-
// Check if this hook can be resolved
|
|
167
|
+
// Check if this hook can be resolved
|
|
152
168
|
const canResolveHook = useMemo(() => {
|
|
153
|
-
void
|
|
154
|
-
if (resource !== 'hook' || !
|
|
155
|
-
|
|
156
|
-
// Check run is not in a terminal state
|
|
169
|
+
void rawEventsLength;
|
|
170
|
+
if (resource !== 'hook' || !rawEvents) return false;
|
|
157
171
|
const terminalStates = ['completed', 'failed', 'cancelled'];
|
|
158
172
|
if (terminalStates.includes(run.status)) return false;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
const hasHookDisposed = rawEvents.some(
|
|
174
|
+
(e) => e.eventType === 'hook_disposed'
|
|
175
|
+
);
|
|
162
176
|
if (hasHookDisposed) return false;
|
|
163
|
-
|
|
164
|
-
// Hook can be resolved
|
|
165
177
|
return true;
|
|
166
|
-
}, [resource,
|
|
178
|
+
}, [resource, rawEvents, rawEventsLength, run.status]);
|
|
167
179
|
|
|
168
180
|
const error = spanDetailError ?? undefined;
|
|
169
181
|
const loading = spanDetailLoading ?? false;
|
|
170
182
|
|
|
171
|
-
// Get the hook token for resolving
|
|
183
|
+
// Get the hook token for resolving
|
|
172
184
|
const hookToken = useMemo(() => {
|
|
173
185
|
if (resource !== 'hook') return undefined;
|
|
174
186
|
const candidate = spanDetailData ?? data;
|
|
@@ -176,12 +188,12 @@ export function EntityDetailPanel({
|
|
|
176
188
|
}, [resource, spanDetailData, data]);
|
|
177
189
|
|
|
178
190
|
useEffect(() => {
|
|
179
|
-
if (error &&
|
|
191
|
+
if (error && selectedSpan && resource) {
|
|
180
192
|
toast.error(`Failed to load ${resource} details`, {
|
|
181
193
|
description: error.message,
|
|
182
194
|
});
|
|
183
195
|
}
|
|
184
|
-
}, [error, resource,
|
|
196
|
+
}, [error, resource, selectedSpan]);
|
|
185
197
|
|
|
186
198
|
const handleWakeUp = async () => {
|
|
187
199
|
if (stoppingSleep || !resourceId) return;
|
|
@@ -255,7 +267,7 @@ export function EntityDetailPanel({
|
|
|
255
267
|
[onResolveHook, hookToken, resolvingHook, spanDetailData, data]
|
|
256
268
|
);
|
|
257
269
|
|
|
258
|
-
if (!
|
|
270
|
+
if (!selectedSpan || !resource || !resourceId) {
|
|
259
271
|
return null;
|
|
260
272
|
}
|
|
261
273
|
|
|
@@ -332,7 +344,9 @@ export function EntityDetailPanel({
|
|
|
332
344
|
error={error ?? undefined}
|
|
333
345
|
onStreamClick={onStreamClick}
|
|
334
346
|
/>
|
|
335
|
-
{resource !== 'run' &&
|
|
347
|
+
{resource !== 'run' && rawEvents && (
|
|
348
|
+
<EventsList events={rawEvents} onLoadEventData={onLoadEventData} />
|
|
349
|
+
)}
|
|
336
350
|
</div>
|
|
337
351
|
);
|
|
338
352
|
}
|