@flanksource/clicky-ui 0.3.56 → 0.3.57

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.
Files changed (141) hide show
  1. package/dist/components/ErrorWrapper.cjs +15 -4
  2. package/dist/components/ErrorWrapper.cjs.map +1 -1
  3. package/dist/components/ErrorWrapper.d.ts +7 -0
  4. package/dist/components/ErrorWrapper.d.ts.map +1 -1
  5. package/dist/components/ErrorWrapper.js +15 -4
  6. package/dist/components/ErrorWrapper.js.map +1 -1
  7. package/dist/data/ai/PromptCatalog/PromptLayerStrip.cjs +6 -3
  8. package/dist/data/ai/PromptCatalog/PromptLayerStrip.cjs.map +1 -1
  9. package/dist/data/ai/PromptCatalog/PromptLayerStrip.js +6 -3
  10. package/dist/data/ai/PromptCatalog/PromptLayerStrip.js.map +1 -1
  11. package/dist/data/ai/PromptCatalog/PromptPage.cjs +2 -1
  12. package/dist/data/ai/PromptCatalog/PromptPage.cjs.map +1 -1
  13. package/dist/data/ai/PromptCatalog/PromptPage.d.ts.map +1 -1
  14. package/dist/data/ai/PromptCatalog/PromptPage.js +2 -1
  15. package/dist/data/ai/PromptCatalog/PromptPage.js.map +1 -1
  16. package/dist/data/ai/PromptCatalog/PromptPageHeader.cjs +9 -6
  17. package/dist/data/ai/PromptCatalog/PromptPageHeader.cjs.map +1 -1
  18. package/dist/data/ai/PromptCatalog/PromptPageHeader.d.ts.map +1 -1
  19. package/dist/data/ai/PromptCatalog/PromptPageHeader.js +9 -6
  20. package/dist/data/ai/PromptCatalog/PromptPageHeader.js.map +1 -1
  21. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.cjs +3 -3
  22. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.cjs.map +1 -1
  23. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.d.ts +2 -1
  24. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.d.ts.map +1 -1
  25. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.js +3 -3
  26. package/dist/data/ai/PromptCatalog/PromptPageSaveBar.js.map +1 -1
  27. package/dist/data/ai/PromptCatalog/prompt-page-model.cjs +4 -3
  28. package/dist/data/ai/PromptCatalog/prompt-page-model.cjs.map +1 -1
  29. package/dist/data/ai/PromptCatalog/prompt-page-model.d.ts +1 -1
  30. package/dist/data/ai/PromptCatalog/prompt-page-model.d.ts.map +1 -1
  31. package/dist/data/ai/PromptCatalog/prompt-page-model.js +4 -3
  32. package/dist/data/ai/PromptCatalog/prompt-page-model.js.map +1 -1
  33. package/dist/data/ai/PromptCatalog/types.d.ts +1 -0
  34. package/dist/data/ai/PromptCatalog/types.d.ts.map +1 -1
  35. package/dist/data/traces/index.d.ts +7 -0
  36. package/dist/data/traces/index.d.ts.map +1 -1
  37. package/dist/data/traces/otel/OperationList.cjs +64 -0
  38. package/dist/data/traces/otel/OperationList.cjs.map +1 -0
  39. package/dist/data/traces/otel/OperationList.d.ts +10 -0
  40. package/dist/data/traces/otel/OperationList.d.ts.map +1 -0
  41. package/dist/data/traces/otel/OperationList.js +64 -0
  42. package/dist/data/traces/otel/OperationList.js.map +1 -0
  43. package/dist/data/traces/otel/OtelTraceDetail.cjs +781 -0
  44. package/dist/data/traces/otel/OtelTraceDetail.cjs.map +1 -0
  45. package/dist/data/traces/otel/OtelTraceDetail.d.ts +7 -0
  46. package/dist/data/traces/otel/OtelTraceDetail.d.ts.map +1 -0
  47. package/dist/data/traces/otel/OtelTraceDetail.js +781 -0
  48. package/dist/data/traces/otel/OtelTraceDetail.js.map +1 -0
  49. package/dist/data/traces/otel/OtelTraceSearch.cjs +145 -0
  50. package/dist/data/traces/otel/OtelTraceSearch.cjs.map +1 -0
  51. package/dist/data/traces/otel/OtelTraceSearch.d.ts +16 -0
  52. package/dist/data/traces/otel/OtelTraceSearch.d.ts.map +1 -0
  53. package/dist/data/traces/otel/OtelTraceSearch.js +145 -0
  54. package/dist/data/traces/otel/OtelTraceSearch.js.map +1 -0
  55. package/dist/data/traces/otel/OtelTraceTable.cjs +47 -0
  56. package/dist/data/traces/otel/OtelTraceTable.cjs.map +1 -0
  57. package/dist/data/traces/otel/OtelTraceTable.d.ts +12 -0
  58. package/dist/data/traces/otel/OtelTraceTable.d.ts.map +1 -0
  59. package/dist/data/traces/otel/OtelTraceTable.js +47 -0
  60. package/dist/data/traces/otel/OtelTraceTable.js.map +1 -0
  61. package/dist/data/traces/otel/QueryBar.cjs +158 -0
  62. package/dist/data/traces/otel/QueryBar.cjs.map +1 -0
  63. package/dist/data/traces/otel/QueryBar.d.ts +12 -0
  64. package/dist/data/traces/otel/QueryBar.d.ts.map +1 -0
  65. package/dist/data/traces/otel/QueryBar.js +158 -0
  66. package/dist/data/traces/otel/QueryBar.js.map +1 -0
  67. package/dist/data/traces/otel/SpanCards.cjs +264 -0
  68. package/dist/data/traces/otel/SpanCards.cjs.map +1 -0
  69. package/dist/data/traces/otel/SpanCards.d.ts +17 -0
  70. package/dist/data/traces/otel/SpanCards.d.ts.map +1 -0
  71. package/dist/data/traces/otel/SpanCards.js +261 -0
  72. package/dist/data/traces/otel/SpanCards.js.map +1 -0
  73. package/dist/data/traces/otel/SpanCategory.cjs +18 -0
  74. package/dist/data/traces/otel/SpanCategory.cjs.map +1 -0
  75. package/dist/data/traces/otel/SpanCategory.d.ts +7 -0
  76. package/dist/data/traces/otel/SpanCategory.d.ts.map +1 -0
  77. package/dist/data/traces/otel/SpanCategory.js +18 -0
  78. package/dist/data/traces/otel/SpanCategory.js.map +1 -0
  79. package/dist/data/traces/otel/TraceResults.cjs +195 -0
  80. package/dist/data/traces/otel/TraceResults.cjs.map +1 -0
  81. package/dist/data/traces/otel/TraceResults.d.ts +19 -0
  82. package/dist/data/traces/otel/TraceResults.d.ts.map +1 -0
  83. package/dist/data/traces/otel/TraceResults.js +193 -0
  84. package/dist/data/traces/otel/TraceResults.js.map +1 -0
  85. package/dist/data/traces/otel/api.cjs +36 -0
  86. package/dist/data/traces/otel/api.cjs.map +1 -0
  87. package/dist/data/traces/otel/api.d.ts +87 -0
  88. package/dist/data/traces/otel/api.d.ts.map +1 -0
  89. package/dist/data/traces/otel/api.js +33 -0
  90. package/dist/data/traces/otel/api.js.map +1 -0
  91. package/dist/data/traces/otel/fixtures.d.ts +4 -0
  92. package/dist/data/traces/otel/fixtures.d.ts.map +1 -0
  93. package/dist/data/traces/otel/metrics.cjs +7 -0
  94. package/dist/data/traces/otel/metrics.cjs.map +1 -0
  95. package/dist/data/traces/otel/metrics.d.ts +2 -0
  96. package/dist/data/traces/otel/metrics.d.ts.map +1 -0
  97. package/dist/data/traces/otel/metrics.js +7 -0
  98. package/dist/data/traces/otel/metrics.js.map +1 -0
  99. package/dist/data/traces/otel/model.cjs +167 -0
  100. package/dist/data/traces/otel/model.cjs.map +1 -0
  101. package/dist/data/traces/otel/model.d.ts +81 -0
  102. package/dist/data/traces/otel/model.d.ts.map +1 -0
  103. package/dist/data/traces/otel/model.js +154 -0
  104. package/dist/data/traces/otel/model.js.map +1 -0
  105. package/dist/data/traces/otel/query.cjs +90 -0
  106. package/dist/data/traces/otel/query.cjs.map +1 -0
  107. package/dist/data/traces/otel/query.d.ts +20 -0
  108. package/dist/data/traces/otel/query.d.ts.map +1 -0
  109. package/dist/data/traces/otel/query.js +86 -0
  110. package/dist/data/traces/otel/query.js.map +1 -0
  111. package/dist/data/traces/otel/result-model.cjs +25 -0
  112. package/dist/data/traces/otel/result-model.cjs.map +1 -0
  113. package/dist/data/traces/otel/result-model.d.ts +5 -0
  114. package/dist/data/traces/otel/result-model.d.ts.map +1 -0
  115. package/dist/data/traces/otel/result-model.js +24 -0
  116. package/dist/data/traces/otel/result-model.js.map +1 -0
  117. package/dist/data/traces/otel/source.cjs +11 -0
  118. package/dist/data/traces/otel/source.cjs.map +1 -0
  119. package/dist/data/traces/otel/source.d.ts +5 -0
  120. package/dist/data/traces/otel/source.d.ts.map +1 -0
  121. package/dist/data/traces/otel/source.js +10 -0
  122. package/dist/data/traces/otel/source.js.map +1 -0
  123. package/dist/data/traces/otel/span-traces.cjs +39 -0
  124. package/dist/data/traces/otel/span-traces.cjs.map +1 -0
  125. package/dist/data/traces/otel/span-traces.d.ts +3 -0
  126. package/dist/data/traces/otel/span-traces.d.ts.map +1 -0
  127. package/dist/data/traces/otel/span-traces.js +39 -0
  128. package/dist/data/traces/otel/span-traces.js.map +1 -0
  129. package/dist/data/version-info.cjs +3 -3
  130. package/dist/data/version-info.js +3 -3
  131. package/dist/data.cjs +11 -0
  132. package/dist/data.js +6 -1
  133. package/dist/index.cjs +11 -0
  134. package/dist/index.js +6 -1
  135. package/dist/profiles/editor/profileEditorSections.cjs +4 -0
  136. package/dist/profiles/editor/profileEditorSections.cjs.map +1 -1
  137. package/dist/profiles/editor/profileEditorSections.d.ts.map +1 -1
  138. package/dist/profiles/editor/profileEditorSections.js +4 -0
  139. package/dist/profiles/editor/profileEditorSections.js.map +1 -1
  140. package/dist/styles.css +1 -1
  141. package/package.json +1 -1
