@datalayer/core 1.0.1 → 1.0.3
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/README.md +1 -1
- package/lib/api/constants.d.ts +3 -0
- package/lib/api/constants.js +3 -0
- package/lib/api/index.d.ts +1 -0
- package/lib/api/index.js +1 -0
- package/lib/api/otel/index.d.ts +12 -0
- package/lib/api/otel/index.js +16 -0
- package/lib/api/otel/logs.d.ts +19 -0
- package/lib/api/otel/logs.js +43 -0
- package/lib/api/otel/metrics.d.ts +31 -0
- package/lib/api/otel/metrics.js +65 -0
- package/lib/api/otel/query.d.ts +16 -0
- package/lib/api/otel/query.js +37 -0
- package/lib/api/otel/services.d.ts +39 -0
- package/lib/api/otel/services.js +81 -0
- package/lib/api/otel/traces.d.ts +24 -0
- package/lib/api/otel/traces.js +53 -0
- package/lib/api/otel/types.d.ts +112 -0
- package/lib/api/otel/types.js +5 -0
- package/lib/api/spacer/index.d.ts +1 -2
- package/lib/api/spacer/index.js +1 -2
- package/lib/components/avatars/BoringAvatar.d.ts +3 -1
- package/lib/components/avatars/BoringAvatar.js +15 -14
- package/lib/components/avatars/BoringAvatar.stories.d.ts +2 -1
- package/lib/components/storage/ContentsBrowser.d.ts +6 -0
- package/lib/components/storage/ContentsBrowser.js +7 -8
- package/lib/config/Configuration.d.ts +4 -0
- package/lib/hooks/index.d.ts +2 -0
- package/lib/hooks/index.js +2 -0
- package/lib/hooks/useCache.d.ts +16 -40
- package/lib/hooks/useCache.js +28 -233
- package/lib/hooks/useProjectStore.d.ts +58 -0
- package/lib/hooks/useProjectStore.js +64 -0
- package/lib/hooks/useProjects.d.ts +590 -0
- package/lib/hooks/useProjects.js +166 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +4 -2
- package/lib/models/Page.d.ts +2 -0
- package/lib/otel/OtelLive.d.ts +12 -0
- package/lib/otel/OtelLive.js +354 -0
- package/lib/otel/OtelLogsList.d.ts +11 -0
- package/lib/otel/OtelLogsList.js +137 -0
- package/lib/otel/OtelMetricsChart.d.ts +22 -0
- package/lib/otel/OtelMetricsChart.js +300 -0
- package/lib/otel/OtelMetricsList.d.ts +15 -0
- package/lib/otel/OtelMetricsList.js +213 -0
- package/lib/otel/OtelSearchBar.d.ts +11 -0
- package/lib/otel/OtelSearchBar.js +22 -0
- package/lib/otel/OtelSpanDetail.d.ts +11 -0
- package/lib/otel/OtelSpanDetail.js +172 -0
- package/lib/otel/OtelSpanTree.d.ts +11 -0
- package/lib/otel/OtelSpanTree.js +176 -0
- package/lib/otel/OtelSqlView.d.ts +16 -0
- package/lib/otel/OtelSqlView.js +239 -0
- package/lib/otel/OtelSystemView.d.ts +15 -0
- package/lib/otel/OtelSystemView.js +75 -0
- package/lib/otel/OtelTimeline.d.ts +11 -0
- package/lib/otel/OtelTimeline.js +101 -0
- package/lib/otel/OtelTimelineRangeSlider.d.ts +16 -0
- package/lib/otel/OtelTimelineRangeSlider.js +338 -0
- package/lib/otel/OtelTracesList.d.ts +13 -0
- package/lib/otel/OtelTracesList.js +199 -0
- package/lib/otel/hooks.d.ts +172 -0
- package/lib/otel/hooks.js +490 -0
- package/lib/otel/index.d.ts +25 -0
- package/lib/otel/index.js +19 -0
- package/lib/otel/types.d.ts +190 -0
- package/lib/otel/types.js +5 -0
- package/lib/otel/utils.d.ts +33 -0
- package/lib/otel/utils.js +181 -0
- package/lib/state/storage/IAMStorage.d.ts +2 -1
- package/lib/state/substates/CoreState.js +1 -0
- package/lib/utils/Jwt.d.ts +42 -0
- package/lib/utils/Jwt.js +44 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/views/iam/SignInSimple.d.ts +38 -0
- package/lib/views/iam/SignInSimple.js +80 -0
- package/lib/views/iam/index.d.ts +2 -0
- package/lib/views/iam/index.js +5 -0
- package/lib/views/iam-tokens/IAMTokenEdit.js +53 -4
- package/lib/views/iam-tokens/IAMTokens.js +65 -33
- package/lib/views/iam-tokens/Tokens.js +64 -32
- package/lib/views/index.d.ts +2 -1
- package/lib/views/index.js +2 -1
- package/lib/views/profile/UserBadge.d.ts +18 -0
- package/lib/views/profile/UserBadge.js +101 -0
- package/lib/views/profile/index.d.ts +2 -0
- package/lib/views/profile/index.js +5 -0
- package/lib/views/secrets/Secrets.js +1 -1
- package/package.json +27 -3
- package/lib/api/spacer/agentSpaces.d.ts +0 -193
- package/lib/api/spacer/agentSpaces.js +0 -127
- package/lib/theme/DatalayerTheme.d.ts +0 -52
- package/lib/theme/DatalayerTheme.js +0 -228
- package/lib/theme/DatalayerThemeProvider.d.ts +0 -29
- package/lib/theme/DatalayerThemeProvider.js +0 -54
- package/lib/theme/Palette.d.ts +0 -4
- package/lib/theme/Palette.js +0 -10
- package/lib/theme/index.d.ts +0 -4
- package/lib/theme/index.js +0 -8
- package/lib/theme/useSystemColorMode.d.ts +0 -9
- package/lib/theme/useSystemColorMode.js +0 -26
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* OtelTimelineRangeSlider – A modern dual-handle timeline range slider
|
|
8
|
+
* for selecting time windows over OTEL signals.
|
|
9
|
+
*
|
|
10
|
+
* Built with Primer React `Box` / `Text` + native pointer events for
|
|
11
|
+
* smooth drag interaction. Fully theme-aware — inherits all colors
|
|
12
|
+
* from the active Primer theme via CSS functional tokens.
|
|
13
|
+
*
|
|
14
|
+
* Inspired by react-timeline-range-slider (d3-scale + react-compound-slider)
|
|
15
|
+
* but implemented from scratch with zero external dependencies beyond Primer.
|
|
16
|
+
*
|
|
17
|
+
* @module otel/OtelTimelineRangeSlider
|
|
18
|
+
*/
|
|
19
|
+
import { useCallback, useRef, useState, useMemo } from 'react';
|
|
20
|
+
import { Box, Text } from '@primer/react';
|
|
21
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
22
|
+
function clamp(v, min, max) {
|
|
23
|
+
return Math.max(min, Math.min(max, v));
|
|
24
|
+
}
|
|
25
|
+
function lerp(a, b, t) {
|
|
26
|
+
return a + (b - a) * t;
|
|
27
|
+
}
|
|
28
|
+
function defaultFormatTick(d) {
|
|
29
|
+
const h = d.getHours().toString().padStart(2, '0');
|
|
30
|
+
const m = d.getMinutes().toString().padStart(2, '0');
|
|
31
|
+
return `${h}:${m}`;
|
|
32
|
+
}
|
|
33
|
+
function generateTicks(start, end, count) {
|
|
34
|
+
const ticks = [];
|
|
35
|
+
const s = start.getTime();
|
|
36
|
+
const e = end.getTime();
|
|
37
|
+
for (let i = 0; i <= count; i++) {
|
|
38
|
+
ticks.push(new Date(lerp(s, e, i / count)));
|
|
39
|
+
}
|
|
40
|
+
return ticks;
|
|
41
|
+
}
|
|
42
|
+
// ── Component ───────────────────────────────────────────────────────
|
|
43
|
+
export const OtelTimelineRangeSlider = ({ timelineStart, timelineEnd, selectedStart, selectedEnd, onRangeChange, onRangeCommit, tickCount = 8, formatTick = defaultFormatTick, height = 56, histogram, }) => {
|
|
44
|
+
const railRef = useRef(null);
|
|
45
|
+
const dragging = useRef(null);
|
|
46
|
+
const dragOrigin = useRef({ pct: 0, startPct: 0, endPct: 0 });
|
|
47
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
48
|
+
const [hoverPct, setHoverPct] = useState(null);
|
|
49
|
+
// Compute percentages
|
|
50
|
+
const tlStart = timelineStart.getTime();
|
|
51
|
+
const tlEnd = timelineEnd.getTime();
|
|
52
|
+
const tlRange = tlEnd - tlStart || 1;
|
|
53
|
+
const startPct = ((selectedStart.getTime() - tlStart) / tlRange) * 100;
|
|
54
|
+
const endPct = ((selectedEnd.getTime() - tlStart) / tlRange) * 100;
|
|
55
|
+
const ticks = useMemo(() => generateTicks(timelineStart, timelineEnd, tickCount), [timelineStart, timelineEnd, tickCount]);
|
|
56
|
+
// Histogram bars
|
|
57
|
+
const histMax = useMemo(() => {
|
|
58
|
+
if (!histogram?.length)
|
|
59
|
+
return 0;
|
|
60
|
+
return Math.max(...histogram.map(h => h.count));
|
|
61
|
+
}, [histogram]);
|
|
62
|
+
// Convert pointer position to percentage
|
|
63
|
+
const pointerToPct = useCallback((clientX) => {
|
|
64
|
+
if (!railRef.current)
|
|
65
|
+
return 0;
|
|
66
|
+
const rect = railRef.current.getBoundingClientRect();
|
|
67
|
+
return clamp(((clientX - rect.left) / rect.width) * 100, 0, 100);
|
|
68
|
+
}, []);
|
|
69
|
+
const pctToDate = useCallback((pct) => new Date(tlStart + (pct / 100) * tlRange), [tlStart, tlRange]);
|
|
70
|
+
// ── Pointer handlers ──
|
|
71
|
+
const handlePointerDown = useCallback((e, target) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
e.target.setPointerCapture(e.pointerId);
|
|
75
|
+
dragging.current = target;
|
|
76
|
+
const pct = pointerToPct(e.clientX);
|
|
77
|
+
dragOrigin.current = { pct, startPct, endPct };
|
|
78
|
+
setIsDragging(true);
|
|
79
|
+
}, [pointerToPct, startPct, endPct]);
|
|
80
|
+
const handlePointerMove = useCallback((e) => {
|
|
81
|
+
if (!dragging.current)
|
|
82
|
+
return;
|
|
83
|
+
const pct = pointerToPct(e.clientX);
|
|
84
|
+
const origin = dragOrigin.current;
|
|
85
|
+
let newStart = startPct;
|
|
86
|
+
let newEnd = endPct;
|
|
87
|
+
if (dragging.current === 'start') {
|
|
88
|
+
newStart = clamp(pct, 0, endPct - 1);
|
|
89
|
+
}
|
|
90
|
+
else if (dragging.current === 'end') {
|
|
91
|
+
newEnd = clamp(pct, startPct + 1, 100);
|
|
92
|
+
}
|
|
93
|
+
else if (dragging.current === 'track') {
|
|
94
|
+
const delta = pct - origin.pct;
|
|
95
|
+
const span = origin.endPct - origin.startPct;
|
|
96
|
+
let s = origin.startPct + delta;
|
|
97
|
+
let e = origin.endPct + delta;
|
|
98
|
+
if (s < 0) {
|
|
99
|
+
s = 0;
|
|
100
|
+
e = span;
|
|
101
|
+
}
|
|
102
|
+
if (e > 100) {
|
|
103
|
+
e = 100;
|
|
104
|
+
s = 100 - span;
|
|
105
|
+
}
|
|
106
|
+
newStart = s;
|
|
107
|
+
newEnd = e;
|
|
108
|
+
}
|
|
109
|
+
onRangeChange(pctToDate(newStart), pctToDate(newEnd));
|
|
110
|
+
}, [pointerToPct, startPct, endPct, onRangeChange, pctToDate]);
|
|
111
|
+
const handlePointerUp = useCallback((e) => {
|
|
112
|
+
if (!dragging.current)
|
|
113
|
+
return;
|
|
114
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
115
|
+
dragging.current = null;
|
|
116
|
+
setIsDragging(false);
|
|
117
|
+
onRangeCommit?.(selectedStart, selectedEnd);
|
|
118
|
+
}, [selectedStart, selectedEnd, onRangeCommit]);
|
|
119
|
+
// Hover handlers – show dotted line + tooltip while not dragging
|
|
120
|
+
const handleMouseMove = useCallback((e) => {
|
|
121
|
+
if (dragging.current)
|
|
122
|
+
return;
|
|
123
|
+
setHoverPct(pointerToPct(e.clientX));
|
|
124
|
+
}, [pointerToPct]);
|
|
125
|
+
const handleMouseLeave = useCallback(() => {
|
|
126
|
+
setHoverPct(null);
|
|
127
|
+
}, []);
|
|
128
|
+
// Format date for hover tooltip (date + time with seconds)
|
|
129
|
+
const formatHoverDate = useCallback((pct) => {
|
|
130
|
+
const d = pctToDate(pct);
|
|
131
|
+
const date = d.toLocaleDateString(undefined, {
|
|
132
|
+
year: 'numeric',
|
|
133
|
+
month: 'short',
|
|
134
|
+
day: 'numeric',
|
|
135
|
+
});
|
|
136
|
+
const time = d.toLocaleTimeString(undefined, {
|
|
137
|
+
hour: '2-digit',
|
|
138
|
+
minute: '2-digit',
|
|
139
|
+
second: '2-digit',
|
|
140
|
+
});
|
|
141
|
+
return `${date} ${time}`;
|
|
142
|
+
}, [pctToDate]);
|
|
143
|
+
// Rail click to jump
|
|
144
|
+
const handleRailClick = useCallback((e) => {
|
|
145
|
+
if (isDragging)
|
|
146
|
+
return;
|
|
147
|
+
const pct = pointerToPct(e.clientX);
|
|
148
|
+
// Jump the closer handle
|
|
149
|
+
const distToStart = Math.abs(pct - startPct);
|
|
150
|
+
const distToEnd = Math.abs(pct - endPct);
|
|
151
|
+
if (distToStart < distToEnd) {
|
|
152
|
+
const newStart = clamp(pct, 0, endPct - 1);
|
|
153
|
+
onRangeChange(pctToDate(newStart), selectedEnd);
|
|
154
|
+
onRangeCommit?.(pctToDate(newStart), selectedEnd);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const newEnd = clamp(pct, startPct + 1, 100);
|
|
158
|
+
onRangeChange(selectedStart, pctToDate(newEnd));
|
|
159
|
+
onRangeCommit?.(selectedStart, pctToDate(newEnd));
|
|
160
|
+
}
|
|
161
|
+
}, [
|
|
162
|
+
isDragging,
|
|
163
|
+
pointerToPct,
|
|
164
|
+
startPct,
|
|
165
|
+
endPct,
|
|
166
|
+
onRangeChange,
|
|
167
|
+
onRangeCommit,
|
|
168
|
+
pctToDate,
|
|
169
|
+
selectedStart,
|
|
170
|
+
selectedEnd,
|
|
171
|
+
]);
|
|
172
|
+
const RAIL_HEIGHT = height - 24; // Reserve 24px for tick labels
|
|
173
|
+
const HANDLE_WIDTH = 10;
|
|
174
|
+
const HANDLE_HEIGHT = RAIL_HEIGHT;
|
|
175
|
+
return (_jsxs(Box, { sx: {
|
|
176
|
+
width: '100%',
|
|
177
|
+
height,
|
|
178
|
+
px: 3,
|
|
179
|
+
py: 1,
|
|
180
|
+
userSelect: 'none',
|
|
181
|
+
bg: 'canvas.subtle',
|
|
182
|
+
borderBottom: '1px solid',
|
|
183
|
+
borderColor: 'border.default',
|
|
184
|
+
}, children: [_jsxs(Box, { ref: railRef, onClick: handleRailClick, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave, sx: {
|
|
185
|
+
position: 'relative',
|
|
186
|
+
width: '100%',
|
|
187
|
+
height: RAIL_HEIGHT,
|
|
188
|
+
cursor: 'pointer',
|
|
189
|
+
}, children: [_jsx(Box, { sx: {
|
|
190
|
+
position: 'absolute',
|
|
191
|
+
inset: 0,
|
|
192
|
+
bg: 'canvas.inset',
|
|
193
|
+
borderRadius: 1,
|
|
194
|
+
border: '1px solid',
|
|
195
|
+
borderColor: 'border.muted',
|
|
196
|
+
} }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'track'), sx: {
|
|
197
|
+
position: 'absolute',
|
|
198
|
+
top: 0,
|
|
199
|
+
bottom: 0,
|
|
200
|
+
left: `${startPct}%`,
|
|
201
|
+
width: `${endPct - startPct}%`,
|
|
202
|
+
bg: 'accent.muted',
|
|
203
|
+
opacity: 0.45,
|
|
204
|
+
borderTop: '2px solid',
|
|
205
|
+
borderBottom: '2px solid',
|
|
206
|
+
borderColor: 'accent.fg',
|
|
207
|
+
cursor: 'grab',
|
|
208
|
+
zIndex: 2,
|
|
209
|
+
transition: isDragging
|
|
210
|
+
? 'none'
|
|
211
|
+
: 'left 0.05s ease, width 0.05s ease',
|
|
212
|
+
':active': { cursor: 'grabbing' },
|
|
213
|
+
} }), histogram &&
|
|
214
|
+
histMax > 0 &&
|
|
215
|
+
histogram.map((h, i) => {
|
|
216
|
+
const pct = ((h.time.getTime() - tlStart) / tlRange) * 100;
|
|
217
|
+
const barWidth = 100 / (histogram.length || 1);
|
|
218
|
+
const barH = (h.count / histMax) * 100;
|
|
219
|
+
return (_jsx(Box, { sx: {
|
|
220
|
+
position: 'absolute',
|
|
221
|
+
bottom: 0,
|
|
222
|
+
left: `${pct}%`,
|
|
223
|
+
width: `${barWidth}%`,
|
|
224
|
+
height: `${barH}%`,
|
|
225
|
+
bg: 'accent.fg',
|
|
226
|
+
opacity: 0.35,
|
|
227
|
+
borderRadius: '1px 1px 0 0',
|
|
228
|
+
pointerEvents: 'none',
|
|
229
|
+
zIndex: 4,
|
|
230
|
+
} }, i));
|
|
231
|
+
}), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'start'), sx: {
|
|
232
|
+
position: 'absolute',
|
|
233
|
+
top: 0,
|
|
234
|
+
left: `${startPct}%`,
|
|
235
|
+
width: HANDLE_WIDTH,
|
|
236
|
+
height: HANDLE_HEIGHT,
|
|
237
|
+
ml: `-${HANDLE_WIDTH / 2}px`,
|
|
238
|
+
bg: 'accent.fg',
|
|
239
|
+
borderRadius: 1,
|
|
240
|
+
cursor: 'ew-resize',
|
|
241
|
+
zIndex: 3,
|
|
242
|
+
display: 'flex',
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
justifyContent: 'center',
|
|
245
|
+
transition: isDragging ? 'none' : 'left 0.05s ease',
|
|
246
|
+
boxShadow: 'shadow.medium',
|
|
247
|
+
':hover': { bg: 'accent.emphasis' },
|
|
248
|
+
}, children: _jsx(Box, { sx: {
|
|
249
|
+
width: '2px',
|
|
250
|
+
height: '40%',
|
|
251
|
+
bg: 'fg.onEmphasis',
|
|
252
|
+
borderRadius: 1,
|
|
253
|
+
} }) }), _jsx(Box, { onPointerDown: e => handlePointerDown(e, 'end'), sx: {
|
|
254
|
+
position: 'absolute',
|
|
255
|
+
top: 0,
|
|
256
|
+
left: `${endPct}%`,
|
|
257
|
+
width: HANDLE_WIDTH,
|
|
258
|
+
height: HANDLE_HEIGHT,
|
|
259
|
+
ml: `-${HANDLE_WIDTH / 2}px`,
|
|
260
|
+
bg: 'accent.fg',
|
|
261
|
+
borderRadius: 1,
|
|
262
|
+
cursor: 'ew-resize',
|
|
263
|
+
zIndex: 3,
|
|
264
|
+
display: 'flex',
|
|
265
|
+
alignItems: 'center',
|
|
266
|
+
justifyContent: 'center',
|
|
267
|
+
transition: isDragging ? 'none' : 'left 0.05s ease',
|
|
268
|
+
boxShadow: 'shadow.medium',
|
|
269
|
+
':hover': { bg: 'accent.emphasis' },
|
|
270
|
+
}, children: _jsx(Box, { sx: {
|
|
271
|
+
width: '2px',
|
|
272
|
+
height: '40%',
|
|
273
|
+
bg: 'fg.onEmphasis',
|
|
274
|
+
borderRadius: 1,
|
|
275
|
+
} }) }), hoverPct !== null && !isDragging && (_jsxs(_Fragment, { children: [_jsx(Box, { sx: {
|
|
276
|
+
position: 'absolute',
|
|
277
|
+
top: 0,
|
|
278
|
+
left: `${hoverPct}%`,
|
|
279
|
+
width: '1px',
|
|
280
|
+
height: RAIL_HEIGHT,
|
|
281
|
+
borderLeft: '1px dashed',
|
|
282
|
+
borderColor: 'fg.default',
|
|
283
|
+
opacity: 0.55,
|
|
284
|
+
pointerEvents: 'none',
|
|
285
|
+
zIndex: 5,
|
|
286
|
+
} }), _jsx(Box, { sx: {
|
|
287
|
+
position: 'absolute',
|
|
288
|
+
top: -36,
|
|
289
|
+
left: `${hoverPct}%`,
|
|
290
|
+
transform: 'translateX(-50%)',
|
|
291
|
+
bg: 'canvas.overlay',
|
|
292
|
+
border: '1px solid',
|
|
293
|
+
borderColor: 'border.default',
|
|
294
|
+
borderRadius: 2,
|
|
295
|
+
px: 2,
|
|
296
|
+
py: '2px',
|
|
297
|
+
pointerEvents: 'none',
|
|
298
|
+
zIndex: 10,
|
|
299
|
+
boxShadow: 'shadow.medium',
|
|
300
|
+
whiteSpace: 'nowrap',
|
|
301
|
+
}, children: _jsx(Text, { sx: {
|
|
302
|
+
fontSize: '11px',
|
|
303
|
+
color: 'fg.default',
|
|
304
|
+
fontFamily: 'mono',
|
|
305
|
+
lineHeight: '18px',
|
|
306
|
+
}, children: formatHoverDate(hoverPct) }) })] })), ticks.map((t, i) => {
|
|
307
|
+
const pct = ((t.getTime() - tlStart) / tlRange) * 100;
|
|
308
|
+
return (_jsx(Box, { sx: {
|
|
309
|
+
position: 'absolute',
|
|
310
|
+
top: 0,
|
|
311
|
+
left: `${pct}%`,
|
|
312
|
+
width: '1px',
|
|
313
|
+
height: RAIL_HEIGHT,
|
|
314
|
+
bg: 'border.muted',
|
|
315
|
+
pointerEvents: 'none',
|
|
316
|
+
opacity: 0.5,
|
|
317
|
+
zIndex: 1,
|
|
318
|
+
} }, i));
|
|
319
|
+
})] }), _jsx(Box, { sx: {
|
|
320
|
+
position: 'relative',
|
|
321
|
+
width: '100%',
|
|
322
|
+
height: 20,
|
|
323
|
+
mt: '2px',
|
|
324
|
+
}, children: ticks.map((t, i) => {
|
|
325
|
+
const pct = ((t.getTime() - tlStart) / tlRange) * 100;
|
|
326
|
+
return (_jsx(Text, { sx: {
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
left: `${pct}%`,
|
|
329
|
+
transform: 'translateX(-50%)',
|
|
330
|
+
fontSize: '10px',
|
|
331
|
+
color: 'fg.subtle',
|
|
332
|
+
whiteSpace: 'nowrap',
|
|
333
|
+
fontFamily: 'mono',
|
|
334
|
+
lineHeight: '16px',
|
|
335
|
+
pointerEvents: 'none',
|
|
336
|
+
}, children: formatTick(t) }, i));
|
|
337
|
+
}) })] }));
|
|
338
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OtelTracesList – Tabular list of spans with Time / Message / Scope / Duration
|
|
3
|
+
* columns, using Primer React components for consistent theming.
|
|
4
|
+
*
|
|
5
|
+
* Spans that share the same trace_id are grouped into collapsible trees:
|
|
6
|
+
* only root spans are shown initially; clicking a row with children
|
|
7
|
+
* expands / collapses the nested child rows (shown indented).
|
|
8
|
+
*
|
|
9
|
+
* @module otel/OtelTracesList
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import type { OtelTracesListProps } from './types';
|
|
13
|
+
export declare const OtelTracesList: React.FC<OtelTracesListProps>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* OtelTracesList – Tabular list of spans with Time / Message / Scope / Duration
|
|
8
|
+
* columns, using Primer React components for consistent theming.
|
|
9
|
+
*
|
|
10
|
+
* Spans that share the same trace_id are grouped into collapsible trees:
|
|
11
|
+
* only root spans are shown initially; clicking a row with children
|
|
12
|
+
* expands / collapses the nested child rows (shown indented).
|
|
13
|
+
*
|
|
14
|
+
* @module otel/OtelTracesList
|
|
15
|
+
*/
|
|
16
|
+
import { useMemo, useCallback, useState } from 'react';
|
|
17
|
+
import { Box, Text, Label, Spinner } from '@primer/react';
|
|
18
|
+
import { Blankslate } from '@primer/react/experimental';
|
|
19
|
+
import { TelescopeIcon, ChevronRightIcon, ChevronDownIcon, } from '@primer/octicons-react';
|
|
20
|
+
import { formatDuration, formatTime, buildSpanTree } from './utils';
|
|
21
|
+
const GRID_COLS = '140px 1fr 160px 90px';
|
|
22
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
23
|
+
/** Extract gen_ai token counts from span attributes. */
|
|
24
|
+
function getTokenUsage(span) {
|
|
25
|
+
const attrs = span.attributes;
|
|
26
|
+
if (!attrs)
|
|
27
|
+
return null;
|
|
28
|
+
const input = attrs['gen_ai.usage.input_tokens'] != null
|
|
29
|
+
? Number(attrs['gen_ai.usage.input_tokens'])
|
|
30
|
+
: undefined;
|
|
31
|
+
const output = attrs['gen_ai.usage.output_tokens'] != null
|
|
32
|
+
? Number(attrs['gen_ai.usage.output_tokens'])
|
|
33
|
+
: undefined;
|
|
34
|
+
if (input == null && output == null)
|
|
35
|
+
return null;
|
|
36
|
+
return { input, output };
|
|
37
|
+
}
|
|
38
|
+
export const OtelTracesList = ({ spans, loading, selectedSpanId, onSelectSpan, }) => {
|
|
39
|
+
// Set of expanded span_ids (show their children)
|
|
40
|
+
const [expanded, setExpanded] = useState(new Set());
|
|
41
|
+
// Build the span tree from flat input
|
|
42
|
+
const roots = useMemo(() => buildSpanTree(spans), [spans]);
|
|
43
|
+
// Flatten visible rows based on which nodes are expanded
|
|
44
|
+
const visibleRows = useMemo(() => {
|
|
45
|
+
const rows = [];
|
|
46
|
+
function walk(node, depth) {
|
|
47
|
+
const children = node.children ?? [];
|
|
48
|
+
rows.push({ span: node, depth, hasChildren: children.length > 0 });
|
|
49
|
+
if (expanded.has(node.span_id)) {
|
|
50
|
+
for (const child of children) {
|
|
51
|
+
walk(child, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const root of roots) {
|
|
56
|
+
walk(root, 0);
|
|
57
|
+
}
|
|
58
|
+
return rows;
|
|
59
|
+
}, [roots, expanded]);
|
|
60
|
+
const toggleExpand = useCallback((spanId) => {
|
|
61
|
+
setExpanded(prev => {
|
|
62
|
+
const next = new Set(prev);
|
|
63
|
+
if (next.has(spanId)) {
|
|
64
|
+
next.delete(spanId);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
next.add(spanId);
|
|
68
|
+
}
|
|
69
|
+
return next;
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
if (loading) {
|
|
73
|
+
return (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', p: 5 }, children: _jsx(Spinner, { size: "medium" }) }));
|
|
74
|
+
}
|
|
75
|
+
if (spans.length === 0) {
|
|
76
|
+
return (_jsxs(Blankslate, { children: [_jsx(Blankslate.Visual, { children: _jsx(TelescopeIcon, { size: 24 }) }), _jsx(Blankslate.Heading, { children: "No traces found" }), _jsx(Blankslate.Description, { children: "Send some telemetry data first." })] }));
|
|
77
|
+
}
|
|
78
|
+
return (_jsxs(Box, { sx: { width: '100%', flex: 1, minHeight: 0, overflow: 'auto' }, children: [_jsx(Box, { sx: {
|
|
79
|
+
display: 'grid',
|
|
80
|
+
gridTemplateColumns: GRID_COLS,
|
|
81
|
+
gap: 2,
|
|
82
|
+
px: 3,
|
|
83
|
+
position: 'sticky',
|
|
84
|
+
top: 0,
|
|
85
|
+
bg: 'canvas.subtle',
|
|
86
|
+
zIndex: 1,
|
|
87
|
+
borderBottom: '2px solid',
|
|
88
|
+
borderColor: 'border.default',
|
|
89
|
+
}, children: ['Time', 'Message', 'Scope', 'Duration'].map(h => (_jsx(Text, { sx: {
|
|
90
|
+
fontWeight: 'bold',
|
|
91
|
+
fontSize: 0,
|
|
92
|
+
color: 'fg.muted',
|
|
93
|
+
textTransform: 'uppercase',
|
|
94
|
+
letterSpacing: '0.05em',
|
|
95
|
+
py: 2,
|
|
96
|
+
...(h === 'Duration' ? { textAlign: 'right' } : {}),
|
|
97
|
+
}, children: h }, h))) }), visibleRows.map(({ span, depth, hasChildren }, idx) => {
|
|
98
|
+
const isSelected = selectedSpanId === span.span_id;
|
|
99
|
+
const isExpanded = expanded.has(span.span_id);
|
|
100
|
+
const indent = depth * 20;
|
|
101
|
+
return (_jsxs(Box, { onClick: () => {
|
|
102
|
+
if (hasChildren) {
|
|
103
|
+
toggleExpand(span.span_id);
|
|
104
|
+
}
|
|
105
|
+
onSelectSpan?.(span);
|
|
106
|
+
}, sx: {
|
|
107
|
+
display: 'grid',
|
|
108
|
+
gridTemplateColumns: GRID_COLS,
|
|
109
|
+
gap: 2,
|
|
110
|
+
px: 3,
|
|
111
|
+
py: '5px',
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
bg: isSelected
|
|
114
|
+
? 'accent.subtle'
|
|
115
|
+
: depth > 0
|
|
116
|
+
? 'canvas.inset'
|
|
117
|
+
: 'canvas.default',
|
|
118
|
+
borderBottom: '1px solid',
|
|
119
|
+
borderColor: 'border.muted',
|
|
120
|
+
':hover': {
|
|
121
|
+
bg: isSelected ? 'accent.subtle' : 'canvas.subtle',
|
|
122
|
+
},
|
|
123
|
+
}, children: [_jsx(Text, { sx: {
|
|
124
|
+
fontSize: 1,
|
|
125
|
+
fontFamily: 'mono',
|
|
126
|
+
color: 'fg.muted',
|
|
127
|
+
whiteSpace: 'nowrap',
|
|
128
|
+
lineHeight: '22px',
|
|
129
|
+
}, children: formatTime(span.start_time) }), _jsxs(Box, { sx: {
|
|
130
|
+
display: 'flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: 1,
|
|
133
|
+
overflow: 'hidden',
|
|
134
|
+
lineHeight: '22px',
|
|
135
|
+
pl: `${indent}px`,
|
|
136
|
+
}, children: [hasChildren ? (_jsx(Box, { sx: {
|
|
137
|
+
flexShrink: 0,
|
|
138
|
+
color: 'fg.muted',
|
|
139
|
+
display: 'flex',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
}, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 14 })) : (_jsx(ChevronRightIcon, { size: 14 })) })) : depth > 0 ? (
|
|
142
|
+
/* Connector dash for child leaves */
|
|
143
|
+
_jsx(Box, { sx: {
|
|
144
|
+
flexShrink: 0,
|
|
145
|
+
width: '14px',
|
|
146
|
+
display: 'flex',
|
|
147
|
+
alignItems: 'center',
|
|
148
|
+
justifyContent: 'center',
|
|
149
|
+
color: 'fg.subtle',
|
|
150
|
+
fontSize: 0,
|
|
151
|
+
}, children: "\u2500" })) : null, _jsx(Text, { sx: {
|
|
152
|
+
fontSize: 1,
|
|
153
|
+
overflow: 'hidden',
|
|
154
|
+
textOverflow: 'ellipsis',
|
|
155
|
+
whiteSpace: 'nowrap',
|
|
156
|
+
}, title: span.span_name, children: span.span_name }), span.status_code === 'ERROR' && (_jsx(Label, { variant: "danger", size: "small", children: "ERROR" })), (() => {
|
|
157
|
+
const usage = getTokenUsage(span);
|
|
158
|
+
if (!usage)
|
|
159
|
+
return null;
|
|
160
|
+
return (_jsxs(Box, { sx: {
|
|
161
|
+
display: 'inline-flex',
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
gap: '2px',
|
|
164
|
+
border: '1px solid',
|
|
165
|
+
borderColor: 'border.default',
|
|
166
|
+
borderRadius: 2,
|
|
167
|
+
px: 1,
|
|
168
|
+
fontSize: '10px',
|
|
169
|
+
fontFamily: 'mono',
|
|
170
|
+
color: 'fg.muted',
|
|
171
|
+
flexShrink: 0,
|
|
172
|
+
lineHeight: '16px',
|
|
173
|
+
}, children: [usage.input != null && (_jsxs(Box, { sx: {
|
|
174
|
+
display: 'inline-flex',
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
gap: '1px',
|
|
177
|
+
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2197" }), _jsx(Text, { children: usage.input })] })), usage.output != null && (_jsxs(Box, { sx: {
|
|
178
|
+
display: 'inline-flex',
|
|
179
|
+
alignItems: 'center',
|
|
180
|
+
gap: '1px',
|
|
181
|
+
}, children: [_jsx(Text, { sx: { fontSize: '9px', color: 'fg.subtle' }, children: "\u2199" }), _jsx(Text, { children: usage.output })] }))] }));
|
|
182
|
+
})(), span.otel_scope_name && (_jsx(Label, { size: "small", variant: "secondary", sx: { flexShrink: 0, fontSize: '10px' }, children: span.otel_scope_name }))] }), _jsx(Text, { sx: {
|
|
183
|
+
fontSize: 1,
|
|
184
|
+
color: 'fg.muted',
|
|
185
|
+
overflow: 'hidden',
|
|
186
|
+
textOverflow: 'ellipsis',
|
|
187
|
+
whiteSpace: 'nowrap',
|
|
188
|
+
lineHeight: '22px',
|
|
189
|
+
}, title: `${span.service_name} / ${span.otel_scope_name ?? ''}`, children: span.service_name }), _jsx(Text, { sx: {
|
|
190
|
+
fontSize: 1,
|
|
191
|
+
fontFamily: 'mono',
|
|
192
|
+
textAlign: 'right',
|
|
193
|
+
lineHeight: '22px',
|
|
194
|
+
color: span.duration_ms != null && span.duration_ms > 1000
|
|
195
|
+
? 'danger.fg'
|
|
196
|
+
: 'fg.default',
|
|
197
|
+
}, children: formatDuration(span.duration_ms) })] }, `${span.trace_id}-${span.span_id}-${idx}`));
|
|
198
|
+
})] }));
|
|
199
|
+
};
|