@workflow/web-shared 5.0.0-beta.26 → 5.0.0-beta.28
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/new-trace-viewer/components/detail-panel-width.d.ts +34 -0
- package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/detail-panel-width.js +67 -0
- package/dist/components/new-trace-viewer/components/detail-panel.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/detail-panel.js +122 -0
- package/dist/components/new-trace-viewer/components/draggable-border.d.ts +31 -0
- package/dist/components/new-trace-viewer/components/draggable-border.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/draggable-border.js +140 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +1 -1
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/event-list.js +3 -5
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +2 -2
- package/dist/components/new-trace-viewer/components/minimap.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/minimap.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/minimap.js +337 -0
- package/dist/components/new-trace-viewer/components/pane-constants.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/pane-constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/pane-constants.js +16 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +1 -4
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/split-pane.js +18 -16
- package/dist/components/new-trace-viewer/components/timeline.d.ts +2 -2
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/timeline.js +5 -7
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +10 -3
- package/dist/components/new-trace-viewer/components/use-element-width.d.ts +8 -0
- package/dist/components/new-trace-viewer/components/use-element-width.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/use-element-width.js +25 -0
- package/dist/components/new-trace-viewer/context.d.ts +2 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/context.js +3 -2
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.js +53 -181
- package/dist/components/new-trace-viewer/utils.d.ts +21 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/utils.js +44 -3
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +38 -35
- package/dist/components/sidebar/attributes-block.d.ts +18 -7
- package/dist/components/sidebar/attributes-block.d.ts.map +1 -1
- package/dist/components/sidebar/attributes-block.js +76 -18
- package/dist/components/sidebar/events-list.d.ts +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +9 -10
- package/dist/components/trace-viewer/components/map.d.ts +2 -2
- package/dist/components/trace-viewer/components/map.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/node.d.ts +7 -7
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
- package/dist/components/ui/collapsible.d.ts +46 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +66 -0
- package/dist/components/ui/data-inspector.js +2 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/hydration.d.ts +12 -2
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +41 -18
- package/dist/lib/utils.d.ts +8 -5
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +13 -16
- package/package.json +6 -6
- package/src/components/new-trace-viewer/components/detail-panel-width.test.ts +123 -0
- package/src/components/new-trace-viewer/components/detail-panel-width.ts +76 -0
- package/src/components/new-trace-viewer/components/detail-panel.tsx +272 -0
- package/src/components/new-trace-viewer/components/draggable-border.tsx +216 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +3 -5
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +1 -1
- package/src/components/new-trace-viewer/components/minimap.tsx +559 -0
- package/src/components/new-trace-viewer/components/pane-constants.ts +19 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +44 -55
- package/src/components/new-trace-viewer/components/timeline.tsx +3 -5
- package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +21 -1
- package/src/components/new-trace-viewer/components/use-element-width.ts +29 -0
- package/src/components/new-trace-viewer/context.tsx +4 -1
- package/src/components/new-trace-viewer/trace-viewer.tsx +93 -312
- package/src/components/new-trace-viewer/utils.test.ts +65 -0
- package/src/components/new-trace-viewer/utils.ts +76 -2
- package/src/components/sidebar/attribute-panel.tsx +106 -105
- package/src/components/sidebar/attributes-block.tsx +170 -50
- package/src/components/sidebar/events-list.tsx +107 -92
- package/src/components/ui/collapsible.tsx +219 -0
- package/src/index.ts +8 -7
- package/src/lib/hydration.ts +48 -25
- package/src/lib/utils.test.ts +33 -0
- package/src/lib/utils.ts +12 -16
- package/dist/components/sidebar/detail-card.d.ts +0 -13
- package/dist/components/sidebar/detail-card.d.ts.map +0 -1
- package/dist/components/sidebar/detail-card.js +0 -36
- package/src/components/sidebar/detail-card.tsx +0 -143
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
ChevronDown,
|
|
6
|
-
ChevronUp,
|
|
7
|
-
RotateCcw,
|
|
8
|
-
Search,
|
|
9
|
-
X,
|
|
10
|
-
ZoomIn,
|
|
11
|
-
ZoomOut,
|
|
12
|
-
} from 'lucide-react';
|
|
3
|
+
import { RotateCcw, Search, ZoomIn, ZoomOut } from 'lucide-react';
|
|
13
4
|
import {
|
|
14
5
|
type ReactNode,
|
|
15
6
|
useCallback,
|
|
@@ -21,24 +12,17 @@ import {
|
|
|
21
12
|
} from 'react';
|
|
22
13
|
import { useLoadMoreOnScroll } from '../../hooks/use-load-more-on-scroll';
|
|
23
14
|
import { useReducedMotion } from '../../hooks/use-reduced-motion';
|
|
24
|
-
import { filterSpanRawEvents } from '../../lib/trace-builder';
|
|
25
|
-
import { ErrorBoundary } from '../error-boundary';
|
|
26
15
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from '../
|
|
30
|
-
import { useSidebarData } from '../sidebar/sidebar-data-context';
|
|
31
|
-
import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
|
|
16
|
+
formatDurationPrecise,
|
|
17
|
+
getHighResInMs,
|
|
18
|
+
} from '../trace-viewer/util/timing';
|
|
32
19
|
import { IconButton } from '../ui/icon-button';
|
|
33
20
|
import { Kbd } from '../ui/kbd';
|
|
34
21
|
import { Spinner } from '../ui/spinner';
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
TooltipContent,
|
|
38
|
-
TooltipProvider,
|
|
39
|
-
TooltipTrigger,
|
|
40
|
-
} from '../ui/tooltip';
|
|
22
|
+
import { TooltipProvider } from '../ui/tooltip';
|
|
23
|
+
import { TraceDetailPanel } from './components/detail-panel';
|
|
41
24
|
import EventList from './components/event-list';
|
|
25
|
+
import { Minimap } from './components/minimap';
|
|
42
26
|
import { SplitPane } from './components/split-pane';
|
|
43
27
|
import {
|
|
44
28
|
TIMELINE_PADDING_PX,
|
|
@@ -50,7 +34,14 @@ import { ROW_HEIGHT_PX, scrollRowIntoView } from './components/use-row-window';
|
|
|
50
34
|
import { ActiveSpanProvider, useActiveSpan } from './context';
|
|
51
35
|
import { searchSpans } from './search';
|
|
52
36
|
import type { TraceWithMeta } from './types';
|
|
53
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
clampViewportToRoot,
|
|
39
|
+
computeRootBounds,
|
|
40
|
+
computeTimeMarkers,
|
|
41
|
+
type ViewportRange,
|
|
42
|
+
wheelDeltaToPixels,
|
|
43
|
+
wheelZoomScaleFactor,
|
|
44
|
+
} from './utils';
|
|
54
45
|
|
|
55
46
|
interface NewTraceViewerProps {
|
|
56
47
|
trace: TraceWithMeta;
|
|
@@ -63,17 +54,12 @@ const MIN_VIEWPORT_MS = 0.001;
|
|
|
63
54
|
|
|
64
55
|
const ZOOM_DEBOUNCE_MS = 150;
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
end: number;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function useAnimatedViewport(initial: Viewport) {
|
|
72
|
-
const [viewport, setViewportState] = useState<Viewport>(initial);
|
|
57
|
+
function useAnimatedViewport(initial: ViewportRange) {
|
|
58
|
+
const [viewport, setViewportState] = useState<ViewportRange>(initial);
|
|
73
59
|
const animRef = useRef<{
|
|
74
60
|
raf: number;
|
|
75
|
-
from:
|
|
76
|
-
to:
|
|
61
|
+
from: ViewportRange;
|
|
62
|
+
to: ViewportRange;
|
|
77
63
|
start: number;
|
|
78
64
|
} | null>(null);
|
|
79
65
|
const currentRef = useRef(initial);
|
|
@@ -88,7 +74,7 @@ function useAnimatedViewport(initial: Viewport) {
|
|
|
88
74
|
}, []);
|
|
89
75
|
|
|
90
76
|
const animateTo = useCallback(
|
|
91
|
-
(target:
|
|
77
|
+
(target: ViewportRange) => {
|
|
92
78
|
cancel();
|
|
93
79
|
|
|
94
80
|
if (reducedMotion) {
|
|
@@ -118,7 +104,7 @@ function useAnimatedViewport(initial: Viewport) {
|
|
|
118
104
|
);
|
|
119
105
|
|
|
120
106
|
const setViewport = useCallback(
|
|
121
|
-
(update:
|
|
107
|
+
(update: ViewportRange | ((prev: ViewportRange) => ViewportRange)) => {
|
|
122
108
|
cancel();
|
|
123
109
|
if (typeof update === 'function') {
|
|
124
110
|
setViewportState((prev) => {
|
|
@@ -139,33 +125,6 @@ function useAnimatedViewport(initial: Viewport) {
|
|
|
139
125
|
return { viewport, setViewport, animateTo };
|
|
140
126
|
}
|
|
141
127
|
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// Hook: bridge ActiveSpanContext + SidebarDataContext → SelectedSpanInfo
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
function useSelectedSpanInfo(): SelectedSpanInfo | null {
|
|
147
|
-
const { activeSpan } = useActiveSpan();
|
|
148
|
-
const sidebar = useSidebarData();
|
|
149
|
-
|
|
150
|
-
return useMemo(() => {
|
|
151
|
-
if (!activeSpan) return null;
|
|
152
|
-
|
|
153
|
-
const resource = activeSpan.attributes?.resource as string | undefined;
|
|
154
|
-
const rawEvents = filterSpanRawEvents(
|
|
155
|
-
sidebar.events,
|
|
156
|
-
resource,
|
|
157
|
-
activeSpan.spanId
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
data: activeSpan.attributes?.data,
|
|
162
|
-
resource,
|
|
163
|
-
spanId: activeSpan.spanId,
|
|
164
|
-
rawEvents,
|
|
165
|
-
};
|
|
166
|
-
}, [activeSpan, sidebar]);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
128
|
// ---------------------------------------------------------------------------
|
|
170
129
|
// Root component
|
|
171
130
|
// ---------------------------------------------------------------------------
|
|
@@ -196,16 +155,15 @@ function NewTraceViewerContent({
|
|
|
196
155
|
hasMore,
|
|
197
156
|
isLoadingMore,
|
|
198
157
|
}: NewTraceViewerProps): ReactNode {
|
|
199
|
-
const {
|
|
200
|
-
useActiveSpan();
|
|
158
|
+
const { activeSpanId, setActiveSpan, clearActiveSpan } = useActiveSpan();
|
|
201
159
|
|
|
202
|
-
const sidebar = useSidebarData();
|
|
203
|
-
const selectedSpan = useSelectedSpanInfo();
|
|
204
160
|
const reducedMotion = useReducedMotion();
|
|
205
161
|
|
|
206
162
|
const [searchQuery, setSearchQuery] = useState('');
|
|
207
163
|
const deferredSearchQuery = useDeferredValue(searchQuery);
|
|
208
164
|
|
|
165
|
+
const paneRootRef = useRef<HTMLDivElement>(null);
|
|
166
|
+
|
|
209
167
|
const searchResult = useMemo(
|
|
210
168
|
() => searchSpans(trace.spans, deferredSearchQuery),
|
|
211
169
|
[trace.spans, deferredSearchQuery]
|
|
@@ -228,7 +186,7 @@ function NewTraceViewerContent({
|
|
|
228
186
|
end: root.startTime + root.duration,
|
|
229
187
|
});
|
|
230
188
|
|
|
231
|
-
const prevRootRef = useRef<
|
|
189
|
+
const prevRootRef = useRef<ViewportRange>({
|
|
232
190
|
start: root.startTime,
|
|
233
191
|
end: root.startTime + root.duration,
|
|
234
192
|
});
|
|
@@ -269,64 +227,51 @@ function NewTraceViewerContent({
|
|
|
269
227
|
animateTo({ start: root.startTime, end: root.startTime + root.duration });
|
|
270
228
|
}, [animateTo, root.startTime, root.duration]);
|
|
271
229
|
|
|
230
|
+
const clampToRoot = useCallback(
|
|
231
|
+
(next: ViewportRange): ViewportRange =>
|
|
232
|
+
clampViewportToRoot(next, root.startTime, root.endTime, MIN_VIEWPORT_MS),
|
|
233
|
+
[root.startTime, root.endTime]
|
|
234
|
+
);
|
|
235
|
+
|
|
272
236
|
// Pan (keeping the current zoom) so `timeMs` is centered in view — used by the
|
|
273
237
|
// off-screen marker indicators to scroll their marker into view.
|
|
274
238
|
const handleRevealTime = useCallback(
|
|
275
239
|
(timeMs: number) => {
|
|
276
|
-
const rootS = root.startTime;
|
|
277
|
-
const rootE = root.startTime + root.duration;
|
|
278
240
|
const { start, end } = viewportRef.current;
|
|
279
241
|
const duration = end - start;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (newEnd > rootE) {
|
|
287
|
-
newEnd = rootE;
|
|
288
|
-
newStart = Math.max(rootS, rootE - duration);
|
|
289
|
-
}
|
|
290
|
-
animateTo({ start: newStart, end: newEnd });
|
|
242
|
+
animateTo(
|
|
243
|
+
clampToRoot({
|
|
244
|
+
start: timeMs - duration / 2,
|
|
245
|
+
end: timeMs + duration / 2,
|
|
246
|
+
})
|
|
247
|
+
);
|
|
291
248
|
},
|
|
292
|
-
[animateTo,
|
|
249
|
+
[animateTo, clampToRoot]
|
|
293
250
|
);
|
|
294
251
|
|
|
295
252
|
const ZOOM_FACTOR = 0.5;
|
|
296
253
|
|
|
297
254
|
const zoomBy = useCallback(
|
|
298
255
|
(factor: number) => {
|
|
299
|
-
const rootS = root.startTime;
|
|
300
|
-
const rootE = root.startTime + root.duration;
|
|
301
|
-
const rootD = root.duration;
|
|
302
|
-
|
|
303
256
|
setViewport((prev) => {
|
|
304
|
-
const prevDuration = prev.end - prev.start;
|
|
305
257
|
const center = (prev.start + prev.end) / 2;
|
|
306
258
|
const newDuration = Math.max(
|
|
307
259
|
MIN_VIEWPORT_MS,
|
|
308
|
-
|
|
260
|
+
(prev.end - prev.start) * factor
|
|
309
261
|
);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
newStart = rootS;
|
|
315
|
-
newEnd = rootS + newDuration;
|
|
316
|
-
}
|
|
317
|
-
if (newEnd > rootE) {
|
|
318
|
-
newEnd = rootE;
|
|
319
|
-
newStart = Math.max(rootS, rootE - newDuration);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return { start: newStart, end: newEnd };
|
|
262
|
+
return clampToRoot({
|
|
263
|
+
start: center - newDuration / 2,
|
|
264
|
+
end: center + newDuration / 2,
|
|
265
|
+
});
|
|
323
266
|
});
|
|
324
267
|
},
|
|
325
|
-
[setViewport,
|
|
268
|
+
[setViewport, clampToRoot]
|
|
326
269
|
);
|
|
327
270
|
|
|
328
271
|
const zoomIn = useCallback(() => zoomBy(ZOOM_FACTOR), [zoomBy]);
|
|
329
272
|
const zoomOut = useCallback(() => zoomBy(1 / ZOOM_FACTOR), [zoomBy]);
|
|
273
|
+
const isAtMinZoom = viewDuration >= root.duration;
|
|
274
|
+
const isAtMaxZoom = viewDuration <= MIN_VIEWPORT_MS;
|
|
330
275
|
|
|
331
276
|
const focusViewportOnSpan = useCallback(
|
|
332
277
|
(spanId: string) => {
|
|
@@ -337,12 +282,8 @@ function NewTraceViewerContent({
|
|
|
337
282
|
const spanEnd = getHighResInMs(span.endTime);
|
|
338
283
|
const spanDuration = spanEnd - spanStart;
|
|
339
284
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const rootD = root.duration;
|
|
343
|
-
|
|
344
|
-
if (spanDuration > rootD * 0.8) {
|
|
345
|
-
animateTo({ start: rootS, end: rootE });
|
|
285
|
+
if (spanDuration > root.duration * 0.8) {
|
|
286
|
+
animateTo({ start: root.startTime, end: root.endTime });
|
|
346
287
|
return;
|
|
347
288
|
}
|
|
348
289
|
|
|
@@ -356,20 +297,16 @@ function NewTraceViewerContent({
|
|
|
356
297
|
newEnd = center + MIN_VIEWPORT_MS / 2;
|
|
357
298
|
}
|
|
358
299
|
|
|
359
|
-
|
|
360
|
-
const duration = newEnd - newStart;
|
|
361
|
-
newStart = rootS;
|
|
362
|
-
newEnd = Math.min(rootE, rootS + duration);
|
|
363
|
-
}
|
|
364
|
-
if (newEnd > rootE) {
|
|
365
|
-
const duration = newEnd - newStart;
|
|
366
|
-
newEnd = rootE;
|
|
367
|
-
newStart = Math.max(rootS, rootE - duration);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
animateTo({ start: newStart, end: newEnd });
|
|
300
|
+
animateTo(clampToRoot({ start: newStart, end: newEnd }));
|
|
371
301
|
},
|
|
372
|
-
[
|
|
302
|
+
[
|
|
303
|
+
animateTo,
|
|
304
|
+
trace.spans,
|
|
305
|
+
root.startTime,
|
|
306
|
+
root.endTime,
|
|
307
|
+
root.duration,
|
|
308
|
+
clampToRoot,
|
|
309
|
+
]
|
|
373
310
|
);
|
|
374
311
|
|
|
375
312
|
// Bring a row into view when keyboard/button navigation lands on a span that
|
|
@@ -440,30 +377,11 @@ function NewTraceViewerContent({
|
|
|
440
377
|
const [altHeld, setAltHeld] = useState(false);
|
|
441
378
|
|
|
442
379
|
useEffect(() => {
|
|
443
|
-
const handleSidebarNavKey = (e: KeyboardEvent): void => {
|
|
444
|
-
const target = e.target as HTMLElement | null;
|
|
445
|
-
if (
|
|
446
|
-
target instanceof HTMLInputElement ||
|
|
447
|
-
target instanceof HTMLTextAreaElement ||
|
|
448
|
-
target?.isContentEditable
|
|
449
|
-
) {
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
const targetId =
|
|
453
|
-
e.key === 'k' ? prevSpanIdRef.current : nextSpanIdRef.current;
|
|
454
|
-
if (targetId) {
|
|
455
|
-
e.preventDefault();
|
|
456
|
-
navigateToSpanRef.current(targetId);
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
|
|
460
380
|
const onKeyDown = (e: KeyboardEvent): void => {
|
|
461
381
|
if (e.key === 'Escape') {
|
|
462
382
|
handleClearActiveSpan();
|
|
463
383
|
} else if (e.key === 'Alt') {
|
|
464
384
|
setAltHeld(true);
|
|
465
|
-
} else if (e.key === 'j' || e.key === 'k') {
|
|
466
|
-
handleSidebarNavKey(e);
|
|
467
385
|
}
|
|
468
386
|
};
|
|
469
387
|
const onKeyUp = (e: KeyboardEvent): void => {
|
|
@@ -488,7 +406,7 @@ function NewTraceViewerContent({
|
|
|
488
406
|
if (hoverFraction == null) return null;
|
|
489
407
|
const absTime = viewport.start + hoverFraction * viewDuration;
|
|
490
408
|
const offset = absTime - root.startTime;
|
|
491
|
-
return { fraction: hoverFraction, label:
|
|
409
|
+
return { fraction: hoverFraction, label: formatDurationPrecise(offset) };
|
|
492
410
|
}, [hoverFraction, viewport.start, viewDuration, root.startTime]);
|
|
493
411
|
|
|
494
412
|
const handleTimelineMouseMove = useCallback(
|
|
@@ -516,12 +434,7 @@ function NewTraceViewerContent({
|
|
|
516
434
|
|
|
517
435
|
useEffect(() => {
|
|
518
436
|
const el = timelineRef.current;
|
|
519
|
-
if (!el) return;
|
|
520
|
-
|
|
521
|
-
const rootS = root.startTime;
|
|
522
|
-
const rootE = root.startTime + root.duration;
|
|
523
|
-
const rootD = root.duration;
|
|
524
|
-
if (rootD <= 0) return;
|
|
437
|
+
if (!el || root.duration <= 0) return;
|
|
525
438
|
|
|
526
439
|
const onWheel = (e: WheelEvent): void => {
|
|
527
440
|
const isZoomGesture = e.ctrlKey || e.metaKey;
|
|
@@ -535,9 +448,6 @@ function NewTraceViewerContent({
|
|
|
535
448
|
if (contentWidth <= 0) return;
|
|
536
449
|
|
|
537
450
|
if (isZoomGesture) {
|
|
538
|
-
let dy = e.deltaY;
|
|
539
|
-
if (e.deltaMode === 1) dy *= 16;
|
|
540
|
-
|
|
541
451
|
const cursorFraction = Math.max(
|
|
542
452
|
0,
|
|
543
453
|
Math.min(
|
|
@@ -545,115 +455,56 @@ function NewTraceViewerContent({
|
|
|
545
455
|
(e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
|
|
546
456
|
)
|
|
547
457
|
);
|
|
548
|
-
const
|
|
549
|
-
const scaleFactor = 2 ** (dy / (isMouseWheel ? 200 : 60));
|
|
458
|
+
const scaleFactor = wheelZoomScaleFactor(e);
|
|
550
459
|
|
|
551
460
|
setViewport((prev) => {
|
|
552
461
|
const prevDuration = prev.end - prev.start;
|
|
553
462
|
const cursorTime = prev.start + cursorFraction * prevDuration;
|
|
463
|
+
// Clamp the duration before anchoring so the cursor keeps its
|
|
464
|
+
// fraction even when the zoom hits the min/max bounds.
|
|
554
465
|
const newDuration = Math.max(
|
|
555
466
|
MIN_VIEWPORT_MS,
|
|
556
|
-
Math.min(
|
|
467
|
+
Math.min(root.duration, prevDuration * scaleFactor)
|
|
557
468
|
);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (newStart < rootS) {
|
|
563
|
-
newStart = rootS;
|
|
564
|
-
newEnd = rootS + newDuration;
|
|
565
|
-
}
|
|
566
|
-
if (newEnd > rootE) {
|
|
567
|
-
newEnd = rootE;
|
|
568
|
-
newStart = Math.max(rootS, rootE - newDuration);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return { start: newStart, end: newEnd };
|
|
469
|
+
return clampToRoot({
|
|
470
|
+
start: cursorTime - cursorFraction * newDuration,
|
|
471
|
+
end: cursorTime + (1 - cursorFraction) * newDuration,
|
|
472
|
+
});
|
|
572
473
|
});
|
|
573
474
|
} else {
|
|
574
|
-
|
|
575
|
-
if (e.deltaMode === 1) dx *= 16;
|
|
576
|
-
|
|
475
|
+
const dx = wheelDeltaToPixels(e.deltaX, e.deltaMode);
|
|
577
476
|
setViewport((prev) => {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (newStart < rootS) {
|
|
585
|
-
newStart = rootS;
|
|
586
|
-
newEnd = rootS + prevDuration;
|
|
587
|
-
}
|
|
588
|
-
if (newEnd > rootE) {
|
|
589
|
-
newEnd = rootE;
|
|
590
|
-
newStart = Math.max(rootS, rootE - prevDuration);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return { start: newStart, end: newEnd };
|
|
477
|
+
const panAmount = (dx / contentWidth) * (prev.end - prev.start);
|
|
478
|
+
return clampToRoot({
|
|
479
|
+
start: prev.start + panAmount,
|
|
480
|
+
end: prev.end + panAmount,
|
|
481
|
+
});
|
|
594
482
|
});
|
|
595
483
|
}
|
|
596
484
|
};
|
|
597
485
|
|
|
598
486
|
el.addEventListener('wheel', onWheel, { passive: false });
|
|
599
487
|
return () => el.removeEventListener('wheel', onWheel);
|
|
600
|
-
}, [root.
|
|
601
|
-
|
|
602
|
-
// Derive the selected span name and metadata for the panel header
|
|
603
|
-
const selectedSpanName = useMemo(() => {
|
|
604
|
-
if (!selectedSpan?.data) return 'Details';
|
|
605
|
-
const data = selectedSpan.data as Record<string, unknown>;
|
|
606
|
-
if (selectedSpan.resource === 'hook') {
|
|
607
|
-
return (data.token as string | undefined) ?? (data.hookId as string);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const stepName = data.stepName as string | undefined;
|
|
611
|
-
const workflowName = data.workflowName as string | undefined;
|
|
612
|
-
return (
|
|
613
|
-
(stepName ? parseStepName(stepName)?.shortName : undefined) ??
|
|
614
|
-
(workflowName ? parseWorkflowName(workflowName)?.shortName : undefined) ??
|
|
615
|
-
stepName ??
|
|
616
|
-
workflowName ??
|
|
617
|
-
(data.hookId as string) ??
|
|
618
|
-
'Details'
|
|
619
|
-
);
|
|
620
|
-
}, [selectedSpan?.data, selectedSpan?.resource]);
|
|
621
|
-
|
|
622
|
-
const { prevSpanId, nextSpanId } = useMemo(() => {
|
|
623
|
-
if (!activeSpanId) return { prevSpanId: null, nextSpanId: null };
|
|
624
|
-
const i = trace.spans.findIndex((s) => s.spanId === activeSpanId);
|
|
625
|
-
if (i === -1) return { prevSpanId: null, nextSpanId: null };
|
|
626
|
-
return {
|
|
627
|
-
prevSpanId: trace.spans[i - 1]?.spanId ?? null,
|
|
628
|
-
nextSpanId: trace.spans[i + 1]?.spanId ?? null,
|
|
629
|
-
};
|
|
630
|
-
}, [activeSpanId, trace.spans]);
|
|
631
|
-
|
|
632
|
-
const handleSelectPrevSpan = useCallback(() => {
|
|
633
|
-
if (prevSpanId) navigateToSpan(prevSpanId);
|
|
634
|
-
}, [prevSpanId, navigateToSpan]);
|
|
635
|
-
|
|
636
|
-
const handleSelectNextSpan = useCallback(() => {
|
|
637
|
-
if (nextSpanId) navigateToSpan(nextSpanId);
|
|
638
|
-
}, [nextSpanId, navigateToSpan]);
|
|
639
|
-
|
|
640
|
-
const prevSpanIdRef = useRef(prevSpanId);
|
|
641
|
-
const nextSpanIdRef = useRef(nextSpanId);
|
|
642
|
-
const navigateToSpanRef = useRef(navigateToSpan);
|
|
643
|
-
prevSpanIdRef.current = prevSpanId;
|
|
644
|
-
nextSpanIdRef.current = nextSpanId;
|
|
645
|
-
navigateToSpanRef.current = navigateToSpan;
|
|
488
|
+
}, [root.duration, setViewport, clampToRoot]);
|
|
646
489
|
|
|
647
490
|
return (
|
|
648
491
|
<div
|
|
492
|
+
ref={paneRootRef}
|
|
649
493
|
data-pane="pane-root"
|
|
650
|
-
|
|
651
|
-
className="relative grid w-full h-full max-h-full grid-cols-[minmax(100px,1fr)] data-[has-detail]:grid-cols-[minmax(100px,1fr)_clamp(280px,360px,100%)]"
|
|
494
|
+
className="relative flex w-full h-full max-h-full"
|
|
652
495
|
>
|
|
653
496
|
<div
|
|
654
497
|
id="trace-parent"
|
|
655
|
-
className="grid grid-rows-[
|
|
498
|
+
className="flex-1 min-w-0 grid grid-rows-[auto_1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
|
|
656
499
|
>
|
|
500
|
+
<Minimap
|
|
501
|
+
spans={trace.spans}
|
|
502
|
+
root={root}
|
|
503
|
+
viewport={viewport}
|
|
504
|
+
minViewportMs={MIN_VIEWPORT_MS}
|
|
505
|
+
onViewportChange={setViewport}
|
|
506
|
+
onAnimateTo={animateTo}
|
|
507
|
+
/>
|
|
657
508
|
<SplitPane
|
|
658
509
|
scrollContainerRef={scrollContainerRef}
|
|
659
510
|
startHeader={
|
|
@@ -713,6 +564,7 @@ function NewTraceViewerContent({
|
|
|
713
564
|
{/* biome-ignore lint/a11y/noStaticElementInteractions: timeline hover and wheel gestures are pointer-only annotations */}
|
|
714
565
|
<div
|
|
715
566
|
ref={timelineRef}
|
|
567
|
+
id="trace-timeline"
|
|
716
568
|
className="block min-h-0 overflow-visible relative"
|
|
717
569
|
onDoubleClick={resetZoom}
|
|
718
570
|
onMouseMove={handleTimelineMouseMove}
|
|
@@ -737,6 +589,7 @@ function NewTraceViewerContent({
|
|
|
737
589
|
variant="muted"
|
|
738
590
|
size="small"
|
|
739
591
|
onClick={zoomOut}
|
|
592
|
+
disabled={isAtMinZoom}
|
|
740
593
|
aria-label="Zoom out"
|
|
741
594
|
>
|
|
742
595
|
<ZoomOut className="w-4 h-4" />
|
|
@@ -753,6 +606,7 @@ function NewTraceViewerContent({
|
|
|
753
606
|
variant="muted"
|
|
754
607
|
size="small"
|
|
755
608
|
onClick={zoomIn}
|
|
609
|
+
disabled={isAtMaxZoom}
|
|
756
610
|
aria-label="Zoom in"
|
|
757
611
|
>
|
|
758
612
|
<ZoomIn className="w-4 h-4" />
|
|
@@ -760,84 +614,11 @@ function NewTraceViewerContent({
|
|
|
760
614
|
</div>
|
|
761
615
|
</div>
|
|
762
616
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
<span className="text-label-14 font-medium text-gray-1000 truncate block">
|
|
769
|
-
{selectedSpanName}
|
|
770
|
-
</span>
|
|
771
|
-
<div className="flex items-center gap-0.5 shrink-0">
|
|
772
|
-
<Tooltip>
|
|
773
|
-
<TooltipTrigger asChild>
|
|
774
|
-
<IconButton
|
|
775
|
-
aria-label="Navigate up"
|
|
776
|
-
aria-keyshortcuts="K"
|
|
777
|
-
onClick={handleSelectPrevSpan}
|
|
778
|
-
disabled={!prevSpanId}
|
|
779
|
-
>
|
|
780
|
-
<ChevronUp className="w-4 h-4" />
|
|
781
|
-
</IconButton>
|
|
782
|
-
</TooltipTrigger>
|
|
783
|
-
{prevSpanId ? (
|
|
784
|
-
<TooltipContent>
|
|
785
|
-
Navigate up
|
|
786
|
-
<Kbd>K</Kbd>
|
|
787
|
-
</TooltipContent>
|
|
788
|
-
) : null}
|
|
789
|
-
</Tooltip>
|
|
790
|
-
<Tooltip>
|
|
791
|
-
<TooltipTrigger asChild>
|
|
792
|
-
<IconButton
|
|
793
|
-
aria-label="Navigate down"
|
|
794
|
-
aria-keyshortcuts="J"
|
|
795
|
-
onClick={handleSelectNextSpan}
|
|
796
|
-
disabled={!nextSpanId}
|
|
797
|
-
>
|
|
798
|
-
<ChevronDown className="w-4 h-4" />
|
|
799
|
-
</IconButton>
|
|
800
|
-
</TooltipTrigger>
|
|
801
|
-
{nextSpanId ? (
|
|
802
|
-
<TooltipContent>
|
|
803
|
-
Navigate down
|
|
804
|
-
<Kbd>J</Kbd>
|
|
805
|
-
</TooltipContent>
|
|
806
|
-
) : null}
|
|
807
|
-
</Tooltip>
|
|
808
|
-
<div aria-hidden className="w-px h-4 bg-gray-alpha-400 mx-1" />
|
|
809
|
-
<IconButton
|
|
810
|
-
aria-label="Close span details"
|
|
811
|
-
aria-keyshortcuts="Escape"
|
|
812
|
-
onClick={handleClearActiveSpan}
|
|
813
|
-
>
|
|
814
|
-
<X className="w-4 h-4" />
|
|
815
|
-
</IconButton>
|
|
816
|
-
</div>
|
|
817
|
-
</div>
|
|
818
|
-
{/* Panel body */}
|
|
819
|
-
<div className="flex-1 overflow-y-auto">
|
|
820
|
-
<ErrorBoundary>
|
|
821
|
-
<EntityDetailPanel
|
|
822
|
-
run={sidebar.run}
|
|
823
|
-
onStreamClick={sidebar.onStreamClick}
|
|
824
|
-
onRunClick={sidebar.onRunClick}
|
|
825
|
-
fetchSpanDetail={sidebar.fetchSpanDetail}
|
|
826
|
-
onWakeUpSleep={sidebar.onWakeUpSleep}
|
|
827
|
-
onLoadEventData={sidebar.onLoadEventData}
|
|
828
|
-
onResolveHook={sidebar.onResolveHook}
|
|
829
|
-
encryptionKey={sidebar.encryptionKey}
|
|
830
|
-
onDecrypt={sidebar.onDecrypt}
|
|
831
|
-
isDecrypting={sidebar.isDecrypting}
|
|
832
|
-
selectedSpan={selectedSpan}
|
|
833
|
-
showSeparateEventOccurrenceTimestamps={
|
|
834
|
-
sidebar.showSeparateEventOccurrenceTimestamps
|
|
835
|
-
}
|
|
836
|
-
/>
|
|
837
|
-
</ErrorBoundary>
|
|
838
|
-
</div>
|
|
839
|
-
</aside>
|
|
840
|
-
) : null}
|
|
617
|
+
<TraceDetailPanel
|
|
618
|
+
containerRef={paneRootRef}
|
|
619
|
+
onNavigateToSpan={navigateToSpan}
|
|
620
|
+
onClose={handleClearActiveSpan}
|
|
621
|
+
/>
|
|
841
622
|
|
|
842
623
|
<TraceShortcutHelper
|
|
843
624
|
hasMultipleSpans={trace.spans.length > 1}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { Span, SpanEvent } from './types';
|
|
3
3
|
import {
|
|
4
|
+
clampViewportToRoot,
|
|
4
5
|
computeOffscreenMarkers,
|
|
5
6
|
computeSpanMarkers,
|
|
6
7
|
computeSpanSegments,
|
|
8
|
+
computeTimeMarkers,
|
|
7
9
|
} from './utils';
|
|
8
10
|
|
|
9
11
|
/** Build a high-res timestamp tuple ([seconds, nanoseconds]) for a given ms. */
|
|
@@ -180,3 +182,66 @@ describe('computeOffscreenMarkers', () => {
|
|
|
180
182
|
});
|
|
181
183
|
});
|
|
182
184
|
});
|
|
185
|
+
|
|
186
|
+
describe('clampViewportToRoot', () => {
|
|
187
|
+
const clamp = (next: { start: number; end: number }) =>
|
|
188
|
+
clampViewportToRoot(next, 100, 1100, 10);
|
|
189
|
+
|
|
190
|
+
it('passes through a window already inside the root', () => {
|
|
191
|
+
expect(clamp({ start: 200, end: 400 })).toEqual({ start: 200, end: 400 });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('shifts a window past the left edge without changing its duration', () => {
|
|
195
|
+
expect(clamp({ start: 50, end: 250 })).toEqual({ start: 100, end: 300 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('shifts a window past the right edge without changing its duration', () => {
|
|
199
|
+
expect(clamp({ start: 1000, end: 1200 })).toEqual({
|
|
200
|
+
start: 900,
|
|
201
|
+
end: 1100,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('clamps a window wider than the root to the full extent', () => {
|
|
206
|
+
expect(clamp({ start: 0, end: 5000 })).toEqual({ start: 100, end: 1100 });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('enforces the minimum duration', () => {
|
|
210
|
+
expect(clamp({ start: 500, end: 502 })).toEqual({ start: 500, end: 510 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('keeps a minimum-duration window inside the root near the right edge', () => {
|
|
214
|
+
expect(clamp({ start: 1098, end: 1099 })).toEqual({
|
|
215
|
+
start: 1090,
|
|
216
|
+
end: 1100,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('computeTimeMarkers', () => {
|
|
222
|
+
it('emits distinct, precise labels across a sub-second-step window', () => {
|
|
223
|
+
// A ~3s window drops the tick step to 500ms. Before the fix this rendered
|
|
224
|
+
// duplicate "2s, 2s, 3s, 3s" labels; now each tick is distinct.
|
|
225
|
+
const labels = computeTimeMarkers(3000, 0).map((m) => m.label);
|
|
226
|
+
expect(labels).toEqual(['0s', '500ms', '1s', '1.5s', '2s', '2.5s', '3s']);
|
|
227
|
+
expect(new Set(labels).size).toBe(labels.length);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('keeps clean whole-second labels when the step is >=1s', () => {
|
|
231
|
+
const labels = computeTimeMarkers(10_000, 0).map((m) => m.label);
|
|
232
|
+
expect(labels).toEqual(['0s', '2s', '4s', '6s', '8s', '10s']);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('still reads in ms when super zoomed in', () => {
|
|
236
|
+
const labels = computeTimeMarkers(120, 0).map((m) => m.label);
|
|
237
|
+
expect(labels).toEqual([
|
|
238
|
+
'0s',
|
|
239
|
+
'20ms',
|
|
240
|
+
'40ms',
|
|
241
|
+
'60ms',
|
|
242
|
+
'80ms',
|
|
243
|
+
'100ms',
|
|
244
|
+
'120ms',
|
|
245
|
+
]);
|
|
246
|
+
});
|
|
247
|
+
});
|