@workflow/web-shared 5.0.0-beta.6 → 5.0.0-beta.8
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/error-boundary.js +1 -1
- package/dist/components/event-list-view.d.ts +12 -0
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +26 -13
- package/dist/components/hook-actions.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/new-trace-viewer/components/copy-button.js +2 -2
- package/dist/components/new-trace-viewer/components/event-list.js +3 -3
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +1 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +1 -1
- package/dist/components/new-trace-viewer/components/split-pane.js +1 -1
- package/dist/components/new-trace-viewer/components/timeline.d.ts +1 -1
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/components/timeline.js +187 -103
- package/dist/components/new-trace-viewer/context.js +1 -1
- package/dist/components/new-trace-viewer/detail-panel.js +2 -2
- package/dist/components/new-trace-viewer/icons.js +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/trace-viewer.js +53 -26
- package/dist/components/new-trace-viewer/types.js +1 -1
- package/dist/components/new-trace-viewer/util/constants.js +1 -1
- package/dist/components/new-trace-viewer/utils.d.ts +8 -4
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
- package/dist/components/new-trace-viewer/utils.js +31 -6
- package/dist/components/run-trace-view.js +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +99 -73
- package/dist/components/sidebar/conversation-view.js +1 -1
- package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +16 -15
- package/dist/components/sidebar/detail-card.d.ts +5 -6
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +32 -16
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +6 -3
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +21 -15
- package/dist/components/sidebar/resolve-hook-modal.js +1 -1
- package/dist/components/sidebar/sidebar-data-context.js +1 -1
- package/dist/components/stream-viewer.js +1 -1
- package/dist/components/trace-viewer/components/map.js +1 -1
- package/dist/components/trace-viewer/components/markers.js +1 -1
- package/dist/components/trace-viewer/components/node.js +1 -1
- package/dist/components/trace-viewer/components/search-input.js +1 -1
- package/dist/components/trace-viewer/components/search.js +1 -1
- package/dist/components/trace-viewer/components/span-content.js +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
- package/dist/components/trace-viewer/components/span-segments.js +1 -1
- package/dist/components/trace-viewer/components/span-strategies.js +1 -1
- package/dist/components/trace-viewer/components/ui.js +1 -1
- package/dist/components/trace-viewer/components/zoom-button.js +1 -1
- package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
- package/dist/components/trace-viewer/context.js +1 -1
- package/dist/components/trace-viewer/index.js +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +1 -1
- package/dist/components/trace-viewer/types.js +1 -1
- package/dist/components/trace-viewer/util/constants.js +1 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
- package/dist/components/trace-viewer/util/timing.js +1 -1
- package/dist/components/trace-viewer/util/tree.js +1 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
- package/dist/components/trace-viewer/worker.js +1 -1
- package/dist/components/trace-viewer-new.js +1 -1
- package/dist/components/ui/alert.js +1 -1
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +39 -0
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/data-inspector.d.ts +1 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +4 -9
- package/dist/components/ui/decrypt-button.js +1 -1
- package/dist/components/ui/error-card.js +1 -1
- package/dist/components/ui/error-stack-block.d.ts.map +1 -1
- package/dist/components/ui/error-stack-block.js +4 -18
- package/dist/components/ui/inspector-theme.js +1 -1
- package/dist/components/ui/load-more-button.js +1 -1
- package/dist/components/ui/menu-dropdown.js +1 -1
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/timestamp-tooltip.js +1 -1
- package/dist/components/workflow-trace-view.js +1 -1
- package/dist/components/workflow-traces/event-colors.js +1 -1
- package/dist/components/workflow-traces/trace-colors.js +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +1 -1
- package/dist/components/workflow-traces/trace-time-utils.js +1 -1
- package/dist/hooks/use-dark-mode.js +1 -1
- package/dist/hooks/use-reduced-motion.d.ts +10 -0
- package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
- package/dist/hooks/use-reduced-motion.js +29 -0
- package/dist/index.js +1 -1
- package/dist/lib/event-analysis.js +1 -1
- package/dist/lib/event-materialization.js +1 -1
- package/dist/lib/hydration.js +1 -1
- package/dist/lib/stream-display.js +1 -1
- package/dist/lib/toast.js +1 -1
- package/dist/lib/trace-builder.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/package.json +5 -5
- package/src/components/event-list-view.tsx +29 -13
- package/src/components/new-trace-viewer/components/copy-button.tsx +1 -1
- package/src/components/new-trace-viewer/components/event-list.tsx +2 -2
- package/src/components/new-trace-viewer/components/timeline.tsx +353 -192
- package/src/components/new-trace-viewer/detail-panel.tsx +1 -1
- package/src/components/new-trace-viewer/trace-viewer.tsx +93 -59
- package/src/components/new-trace-viewer/utils.ts +40 -8
- package/src/components/sidebar/attribute-panel.tsx +212 -235
- package/src/components/sidebar/copyable-data-block.tsx +49 -27
- package/src/components/sidebar/detail-card.tsx +119 -41
- package/src/components/sidebar/entity-detail-panel.tsx +28 -28
- package/src/components/sidebar/events-list.tsx +51 -79
- package/src/components/ui/button.tsx +61 -0
- package/src/components/ui/data-inspector.tsx +9 -18
- package/src/components/ui/error-stack-block.tsx +7 -26
- package/src/hooks/use-reduced-motion.ts +34 -0
- package/dist/components/error-boundary.js.map +0 -1
- package/dist/components/event-list-view.js.map +0 -1
- package/dist/components/hook-actions.js.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
- package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +0 -1
- package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
- package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
- package/dist/components/new-trace-viewer/context.js.map +0 -1
- package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
- package/dist/components/new-trace-viewer/icons.js.map +0 -1
- package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/new-trace-viewer/types.js.map +0 -1
- package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
- package/dist/components/new-trace-viewer/utils.js.map +0 -1
- package/dist/components/run-trace-view.js.map +0 -1
- package/dist/components/sidebar/attribute-panel.js.map +0 -1
- package/dist/components/sidebar/conversation-view.js.map +0 -1
- package/dist/components/sidebar/copyable-data-block.js.map +0 -1
- package/dist/components/sidebar/detail-card.js.map +0 -1
- package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
- package/dist/components/sidebar/events-list.js.map +0 -1
- package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
- package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
- package/dist/components/stream-viewer.js.map +0 -1
- package/dist/components/trace-viewer/components/map.js.map +0 -1
- package/dist/components/trace-viewer/components/markers.js.map +0 -1
- package/dist/components/trace-viewer/components/node.js.map +0 -1
- package/dist/components/trace-viewer/components/search-input.js.map +0 -1
- package/dist/components/trace-viewer/components/search.js.map +0 -1
- package/dist/components/trace-viewer/components/span-content.js.map +0 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
- package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
- package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
- package/dist/components/trace-viewer/components/ui.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
- package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
- package/dist/components/trace-viewer/context.js.map +0 -1
- package/dist/components/trace-viewer/index.js.map +0 -1
- package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
- package/dist/components/trace-viewer/types.js.map +0 -1
- package/dist/components/trace-viewer/util/constants.js.map +0 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
- package/dist/components/trace-viewer/util/timing.js.map +0 -1
- package/dist/components/trace-viewer/util/tree.js.map +0 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
- package/dist/components/trace-viewer/worker.js.map +0 -1
- package/dist/components/trace-viewer-new.js.map +0 -1
- package/dist/components/ui/alert.js.map +0 -1
- package/dist/components/ui/card.js.map +0 -1
- package/dist/components/ui/data-inspector.js.map +0 -1
- package/dist/components/ui/decrypt-button.js.map +0 -1
- package/dist/components/ui/error-card.js.map +0 -1
- package/dist/components/ui/error-stack-block.js.map +0 -1
- package/dist/components/ui/inspector-theme.js.map +0 -1
- package/dist/components/ui/load-more-button.js.map +0 -1
- package/dist/components/ui/menu-dropdown.js.map +0 -1
- package/dist/components/ui/skeleton.js.map +0 -1
- package/dist/components/ui/spinner.js.map +0 -1
- package/dist/components/ui/timestamp-tooltip.js.map +0 -1
- package/dist/components/workflow-trace-view.js.map +0 -1
- package/dist/components/workflow-traces/event-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-colors.js.map +0 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
- package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
- package/dist/hooks/use-dark-mode.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/event-analysis.js.map +0 -1
- package/dist/lib/event-materialization.js.map +0 -1
- package/dist/lib/hydration.js.map +0 -1
- package/dist/lib/stream-display.js.map +0 -1
- package/dist/lib/toast.js.map +0 -1
- package/dist/lib/trace-builder.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
|
@@ -1,200 +1,321 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
|
4
|
-
import type { ReactNode } from 'react';
|
|
5
|
-
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
5
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { cn } from '../../../lib/utils';
|
|
7
7
|
import type { Span } from '../../trace-viewer/types';
|
|
8
8
|
import { formatDuration, getHighResInMs } from '../../trace-viewer/util/timing';
|
|
9
|
-
import type { SegmentStatus, TimeMarker } from '../utils';
|
|
9
|
+
import type { Segment, SegmentStatus, TimeMarker } from '../utils';
|
|
10
10
|
import {
|
|
11
11
|
computeSpanGaps,
|
|
12
12
|
computeSpanSegments,
|
|
13
|
-
getSpanDurationMs,
|
|
14
13
|
getResourceColor,
|
|
14
|
+
getSpanDurationMs,
|
|
15
15
|
} from '../utils';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
> = {
|
|
21
|
-
queued: { className: 'bg-gray-500' },
|
|
22
|
-
retrying: {
|
|
23
|
-
className: 'box-border bg-gray-500',
|
|
24
|
-
},
|
|
25
|
-
waiting: { className: 'bg-gray-500' },
|
|
26
|
-
running: { className: 'bg-blue-700' },
|
|
27
|
-
failed: { className: 'bg-red-700' },
|
|
28
|
-
succeeded: { className: 'bg-green-700' },
|
|
29
|
-
sleeping: { className: 'bg-gray-500' },
|
|
30
|
-
received: { className: 'bg-blue-700' },
|
|
31
|
-
};
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Constants
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
32
20
|
|
|
33
|
-
const BAR_HEIGHT_PX = 24;
|
|
34
21
|
const TINY_BAR_BOX_SIZE_PX = 24;
|
|
35
22
|
const TINY_BAR_WIDTH_PX = 4;
|
|
36
|
-
const SEGMENT_GAP_PX = 1;
|
|
37
|
-
// Keep this in sync with the rendered row height in the timeline/event list.
|
|
38
|
-
const ROW_HEIGHT = 40;
|
|
39
|
-
const CONTAINER_PAD_Y = 8;
|
|
40
|
-
const END_CAP_HEIGHT = 8;
|
|
41
23
|
export const TIMELINE_PADDING_PX = 16;
|
|
42
|
-
const ORIGIN_MARKER_EPSILON = 0.000001;
|
|
43
24
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}) {
|
|
55
|
-
const centerY = CONTAINER_PAD_Y + (rowIndex + 1) * ROW_HEIGHT;
|
|
25
|
+
const SEGMENT_CLASSES: Record<SegmentStatus, string> = {
|
|
26
|
+
queued: 'bg-gray-400 border border-gray-500',
|
|
27
|
+
retrying: 'bg-gray-400 border border-gray-500',
|
|
28
|
+
waiting: 'bg-gray-400 border border-gray-500',
|
|
29
|
+
running: 'bg-blue-200 border border-blue-500',
|
|
30
|
+
failed: 'bg-red-200 border border-red-500',
|
|
31
|
+
succeeded: 'bg-green-200 border border-green-500',
|
|
32
|
+
sleeping: 'bg-gray-400 border border-gray-500',
|
|
33
|
+
received: 'bg-blue-200 border border-blue-500',
|
|
34
|
+
};
|
|
56
35
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
left: `${leftFrac * 100}%`,
|
|
62
|
-
width: `${(rightFrac - leftFrac) * 100}%`,
|
|
63
|
-
top: centerY - END_CAP_HEIGHT / 2,
|
|
64
|
-
height: END_CAP_HEIGHT,
|
|
65
|
-
}}
|
|
66
|
-
>
|
|
67
|
-
<div className="absolute left-0 top-0 w-px h-full bg-amber-800" />
|
|
68
|
-
<div className="absolute left-0 right-0 top-1/2 h-px bg-amber-800" />
|
|
69
|
-
<div className="absolute right-0 top-0 w-px h-full bg-amber-800" />
|
|
70
|
-
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-label-12 leading-none whitespace-nowrap rounded-xs px-1 py-0.5 text-gray-100 bg-amber-800">
|
|
71
|
-
{label}
|
|
72
|
-
</span>
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
});
|
|
36
|
+
const TIMELINE_INSET_STYLE: CSSProperties = {
|
|
37
|
+
left: TIMELINE_PADDING_PX,
|
|
38
|
+
right: TIMELINE_PADDING_PX,
|
|
39
|
+
};
|
|
76
40
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
viewDuration,
|
|
81
|
-
containerWidth,
|
|
82
|
-
isSelected,
|
|
83
|
-
onClick,
|
|
84
|
-
}: {
|
|
85
|
-
span: Span;
|
|
86
|
-
viewStart: number;
|
|
87
|
-
viewDuration: number;
|
|
88
|
-
containerWidth: number;
|
|
89
|
-
isSelected: boolean;
|
|
90
|
-
onClick: () => void;
|
|
91
|
-
}): ReactNode {
|
|
92
|
-
const startTime = getHighResInMs(span.startTime);
|
|
93
|
-
const endTime = getHighResInMs(span.endTime);
|
|
94
|
-
const totalDurationMs = getSpanDurationMs(span);
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Bar geometry
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
95
44
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const widthFrac = rightFracRaw - leftFracRaw;
|
|
45
|
+
type BarMode =
|
|
46
|
+
| { kind: 'arrow'; direction: 'left' | 'right' }
|
|
47
|
+
| { kind: 'tiny' }
|
|
48
|
+
| { kind: 'full' };
|
|
101
49
|
|
|
102
|
-
|
|
103
|
-
|
|
50
|
+
interface BarGeometry {
|
|
51
|
+
mode: BarMode;
|
|
52
|
+
leftPct: number;
|
|
53
|
+
widthPct: number;
|
|
54
|
+
visibleStartMs: number;
|
|
55
|
+
visibleEndMs: number;
|
|
56
|
+
visiblePixelWidth: number;
|
|
57
|
+
}
|
|
104
58
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Compute the bar's geometry inside the timeline viewport. Percentages are
|
|
61
|
+
* always in [0, 100] so we never emit CSS values that exceed browser layout
|
|
62
|
+
* limits at extreme zoom.
|
|
63
|
+
*/
|
|
64
|
+
function computeBarGeometry(
|
|
65
|
+
startMs: number,
|
|
66
|
+
endMs: number,
|
|
67
|
+
viewStart: number,
|
|
68
|
+
viewEnd: number,
|
|
69
|
+
containerWidth: number
|
|
70
|
+
): BarGeometry {
|
|
71
|
+
const viewDuration = viewEnd - viewStart;
|
|
72
|
+
const visibleStartMs = Math.max(startMs, viewStart);
|
|
73
|
+
const visibleEndMs = Math.min(endMs, viewEnd);
|
|
74
|
+
const visibleDurationMs = Math.max(0, visibleEndMs - visibleStartMs);
|
|
75
|
+
const widthFrac = viewDuration > 0 ? visibleDurationMs / viewDuration : 0;
|
|
76
|
+
const visiblePixelWidth = widthFrac * containerWidth;
|
|
113
77
|
|
|
114
|
-
const
|
|
78
|
+
const isTiny = containerWidth > 0 && visiblePixelWidth < TINY_BAR_BOX_SIZE_PX;
|
|
79
|
+
const extendsOffLeft = startMs < viewStart;
|
|
80
|
+
const extendsOffRight = endMs > viewEnd;
|
|
115
81
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
82
|
+
const mode: BarMode =
|
|
83
|
+
isTiny && (extendsOffLeft || extendsOffRight)
|
|
84
|
+
? { kind: 'arrow', direction: extendsOffRight ? 'right' : 'left' }
|
|
85
|
+
: isTiny
|
|
86
|
+
? { kind: 'tiny' }
|
|
87
|
+
: { kind: 'full' };
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
mode,
|
|
91
|
+
leftPct:
|
|
92
|
+
viewDuration > 0
|
|
93
|
+
? ((visibleStartMs - viewStart) / viewDuration) * 100
|
|
94
|
+
: 0,
|
|
95
|
+
widthPct: widthFrac * 100,
|
|
96
|
+
visibleStartMs,
|
|
97
|
+
visibleEndMs,
|
|
98
|
+
visiblePixelWidth,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getBarPositionStyle(geometry: BarGeometry): {
|
|
103
|
+
left: string;
|
|
104
|
+
width: string;
|
|
105
|
+
} {
|
|
106
|
+
switch (geometry.mode.kind) {
|
|
107
|
+
case 'arrow':
|
|
108
|
+
return {
|
|
109
|
+
left:
|
|
110
|
+
geometry.mode.direction === 'right'
|
|
111
|
+
? `calc(100% - ${TINY_BAR_BOX_SIZE_PX}px)`
|
|
112
|
+
: '0px',
|
|
113
|
+
width: `${TINY_BAR_BOX_SIZE_PX}px`,
|
|
114
|
+
};
|
|
115
|
+
case 'tiny':
|
|
116
|
+
return {
|
|
117
|
+
left: `min(${geometry.leftPct}%, calc(100% - ${TINY_BAR_WIDTH_PX}px))`,
|
|
118
|
+
width: `${TINY_BAR_WIDTH_PX}px`,
|
|
119
|
+
};
|
|
120
|
+
case 'full':
|
|
121
|
+
return {
|
|
122
|
+
left: `${geometry.leftPct}%`,
|
|
123
|
+
width: `max(${geometry.widthPct}%, 4px)`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Segment projection
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
interface VisibleSegment {
|
|
133
|
+
status: SegmentStatus;
|
|
134
|
+
leftPct: number;
|
|
135
|
+
widthPct: number;
|
|
136
|
+
pixelWidth: number;
|
|
137
|
+
fullDurationMs: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Project status segments onto the visible portion of a bar. Segments fully
|
|
142
|
+
* outside the visible window are dropped; segments crossing the edge are
|
|
143
|
+
* clipped to [0%, 100%] of the visible bar.
|
|
144
|
+
*/
|
|
145
|
+
function projectSegments(
|
|
146
|
+
segments: Segment[],
|
|
147
|
+
spanStartMs: number,
|
|
148
|
+
spanDurationMs: number,
|
|
149
|
+
geometry: BarGeometry
|
|
150
|
+
): VisibleSegment[] {
|
|
151
|
+
const visibleDurationMs = geometry.visibleEndMs - geometry.visibleStartMs;
|
|
152
|
+
if (visibleDurationMs <= 0) return [];
|
|
153
|
+
|
|
154
|
+
return segments.flatMap((seg) => {
|
|
155
|
+
const segStartMs = spanStartMs + seg.startFraction * spanDurationMs;
|
|
156
|
+
const segEndMs = spanStartMs + seg.endFraction * spanDurationMs;
|
|
157
|
+
const startMs = Math.max(segStartMs, geometry.visibleStartMs);
|
|
158
|
+
const endMs = Math.min(segEndMs, geometry.visibleEndMs);
|
|
159
|
+
if (endMs <= startMs) return [];
|
|
160
|
+
|
|
161
|
+
const widthFrac = (endMs - startMs) / visibleDurationMs;
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
status: seg.status,
|
|
165
|
+
leftPct:
|
|
166
|
+
((startMs - geometry.visibleStartMs) / visibleDurationMs) * 100,
|
|
167
|
+
widthPct: widthFrac * 100,
|
|
168
|
+
pixelWidth: widthFrac * geometry.visiblePixelWidth,
|
|
169
|
+
fullDurationMs: (seg.endFraction - seg.startFraction) * spanDurationMs,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Small render helpers
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function DurationLabel({ label }: { label: string }): ReactNode {
|
|
180
|
+
return (
|
|
181
|
+
<span className="pointer-events-none absolute inset-0 flex items-center justify-start overflow-hidden px-1 text-[10px] font-mono font-medium leading-none whitespace-nowrap text-left text-gray-1000 tabular-nums opacity-0 group-hover/timeline-row:opacity-100">
|
|
128
182
|
{label}
|
|
129
183
|
</span>
|
|
130
184
|
);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
const barContent = showBoundaryArrow ? (
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function BoundaryArrow({
|
|
188
|
+
direction,
|
|
189
|
+
}: {
|
|
190
|
+
direction: 'left' | 'right';
|
|
191
|
+
}): ReactNode {
|
|
192
|
+
const Icon = direction === 'right' ? ArrowRight : ArrowLeft;
|
|
193
|
+
return (
|
|
141
194
|
<div className="flex h-6 w-6 items-center justify-center rounded-[0.25rem]">
|
|
142
|
-
<
|
|
195
|
+
<Icon className="size-3 text-gray-900" />
|
|
143
196
|
</div>
|
|
144
|
-
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function PlainBar({
|
|
201
|
+
bg,
|
|
202
|
+
border,
|
|
203
|
+
label,
|
|
204
|
+
}: {
|
|
205
|
+
bg: string;
|
|
206
|
+
border: string;
|
|
207
|
+
label: string | null;
|
|
208
|
+
}): ReactNode {
|
|
209
|
+
return (
|
|
145
210
|
<div
|
|
146
|
-
className="h-6 rounded-[0.25rem]"
|
|
147
|
-
style={{ background:
|
|
148
|
-
|
|
149
|
-
|
|
211
|
+
className="relative h-6 w-full min-w-1 rounded-[0.25rem] border"
|
|
212
|
+
style={{ background: bg, borderColor: border }}
|
|
213
|
+
>
|
|
214
|
+
{label ? <DurationLabel label={label} /> : null}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function SegmentBar({ segments }: { segments: VisibleSegment[] }): ReactNode {
|
|
220
|
+
return (
|
|
150
221
|
<div className="relative h-6 w-full">
|
|
151
222
|
{segments.map((seg, i) => {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
seg.status === 'queued' && segPixelWidth < 20
|
|
162
|
-
? { background: 'var(--ds-gray-500)' }
|
|
163
|
-
: SEGMENT_CONFIG[seg.status].style;
|
|
223
|
+
const label = formatDuration(seg.fullDurationMs);
|
|
224
|
+
// Only render the label when there's enough room for it without clipping.
|
|
225
|
+
const showLabel = seg.pixelWidth >= Math.max(40, label.length * 6 + 12);
|
|
226
|
+
// Beef up the queued segment when it's too narrow to read.
|
|
227
|
+
const isNarrowQueued = seg.status === 'queued' && seg.pixelWidth < 20;
|
|
228
|
+
const overrideBg = isNarrowQueued ? 'var(--ds-gray-400)' : undefined;
|
|
229
|
+
const overrideBorder = isNarrowQueued
|
|
230
|
+
? 'var(--ds-gray-500)'
|
|
231
|
+
: undefined;
|
|
164
232
|
|
|
165
233
|
return (
|
|
166
234
|
<div
|
|
167
235
|
key={`${seg.status}-${i}`}
|
|
168
236
|
className={cn(
|
|
169
237
|
'absolute h-full rounded-[0.25rem]',
|
|
170
|
-
|
|
238
|
+
SEGMENT_CLASSES[seg.status]
|
|
171
239
|
)}
|
|
172
240
|
style={{
|
|
173
|
-
|
|
174
|
-
|
|
241
|
+
// 1px gap between adjacent segments, distributed equally.
|
|
242
|
+
left: `calc(${seg.leftPct}% + 0.5px)`,
|
|
243
|
+
width: `calc(${seg.widthPct}% - 1px)`,
|
|
175
244
|
minWidth: 1,
|
|
176
|
-
|
|
245
|
+
background: overrideBg,
|
|
246
|
+
borderColor: overrideBorder,
|
|
177
247
|
}}
|
|
178
248
|
>
|
|
179
|
-
{
|
|
180
|
-
? renderDurationLabel(segDurationLabel)
|
|
181
|
-
: null}
|
|
249
|
+
{showLabel ? <DurationLabel label={label} /> : null}
|
|
182
250
|
</div>
|
|
183
251
|
);
|
|
184
252
|
})}
|
|
185
253
|
</div>
|
|
186
|
-
) : (
|
|
187
|
-
<div
|
|
188
|
-
className="relative h-6 rounded-[0.25rem]"
|
|
189
|
-
style={{
|
|
190
|
-
width: '100%',
|
|
191
|
-
minWidth: 4,
|
|
192
|
-
background: fallbackColor,
|
|
193
|
-
}}
|
|
194
|
-
>
|
|
195
|
-
{showBarDurationLabel ? renderDurationLabel(totalDurationLabel) : null}
|
|
196
|
-
</div>
|
|
197
254
|
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// TimelineBar
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
const TimelineBar = memo(function TimelineBar({
|
|
262
|
+
span,
|
|
263
|
+
viewStart,
|
|
264
|
+
viewDuration,
|
|
265
|
+
containerWidth,
|
|
266
|
+
isSelected,
|
|
267
|
+
onSelect,
|
|
268
|
+
}: {
|
|
269
|
+
span: Span;
|
|
270
|
+
viewStart: number;
|
|
271
|
+
viewDuration: number;
|
|
272
|
+
containerWidth: number;
|
|
273
|
+
isSelected: boolean;
|
|
274
|
+
onSelect: (spanId: string) => void;
|
|
275
|
+
}): ReactNode {
|
|
276
|
+
const startMs = getHighResInMs(span.startTime);
|
|
277
|
+
const endMs = getHighResInMs(span.endTime);
|
|
278
|
+
const totalDurationMs = getSpanDurationMs(span);
|
|
279
|
+
|
|
280
|
+
const geometry = useMemo(
|
|
281
|
+
() =>
|
|
282
|
+
computeBarGeometry(
|
|
283
|
+
startMs,
|
|
284
|
+
endMs,
|
|
285
|
+
viewStart,
|
|
286
|
+
viewStart + viewDuration,
|
|
287
|
+
containerWidth
|
|
288
|
+
),
|
|
289
|
+
[startMs, endMs, viewStart, viewDuration, containerWidth]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const baseSegments = useMemo(() => computeSpanSegments(span), [span]);
|
|
293
|
+
const segments = useMemo(
|
|
294
|
+
() =>
|
|
295
|
+
geometry.mode.kind === 'full'
|
|
296
|
+
? projectSegments(baseSegments, startMs, totalDurationMs, geometry)
|
|
297
|
+
: [],
|
|
298
|
+
[geometry, baseSegments, startMs, totalDurationMs]
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const workflowStatus = (span.attributes.data as Record<string, unknown>)
|
|
302
|
+
?.status as string | undefined;
|
|
303
|
+
const isErrored = span.status.code === 2 || workflowStatus === 'failed';
|
|
304
|
+
const colors = getResourceColor(span.resource);
|
|
305
|
+
const fallbackBg = isErrored
|
|
306
|
+
? (colors.errorBg ?? 'var(--ds-red-200)')
|
|
307
|
+
: colors.bg;
|
|
308
|
+
const fallbackBorder = isErrored
|
|
309
|
+
? (colors.errorBorder ?? 'var(--ds-red-500)')
|
|
310
|
+
: colors.border;
|
|
311
|
+
|
|
312
|
+
const totalLabel = formatDuration(totalDurationMs);
|
|
313
|
+
const showTotalLabel =
|
|
314
|
+
geometry.visiblePixelWidth >= Math.max(40, totalLabel.length * 6 + 12);
|
|
315
|
+
|
|
316
|
+
const handleClick = useCallback(() => {
|
|
317
|
+
onSelect(span.spanId);
|
|
318
|
+
}, [onSelect, span.spanId]);
|
|
198
319
|
|
|
199
320
|
return (
|
|
200
321
|
<div
|
|
@@ -202,37 +323,30 @@ const TimelineBar = memo(function TimelineBar({
|
|
|
202
323
|
aria-selected={isSelected}
|
|
203
324
|
aria-expanded={isSelected}
|
|
204
325
|
aria-level={1}
|
|
205
|
-
className=
|
|
206
|
-
|
|
207
|
-
)}
|
|
208
|
-
onMouseEnter={() => setIsRowHovered(true)}
|
|
209
|
-
onMouseLeave={() => setIsRowHovered(false)}
|
|
210
|
-
onClick={onClick}
|
|
326
|
+
className="group/timeline-row h-10 relative flex items-center hover:bg-gray-100 aria-selected:bg-gray-100 aria-selected:hover:bg-gray-200"
|
|
327
|
+
onClick={handleClick}
|
|
211
328
|
>
|
|
212
|
-
<div
|
|
213
|
-
className="absolute inset-y-0"
|
|
214
|
-
style={{
|
|
215
|
-
left: TIMELINE_PADDING_PX,
|
|
216
|
-
right: TIMELINE_PADDING_PX,
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
329
|
+
<div className="absolute inset-y-0" style={TIMELINE_INSET_STYLE}>
|
|
219
330
|
<div
|
|
220
|
-
className="absolute top-1/2 -translate-y-1/2"
|
|
221
|
-
style={
|
|
222
|
-
left: showBoundaryArrow
|
|
223
|
-
? `min(max(${leftPct}%, 0px), calc(100% - ${TINY_BAR_BOX_SIZE_PX}px))`
|
|
224
|
-
: isTinyBar
|
|
225
|
-
? `min(${leftPct}%, calc(100% - ${TINY_BAR_WIDTH_PX}px))`
|
|
226
|
-
: `${leftPct}%`,
|
|
227
|
-
width: showBoundaryArrow
|
|
228
|
-
? `${TINY_BAR_BOX_SIZE_PX}px`
|
|
229
|
-
: isTinyBar
|
|
230
|
-
? `${TINY_BAR_WIDTH_PX}px`
|
|
231
|
-
: `max(${widthPct}%, 4px)`,
|
|
232
|
-
height: BAR_HEIGHT_PX,
|
|
233
|
-
}}
|
|
331
|
+
className="absolute top-1/2 h-6 -translate-y-1/2"
|
|
332
|
+
style={getBarPositionStyle(geometry)}
|
|
234
333
|
>
|
|
235
|
-
{
|
|
334
|
+
{geometry.mode.kind === 'arrow' ? (
|
|
335
|
+
<BoundaryArrow direction={geometry.mode.direction} />
|
|
336
|
+
) : geometry.mode.kind === 'tiny' ? (
|
|
337
|
+
<div
|
|
338
|
+
className="h-6 rounded-[0.25rem] border"
|
|
339
|
+
style={{ background: fallbackBg, borderColor: fallbackBorder }}
|
|
340
|
+
/>
|
|
341
|
+
) : segments.length > 0 ? (
|
|
342
|
+
<SegmentBar segments={segments} />
|
|
343
|
+
) : (
|
|
344
|
+
<PlainBar
|
|
345
|
+
bg={fallbackBg}
|
|
346
|
+
border={fallbackBorder}
|
|
347
|
+
label={showTotalLabel ? totalLabel : null}
|
|
348
|
+
/>
|
|
349
|
+
)}
|
|
236
350
|
</div>
|
|
237
351
|
</div>
|
|
238
352
|
</div>
|
|
@@ -241,6 +355,57 @@ const TimelineBar = memo(function TimelineBar({
|
|
|
241
355
|
|
|
242
356
|
export { TimelineBar };
|
|
243
357
|
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// DeltaIndicator (Alt-key gap overlay)
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
// Row and indicator sizes are local to DeltaIndicator since it's the only
|
|
363
|
+
// place that needs to compute Y positions from a row index. ROW_HEIGHT must
|
|
364
|
+
// match the `h-10` (40px) row used in TimelineBar.
|
|
365
|
+
const DELTA_ROW_HEIGHT_PX = 40;
|
|
366
|
+
const DELTA_CAP_HEIGHT_PX = 8;
|
|
367
|
+
// Vertical offset to sit the indicator inside the gap between row N and N+1,
|
|
368
|
+
// aligned with where the bar starts in the next row (rows center a 24px bar
|
|
369
|
+
// inside 40px, so bars start ~8px from the top of the row).
|
|
370
|
+
const DELTA_ROW_OFFSET_PX = 8;
|
|
371
|
+
|
|
372
|
+
const DeltaIndicator = memo(function DeltaIndicator({
|
|
373
|
+
leftFrac,
|
|
374
|
+
rightFrac,
|
|
375
|
+
label,
|
|
376
|
+
rowIndex,
|
|
377
|
+
}: {
|
|
378
|
+
leftFrac: number;
|
|
379
|
+
rightFrac: number;
|
|
380
|
+
label: string;
|
|
381
|
+
rowIndex: number;
|
|
382
|
+
}) {
|
|
383
|
+
const centerY = DELTA_ROW_OFFSET_PX + (rowIndex + 1) * DELTA_ROW_HEIGHT_PX;
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
<div
|
|
387
|
+
className="absolute pointer-events-none"
|
|
388
|
+
style={{
|
|
389
|
+
left: `${leftFrac * 100}%`,
|
|
390
|
+
width: `${(rightFrac - leftFrac) * 100}%`,
|
|
391
|
+
top: centerY - DELTA_CAP_HEIGHT_PX / 2,
|
|
392
|
+
height: DELTA_CAP_HEIGHT_PX,
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
<div className="absolute left-0 top-0 w-px h-full bg-amber-800" />
|
|
396
|
+
<div className="absolute left-0 right-0 top-1/2 h-px bg-amber-800" />
|
|
397
|
+
<div className="absolute right-0 top-0 w-px h-full bg-amber-800" />
|
|
398
|
+
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-label-12 leading-none whitespace-nowrap rounded-xs px-1 py-0.5 text-gray-100 bg-amber-800">
|
|
399
|
+
{label}
|
|
400
|
+
</span>
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// TimelineHeader
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
|
|
244
409
|
export function TimelineHeader({
|
|
245
410
|
markers,
|
|
246
411
|
hoverInfo,
|
|
@@ -273,6 +438,10 @@ export function TimelineHeader({
|
|
|
273
438
|
);
|
|
274
439
|
}
|
|
275
440
|
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// Timeline
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
276
445
|
export function Timeline({
|
|
277
446
|
spans,
|
|
278
447
|
viewStart,
|
|
@@ -317,13 +486,11 @@ export function Timeline({
|
|
|
317
486
|
<div
|
|
318
487
|
aria-hidden
|
|
319
488
|
className="absolute inset-y-0 pointer-events-none"
|
|
320
|
-
style={
|
|
321
|
-
left: TIMELINE_PADDING_PX,
|
|
322
|
-
right: TIMELINE_PADDING_PX,
|
|
323
|
-
}}
|
|
489
|
+
style={TIMELINE_INSET_STYLE}
|
|
324
490
|
>
|
|
325
491
|
{markers.map((marker) =>
|
|
326
|
-
|
|
492
|
+
// Skip the "0s" origin marker since the left edge already implies it.
|
|
493
|
+
Math.abs(marker.value) > 0.000001 ? (
|
|
327
494
|
<div
|
|
328
495
|
key={`${marker.position}-${marker.label}`}
|
|
329
496
|
className="absolute top-0 bottom-0 w-px bg-gray-alpha-300"
|
|
@@ -335,13 +502,10 @@ export function Timeline({
|
|
|
335
502
|
{hoverFraction != null && (
|
|
336
503
|
<div
|
|
337
504
|
className="absolute inset-y-0 pointer-events-none z-10"
|
|
338
|
-
style={
|
|
339
|
-
left: TIMELINE_PADDING_PX,
|
|
340
|
-
right: TIMELINE_PADDING_PX,
|
|
341
|
-
}}
|
|
505
|
+
style={TIMELINE_INSET_STYLE}
|
|
342
506
|
>
|
|
343
507
|
<div
|
|
344
|
-
className="absolute top-0 bottom-0 w-px bg-gray-alpha-
|
|
508
|
+
className="absolute top-0 bottom-0 w-px bg-gray-alpha-500"
|
|
345
509
|
style={{ left: `${hoverFraction * 100}%` }}
|
|
346
510
|
/>
|
|
347
511
|
</div>
|
|
@@ -354,17 +518,14 @@ export function Timeline({
|
|
|
354
518
|
viewDuration={viewDuration}
|
|
355
519
|
containerWidth={timelineWidth}
|
|
356
520
|
isSelected={selectedId === span.spanId}
|
|
357
|
-
|
|
521
|
+
onSelect={onSelect}
|
|
358
522
|
/>
|
|
359
523
|
))}
|
|
360
524
|
{altHeld && (
|
|
361
525
|
<div
|
|
362
526
|
aria-hidden
|
|
363
527
|
className="absolute inset-y-0 pointer-events-none"
|
|
364
|
-
style={
|
|
365
|
-
left: TIMELINE_PADDING_PX,
|
|
366
|
-
right: TIMELINE_PADDING_PX,
|
|
367
|
-
}}
|
|
528
|
+
style={TIMELINE_INSET_STYLE}
|
|
368
529
|
>
|
|
369
530
|
{gaps.map((gap) => (
|
|
370
531
|
<DeltaIndicator
|
|
@@ -29,7 +29,7 @@ export function DetailPanel({
|
|
|
29
29
|
</span>
|
|
30
30
|
<button
|
|
31
31
|
type="button"
|
|
32
|
-
className="p-1 rounded-md text-gray-
|
|
32
|
+
className="p-1 rounded-md text-gray-1000 hover:bg-gray-alpha-100 transition-colors"
|
|
33
33
|
onClick={onClose}
|
|
34
34
|
>
|
|
35
35
|
<X className="w-4 h-4" />
|