@@ -0,0 +1,781 @@
1
+ import { UiChevronDown } from "../../../icons/components/UiChevronDown.js";
2
+ import { UiChevronRight } from "../../../icons/components/UiChevronRight.js";
3
+ import { UiCollapseAll } from "../../../icons/components/UiCollapseAll.js";
4
+ import { UiExpandAll } from "../../../icons/components/UiExpandAll.js";
5
+ import { UiCoins } from "../../../icons/components/UiCoins.js";
6
+ import { UiWarningTriangle } from "../../../icons/components/UiWarningTriangle.js";
7
+ import { UiSparkles } from "../../../icons/components/UiSparkles.js";
8
+ import { UiRobotAi } from "../../../icons/components/UiRobotAi.js";
9
+ import { UiDatabase } from "../../../icons/components/UiDatabase.js";
10
+ import { UiClock } from "../../../icons/components/UiClock.js";
11
+ import { Icon } from "../../Icon.js";
12
+ import { Button } from "../../../components/button.js";
13
+ import { CopyButton } from "../../../components/CopyButton.js";
14
+ import { Badge } from "../../Badge.js";
15
+ import { SearchInput } from "../../../components/SearchInput.js";
16
+ import { Switch } from "../../../components/Switch.js";
17
+ import { Properties } from "../../Properties.js";
18
+ import { Callout } from "../../Callout.js";
19
+ import { CodeBlock } from "../../CodeBlock.js";
20
+ import { SplitPane } from "../../../layout/SplitPane.js";
21
+ import { Section } from "../../../layout/Section.js";
22
+ import { StatStrip } from "../../StatStrip.js";
23
+ import { Tinted } from "./SpanCategory.js";
24
+ import { CATEGORY_COLOR, CATEGORY_LABEL, buildRows, category, duration, indexTrace, isError, misparented, niceStep, num, selfTimeMs, shortOperation, withAncestors } from "./model.js";
25
+ import { metric } from "./metrics.js";
26
+ import { AgentCard, DbCard, GroupDetail, LlmCard } from "./SpanCards.js";
27
+ import { useEffect, useMemo, useRef, useState } from "react";
28
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
29
+ // src/data/traces/otel/OtelTraceDetail.tsx
30
+ /**
31
+ * OpenTelemetry waterfall with grouped calls and an adjacent span inspector.
32
+ */
33
+ var CATEGORY_ICON = {
34
+ agent: UiRobotAi,
35
+ llm: UiSparkles,
36
+ db: UiDatabase,
37
+ other: UiClock
38
+ };
39
+ var ROW_HEIGHT = 28;
40
+ var NAME_COLUMN = "minmax(15rem, 36%) minmax(0, 1fr)";
41
+ function OtelTraceDetail({ trace: TRACE, selectedSpanId, onSelectSpan }) {
42
+ const index = useMemo(() => indexTrace(TRACE), [TRACE]);
43
+ const suspects = useMemo(() => misparented(index), [index]);
44
+ const selected = selectedSpanId ?? TRACE.spans[0]?.spanId ?? "";
45
+ const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
46
+ const groupOf = (id) => buildRows(index, {
47
+ collapsed: /* @__PURE__ */ new Set(),
48
+ expanded: /* @__PURE__ */ new Set(),
49
+ group: true
50
+ }).find((row) => row.type === "group" && row.spans.some((span) => span.spanId === id))?.id;
51
+ const [expanded, setExpanded] = useState(() => new Set([groupOf(selected)].filter((id) => id !== void 0)));
52
+ const [group, setGroup] = useState(true);
53
+ const [query, setQuery] = useState("");
54
+ const [categories, setCategories] = useState(/* @__PURE__ */ new Set());
55
+ const [onlySuspects, setOnlySuspects] = useState(false);
56
+ const visible = useMemo(() => {
57
+ const q = query.trim().toLowerCase();
58
+ if (!q && categories.size === 0 && !onlySuspects) return void 0;
59
+ const hits = TRACE.spans.filter((span) => (!q || [
60
+ span.operation,
61
+ span.service,
62
+ ...Object.values(span.attributes ?? {}).map(String)
63
+ ].some((text) => text.toLowerCase().includes(q))) && (categories.size === 0 || categories.has(category(span))) && (!onlySuspects || suspects.has(span.spanId)));
64
+ return withAncestors(index, hits.map((span) => span.spanId));
65
+ }, [
66
+ index,
67
+ query,
68
+ categories,
69
+ onlySuspects,
70
+ suspects,
71
+ TRACE
72
+ ]);
73
+ const rows = useMemo(() => buildRows(index, {
74
+ collapsed,
75
+ expanded,
76
+ group,
77
+ ...visible ? { visible } : {}
78
+ }), [
79
+ index,
80
+ collapsed,
81
+ expanded,
82
+ group,
83
+ visible
84
+ ]);
85
+ const select = (id) => {
86
+ const group = groupOf(id);
87
+ if (group) setExpanded((previous) => new Set(previous).add(group));
88
+ onSelectSpan(id);
89
+ };
90
+ const toggle = (set, id) => set((previous) => {
91
+ const next = new Set(previous);
92
+ if (!next.delete(id)) next.add(id);
93
+ return next;
94
+ });
95
+ const selectedSpan = index.byId.get(selected);
96
+ const selectedGroup = rows.find((row) => row.type === "group" && row.id === selected);
97
+ return /* @__PURE__ */ jsx("div", {
98
+ className: "space-y-8",
99
+ children: /* @__PURE__ */ jsxs("section", {
100
+ className: "space-y-4 rounded-xl border border-border bg-background p-density-4",
101
+ children: [
102
+ /* @__PURE__ */ jsx(TraceHeader, {
103
+ index,
104
+ trace: TRACE
105
+ }),
106
+ TRACE.missingParents.length > 0 && /* @__PURE__ */ jsxs("p", {
107
+ role: "status",
108
+ children: [
109
+ "Partial trace: ",
110
+ TRACE.missingParents.length,
111
+ " parent spans are missing from this source."
112
+ ]
113
+ }),
114
+ suspects.size > 0 && /* @__PURE__ */ jsx(Callout, {
115
+ variant: "warning",
116
+ title: `${suspects.size} spans run inside a nested agent but hang off its parent`,
117
+ children: /* @__PURE__ */ jsxs("p", {
118
+ className: "text-sm",
119
+ children: [
120
+ "They start and end within ",
121
+ /* @__PURE__ */ jsx("code", { children: shortOperation(suspects.values().next().value.operation) }),
122
+ ", yet their parent is the root span, so the nested agent looks like it made a single LLM call. This usually means the agent's context is not propagated to its database client.",
123
+ " ",
124
+ /* @__PURE__ */ jsx(Button, {
125
+ size: "sm",
126
+ variant: "link",
127
+ className: "h-auto p-0",
128
+ onClick: () => setOnlySuspects((on) => !on),
129
+ children: onlySuspects ? "Show all spans" : "Show only these spans"
130
+ })
131
+ ]
132
+ })
133
+ }),
134
+ /* @__PURE__ */ jsx(Toolbar, {
135
+ trace: TRACE,
136
+ query,
137
+ setQuery,
138
+ categories,
139
+ toggleCategory: (c) => setCategories((previous) => {
140
+ const next = new Set(previous);
141
+ if (!next.delete(c)) next.add(c);
142
+ return next;
143
+ }),
144
+ group,
145
+ setGroup,
146
+ expandAll: () => {
147
+ setCollapsed(/* @__PURE__ */ new Set());
148
+ setExpanded(new Set(rows.filter((row) => row.type === "group").map((row) => row.id)));
149
+ },
150
+ collapseAll: () => {
151
+ setExpanded(/* @__PURE__ */ new Set());
152
+ setCollapsed(new Set(TRACE.spans.filter((span) => index.children.has(span.spanId) && !span.parentId).map((span) => span.spanId)));
153
+ },
154
+ shown: rows.length
155
+ }),
156
+ /* @__PURE__ */ jsx(SplitPane, {
157
+ stackOnMobile: true,
158
+ defaultSplit: 62,
159
+ minLeft: 40,
160
+ minRight: 25,
161
+ className: "rounded-lg border border-border",
162
+ left: /* @__PURE__ */ jsx(Waterfall, {
163
+ index,
164
+ rows,
165
+ selected,
166
+ onSelect: select,
167
+ suspects,
168
+ onToggleSpan: (id) => toggle(setCollapsed, id),
169
+ onToggleGroup: (id) => toggle(setExpanded, id),
170
+ expanded,
171
+ collapsed
172
+ }),
173
+ right: /* @__PURE__ */ jsx("div", {
174
+ className: "h-72 overflow-auto p-density-3 md:h-[calc(100dvh-14rem)] md:min-h-[30rem]",
175
+ children: selectedGroup ? /* @__PURE__ */ jsx(GroupDetail, {
176
+ index,
177
+ spans: selectedGroup.spans,
178
+ onSelect: select
179
+ }) : selectedSpan ? /* @__PURE__ */ jsx(SpanDetail, {
180
+ index,
181
+ span: selectedSpan,
182
+ host: suspects.get(selectedSpan.spanId),
183
+ onSelect: select
184
+ }) : /* @__PURE__ */ jsx("p", {
185
+ className: "text-sm text-muted-foreground",
186
+ children: "Select a span."
187
+ })
188
+ })
189
+ })
190
+ ]
191
+ })
192
+ });
193
+ }
194
+ function TraceHeader({ index, trace: TRACE }) {
195
+ const { summary } = TRACE;
196
+ const by = (c) => TRACE.spans.filter((span) => category(span) === c);
197
+ const llm = by("llm");
198
+ const db = by("db");
199
+ const sum = (spans, key) => {
200
+ const values = spans.map((span) => num(span.attributes?.[key])).filter((value) => value !== void 0);
201
+ return values.length ? values.reduce((total, value) => total + value, 0) : void 0;
202
+ };
203
+ const root = index.roots[0];
204
+ const cost = num(root.attributes?.["captain.cost_usd"]);
205
+ const budget = num(root.attributes?.["captain.budget.cost_usd"]);
206
+ const tokensIn = sum(llm, "gen_ai.usage.input_tokens");
207
+ const tokensOut = sum(llm, "gen_ai.usage.output_tokens");
208
+ const ms = (spans) => spans.reduce((total, span) => total + span.durationMs, 0);
209
+ return /* @__PURE__ */ jsxs("div", {
210
+ className: "space-y-3",
211
+ children: [/* @__PURE__ */ jsx("div", {
212
+ className: "flex flex-wrap items-start justify-between gap-3",
213
+ children: /* @__PURE__ */ jsxs("div", {
214
+ className: "min-w-0 space-y-1",
215
+ children: [/* @__PURE__ */ jsxs("h2", {
216
+ className: "flex items-center gap-2 text-xl font-semibold tracking-tight",
217
+ children: [
218
+ /* @__PURE__ */ jsx(Tinted, {
219
+ icon: UiRobotAi,
220
+ color: CATEGORY_COLOR.agent
221
+ }),
222
+ shortOperation(summary.operation),
223
+ summary.errors === 0 ? /* @__PURE__ */ jsx(Badge, {
224
+ tone: "success",
225
+ variant: "soft",
226
+ children: "no errors"
227
+ }) : /* @__PURE__ */ jsxs(Badge, {
228
+ tone: "danger",
229
+ variant: "soft",
230
+ children: [summary.errors, " errors"]
231
+ })
232
+ ]
233
+ }), /* @__PURE__ */ jsxs("p", {
234
+ className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground",
235
+ children: [
236
+ /* @__PURE__ */ jsx("code", { children: summary.traceId }),
237
+ /* @__PURE__ */ jsx(CopyButton, {
238
+ value: summary.traceId,
239
+ label: "Copy trace ID"
240
+ }),
241
+ /* @__PURE__ */ jsxs("span", { children: [
242
+ "· ",
243
+ summary.services.join(", "),
244
+ " · ",
245
+ new Date(summary.startedAt).toLocaleString()
246
+ ] })
247
+ ]
248
+ })]
249
+ })
250
+ }), /* @__PURE__ */ jsx(StatStrip, { items: [
251
+ {
252
+ label: "Duration",
253
+ value: duration(summary.durationMs),
254
+ sub: `${summary.spanCount} spans`,
255
+ icon: UiClock
256
+ },
257
+ {
258
+ label: "LLM",
259
+ value: duration(ms(llm)),
260
+ sub: `${llm.length} calls · ${metric(tokensIn)} in / ${metric(tokensOut)} out`,
261
+ icon: UiSparkles
262
+ },
263
+ {
264
+ label: "Database",
265
+ value: duration(ms(db)),
266
+ sub: `${db.length} calls · ${new Set(db.map((span) => span.attributes?.["db.sql.table"])).size} tables`,
267
+ icon: UiDatabase
268
+ },
269
+ {
270
+ label: "Cost",
271
+ value: metric(cost, (value) => `$${value.toFixed(4)}`),
272
+ sub: budget ? `of $${budget} budget` : "budget unavailable",
273
+ icon: UiCoins
274
+ },
275
+ {
276
+ label: "Root self-time",
277
+ value: duration(selfTimeMs(index, root)),
278
+ sub: "not covered by any child span",
279
+ icon: UiRobotAi,
280
+ tone: "warning"
281
+ }
282
+ ] })]
283
+ });
284
+ }
285
+ function Toolbar(props) {
286
+ const counts = props.trace.spans.reduce((acc, span) => ({
287
+ ...acc,
288
+ [category(span)]: (acc[category(span)] ?? 0) + 1
289
+ }), {});
290
+ return /* @__PURE__ */ jsxs("div", {
291
+ className: "flex flex-wrap items-center gap-2",
292
+ children: [
293
+ /* @__PURE__ */ jsx(SearchInput, {
294
+ className: "w-72",
295
+ placeholder: "Filter by name, table or attribute",
296
+ value: props.query,
297
+ onChange: props.setQuery
298
+ }),
299
+ Object.keys(counts).map((c) => {
300
+ const on = props.categories.has(c);
301
+ return /* @__PURE__ */ jsxs(Button, {
302
+ size: "sm",
303
+ variant: on ? "default" : "outline",
304
+ "aria-pressed": on,
305
+ onClick: () => props.toggleCategory(c),
306
+ children: [
307
+ /* @__PURE__ */ jsx("span", {
308
+ className: "mr-1.5 inline-block size-2 rounded-full",
309
+ style: { background: CATEGORY_COLOR[c] }
310
+ }),
311
+ CATEGORY_LABEL[c],
312
+ " ",
313
+ /* @__PURE__ */ jsx("span", {
314
+ className: "ml-1 text-muted-foreground",
315
+ children: counts[c]
316
+ })
317
+ ]
318
+ }, c);
319
+ }),
320
+ /* @__PURE__ */ jsxs("label", {
321
+ className: "ml-2 inline-flex items-center gap-2 text-xs text-muted-foreground",
322
+ children: [/* @__PURE__ */ jsx(Switch, {
323
+ checked: props.group,
324
+ "aria-label": "Group repeated database calls",
325
+ onChange: props.setGroup
326
+ }), "Group repeated DB calls"]
327
+ }),
328
+ /* @__PURE__ */ jsxs("div", {
329
+ className: "ml-auto flex items-center gap-1",
330
+ children: [
331
+ /* @__PURE__ */ jsxs("span", {
332
+ className: "mr-2 text-xs text-muted-foreground",
333
+ children: [props.shown, " rows"]
334
+ }),
335
+ /* @__PURE__ */ jsxs(Button, {
336
+ size: "sm",
337
+ variant: "ghost",
338
+ onClick: props.expandAll,
339
+ children: [/* @__PURE__ */ jsx(Icon, {
340
+ icon: UiExpandAll,
341
+ className: "mr-1"
342
+ }), "Expand all"]
343
+ }),
344
+ /* @__PURE__ */ jsxs(Button, {
345
+ size: "sm",
346
+ variant: "ghost",
347
+ onClick: props.collapseAll,
348
+ children: [/* @__PURE__ */ jsx(Icon, {
349
+ icon: UiCollapseAll,
350
+ className: "mr-1"
351
+ }), "Collapse"]
352
+ })
353
+ ]
354
+ })
355
+ ]
356
+ });
357
+ }
358
+ function Waterfall(props) {
359
+ const { index, rows, selected, onSelect } = props;
360
+ const total = Math.max(index.totalMs, .001);
361
+ const step = niceStep(total);
362
+ const ticks = Array.from({ length: Math.floor(total / step) + 1 }, (_, i) => i * step);
363
+ const scroller = useRef(null);
364
+ useEffect(() => {
365
+ scroller.current?.querySelector(`[data-row-id="${CSS.escape(selected)}"]`)?.scrollIntoView?.({ block: "nearest" });
366
+ }, [selected]);
367
+ const onKey = (event) => {
368
+ const at = rows.findIndex((row) => row.id === selected);
369
+ const next = event.key === "ArrowDown" ? rows[at + 1] : event.key === "ArrowUp" ? rows[at - 1] : void 0;
370
+ const row = rows[at];
371
+ if (next) onSelect(next.id);
372
+ else if (row && (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
373
+ if ((row.type === "group" ? props.expanded.has(row.id) : !props.collapsed.has(row.id)) !== (event.key === "ArrowRight") && (row.type === "group" || row.childCount > 0)) (row.type === "group" ? props.onToggleGroup : props.onToggleSpan)(row.id);
374
+ } else return;
375
+ event.preventDefault();
376
+ };
377
+ const pct = (ms) => `${ms / total * 100}%`;
378
+ return /* @__PURE__ */ jsx("div", {
379
+ ref: scroller,
380
+ tabIndex: 0,
381
+ onKeyDown: onKey,
382
+ "aria-label": "Span waterfall",
383
+ className: "h-72 overflow-auto text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring md:h-[calc(100dvh-14rem)] md:min-h-[30rem]",
384
+ children: /* @__PURE__ */ jsxs("div", {
385
+ className: "min-w-[40rem]",
386
+ children: [/* @__PURE__ */ jsxs("div", {
387
+ className: "sticky top-0 z-10 grid border-b border-border bg-muted text-muted-foreground",
388
+ style: {
389
+ gridTemplateColumns: NAME_COLUMN,
390
+ height: ROW_HEIGHT
391
+ },
392
+ children: [/* @__PURE__ */ jsx("div", {
393
+ className: "flex items-center px-density-2 font-medium",
394
+ children: "Span"
395
+ }), /* @__PURE__ */ jsx("div", {
396
+ className: "relative mr-12",
397
+ children: ticks.map((t) => /* @__PURE__ */ jsx("span", {
398
+ className: "absolute top-1.5 -translate-x-1/2 tabular-nums",
399
+ style: { left: pct(t) },
400
+ children: t === 0 ? "0" : duration(t)
401
+ }, t))
402
+ })]
403
+ }), /* @__PURE__ */ jsxs("div", {
404
+ className: "relative",
405
+ children: [
406
+ /* @__PURE__ */ jsxs("div", {
407
+ "aria-hidden": true,
408
+ className: "pointer-events-none absolute inset-y-0 grid",
409
+ style: {
410
+ gridTemplateColumns: NAME_COLUMN,
411
+ left: 0,
412
+ right: 0
413
+ },
414
+ children: [/* @__PURE__ */ jsx("div", {}), /* @__PURE__ */ jsx("div", {
415
+ className: "relative mr-12",
416
+ children: ticks.map((t) => /* @__PURE__ */ jsx("span", {
417
+ className: "absolute inset-y-0 border-l border-border",
418
+ style: {
419
+ left: pct(t),
420
+ opacity: .6
421
+ }
422
+ }, t))
423
+ })]
424
+ }),
425
+ rows.map((row) => /* @__PURE__ */ jsx(WaterfallRow, {
426
+ ...props,
427
+ row,
428
+ pct,
429
+ active: row.id === selected
430
+ }, row.id)),
431
+ rows.length === 0 && /* @__PURE__ */ jsx("p", {
432
+ className: "p-density-4 text-sm text-muted-foreground",
433
+ children: "No spans match these filters."
434
+ })
435
+ ]
436
+ })]
437
+ })
438
+ });
439
+ }
440
+ function WaterfallRow({ index, row, active, pct, onSelect, suspects, onToggleSpan, onToggleGroup, expanded, collapsed }) {
441
+ const indent = { paddingLeft: `${row.depth * 14 + 4}px` };
442
+ const toggleButton = (open, onClick, label) => /* @__PURE__ */ jsx("button", {
443
+ type: "button",
444
+ "aria-label": `${open ? "Collapse" : "Expand"} ${label}`,
445
+ className: "flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted",
446
+ onClick: (event) => {
447
+ event.stopPropagation();
448
+ onClick();
449
+ },
450
+ children: /* @__PURE__ */ jsx(Icon, { icon: open ? UiChevronDown : UiChevronRight })
451
+ });
452
+ const rowClass = `relative grid w-full cursor-pointer items-center text-left ${active ? "bg-accent" : "hover:bg-muted"}`;
453
+ if (row.type === "group") {
454
+ const open = expanded.has(row.id);
455
+ const ops = Object.entries(row.spans.reduce((acc, span) => ({
456
+ ...acc,
457
+ [span.operation.replace("gorm.", "")]: (acc[span.operation.replace("gorm.", "")] ?? 0) + 1
458
+ }), {}));
459
+ const first = index.offsetMs(row.spans[0]);
460
+ const last = row.spans.at(-1);
461
+ const end = index.offsetMs(last) + last.durationMs;
462
+ const busy = row.spans.reduce((total, span) => total + span.durationMs, 0);
463
+ const suspectCount = row.spans.filter((span) => suspects.has(span.spanId)).length;
464
+ return /* @__PURE__ */ jsxs("div", {
465
+ role: "button",
466
+ tabIndex: -1,
467
+ "data-row-id": row.id,
468
+ "aria-selected": active,
469
+ className: rowClass,
470
+ style: {
471
+ gridTemplateColumns: NAME_COLUMN,
472
+ height: ROW_HEIGHT
473
+ },
474
+ onClick: () => onSelect(row.id),
475
+ children: [/* @__PURE__ */ jsxs("div", {
476
+ className: "flex min-w-0 items-center gap-1 pr-2",
477
+ style: indent,
478
+ children: [
479
+ toggleButton(open, () => onToggleGroup(row.id), "database group"),
480
+ /* @__PURE__ */ jsx(Tinted, {
481
+ icon: UiDatabase,
482
+ color: CATEGORY_COLOR.db
483
+ }),
484
+ /* @__PURE__ */ jsxs("span", {
485
+ className: "truncate",
486
+ children: [
487
+ /* @__PURE__ */ jsxs("span", {
488
+ className: "font-medium",
489
+ children: [row.spans.length, " DB calls"]
490
+ }),
491
+ " ",
492
+ /* @__PURE__ */ jsx("span", {
493
+ className: "text-muted-foreground",
494
+ children: ops.map(([op, n]) => `${op} ×${n}`).join(", ")
495
+ })
496
+ ]
497
+ }),
498
+ suspectCount > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Tinted, {
499
+ icon: UiWarningTriangle,
500
+ color: "var(--fs-warning)",
501
+ label: `${suspectCount} calls run inside a nested agent but are parented elsewhere`
502
+ }), /* @__PURE__ */ jsx("span", {
503
+ className: "text-muted-foreground",
504
+ children: suspectCount
505
+ })] })
506
+ ]
507
+ }), /* @__PURE__ */ jsxs("div", {
508
+ className: "relative mr-12 h-full",
509
+ children: [
510
+ /* @__PURE__ */ jsx("span", {
511
+ className: "absolute rounded-sm",
512
+ style: {
513
+ left: pct(first),
514
+ width: pct(end - first),
515
+ top: 11,
516
+ height: 6,
517
+ background: CATEGORY_COLOR.db,
518
+ opacity: .18
519
+ }
520
+ }),
521
+ row.spans.map((span) => /* @__PURE__ */ jsx("span", {
522
+ className: "absolute rounded-sm",
523
+ style: {
524
+ left: pct(index.offsetMs(span)),
525
+ width: pct(span.durationMs),
526
+ minWidth: 2,
527
+ top: 8,
528
+ height: 12,
529
+ background: CATEGORY_COLOR.db
530
+ }
531
+ }, span.spanId)),
532
+ /* @__PURE__ */ jsx(BarLabel, {
533
+ start: first / Math.max(index.totalMs, .001) * 100,
534
+ end: end / Math.max(index.totalMs, .001) * 100,
535
+ text: `${duration(busy)} busy`
536
+ })
537
+ ]
538
+ })]
539
+ });
540
+ }
541
+ const { span } = row;
542
+ const c = category(span);
543
+ const parent = span.parentId ? index.byId.get(span.parentId) : void 0;
544
+ const left = index.offsetMs(span) / Math.max(index.totalMs, .001) * 100;
545
+ const width = span.durationMs / Math.max(index.totalMs, .001) * 100;
546
+ const error = isError(span);
547
+ const label = shortOperation(span.operation);
548
+ const table = span.attributes?.["db.sql.table"];
549
+ return /* @__PURE__ */ jsxs("div", {
550
+ role: "button",
551
+ tabIndex: -1,
552
+ "data-row-id": row.id,
553
+ "aria-selected": active,
554
+ className: rowClass,
555
+ style: {
556
+ gridTemplateColumns: NAME_COLUMN,
557
+ height: ROW_HEIGHT
558
+ },
559
+ onClick: () => onSelect(row.id),
560
+ title: span.operation,
561
+ children: [/* @__PURE__ */ jsxs("div", {
562
+ className: "flex min-w-0 items-center gap-1 pr-2",
563
+ style: indent,
564
+ children: [
565
+ row.childCount > 0 ? toggleButton(!collapsed.has(span.spanId), () => onToggleSpan(span.spanId), label) : /* @__PURE__ */ jsx("span", { className: "size-5 shrink-0" }),
566
+ /* @__PURE__ */ jsx(Tinted, {
567
+ icon: CATEGORY_ICON[c],
568
+ color: CATEGORY_COLOR[c]
569
+ }),
570
+ parent && parent.service !== span.service && /* @__PURE__ */ jsx(Badge, {
571
+ size: "sm",
572
+ variant: "outline",
573
+ children: span.service
574
+ }),
575
+ /* @__PURE__ */ jsx("span", {
576
+ className: `truncate ${c === "db" ? "" : "font-medium"}`,
577
+ children: label
578
+ }),
579
+ typeof table === "string" && /* @__PURE__ */ jsx("span", {
580
+ className: "truncate text-muted-foreground",
581
+ children: table
582
+ }),
583
+ row.childCount > 0 && collapsed.has(span.spanId) && /* @__PURE__ */ jsxs("span", {
584
+ className: "text-muted-foreground",
585
+ children: ["+", row.childCount]
586
+ }),
587
+ suspects.has(span.spanId) && /* @__PURE__ */ jsx(Tinted, {
588
+ icon: UiWarningTriangle,
589
+ color: "var(--fs-warning)",
590
+ label: "Runs inside a nested agent but is parented elsewhere"
591
+ }),
592
+ error && /* @__PURE__ */ jsx(Badge, {
593
+ size: "sm",
594
+ tone: "danger",
595
+ variant: "soft",
596
+ children: "error"
597
+ })
598
+ ]
599
+ }), /* @__PURE__ */ jsxs("div", {
600
+ className: "relative mr-12 h-full",
601
+ children: [/* @__PURE__ */ jsx("span", {
602
+ className: "absolute rounded-sm",
603
+ style: {
604
+ left: `${left}%`,
605
+ width: `${width}%`,
606
+ minWidth: 2,
607
+ top: 7,
608
+ height: 14,
609
+ background: error ? "var(--destructive)" : CATEGORY_COLOR[c],
610
+ opacity: c === "agent" ? .85 : 1
611
+ }
612
+ }), /* @__PURE__ */ jsx(BarLabel, {
613
+ start: left,
614
+ end: left + width,
615
+ inside: width > 14,
616
+ text: duration(span.durationMs)
617
+ })]
618
+ })]
619
+ });
620
+ }
621
+ /** Duration next to the bar's end — or inside a wide bar — and flipped left of the start near the right edge. */
622
+ function BarLabel({ start, end, inside, text }) {
623
+ if (inside) return /* @__PURE__ */ jsx("span", {
624
+ className: "absolute top-1.5 px-1.5 font-medium text-white tabular-nums",
625
+ style: { left: `${start}%` },
626
+ children: text
627
+ });
628
+ const position = end > 90 ? {
629
+ right: `${100 - start}%`,
630
+ paddingRight: 4
631
+ } : {
632
+ left: `${end}%`,
633
+ paddingLeft: 4
634
+ };
635
+ return /* @__PURE__ */ jsx("span", {
636
+ className: "absolute top-1.5 whitespace-nowrap tabular-nums text-muted-foreground",
637
+ style: position,
638
+ children: text
639
+ });
640
+ }
641
+ function SpanDetail({ index, span, host, onSelect }) {
642
+ const c = category(span);
643
+ const ancestors = [];
644
+ for (let p = span.parentId ? index.byId.get(span.parentId) : void 0; p; p = p.parentId ? index.byId.get(p.parentId) : void 0) ancestors.unshift(p);
645
+ const attributes = Object.entries(span.attributes ?? {});
646
+ return /* @__PURE__ */ jsxs("div", {
647
+ className: "space-y-4",
648
+ children: [
649
+ /* @__PURE__ */ jsxs("div", {
650
+ className: "space-y-1.5",
651
+ children: [
652
+ ancestors.length > 0 && /* @__PURE__ */ jsx("p", {
653
+ className: "flex flex-wrap items-center gap-1 text-xs text-muted-foreground",
654
+ children: ancestors.map((a) => /* @__PURE__ */ jsxs("span", {
655
+ className: "inline-flex items-center gap-1",
656
+ children: [/* @__PURE__ */ jsx("button", {
657
+ type: "button",
658
+ className: "hover:underline",
659
+ onClick: () => onSelect(a.spanId),
660
+ children: shortOperation(a.operation)
661
+ }), /* @__PURE__ */ jsx(Icon, { icon: UiChevronRight })]
662
+ }, a.spanId))
663
+ }),
664
+ /* @__PURE__ */ jsxs("h3", {
665
+ className: "flex items-center gap-2 text-base font-semibold",
666
+ title: span.operation,
667
+ children: [/* @__PURE__ */ jsx(Tinted, {
668
+ icon: CATEGORY_ICON[c],
669
+ color: CATEGORY_COLOR[c]
670
+ }), /* @__PURE__ */ jsx("span", {
671
+ className: "truncate",
672
+ children: shortOperation(span.operation)
673
+ })]
674
+ }),
675
+ /* @__PURE__ */ jsxs("div", {
676
+ className: "flex flex-wrap items-center gap-1.5 text-xs",
677
+ children: [
678
+ /* @__PURE__ */ jsx(Badge, {
679
+ variant: "soft",
680
+ size: "sm",
681
+ children: CATEGORY_LABEL[c]
682
+ }),
683
+ span.kind && /* @__PURE__ */ jsx(Badge, {
684
+ variant: "outline",
685
+ size: "sm",
686
+ children: span.kind
687
+ }),
688
+ isError(span) && /* @__PURE__ */ jsx(Badge, {
689
+ tone: "danger",
690
+ variant: "soft",
691
+ size: "sm",
692
+ children: "error"
693
+ }),
694
+ /* @__PURE__ */ jsxs("span", {
695
+ className: "tabular-nums text-muted-foreground",
696
+ children: [
697
+ duration(span.durationMs),
698
+ " · starts at +",
699
+ duration(index.offsetMs(span))
700
+ ]
701
+ }),
702
+ /* @__PURE__ */ jsxs("span", {
703
+ className: "ml-auto inline-flex items-center gap-1 text-muted-foreground",
704
+ children: [/* @__PURE__ */ jsx("code", { children: span.spanId }), /* @__PURE__ */ jsx(CopyButton, {
705
+ value: span.spanId,
706
+ label: "Copy span ID"
707
+ })]
708
+ })
709
+ ]
710
+ })
711
+ ]
712
+ }),
713
+ host && /* @__PURE__ */ jsx(Callout, {
714
+ variant: "warning",
715
+ title: "Likely mis-parented",
716
+ children: /* @__PURE__ */ jsxs("p", {
717
+ className: "text-sm",
718
+ children: [
719
+ "Runs entirely inside ",
720
+ /* @__PURE__ */ jsx("button", {
721
+ type: "button",
722
+ className: "underline",
723
+ onClick: () => onSelect(host.spanId),
724
+ children: shortOperation(host.operation)
725
+ }),
726
+ " but its parent is ",
727
+ span.parentId ? shortOperation(index.byId.get(span.parentId)?.operation ?? span.parentId) : "missing",
728
+ "."
729
+ ]
730
+ })
731
+ }),
732
+ c === "agent" && /* @__PURE__ */ jsx(AgentCard, {
733
+ index,
734
+ span
735
+ }),
736
+ c === "llm" && /* @__PURE__ */ jsx(LlmCard, { span }),
737
+ c === "db" && /* @__PURE__ */ jsx(DbCard, { span }),
738
+ /* @__PURE__ */ jsx(Section, {
739
+ title: `All attributes (${attributes.length})`,
740
+ defaultOpen: c === "other",
741
+ children: /* @__PURE__ */ jsx(Properties, {
742
+ renderLabel: (key) => /* @__PURE__ */ jsx("code", {
743
+ className: "text-xs",
744
+ children: key
745
+ }),
746
+ items: attributes.map(([key, value]) => ({
747
+ key,
748
+ value
749
+ }))
750
+ })
751
+ }),
752
+ span.otlp && /* @__PURE__ */ jsx(Section, {
753
+ title: "OpenTelemetry metadata",
754
+ defaultOpen: false,
755
+ children: /* @__PURE__ */ jsx(CodeBlock, {
756
+ language: "json",
757
+ source: JSON.stringify(span.otlp, null, 2),
758
+ jsonDefaultOpenDepth: 4,
759
+ copyable: true
760
+ })
761
+ }),
762
+ /* @__PURE__ */ jsx(Section, {
763
+ title: `Resource (${Object.keys(span.resource ?? {}).length})`,
764
+ defaultOpen: false,
765
+ children: /* @__PURE__ */ jsx(Properties, {
766
+ renderLabel: (key) => /* @__PURE__ */ jsx("code", {
767
+ className: "text-xs",
768
+ children: key
769
+ }),
770
+ items: Object.entries(span.resource ?? {}).map(([key, value]) => ({
771
+ key,
772
+ value
773
+ }))
774
+ })
775
+ })
776
+ ]
777
+ });
778
+ }
779
+ export { OtelTraceDetail };
780
+
781
+ //# sourceMappingURL=OtelTraceDetail.js.map