@assistant-ui/react-o11y 0.0.33 → 0.0.34
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 +3 -3
- package/dist/context/SpanByIndexProvider.js +15 -19
- package/dist/context/SpanByIndexProvider.js.map +1 -1
- package/dist/resources/SpanResource.d.ts.map +1 -1
- package/dist/resources/SpanResource.js +79 -41
- package/dist/resources/SpanResource.js.map +1 -1
- package/package.json +4 -4
- package/src/context/SpanByIndexProvider.tsx +7 -4
- package/src/resources/SpanResource.test.tsx +106 -0
- package/src/resources/SpanResource.tsx +80 -17
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @assistant-ui/react-o11y
|
|
|
13
13
|
```tsx
|
|
14
14
|
"use client";
|
|
15
15
|
|
|
16
|
-
import { AuiProvider,
|
|
16
|
+
import { AuiProvider, AuiConfig } from "@assistant-ui/store";
|
|
17
17
|
import { SpanPrimitive, SpanResource } from "@assistant-ui/react-o11y";
|
|
18
18
|
|
|
19
19
|
const spans = [
|
|
@@ -22,9 +22,9 @@ const spans = [
|
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
export function Waterfall() {
|
|
25
|
-
const
|
|
25
|
+
const config = AuiConfig({ span: SpanResource({ spans }) });
|
|
26
26
|
return (
|
|
27
|
-
<AuiProvider
|
|
27
|
+
<AuiProvider extends={null} config={config}>
|
|
28
28
|
<SpanPrimitive.Timeline>
|
|
29
29
|
<SpanPrimitive.Children>
|
|
30
30
|
{() => (
|
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
2
|
-
import { AuiProvider, Derived, useAui } from "@assistant-ui/store";
|
|
2
|
+
import { AuiConfig, AuiProvider, Derived, useAui } from "@assistant-ui/store";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/context/SpanByIndexProvider.tsx
|
|
5
5
|
const SpanByIndexProvider = (t0) => {
|
|
6
|
-
const $ = c(
|
|
6
|
+
const $ = c(7);
|
|
7
7
|
const { index, children } = t0;
|
|
8
8
|
const parentAui = useAui();
|
|
9
9
|
let t1;
|
|
10
10
|
if ($[0] !== index || $[1] !== parentAui) {
|
|
11
|
-
t1 = Derived({
|
|
11
|
+
t1 = AuiConfig({ span: Derived({
|
|
12
12
|
source: "span",
|
|
13
13
|
query: { index },
|
|
14
14
|
get: () => parentAui.span.child({ index })
|
|
15
|
-
});
|
|
15
|
+
}) });
|
|
16
16
|
$[0] = index;
|
|
17
17
|
$[1] = parentAui;
|
|
18
18
|
$[2] = t1;
|
|
19
19
|
} else t1 = $[2];
|
|
20
|
+
const config = t1;
|
|
20
21
|
let t2;
|
|
21
|
-
if ($[3] !==
|
|
22
|
-
t2 =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} else t2 = $[4];
|
|
26
|
-
const aui = useAui(t2);
|
|
27
|
-
let t3;
|
|
28
|
-
if ($[5] !== aui || $[6] !== children) {
|
|
29
|
-
t3 = /* @__PURE__ */ jsx(AuiProvider, {
|
|
30
|
-
value: aui,
|
|
22
|
+
if ($[3] !== children || $[4] !== config || $[5] !== parentAui) {
|
|
23
|
+
t2 = /* @__PURE__ */ jsx(AuiProvider, {
|
|
24
|
+
extends: parentAui,
|
|
25
|
+
config,
|
|
31
26
|
children
|
|
32
27
|
});
|
|
33
|
-
$[
|
|
34
|
-
$[
|
|
35
|
-
$[
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
$[3] = children;
|
|
29
|
+
$[4] = config;
|
|
30
|
+
$[5] = parentAui;
|
|
31
|
+
$[6] = t2;
|
|
32
|
+
} else t2 = $[6];
|
|
33
|
+
return t2;
|
|
38
34
|
};
|
|
39
35
|
//#endregion
|
|
40
36
|
export { SpanByIndexProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpanByIndexProvider.js","names":["FC","PropsWithChildren","useAui","AuiProvider","Derived","SpanByIndexProvider","index","t0","$","_c","children","parentAui","t1","source","query","get","
|
|
1
|
+
{"version":3,"file":"SpanByIndexProvider.js","names":["FC","PropsWithChildren","useAui","AuiConfig","AuiProvider","Derived","SpanByIndexProvider","index","t0","$","_c","children","parentAui","t1","span","source","query","get","child","config","t2"],"sources":["../../src/context/SpanByIndexProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { useAui, AuiConfig, AuiProvider, Derived } from \"@assistant-ui/store\";\n\nexport const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({\n index,\n children,\n}) => {\n const parentAui = useAui();\n\n const config = AuiConfig({\n span: Derived({\n source: \"span\",\n query: { index },\n get: () => parentAui.span.child({ index }),\n }),\n });\n return (\n <AuiProvider extends={parentAui} config={config}>\n {children}\n </AuiProvider>\n );\n};\n"],"mappings":";;;;AAGA,MAAaM,uBAAgEE,OAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAC,MAAA,EAAAH,OAAAI,aAAAH;CAI5E,MAAAI,YAAkBV,OAAO;CAAE,IAAAW;CAAA,IAAAJ,EAAA,OAAAF,SAAAE,EAAA,OAAAG,WAAA;EAEZC,KAAAV,UAAU,EAAAW,MACjBT,QAAQ;GAAAU,QACJ;GAAMC,OACP,EAAAT,MAAQ;GAACU,WACLL,UAASE,KAAKI,MAAO,EAAAX,MAAQ,CAAC;EAC3C,CAAC,EACH,CAAC;EAACE,EAAA,KAAAF;EAAAE,EAAA,KAAAG;EAAAH,EAAA,KAAAI;CAAA,OAAAA,KAAAJ,EAAA;CANF,MAAAU,SAAeN;CAMZ,IAAAO;CAAA,IAAAX,EAAA,OAAAE,YAAAF,EAAA,OAAAU,UAAAV,EAAA,OAAAG,WAAA;EAEDQ,KAAA,oBAAC,aAAD;GAAsBR,SAAAA;GAAmBO;GACtCR;EADS,CAAA;EAEEF,EAAA,KAAAE;EAAAF,EAAA,KAAAU;EAAAV,EAAA,KAAAG;EAAAH,EAAA,KAAAW;CAAA,OAAAA,KAAAX,EAAA;CAAA,OAFdW;AAEc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpanResource.d.ts","names":[],"sources":["../../src/resources/SpanResource.tsx"],"mappings":";;;KAOY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;
|
|
1
|
+
{"version":3,"file":"SpanResource.d.ts","names":[],"sources":["../../src/resources/SpanResource.tsx"],"mappings":";;;KAOY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;cA6PW,0CAAY,SAAA;EAlEhB,OAAA"}
|
|
@@ -3,32 +3,68 @@ import { useState } from "@assistant-ui/tap/react-shim";
|
|
|
3
3
|
import { useClientLookup } from "@assistant-ui/store";
|
|
4
4
|
import { resource, withKey } from "@assistant-ui/tap";
|
|
5
5
|
//#region src/resources/SpanResource.tsx
|
|
6
|
-
function calculateDepth(spanId,
|
|
6
|
+
function calculateDepth(spanId, parents, cache) {
|
|
7
7
|
const cached = cache.get(spanId);
|
|
8
8
|
if (cached !== void 0) return cached;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const path = [];
|
|
10
|
+
let current = spanId;
|
|
11
|
+
let depth = -1;
|
|
12
|
+
while (current !== null) {
|
|
13
|
+
const currentDepth = cache.get(current);
|
|
14
|
+
if (currentDepth !== void 0) {
|
|
15
|
+
depth = currentDepth;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
path.push(current);
|
|
19
|
+
current = parents.get(current) ?? null;
|
|
20
|
+
}
|
|
21
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
22
|
+
depth += 1;
|
|
23
|
+
cache.set(path[index], depth);
|
|
13
24
|
}
|
|
14
|
-
const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;
|
|
15
|
-
cache.set(spanId, depth);
|
|
16
25
|
return depth;
|
|
17
26
|
}
|
|
27
|
+
function normalizeSpanParents(spanMap) {
|
|
28
|
+
const parents = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const span of spanMap.values()) parents.set(span.id, span.parentSpanId && spanMap.has(span.parentSpanId) ? span.parentSpanId : null);
|
|
30
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const spanId of spanMap.keys()) {
|
|
32
|
+
if (resolved.has(spanId)) continue;
|
|
33
|
+
const path = [];
|
|
34
|
+
const pathIds = /* @__PURE__ */ new Set();
|
|
35
|
+
let current = spanId;
|
|
36
|
+
while (current !== null && !resolved.has(current)) {
|
|
37
|
+
if (pathIds.has(current)) {
|
|
38
|
+
parents.set(current, null);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
path.push(current);
|
|
42
|
+
pathIds.add(current);
|
|
43
|
+
current = parents.get(current) ?? null;
|
|
44
|
+
}
|
|
45
|
+
for (const id of path) resolved.add(id);
|
|
46
|
+
}
|
|
47
|
+
return parents;
|
|
48
|
+
}
|
|
18
49
|
function enrichSpans(rawSpans) {
|
|
19
50
|
const spanMap = /* @__PURE__ */ new Map();
|
|
20
51
|
for (const span of rawSpans) spanMap.set(span.id, span);
|
|
52
|
+
const parents = normalizeSpanParents(spanMap);
|
|
21
53
|
const depthCache = /* @__PURE__ */ new Map();
|
|
22
54
|
const childrenCount = /* @__PURE__ */ new Map();
|
|
23
|
-
for (const span of rawSpans)
|
|
55
|
+
for (const span of rawSpans) {
|
|
56
|
+
const parentSpanId = parents.get(span.id);
|
|
57
|
+
if (parentSpanId) childrenCount.set(parentSpanId, (childrenCount.get(parentSpanId) ?? 0) + 1);
|
|
58
|
+
}
|
|
24
59
|
const allSpans = /* @__PURE__ */ new Map();
|
|
25
60
|
let min = Infinity;
|
|
26
61
|
let max = -Infinity;
|
|
27
62
|
for (const span of rawSpans) {
|
|
28
|
-
const depth = calculateDepth(span.id,
|
|
63
|
+
const depth = calculateDepth(span.id, parents, depthCache);
|
|
29
64
|
const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;
|
|
30
65
|
allSpans.set(span.id, {
|
|
31
66
|
...span,
|
|
67
|
+
parentSpanId: parents.get(span.id) ?? null,
|
|
32
68
|
depth,
|
|
33
69
|
hasChildren,
|
|
34
70
|
isCollapsed: false
|
|
@@ -42,16 +78,17 @@ function enrichSpans(rawSpans) {
|
|
|
42
78
|
if (max === min) max = min + 100;
|
|
43
79
|
return {
|
|
44
80
|
allSpans,
|
|
81
|
+
parents,
|
|
45
82
|
timeRange: {
|
|
46
83
|
min,
|
|
47
84
|
max
|
|
48
85
|
}
|
|
49
86
|
};
|
|
50
87
|
}
|
|
51
|
-
function buildFlatList(allSpans, collapsedIds) {
|
|
88
|
+
function buildFlatList(allSpans, parents, collapsedIds) {
|
|
52
89
|
const children = /* @__PURE__ */ new Map();
|
|
53
90
|
for (const span of allSpans.values()) {
|
|
54
|
-
const parent = span.
|
|
91
|
+
const parent = parents.get(span.id) ?? null;
|
|
55
92
|
let group = children.get(parent);
|
|
56
93
|
if (!group) {
|
|
57
94
|
group = [];
|
|
@@ -93,7 +130,7 @@ const useSpanChildResource = ({ span, timeRange, onToggleCollapse }) => {
|
|
|
93
130
|
};
|
|
94
131
|
const SpanChildResource = resource(useSpanChildResource);
|
|
95
132
|
const useSpanResource = (t0) => {
|
|
96
|
-
const $ = c(
|
|
133
|
+
const $ = c(20);
|
|
97
134
|
const { spans } = t0;
|
|
98
135
|
let t1;
|
|
99
136
|
if ($[0] !== spans) {
|
|
@@ -101,18 +138,19 @@ const useSpanResource = (t0) => {
|
|
|
101
138
|
$[0] = spans;
|
|
102
139
|
$[1] = t1;
|
|
103
140
|
} else t1 = $[1];
|
|
104
|
-
const { allSpans, timeRange } = t1;
|
|
141
|
+
const { allSpans, parents, timeRange } = t1;
|
|
105
142
|
const [collapsedIds, setCollapsedIds] = useState(_temp);
|
|
106
143
|
let t2;
|
|
107
|
-
if ($[2] !== allSpans || $[3] !== collapsedIds) {
|
|
108
|
-
t2 = buildFlatList(allSpans, collapsedIds);
|
|
144
|
+
if ($[2] !== allSpans || $[3] !== collapsedIds || $[4] !== parents) {
|
|
145
|
+
t2 = buildFlatList(allSpans, parents, collapsedIds);
|
|
109
146
|
$[2] = allSpans;
|
|
110
147
|
$[3] = collapsedIds;
|
|
111
|
-
$[4] =
|
|
112
|
-
|
|
148
|
+
$[4] = parents;
|
|
149
|
+
$[5] = t2;
|
|
150
|
+
} else t2 = $[5];
|
|
113
151
|
const visibleSpans = t2;
|
|
114
152
|
let t3;
|
|
115
|
-
if ($[
|
|
153
|
+
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
|
116
154
|
t3 = (spanId) => {
|
|
117
155
|
setCollapsedIds((prev) => {
|
|
118
156
|
const next = new Set(prev);
|
|
@@ -121,31 +159,31 @@ const useSpanResource = (t0) => {
|
|
|
121
159
|
return next;
|
|
122
160
|
});
|
|
123
161
|
};
|
|
124
|
-
$[
|
|
125
|
-
} else t3 = $[
|
|
162
|
+
$[6] = t3;
|
|
163
|
+
} else t3 = $[6];
|
|
126
164
|
const toggleCollapse = t3;
|
|
127
165
|
let t4;
|
|
128
|
-
if ($[
|
|
166
|
+
if ($[7] !== timeRange || $[8] !== visibleSpans) {
|
|
129
167
|
let t5;
|
|
130
|
-
if ($[
|
|
168
|
+
if ($[10] !== timeRange) {
|
|
131
169
|
t5 = (span) => withKey(span.id, SpanChildResource({
|
|
132
170
|
span,
|
|
133
171
|
timeRange,
|
|
134
172
|
onToggleCollapse: toggleCollapse
|
|
135
173
|
}));
|
|
136
|
-
$[
|
|
137
|
-
$[
|
|
138
|
-
} else t5 = $[
|
|
174
|
+
$[10] = timeRange;
|
|
175
|
+
$[11] = t5;
|
|
176
|
+
} else t5 = $[11];
|
|
139
177
|
t4 = visibleSpans.map(t5);
|
|
140
|
-
$[
|
|
141
|
-
$[
|
|
142
|
-
$[
|
|
143
|
-
} else t4 = $[
|
|
178
|
+
$[7] = timeRange;
|
|
179
|
+
$[8] = visibleSpans;
|
|
180
|
+
$[9] = t4;
|
|
181
|
+
} else t4 = $[9];
|
|
144
182
|
const lookup = useClientLookup(t4);
|
|
145
183
|
const t5 = timeRange.max - timeRange.min;
|
|
146
184
|
const t6 = lookup.state.length > 0;
|
|
147
185
|
let t7;
|
|
148
|
-
if ($[
|
|
186
|
+
if ($[12] !== lookup.state || $[13] !== t5 || $[14] !== t6 || $[15] !== timeRange) {
|
|
149
187
|
const rootState = {
|
|
150
188
|
id: "__root__",
|
|
151
189
|
parentSpanId: null,
|
|
@@ -162,23 +200,23 @@ const useSpanResource = (t0) => {
|
|
|
162
200
|
timeRange
|
|
163
201
|
};
|
|
164
202
|
t7 = () => rootState;
|
|
165
|
-
$[
|
|
166
|
-
$[
|
|
167
|
-
$[
|
|
168
|
-
$[
|
|
169
|
-
$[
|
|
170
|
-
} else t7 = $[
|
|
203
|
+
$[12] = lookup.state;
|
|
204
|
+
$[13] = t5;
|
|
205
|
+
$[14] = t6;
|
|
206
|
+
$[15] = timeRange;
|
|
207
|
+
$[16] = t7;
|
|
208
|
+
} else t7 = $[16];
|
|
171
209
|
let t8;
|
|
172
|
-
if ($[
|
|
210
|
+
if ($[17] !== lookup.get || $[18] !== t7) {
|
|
173
211
|
t8 = {
|
|
174
212
|
getState: t7,
|
|
175
213
|
child: lookup.get,
|
|
176
214
|
toggleCollapse: _temp2
|
|
177
215
|
};
|
|
178
|
-
$[
|
|
179
|
-
$[
|
|
180
|
-
$[
|
|
181
|
-
} else t8 = $[
|
|
216
|
+
$[17] = lookup.get;
|
|
217
|
+
$[18] = t7;
|
|
218
|
+
$[19] = t8;
|
|
219
|
+
} else t8 = $[19];
|
|
182
220
|
return t8;
|
|
183
221
|
};
|
|
184
222
|
const SpanResource = resource(useSpanResource);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SpanResource.js","names":["useMemo","useState","resource","withKey","useClientLookup","ClientOutput","SpanItemState","SpanState","SpanData","id","parentSpanId","name","type","status","startedAt","endedAt","latencyMs","calculateDepth","spanId","spanMap","Map","cache","cached","get","undefined","span","set","depth","enrichSpans","rawSpans","allSpans","timeRange","min","max","depthCache","childrenCount","Infinity","hasChildren","isCollapsed","end","Date","now","buildFlatList","collapsedIds","Set","children","values","parent","group","push","sort","a","b","result","dfs","parentId","kids","has","useSpanChildResource","onToggleCollapse","state","getState","child","Error","toggleCollapse","SpanChildResource","useSpanResource","t0","$","_c","spans","t1","setCollapsedIds","_temp","t2","visibleSpans","t3","Symbol","for","prev","next","delete","add","t4","t5","map","lookup","t6","length","t7","rootState","t8","_temp2","SpanResource"],"sources":["../../src/resources/SpanResource.tsx"],"sourcesContent":["import \"../o11y-scope\";\n\nimport { useMemo, useState } from \"react\";\nimport { resource, withKey } from \"@assistant-ui/tap\";\nimport { useClientLookup, type ClientOutput } from \"@assistant-ui/store\";\nimport type { SpanItemState, SpanState } from \"../o11y-scope\";\n\nexport type SpanData = {\n id: string;\n parentSpanId: string | null;\n name: string;\n type: string;\n status: \"running\" | \"completed\" | \"failed\" | \"skipped\";\n startedAt: number;\n endedAt: number | null;\n latencyMs: number | null;\n};\n\nfunction calculateDepth(\n spanId: string,\n spanMap: Map<string, SpanData>,\n cache: Map<string, number>,\n): number {\n const cached = cache.get(spanId);\n if (cached !== undefined) return cached;\n\n const span = spanMap.get(spanId);\n if (!span?.parentSpanId) {\n cache.set(spanId, 0);\n return 0;\n }\n\n const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;\n cache.set(spanId, depth);\n return depth;\n}\n\nfunction enrichSpans(rawSpans: SpanData[]): {\n allSpans: Map<string, SpanItemState>;\n timeRange: { min: number; max: number };\n} {\n const spanMap = new Map<string, SpanData>();\n for (const span of rawSpans) {\n spanMap.set(span.id, span);\n }\n\n const depthCache = new Map<string, number>();\n const childrenCount = new Map<string, number>();\n for (const span of rawSpans) {\n if (span.parentSpanId) {\n childrenCount.set(\n span.parentSpanId,\n (childrenCount.get(span.parentSpanId) ?? 0) + 1,\n );\n }\n }\n\n const allSpans = new Map<string, SpanItemState>();\n let min = Infinity;\n let max = -Infinity;\n\n for (const span of rawSpans) {\n const depth = calculateDepth(span.id, spanMap, depthCache);\n const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;\n allSpans.set(span.id, { ...span, depth, hasChildren, isCollapsed: false });\n\n if (span.startedAt < min) min = span.startedAt;\n const end = span.endedAt ?? Date.now();\n if (end > max) max = end;\n }\n\n if (min === Infinity) min = Date.now();\n if (max === -Infinity) max = Date.now();\n if (max === min) max = min + 100;\n\n return { allSpans, timeRange: { min, max } };\n}\n\nfunction buildFlatList(\n allSpans: Map<string, SpanItemState>,\n collapsedIds: Set<string>,\n): SpanItemState[] {\n const children = new Map<string | null, SpanItemState[]>();\n for (const span of allSpans.values()) {\n const parent = span.parentSpanId ?? null;\n let group = children.get(parent);\n if (!group) {\n group = [];\n children.set(parent, group);\n }\n group.push(span);\n }\n\n for (const group of children.values()) {\n group.sort((a, b) => a.startedAt - b.startedAt);\n }\n\n const result: SpanItemState[] = [];\n function dfs(parentId: string | null) {\n const kids = children.get(parentId);\n if (!kids) return;\n for (const span of kids) {\n result.push({ ...span, isCollapsed: collapsedIds.has(span.id) });\n if (!collapsedIds.has(span.id)) {\n dfs(span.id);\n }\n }\n }\n dfs(null);\n return result;\n}\n\nconst useSpanChildResource = ({\n span,\n timeRange,\n onToggleCollapse,\n}: {\n span: SpanItemState;\n timeRange: { min: number; max: number };\n onToggleCollapse: (spanId: string) => void;\n}): ClientOutput<\"span\"> => {\n const state: SpanState = {\n ...span,\n children: [],\n timeRange,\n };\n return {\n getState: () => state,\n child: () => {\n throw new Error(\"child spans do not have children in flat mode\");\n },\n toggleCollapse: () => {\n onToggleCollapse(span.id);\n },\n };\n};\n\nconst SpanChildResource = resource(useSpanChildResource);\n\nconst useSpanResource = ({\n spans,\n}: {\n spans: SpanData[];\n}): ClientOutput<\"span\"> => {\n const { allSpans, timeRange } = useMemo(() => enrichSpans(spans), [spans]);\n\n const [collapsedIds, setCollapsedIds] = useState<Set<string>>(\n () => new Set(),\n );\n\n const visibleSpans = useMemo(\n () => buildFlatList(allSpans, collapsedIds),\n [allSpans, collapsedIds],\n );\n\n const toggleCollapse = (spanId: string) => {\n setCollapsedIds((prev) => {\n const next = new Set(prev);\n if (next.has(spanId)) {\n next.delete(spanId);\n } else {\n next.add(spanId);\n }\n return next;\n });\n };\n\n const lookup = useClientLookup(\n visibleSpans.map((span) =>\n withKey(\n span.id,\n SpanChildResource({\n span,\n timeRange,\n onToggleCollapse: toggleCollapse,\n }),\n ),\n ),\n );\n\n const rootState: SpanState = {\n id: \"__root__\",\n parentSpanId: null,\n name: \"\",\n type: \"root\",\n status: \"completed\",\n startedAt: timeRange.min,\n endedAt: timeRange.max,\n latencyMs: timeRange.max - timeRange.min,\n depth: -1,\n hasChildren: lookup.state.length > 0,\n isCollapsed: false,\n children: lookup.state,\n timeRange,\n };\n\n return {\n getState: () => rootState,\n child: lookup.get,\n toggleCollapse: () => {\n // Root span collapse is a no-op\n },\n };\n};\n\nexport const SpanResource = resource(useSpanResource);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SpanResource.js","names":["useMemo","useState","resource","withKey","useClientLookup","ClientOutput","SpanItemState","SpanState","SpanData","id","parentSpanId","name","type","status","startedAt","endedAt","latencyMs","calculateDepth","spanId","parents","Map","cache","cached","get","undefined","path","current","depth","currentDepth","push","index","length","set","normalizeSpanParents","spanMap","span","values","has","resolved","Set","keys","pathIds","add","enrichSpans","rawSpans","allSpans","timeRange","min","max","depthCache","childrenCount","Infinity","hasChildren","isCollapsed","end","Date","now","buildFlatList","collapsedIds","children","parent","group","sort","a","b","result","dfs","parentId","kids","useSpanChildResource","onToggleCollapse","state","getState","child","Error","toggleCollapse","SpanChildResource","useSpanResource","t0","$","_c","spans","t1","setCollapsedIds","_temp","t2","visibleSpans","t3","Symbol","for","prev","next","delete","t4","t5","map","lookup","t6","t7","rootState","t8","_temp2","SpanResource"],"sources":["../../src/resources/SpanResource.tsx"],"sourcesContent":["import \"../o11y-scope\";\n\nimport { useMemo, useState } from \"react\";\nimport { resource, withKey } from \"@assistant-ui/tap\";\nimport { useClientLookup, type ClientOutput } from \"@assistant-ui/store\";\nimport type { SpanItemState, SpanState } from \"../o11y-scope\";\n\nexport type SpanData = {\n id: string;\n parentSpanId: string | null;\n name: string;\n type: string;\n status: \"running\" | \"completed\" | \"failed\" | \"skipped\";\n startedAt: number;\n endedAt: number | null;\n latencyMs: number | null;\n};\n\nfunction calculateDepth(\n spanId: string,\n parents: Map<string, string | null>,\n cache: Map<string, number>,\n): number {\n const cached = cache.get(spanId);\n if (cached !== undefined) return cached;\n\n const path: string[] = [];\n let current: string | null = spanId;\n let depth = -1;\n\n while (current !== null) {\n const currentDepth = cache.get(current);\n if (currentDepth !== undefined) {\n depth = currentDepth;\n break;\n }\n\n path.push(current);\n current = parents.get(current) ?? null;\n }\n\n for (let index = path.length - 1; index >= 0; index--) {\n depth += 1;\n cache.set(path[index]!, depth);\n }\n\n return depth;\n}\n\nfunction normalizeSpanParents(\n spanMap: Map<string, SpanData>,\n): Map<string, string | null> {\n const parents = new Map<string, string | null>();\n for (const span of spanMap.values()) {\n parents.set(\n span.id,\n span.parentSpanId && spanMap.has(span.parentSpanId)\n ? span.parentSpanId\n : null,\n );\n }\n\n const resolved = new Set<string>();\n for (const spanId of spanMap.keys()) {\n if (resolved.has(spanId)) continue;\n\n const path: string[] = [];\n const pathIds = new Set<string>();\n let current: string | null = spanId;\n\n while (current !== null && !resolved.has(current)) {\n if (pathIds.has(current)) {\n parents.set(current, null);\n break;\n }\n\n path.push(current);\n pathIds.add(current);\n current = parents.get(current) ?? null;\n }\n\n for (const id of path) resolved.add(id);\n }\n\n return parents;\n}\n\nfunction enrichSpans(rawSpans: SpanData[]): {\n allSpans: Map<string, SpanItemState>;\n parents: Map<string, string | null>;\n timeRange: { min: number; max: number };\n} {\n const spanMap = new Map<string, SpanData>();\n for (const span of rawSpans) {\n spanMap.set(span.id, span);\n }\n\n const parents = normalizeSpanParents(spanMap);\n const depthCache = new Map<string, number>();\n const childrenCount = new Map<string, number>();\n for (const span of rawSpans) {\n const parentSpanId = parents.get(span.id);\n if (parentSpanId) {\n childrenCount.set(\n parentSpanId,\n (childrenCount.get(parentSpanId) ?? 0) + 1,\n );\n }\n }\n\n const allSpans = new Map<string, SpanItemState>();\n let min = Infinity;\n let max = -Infinity;\n\n for (const span of rawSpans) {\n const depth = calculateDepth(span.id, parents, depthCache);\n const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;\n allSpans.set(span.id, {\n ...span,\n parentSpanId: parents.get(span.id) ?? null,\n depth,\n hasChildren,\n isCollapsed: false,\n });\n\n if (span.startedAt < min) min = span.startedAt;\n const end = span.endedAt ?? Date.now();\n if (end > max) max = end;\n }\n\n if (min === Infinity) min = Date.now();\n if (max === -Infinity) max = Date.now();\n if (max === min) max = min + 100;\n\n return { allSpans, parents, timeRange: { min, max } };\n}\n\nfunction buildFlatList(\n allSpans: Map<string, SpanItemState>,\n parents: Map<string, string | null>,\n collapsedIds: Set<string>,\n): SpanItemState[] {\n const children = new Map<string | null, SpanItemState[]>();\n for (const span of allSpans.values()) {\n const parent = parents.get(span.id) ?? null;\n let group = children.get(parent);\n if (!group) {\n group = [];\n children.set(parent, group);\n }\n group.push(span);\n }\n\n for (const group of children.values()) {\n group.sort((a, b) => a.startedAt - b.startedAt);\n }\n\n const result: SpanItemState[] = [];\n function dfs(parentId: string | null) {\n const kids = children.get(parentId);\n if (!kids) return;\n for (const span of kids) {\n result.push({ ...span, isCollapsed: collapsedIds.has(span.id) });\n if (!collapsedIds.has(span.id)) {\n dfs(span.id);\n }\n }\n }\n dfs(null);\n return result;\n}\n\nconst useSpanChildResource = ({\n span,\n timeRange,\n onToggleCollapse,\n}: {\n span: SpanItemState;\n timeRange: { min: number; max: number };\n onToggleCollapse: (spanId: string) => void;\n}): ClientOutput<\"span\"> => {\n const state: SpanState = {\n ...span,\n children: [],\n timeRange,\n };\n return {\n getState: () => state,\n child: () => {\n throw new Error(\"child spans do not have children in flat mode\");\n },\n toggleCollapse: () => {\n onToggleCollapse(span.id);\n },\n };\n};\n\nconst SpanChildResource = resource(useSpanChildResource);\n\nconst useSpanResource = ({\n spans,\n}: {\n spans: SpanData[];\n}): ClientOutput<\"span\"> => {\n const { allSpans, parents, timeRange } = useMemo(\n () => enrichSpans(spans),\n [spans],\n );\n\n const [collapsedIds, setCollapsedIds] = useState<Set<string>>(\n () => new Set(),\n );\n\n const visibleSpans = useMemo(\n () => buildFlatList(allSpans, parents, collapsedIds),\n [allSpans, parents, collapsedIds],\n );\n\n const toggleCollapse = (spanId: string) => {\n setCollapsedIds((prev) => {\n const next = new Set(prev);\n if (next.has(spanId)) {\n next.delete(spanId);\n } else {\n next.add(spanId);\n }\n return next;\n });\n };\n\n const lookup = useClientLookup(\n visibleSpans.map((span) =>\n withKey(\n span.id,\n SpanChildResource({\n span,\n timeRange,\n onToggleCollapse: toggleCollapse,\n }),\n ),\n ),\n );\n\n const rootState: SpanState = {\n id: \"__root__\",\n parentSpanId: null,\n name: \"\",\n type: \"root\",\n status: \"completed\",\n startedAt: timeRange.min,\n endedAt: timeRange.max,\n latencyMs: timeRange.max - timeRange.min,\n depth: -1,\n hasChildren: lookup.state.length > 0,\n isCollapsed: false,\n children: lookup.state,\n timeRange,\n };\n\n return {\n getState: () => rootState,\n child: lookup.get,\n toggleCollapse: () => {\n // Root span collapse is a no-op\n },\n };\n};\n\nexport const SpanResource = resource(useSpanResource);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-o11y",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"description": "Observability primitives for @assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"observability",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"sideEffects": false,
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@radix-ui/react-primitive": "^2.1.10",
|
|
32
|
-
"@assistant-ui/store": "0.3.
|
|
33
|
-
"@assistant-ui/tap": "0.9.
|
|
32
|
+
"@assistant-ui/store": "0.3.4",
|
|
33
|
+
"@assistant-ui/tap": "0.9.10"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@types/react": "*",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"react": "^19.2.8",
|
|
48
48
|
"react-dom": "^19.2.8",
|
|
49
49
|
"vitest": "^4.1.10",
|
|
50
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
50
|
+
"@assistant-ui/x-buildutils": "0.0.21"
|
|
51
51
|
},
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FC, PropsWithChildren } from "react";
|
|
2
|
-
import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
|
|
2
|
+
import { useAui, AuiConfig, AuiProvider, Derived } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({
|
|
5
5
|
index,
|
|
@@ -7,13 +7,16 @@ export const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({
|
|
|
7
7
|
}) => {
|
|
8
8
|
const parentAui = useAui();
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const config = AuiConfig({
|
|
11
11
|
span: Derived({
|
|
12
12
|
source: "span",
|
|
13
13
|
query: { index },
|
|
14
14
|
get: () => parentAui.span.child({ index }),
|
|
15
15
|
}),
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
return (
|
|
18
|
+
<AuiProvider extends={parentAui} config={config}>
|
|
19
|
+
{children}
|
|
20
|
+
</AuiProvider>
|
|
21
|
+
);
|
|
19
22
|
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { AuiProvider, useAui } from "@assistant-ui/store";
|
|
4
|
+
import * as SpanPrimitive from "../primitives/span";
|
|
5
|
+
import { SpanResource, type SpanData } from "./SpanResource";
|
|
6
|
+
|
|
7
|
+
const createSpan = (id: string, parentSpanId: string | null): SpanData => ({
|
|
8
|
+
id,
|
|
9
|
+
parentSpanId,
|
|
10
|
+
name: id,
|
|
11
|
+
type: "test",
|
|
12
|
+
status: "completed",
|
|
13
|
+
startedAt: 0,
|
|
14
|
+
endedAt: 1,
|
|
15
|
+
latencyMs: 1,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const SpanFixture = ({ spans }: { spans: SpanData[] }) => {
|
|
19
|
+
const aui = useAui({ span: SpanResource({ spans }) });
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<AuiProvider value={aui}>
|
|
23
|
+
<SpanPrimitive.Children>
|
|
24
|
+
{({ span }) => (
|
|
25
|
+
<span
|
|
26
|
+
data-span-id={span.id}
|
|
27
|
+
data-parent-span-id={span.parentSpanId ?? "root"}
|
|
28
|
+
data-span-depth={span.depth}
|
|
29
|
+
/>
|
|
30
|
+
)}
|
|
31
|
+
</SpanPrimitive.Children>
|
|
32
|
+
</AuiProvider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const renderSpans = (spans: SpanData[]) =>
|
|
37
|
+
renderToStaticMarkup(<SpanFixture spans={spans} />);
|
|
38
|
+
|
|
39
|
+
describe("SpanResource", () => {
|
|
40
|
+
it("preserves valid parent hierarchies", () => {
|
|
41
|
+
const html = renderSpans([
|
|
42
|
+
createSpan("root", null),
|
|
43
|
+
createSpan("child", "root"),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
expect(html).toContain(
|
|
47
|
+
'data-span-id="root" data-parent-span-id="root" data-span-depth="0"',
|
|
48
|
+
);
|
|
49
|
+
expect(html).toContain(
|
|
50
|
+
'data-span-id="child" data-parent-span-id="root" data-span-depth="1"',
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("renders cyclic parent hierarchies without recursing indefinitely", () => {
|
|
55
|
+
const html = renderSpans([
|
|
56
|
+
createSpan("first", "second"),
|
|
57
|
+
createSpan("second", "first"),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
expect(html.match(/data-span-id=/g)).toHaveLength(2);
|
|
61
|
+
expect(html).toContain(
|
|
62
|
+
'data-span-id="first" data-parent-span-id="root" data-span-depth="0"',
|
|
63
|
+
);
|
|
64
|
+
expect(html).toContain(
|
|
65
|
+
'data-span-id="second" data-parent-span-id="first" data-span-depth="1"',
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("renders every span when a parent chain enters a longer cycle", () => {
|
|
70
|
+
const html = renderSpans([
|
|
71
|
+
createSpan("branch", "first"),
|
|
72
|
+
createSpan("first", "second"),
|
|
73
|
+
createSpan("second", "third"),
|
|
74
|
+
createSpan("third", "first"),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
expect(html.match(/data-span-id=/g)).toHaveLength(4);
|
|
78
|
+
expect(html).toContain(
|
|
79
|
+
'data-span-id="first" data-parent-span-id="root" data-span-depth="0"',
|
|
80
|
+
);
|
|
81
|
+
expect(html).toContain(
|
|
82
|
+
'data-span-id="third" data-parent-span-id="first" data-span-depth="1"',
|
|
83
|
+
);
|
|
84
|
+
expect(html).toContain(
|
|
85
|
+
'data-span-id="second" data-parent-span-id="third" data-span-depth="2"',
|
|
86
|
+
);
|
|
87
|
+
expect(html).toContain(
|
|
88
|
+
'data-span-id="branch" data-parent-span-id="first" data-span-depth="1"',
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("renders self-parented and orphaned spans as roots", () => {
|
|
93
|
+
const html = renderSpans([
|
|
94
|
+
createSpan("self", "self"),
|
|
95
|
+
createSpan("orphan", "missing"),
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
expect(html.match(/data-span-id=/g)).toHaveLength(2);
|
|
99
|
+
expect(html).toContain(
|
|
100
|
+
'data-span-id="self" data-parent-span-id="root" data-span-depth="0"',
|
|
101
|
+
);
|
|
102
|
+
expect(html).toContain(
|
|
103
|
+
'data-span-id="orphan" data-parent-span-id="root" data-span-depth="0"',
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -18,25 +18,76 @@ export type SpanData = {
|
|
|
18
18
|
|
|
19
19
|
function calculateDepth(
|
|
20
20
|
spanId: string,
|
|
21
|
-
|
|
21
|
+
parents: Map<string, string | null>,
|
|
22
22
|
cache: Map<string, number>,
|
|
23
23
|
): number {
|
|
24
24
|
const cached = cache.get(spanId);
|
|
25
25
|
if (cached !== undefined) return cached;
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const path: string[] = [];
|
|
28
|
+
let current: string | null = spanId;
|
|
29
|
+
let depth = -1;
|
|
30
|
+
|
|
31
|
+
while (current !== null) {
|
|
32
|
+
const currentDepth = cache.get(current);
|
|
33
|
+
if (currentDepth !== undefined) {
|
|
34
|
+
depth = currentDepth;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
path.push(current);
|
|
39
|
+
current = parents.get(current) ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (let index = path.length - 1; index >= 0; index--) {
|
|
43
|
+
depth += 1;
|
|
44
|
+
cache.set(path[index]!, depth);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;
|
|
34
|
-
cache.set(spanId, depth);
|
|
35
47
|
return depth;
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
function normalizeSpanParents(
|
|
51
|
+
spanMap: Map<string, SpanData>,
|
|
52
|
+
): Map<string, string | null> {
|
|
53
|
+
const parents = new Map<string, string | null>();
|
|
54
|
+
for (const span of spanMap.values()) {
|
|
55
|
+
parents.set(
|
|
56
|
+
span.id,
|
|
57
|
+
span.parentSpanId && spanMap.has(span.parentSpanId)
|
|
58
|
+
? span.parentSpanId
|
|
59
|
+
: null,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resolved = new Set<string>();
|
|
64
|
+
for (const spanId of spanMap.keys()) {
|
|
65
|
+
if (resolved.has(spanId)) continue;
|
|
66
|
+
|
|
67
|
+
const path: string[] = [];
|
|
68
|
+
const pathIds = new Set<string>();
|
|
69
|
+
let current: string | null = spanId;
|
|
70
|
+
|
|
71
|
+
while (current !== null && !resolved.has(current)) {
|
|
72
|
+
if (pathIds.has(current)) {
|
|
73
|
+
parents.set(current, null);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
path.push(current);
|
|
78
|
+
pathIds.add(current);
|
|
79
|
+
current = parents.get(current) ?? null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const id of path) resolved.add(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return parents;
|
|
86
|
+
}
|
|
87
|
+
|
|
38
88
|
function enrichSpans(rawSpans: SpanData[]): {
|
|
39
89
|
allSpans: Map<string, SpanItemState>;
|
|
90
|
+
parents: Map<string, string | null>;
|
|
40
91
|
timeRange: { min: number; max: number };
|
|
41
92
|
} {
|
|
42
93
|
const spanMap = new Map<string, SpanData>();
|
|
@@ -44,13 +95,15 @@ function enrichSpans(rawSpans: SpanData[]): {
|
|
|
44
95
|
spanMap.set(span.id, span);
|
|
45
96
|
}
|
|
46
97
|
|
|
98
|
+
const parents = normalizeSpanParents(spanMap);
|
|
47
99
|
const depthCache = new Map<string, number>();
|
|
48
100
|
const childrenCount = new Map<string, number>();
|
|
49
101
|
for (const span of rawSpans) {
|
|
50
|
-
|
|
102
|
+
const parentSpanId = parents.get(span.id);
|
|
103
|
+
if (parentSpanId) {
|
|
51
104
|
childrenCount.set(
|
|
52
|
-
|
|
53
|
-
(childrenCount.get(
|
|
105
|
+
parentSpanId,
|
|
106
|
+
(childrenCount.get(parentSpanId) ?? 0) + 1,
|
|
54
107
|
);
|
|
55
108
|
}
|
|
56
109
|
}
|
|
@@ -60,9 +113,15 @@ function enrichSpans(rawSpans: SpanData[]): {
|
|
|
60
113
|
let max = -Infinity;
|
|
61
114
|
|
|
62
115
|
for (const span of rawSpans) {
|
|
63
|
-
const depth = calculateDepth(span.id,
|
|
116
|
+
const depth = calculateDepth(span.id, parents, depthCache);
|
|
64
117
|
const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;
|
|
65
|
-
allSpans.set(span.id, {
|
|
118
|
+
allSpans.set(span.id, {
|
|
119
|
+
...span,
|
|
120
|
+
parentSpanId: parents.get(span.id) ?? null,
|
|
121
|
+
depth,
|
|
122
|
+
hasChildren,
|
|
123
|
+
isCollapsed: false,
|
|
124
|
+
});
|
|
66
125
|
|
|
67
126
|
if (span.startedAt < min) min = span.startedAt;
|
|
68
127
|
const end = span.endedAt ?? Date.now();
|
|
@@ -73,16 +132,17 @@ function enrichSpans(rawSpans: SpanData[]): {
|
|
|
73
132
|
if (max === -Infinity) max = Date.now();
|
|
74
133
|
if (max === min) max = min + 100;
|
|
75
134
|
|
|
76
|
-
return { allSpans, timeRange: { min, max } };
|
|
135
|
+
return { allSpans, parents, timeRange: { min, max } };
|
|
77
136
|
}
|
|
78
137
|
|
|
79
138
|
function buildFlatList(
|
|
80
139
|
allSpans: Map<string, SpanItemState>,
|
|
140
|
+
parents: Map<string, string | null>,
|
|
81
141
|
collapsedIds: Set<string>,
|
|
82
142
|
): SpanItemState[] {
|
|
83
143
|
const children = new Map<string | null, SpanItemState[]>();
|
|
84
144
|
for (const span of allSpans.values()) {
|
|
85
|
-
const parent = span.
|
|
145
|
+
const parent = parents.get(span.id) ?? null;
|
|
86
146
|
let group = children.get(parent);
|
|
87
147
|
if (!group) {
|
|
88
148
|
group = [];
|
|
@@ -142,15 +202,18 @@ const useSpanResource = ({
|
|
|
142
202
|
}: {
|
|
143
203
|
spans: SpanData[];
|
|
144
204
|
}): ClientOutput<"span"> => {
|
|
145
|
-
const { allSpans, timeRange } = useMemo(
|
|
205
|
+
const { allSpans, parents, timeRange } = useMemo(
|
|
206
|
+
() => enrichSpans(spans),
|
|
207
|
+
[spans],
|
|
208
|
+
);
|
|
146
209
|
|
|
147
210
|
const [collapsedIds, setCollapsedIds] = useState<Set<string>>(
|
|
148
211
|
() => new Set(),
|
|
149
212
|
);
|
|
150
213
|
|
|
151
214
|
const visibleSpans = useMemo(
|
|
152
|
-
() => buildFlatList(allSpans, collapsedIds),
|
|
153
|
-
[allSpans, collapsedIds],
|
|
215
|
+
() => buildFlatList(allSpans, parents, collapsedIds),
|
|
216
|
+
[allSpans, parents, collapsedIds],
|
|
154
217
|
);
|
|
155
218
|
|
|
156
219
|
const toggleCollapse = (spanId: string) => {
|