@workflow/web-shared 5.0.0-beta.3 → 5.0.0-beta.4
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/event-list.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/event-list.js +7 -4
- package/dist/components/new-trace-viewer/components/event-list.js.map +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/split-pane.js +2 -3
- package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -1
- package/dist/components/new-trace-viewer/components/timeline.d.ts +10 -11
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/timeline.js +57 -53
- package/dist/components/new-trace-viewer/components/timeline.js.map +1 -1
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/context.js +5 -7
- package/dist/components/new-trace-viewer/context.js.map +1 -1
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/detail-panel.js +2 -1
- package/dist/components/new-trace-viewer/detail-panel.js.map +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.js +37 -17
- package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -1
- package/dist/components/new-trace-viewer/utils.d.ts +3 -8
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/utils.js +12 -65
- package/dist/components/new-trace-viewer/utils.js.map +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts +1 -1
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +5 -8
- package/dist/components/trace-viewer/util/timing.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +16 -3
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +2 -1
- package/dist/lib/trace-builder.js.map +1 -1
- package/dist/lib/utils.d.ts +9 -7
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +27 -27
- package/dist/lib/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/components/new-trace-viewer/components/event-list.tsx +27 -20
- package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
- package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
- package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
- package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +2 -3
- package/src/components/new-trace-viewer/components/timeline.tsx +119 -115
- package/src/components/new-trace-viewer/context.tsx +7 -9
- package/src/components/new-trace-viewer/detail-panel.tsx +2 -1
- package/src/components/new-trace-viewer/trace-viewer.tsx +120 -60
- package/src/components/new-trace-viewer/utils.ts +19 -93
- package/src/components/trace-viewer/util/timing.ts +5 -9
- package/src/components/workflow-traces/trace-span-construction.ts +22 -4
- package/src/lib/trace-builder.ts +2 -1
- package/src/lib/utils.ts +29 -29
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
|
|
4
|
-
import { Search, X } from 'lucide-react';
|
|
4
|
+
import { RotateCcw, Search, X, ZoomIn, ZoomOut } from 'lucide-react';
|
|
5
5
|
import {
|
|
6
6
|
type ReactNode,
|
|
7
7
|
useCallback,
|
|
@@ -19,19 +19,22 @@ import { useSidebarDataOptional } from '../sidebar/sidebar-data-context';
|
|
|
19
19
|
import type { Trace } from '../trace-viewer/types';
|
|
20
20
|
import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
|
|
21
21
|
import { CopyButton } from './components/copy-button';
|
|
22
|
-
import { SplitPane } from './components/split-pane';
|
|
23
22
|
import EventList from './components/event-list';
|
|
24
|
-
import {
|
|
23
|
+
import { SplitPane } from './components/split-pane';
|
|
24
|
+
import {
|
|
25
|
+
Timeline,
|
|
26
|
+
TimelineHeader,
|
|
27
|
+
TIMELINE_PADDING_PX,
|
|
28
|
+
} from './components/timeline';
|
|
25
29
|
import { ActiveSpanProvider, useActiveSpan } from './context';
|
|
26
30
|
import { DetailPanel } from './detail-panel';
|
|
27
|
-
import {
|
|
31
|
+
import { computeRootBounds, computeTimeMarkers } from './utils';
|
|
28
32
|
|
|
29
33
|
interface NewTraceViewerProps {
|
|
30
34
|
trace: Trace;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
const MIN_VIEWPORT_MS = 0.001;
|
|
34
|
-
const TIMELINE_PADDING = 16;
|
|
35
38
|
|
|
36
39
|
interface Viewport {
|
|
37
40
|
start: number;
|
|
@@ -189,19 +192,51 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
189
192
|
|
|
190
193
|
const viewDuration = viewport.end - viewport.start;
|
|
191
194
|
|
|
192
|
-
const
|
|
193
|
-
() =>
|
|
194
|
-
[
|
|
195
|
+
const timeMarkers = useMemo(
|
|
196
|
+
() => computeTimeMarkers(viewDuration, viewport.start - root.startTime),
|
|
197
|
+
[viewDuration, viewport.start, root.startTime]
|
|
195
198
|
);
|
|
196
199
|
|
|
197
|
-
const isZoomed =
|
|
198
|
-
viewport.start > root.startTime + 0.01 ||
|
|
199
|
-
viewport.end < root.startTime + root.duration - 0.01;
|
|
200
|
-
|
|
201
200
|
const resetZoom = useCallback(() => {
|
|
202
201
|
animateTo({ start: root.startTime, end: root.startTime + root.duration });
|
|
203
202
|
}, [animateTo, root.startTime, root.duration]);
|
|
204
203
|
|
|
204
|
+
const ZOOM_FACTOR = 0.5;
|
|
205
|
+
|
|
206
|
+
const zoomBy = useCallback(
|
|
207
|
+
(factor: number) => {
|
|
208
|
+
const rootS = root.startTime;
|
|
209
|
+
const rootE = root.startTime + root.duration;
|
|
210
|
+
const rootD = root.duration;
|
|
211
|
+
|
|
212
|
+
setViewport((prev) => {
|
|
213
|
+
const prevDuration = prev.end - prev.start;
|
|
214
|
+
const center = (prev.start + prev.end) / 2;
|
|
215
|
+
const newDuration = Math.max(
|
|
216
|
+
MIN_VIEWPORT_MS,
|
|
217
|
+
Math.min(rootD, prevDuration * factor)
|
|
218
|
+
);
|
|
219
|
+
let newStart = center - newDuration / 2;
|
|
220
|
+
let newEnd = center + newDuration / 2;
|
|
221
|
+
|
|
222
|
+
if (newStart < rootS) {
|
|
223
|
+
newStart = rootS;
|
|
224
|
+
newEnd = rootS + newDuration;
|
|
225
|
+
}
|
|
226
|
+
if (newEnd > rootE) {
|
|
227
|
+
newEnd = rootE;
|
|
228
|
+
newStart = Math.max(rootS, rootE - newDuration);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { start: newStart, end: newEnd };
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
[setViewport, root.startTime, root.duration]
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const zoomIn = useCallback(() => zoomBy(ZOOM_FACTOR), [zoomBy]);
|
|
238
|
+
const zoomOut = useCallback(() => zoomBy(1 / ZOOM_FACTOR), [zoomBy]);
|
|
239
|
+
|
|
205
240
|
const handleSelectSpan = useCallback(
|
|
206
241
|
(spanId: string) => {
|
|
207
242
|
if (spanId === activeSpanId) {
|
|
@@ -292,22 +327,24 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
292
327
|
|
|
293
328
|
const hoverInfo = useMemo(() => {
|
|
294
329
|
if (hoverFraction == null) return null;
|
|
295
|
-
const absTime =
|
|
330
|
+
const absTime = viewport.start + hoverFraction * viewDuration;
|
|
296
331
|
const offset = absTime - root.startTime;
|
|
297
332
|
return { fraction: hoverFraction, label: formatDuration(offset, true) };
|
|
298
|
-
}, [hoverFraction,
|
|
333
|
+
}, [hoverFraction, viewport.start, viewDuration, root.startTime]);
|
|
299
334
|
|
|
300
335
|
const handleTimelineMouseMove = useCallback(
|
|
301
336
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
302
337
|
const el = timelineRef.current;
|
|
303
338
|
if (!el) return;
|
|
304
339
|
const rect = el.getBoundingClientRect();
|
|
305
|
-
const
|
|
306
|
-
const contentWidth = rect.width - padding * 2;
|
|
340
|
+
const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
|
|
307
341
|
if (contentWidth <= 0) return;
|
|
308
342
|
const fraction = Math.max(
|
|
309
343
|
0,
|
|
310
|
-
Math.min(
|
|
344
|
+
Math.min(
|
|
345
|
+
1,
|
|
346
|
+
(e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
|
|
347
|
+
)
|
|
311
348
|
);
|
|
312
349
|
setHoverFraction(fraction);
|
|
313
350
|
},
|
|
@@ -335,7 +372,7 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
335
372
|
e.preventDefault();
|
|
336
373
|
|
|
337
374
|
const rect = el.getBoundingClientRect();
|
|
338
|
-
const contentWidth = rect.width -
|
|
375
|
+
const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
|
|
339
376
|
if (contentWidth <= 0) return;
|
|
340
377
|
|
|
341
378
|
if (isZoomGesture) {
|
|
@@ -344,7 +381,10 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
344
381
|
|
|
345
382
|
const cursorFraction = Math.max(
|
|
346
383
|
0,
|
|
347
|
-
Math.min(
|
|
384
|
+
Math.min(
|
|
385
|
+
1,
|
|
386
|
+
(e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
|
|
387
|
+
)
|
|
348
388
|
);
|
|
349
389
|
const scaleFactor = Math.pow(2, dy / 200);
|
|
350
390
|
|
|
@@ -429,52 +469,44 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
429
469
|
|
|
430
470
|
return (
|
|
431
471
|
<div
|
|
432
|
-
data-pane=
|
|
472
|
+
data-pane='pane-root'
|
|
433
473
|
data-has-detail={activeSpan ? '' : undefined}
|
|
434
|
-
className=
|
|
474
|
+
className='grid w-full h-full max-h-full grid-cols-[minmax(100px,1fr)] data-[has-detail]:grid-cols-[minmax(100px,1fr)_clamp(280px,420px,100%)]'
|
|
435
475
|
>
|
|
436
476
|
<div
|
|
437
|
-
id=
|
|
438
|
-
className=
|
|
477
|
+
id='trace-parent'
|
|
478
|
+
className='grid grid-rows-[1fr] h-full min-h-0 overflow-hidden relative bg-background-100'
|
|
439
479
|
>
|
|
440
480
|
<SplitPane
|
|
441
481
|
startHeader={
|
|
442
|
-
<div className=
|
|
443
|
-
<Search className=
|
|
482
|
+
<div className='bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-center px-2 gap-1.5'>
|
|
483
|
+
<Search className='w-3.5 h-3.5 shrink-0 text-gray-800' />
|
|
444
484
|
<input
|
|
445
|
-
id=
|
|
446
|
-
name=
|
|
447
|
-
type=
|
|
485
|
+
id='trace-viewer-search'
|
|
486
|
+
name='trace-viewer-search'
|
|
487
|
+
type='text'
|
|
448
488
|
value={searchQuery}
|
|
449
489
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
450
|
-
placeholder=
|
|
451
|
-
aria-label=
|
|
452
|
-
className=
|
|
490
|
+
placeholder='Search spans...'
|
|
491
|
+
aria-label='Search spans'
|
|
492
|
+
className='flex-1 min-w-0 bg-transparent text-sm text-gray-1000 placeholder:text-gray-800 outline-none'
|
|
453
493
|
/>
|
|
454
494
|
{searchQuery && (
|
|
455
495
|
<button
|
|
456
|
-
type=
|
|
496
|
+
type='button'
|
|
457
497
|
onClick={() => setSearchQuery('')}
|
|
458
|
-
className=
|
|
498
|
+
className='shrink-0 p-0.5 rounded-sm text-gray-800 hover:text-gray-1000 hover:bg-gray-200 transition-colors'
|
|
459
499
|
>
|
|
460
|
-
<X className=
|
|
500
|
+
<X className='w-3 h-3' />
|
|
461
501
|
</button>
|
|
462
502
|
)}
|
|
463
503
|
</div>
|
|
464
504
|
}
|
|
465
505
|
endHeader={
|
|
466
|
-
<TimelineHeader
|
|
467
|
-
viewStart={viewport.start}
|
|
468
|
-
viewDuration={viewDuration}
|
|
469
|
-
rootStart={root.startTime}
|
|
470
|
-
compression={compression}
|
|
471
|
-
isZoomed={isZoomed}
|
|
472
|
-
onResetZoom={resetZoom}
|
|
473
|
-
hoverInfo={hoverInfo}
|
|
474
|
-
/>
|
|
506
|
+
<TimelineHeader markers={timeMarkers} hoverInfo={hoverInfo} />
|
|
475
507
|
}
|
|
476
508
|
>
|
|
477
|
-
<div className=
|
|
509
|
+
<div className='block overflow-visible'>
|
|
478
510
|
<EventList
|
|
479
511
|
spans={filteredSpans}
|
|
480
512
|
activeSpanId={activeSpanId}
|
|
@@ -483,14 +515,16 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
483
515
|
</div>
|
|
484
516
|
<div
|
|
485
517
|
ref={timelineRef}
|
|
486
|
-
className=
|
|
518
|
+
className='block min-h-0 overflow-visible relative'
|
|
487
519
|
onDoubleClick={resetZoom}
|
|
488
520
|
onMouseMove={handleTimelineMouseMove}
|
|
489
521
|
onMouseLeave={handleTimelineMouseLeave}
|
|
490
522
|
>
|
|
491
523
|
<Timeline
|
|
492
524
|
spans={filteredSpans}
|
|
493
|
-
|
|
525
|
+
viewStart={viewport.start}
|
|
526
|
+
viewEnd={viewport.end}
|
|
527
|
+
markers={timeMarkers}
|
|
494
528
|
selectedId={activeSpanId}
|
|
495
529
|
onSelect={handleSelectSpan}
|
|
496
530
|
hoverFraction={hoverFraction}
|
|
@@ -498,20 +532,46 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
498
532
|
/>
|
|
499
533
|
</div>
|
|
500
534
|
</SplitPane>
|
|
535
|
+
<div className='absolute right-3 bottom-3 z-[5] flex items-center border border-gray-alpha-400 rounded-lg bg-background-100 shadow-sm overflow-hidden divide-x divide-gray-alpha-400'>
|
|
536
|
+
<button
|
|
537
|
+
type='button'
|
|
538
|
+
className='flex items-center justify-center w-8 h-8 text-gray-900 cursor-pointer transition-colors duration-[120ms] ease-in-out hover:text-gray-1000 hover:bg-gray-alpha-100'
|
|
539
|
+
onClick={zoomOut}
|
|
540
|
+
aria-label='Zoom out'
|
|
541
|
+
>
|
|
542
|
+
<ZoomOut className='w-4 h-4' />
|
|
543
|
+
</button>
|
|
544
|
+
<button
|
|
545
|
+
type='button'
|
|
546
|
+
className='flex items-center justify-center w-8 h-8 text-gray-900 cursor-pointer transition-colors duration-[120ms] ease-in-out hover:text-gray-1000 hover:bg-gray-alpha-100'
|
|
547
|
+
onClick={resetZoom}
|
|
548
|
+
aria-label='Reset zoom'
|
|
549
|
+
>
|
|
550
|
+
<RotateCcw className='w-3.5 h-3.5' />
|
|
551
|
+
</button>
|
|
552
|
+
<button
|
|
553
|
+
type='button'
|
|
554
|
+
className='flex items-center justify-center w-8 h-8 text-gray-900 cursor-pointer transition-colors duration-[120ms] ease-in-out hover:text-gray-1000 hover:bg-gray-alpha-100'
|
|
555
|
+
onClick={zoomIn}
|
|
556
|
+
aria-label='Zoom in'
|
|
557
|
+
>
|
|
558
|
+
<ZoomIn className='w-4 h-4' />
|
|
559
|
+
</button>
|
|
560
|
+
</div>
|
|
501
561
|
</div>
|
|
502
562
|
|
|
503
563
|
{/* Detail panel */}
|
|
504
564
|
{activeSpan && sidebar ? (
|
|
505
|
-
<aside className=
|
|
565
|
+
<aside className='flex flex-col h-full max-h-full bg-background-100 border-l border-gray-alpha-400 overflow-auto'>
|
|
506
566
|
{/* Panel header */}
|
|
507
|
-
<div className=
|
|
508
|
-
<div className=
|
|
509
|
-
<div className=
|
|
510
|
-
<span className=
|
|
567
|
+
<div className='flex-shrink-0 px-4 pt-4 pb-3'>
|
|
568
|
+
<div className='flex items-start justify-between gap-2'>
|
|
569
|
+
<div className='min-w-0 flex-1'>
|
|
570
|
+
<span className='text-[15px] font-semibold text-gray-1000 truncate block'>
|
|
511
571
|
{selectedSpanName}
|
|
512
572
|
</span>
|
|
513
573
|
{selectedResourceId && (
|
|
514
|
-
<div className=
|
|
574
|
+
<div className='mt-1 flex items-center gap-2'>
|
|
515
575
|
{selectedResource && (
|
|
516
576
|
<span
|
|
517
577
|
className={`inline-flex items-center rounded-md px-1.5 py-0.5 text-[11px] font-medium leading-none shrink-0 ${
|
|
@@ -529,30 +589,30 @@ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
|
|
|
529
589
|
</span>
|
|
530
590
|
)}
|
|
531
591
|
<div
|
|
532
|
-
className=
|
|
592
|
+
className='flex items-center gap-1 text-[13px] font-mono text-gray-700 min-w-0'
|
|
533
593
|
title={selectedResourceId}
|
|
534
594
|
>
|
|
535
|
-
<span className=
|
|
595
|
+
<span className='truncate'>{selectedResourceId}</span>
|
|
536
596
|
<CopyButton
|
|
537
597
|
copyText={selectedResourceId}
|
|
538
|
-
ariaLabel=
|
|
539
|
-
className=
|
|
598
|
+
ariaLabel='Copy ID'
|
|
599
|
+
className='shrink-0'
|
|
540
600
|
/>
|
|
541
601
|
</div>
|
|
542
602
|
</div>
|
|
543
603
|
)}
|
|
544
604
|
</div>
|
|
545
605
|
<button
|
|
546
|
-
type=
|
|
547
|
-
className=
|
|
606
|
+
type='button'
|
|
607
|
+
className='p-1 rounded-md text-gray-900 hover:text-gray-1000 hover:bg-gray-alpha-200 transition-colors shrink-0'
|
|
548
608
|
onClick={clearActiveSpan}
|
|
549
609
|
>
|
|
550
|
-
<X className=
|
|
610
|
+
<X className='w-4 h-4' />
|
|
551
611
|
</button>
|
|
552
612
|
</div>
|
|
553
613
|
</div>
|
|
554
614
|
{/* Panel body */}
|
|
555
|
-
<div className=
|
|
615
|
+
<div className='flex-1 overflow-y-auto'>
|
|
556
616
|
<ErrorBoundary>
|
|
557
617
|
<EntityDetailPanel
|
|
558
618
|
run={sidebar.run}
|
|
@@ -30,33 +30,11 @@ export function computeRootBounds(spans: Span[]): RootBounds {
|
|
|
30
30
|
return { startTime: minStart, endTime: maxEnd, duration };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
toVisual(time: number): number;
|
|
39
|
-
fromVisual(fraction: number): number;
|
|
40
|
-
isCompressed: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function buildTimeCompression(
|
|
44
|
-
_spans: Span[],
|
|
45
|
-
viewStart: number,
|
|
46
|
-
viewEnd: number
|
|
47
|
-
): TimeCompression {
|
|
48
|
-
const range = viewEnd - viewStart;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
isCompressed: false,
|
|
52
|
-
toVisual(time: number): number {
|
|
53
|
-
if (range <= 0) return 0;
|
|
54
|
-
return Math.min(Math.max((time - viewStart) / range, 0), 1);
|
|
55
|
-
},
|
|
56
|
-
fromVisual(fraction: number): number {
|
|
57
|
-
return viewStart + fraction * range;
|
|
58
|
-
},
|
|
59
|
-
};
|
|
33
|
+
export function getSpanDurationMs(span: Span): number {
|
|
34
|
+
return Math.max(
|
|
35
|
+
0,
|
|
36
|
+
getHighResInMs(span.endTime) - getHighResInMs(span.startTime)
|
|
37
|
+
);
|
|
60
38
|
}
|
|
61
39
|
|
|
62
40
|
// ---------------------------------------------------------------------------
|
|
@@ -66,6 +44,7 @@ export function buildTimeCompression(
|
|
|
66
44
|
export interface TimeMarker {
|
|
67
45
|
position: number;
|
|
68
46
|
label: string;
|
|
47
|
+
value: number;
|
|
69
48
|
}
|
|
70
49
|
|
|
71
50
|
const NICE_INTERVALS = [
|
|
@@ -102,36 +81,7 @@ export function computeTimeMarkers(
|
|
|
102
81
|
markers.push({
|
|
103
82
|
position: Math.min(Math.max(position, 0), 1),
|
|
104
83
|
label: formatDuration(Math.abs(t), true),
|
|
105
|
-
|
|
106
|
-
if (markers.length >= MAX_MARKERS) break;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return markers;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function computeCompressedTimeMarkers(
|
|
113
|
-
compression: TimeCompression,
|
|
114
|
-
viewStart: number,
|
|
115
|
-
viewEnd: number,
|
|
116
|
-
rootStart: number
|
|
117
|
-
): TimeMarker[] {
|
|
118
|
-
const viewDuration = viewEnd - viewStart;
|
|
119
|
-
if (viewDuration <= 0) return [];
|
|
120
|
-
|
|
121
|
-
const maxTicks = 6;
|
|
122
|
-
const interval = pickInterval(viewDuration, maxTicks);
|
|
123
|
-
const offset = viewStart - rootStart;
|
|
124
|
-
|
|
125
|
-
const firstTick = Math.ceil(offset / interval) * interval;
|
|
126
|
-
const markers: TimeMarker[] = [];
|
|
127
|
-
|
|
128
|
-
for (let t = firstTick; t <= offset + viewDuration; t += interval) {
|
|
129
|
-
const absTime = rootStart + t;
|
|
130
|
-
const position = compression.toVisual(absTime);
|
|
131
|
-
if (position < -0.01 || position > 1.01) continue;
|
|
132
|
-
markers.push({
|
|
133
|
-
position: Math.min(Math.max(position, 0), 1),
|
|
134
|
-
label: formatDuration(Math.abs(t), true),
|
|
84
|
+
value: t,
|
|
135
85
|
});
|
|
136
86
|
if (markers.length >= MAX_MARKERS) break;
|
|
137
87
|
}
|
|
@@ -152,8 +102,12 @@ export interface SpanGap {
|
|
|
152
102
|
|
|
153
103
|
export function computeSpanGaps(
|
|
154
104
|
spans: Span[],
|
|
155
|
-
|
|
105
|
+
viewStart: number,
|
|
106
|
+
viewEnd: number
|
|
156
107
|
): SpanGap[] {
|
|
108
|
+
const range = viewEnd - viewStart;
|
|
109
|
+
if (range <= 0) return [];
|
|
110
|
+
|
|
157
111
|
const gaps: SpanGap[] = [];
|
|
158
112
|
for (let i = 0; i < spans.length - 1; i++) {
|
|
159
113
|
const endTime = getHighResInMs(spans[i].endTime);
|
|
@@ -161,8 +115,8 @@ export function computeSpanGaps(
|
|
|
161
115
|
const gapMs = startTime - endTime;
|
|
162
116
|
if (gapMs <= 0) continue;
|
|
163
117
|
|
|
164
|
-
const leftFrac =
|
|
165
|
-
const rightFrac =
|
|
118
|
+
const leftFrac = Math.min(Math.max((endTime - viewStart) / range, 0), 1);
|
|
119
|
+
const rightFrac = Math.min(Math.max((startTime - viewStart) / range, 0), 1);
|
|
166
120
|
if (rightFrac - leftFrac < 0.001) continue;
|
|
167
121
|
|
|
168
122
|
gaps.push({ gapMs, leftFrac, rightFrac, rowIndex: i });
|
|
@@ -379,39 +333,12 @@ function computeHookSegmentsFromSpan(
|
|
|
379
333
|
}
|
|
380
334
|
|
|
381
335
|
function computeSleepSegmentsFromSpan(
|
|
382
|
-
|
|
336
|
+
_startMs: number,
|
|
383
337
|
duration: number,
|
|
384
|
-
|
|
338
|
+
_events: SpanEvent[]
|
|
385
339
|
): Segment[] {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const completedTime = events
|
|
390
|
-
.filter((e) => e.name === 'wait_completed')
|
|
391
|
-
.map((e) => getHighResInMs(e.timestamp))
|
|
392
|
-
.sort((a, b) => a - b)[0];
|
|
393
|
-
|
|
394
|
-
if (completedTime === undefined) {
|
|
395
|
-
segments.push({ startFraction: 0, endFraction: 1, status: 'sleeping' });
|
|
396
|
-
return segments;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const completedFrac = timeToFraction(completedTime, startMs, duration);
|
|
400
|
-
if (completedFrac > 0.001) {
|
|
401
|
-
segments.push({
|
|
402
|
-
startFraction: 0,
|
|
403
|
-
endFraction: completedFrac,
|
|
404
|
-
status: 'sleeping',
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
segments.push({
|
|
409
|
-
startFraction: completedFrac,
|
|
410
|
-
endFraction: 1,
|
|
411
|
-
status: 'succeeded',
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
return segments;
|
|
340
|
+
if (duration <= 0) return [];
|
|
341
|
+
return [{ startFraction: 0, endFraction: 1, status: 'sleeping' }];
|
|
415
342
|
}
|
|
416
343
|
|
|
417
344
|
function computeRunSegmentsFromSpan(
|
|
@@ -497,8 +424,7 @@ function computeV1RunSegments(
|
|
|
497
424
|
*/
|
|
498
425
|
export function computeSpanSegments(span: Span): Segment[] {
|
|
499
426
|
const startMs = getHighResInMs(span.startTime);
|
|
500
|
-
const
|
|
501
|
-
const duration = endMs - startMs;
|
|
427
|
+
const duration = getSpanDurationMs(span);
|
|
502
428
|
const activeStartMs = span.activeStartTime
|
|
503
429
|
? getHighResInMs(span.activeStartTime)
|
|
504
430
|
: undefined;
|
|
@@ -14,22 +14,18 @@ export function getMsInHighRes(ms: number): [number, number] {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Formats a duration for timeline display (compact
|
|
17
|
+
* Formats a duration for timeline display (compact format).
|
|
18
18
|
* @deprecated Use formatDuration(ms, true) instead.
|
|
19
19
|
*/
|
|
20
20
|
export const formatDurationForTimeline = (ms: number): string =>
|
|
21
21
|
formatDuration(ms, true);
|
|
22
22
|
|
|
23
|
-
const timeSelectionFormatter = new Intl.NumberFormat(undefined, {
|
|
24
|
-
minimumFractionDigits: 2,
|
|
25
|
-
maximumFractionDigits: 2,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
23
|
export function formatTimeSelection(ms: number): string {
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
const roundedMs = Math.round(ms);
|
|
25
|
+
if (roundedMs < 1000) {
|
|
26
|
+
return `${roundedMs}ms`;
|
|
31
27
|
}
|
|
32
|
-
return `${
|
|
28
|
+
return `${Math.round(ms / 1000)}s`;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -59,7 +59,7 @@ export const waitEventsToWaitEntity = (
|
|
|
59
59
|
waitId: string;
|
|
60
60
|
runId: string;
|
|
61
61
|
createdAt: Date;
|
|
62
|
-
resumeAt
|
|
62
|
+
resumeAt?: Date;
|
|
63
63
|
completedAt?: Date;
|
|
64
64
|
} | null => {
|
|
65
65
|
const startEvent = events.find((event) => event.eventType === 'wait_created');
|
|
@@ -73,7 +73,9 @@ export const waitEventsToWaitEntity = (
|
|
|
73
73
|
waitId: startEvent.correlationId,
|
|
74
74
|
runId: startEvent.runId,
|
|
75
75
|
createdAt: startEvent.createdAt,
|
|
76
|
-
resumeAt: startEvent.eventData?.resumeAt
|
|
76
|
+
resumeAt: startEvent.eventData?.resumeAt
|
|
77
|
+
? new Date(startEvent.eventData.resumeAt)
|
|
78
|
+
: undefined,
|
|
77
79
|
completedAt: completedEvent?.createdAt,
|
|
78
80
|
};
|
|
79
81
|
};
|
|
@@ -81,13 +83,29 @@ export const waitEventsToWaitEntity = (
|
|
|
81
83
|
/**
|
|
82
84
|
* Converts a workflow Wait to an OpenTelemetry Span
|
|
83
85
|
*/
|
|
84
|
-
export function waitToSpan(
|
|
86
|
+
export function waitToSpan(
|
|
87
|
+
events: Event[],
|
|
88
|
+
maxEndTime: Date,
|
|
89
|
+
fallbackEndTime = maxEndTime
|
|
90
|
+
): Span | null {
|
|
85
91
|
const wait = waitEventsToWaitEntity(events);
|
|
86
92
|
if (!wait) {
|
|
87
93
|
return null;
|
|
88
94
|
}
|
|
89
95
|
const startTime = wait.createdAt;
|
|
90
|
-
const
|
|
96
|
+
const startMs = startTime.getTime();
|
|
97
|
+
let endTime = wait.completedAt;
|
|
98
|
+
if (!endTime) {
|
|
99
|
+
const fallbackCap =
|
|
100
|
+
wait.resumeAt && wait.resumeAt.getTime() < fallbackEndTime.getTime()
|
|
101
|
+
? wait.resumeAt
|
|
102
|
+
: fallbackEndTime;
|
|
103
|
+
endTime =
|
|
104
|
+
maxEndTime.getTime() > startMs &&
|
|
105
|
+
maxEndTime.getTime() < fallbackCap.getTime()
|
|
106
|
+
? maxEndTime
|
|
107
|
+
: fallbackCap;
|
|
108
|
+
}
|
|
91
109
|
const start = dateToOtelTime(startTime);
|
|
92
110
|
const end = dateToOtelTime(endTime);
|
|
93
111
|
const duration = calculateDuration(startTime, endTime);
|
package/src/lib/trace-builder.ts
CHANGED
|
@@ -117,6 +117,7 @@ function buildSpans(
|
|
|
117
117
|
// unknown territory. Even when the run is completed, we may not have loaded
|
|
118
118
|
// all events yet, so always use the latest event we've actually seen.
|
|
119
119
|
const childMaxEnd = latestKnownTime;
|
|
120
|
+
const runMaxEnd = run.completedAt ?? now;
|
|
120
121
|
|
|
121
122
|
const stepSpans = Array.from(groupedEvents.eventsByStepId.values())
|
|
122
123
|
.map((events) => stepToSpan(events, childMaxEnd))
|
|
@@ -127,7 +128,7 @@ function buildSpans(
|
|
|
127
128
|
.filter((span): span is Span => span !== null);
|
|
128
129
|
|
|
129
130
|
const waitSpans = Array.from(groupedEvents.timerEvents.values())
|
|
130
|
-
.map((events) => waitToSpan(events, childMaxEnd))
|
|
131
|
+
.map((events) => waitToSpan(events, childMaxEnd, runMaxEnd))
|
|
131
132
|
.filter((span): span is Span => span !== null);
|
|
132
133
|
|
|
133
134
|
return {
|