@wakastellar/ui 2.1.2 → 2.3.2

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 (123) hide show
  1. package/dist/blocks/apm-overview/index.d.ts +58 -0
  2. package/dist/blocks/cicd-builder/index.d.ts +47 -0
  3. package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
  4. package/dist/blocks/container-orchestrator/index.d.ts +63 -0
  5. package/dist/blocks/database-admin/index.d.ts +84 -0
  6. package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
  7. package/dist/blocks/incident-manager/index.d.ts +44 -0
  8. package/dist/blocks/index.d.ts +10 -0
  9. package/dist/blocks/infrastructure-map/index.d.ts +32 -0
  10. package/dist/blocks/on-call-schedule/index.d.ts +43 -0
  11. package/dist/blocks/release-notes/index.d.ts +49 -0
  12. package/dist/components/index.d.ts +34 -0
  13. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  14. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  15. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  16. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  17. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  18. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  19. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  20. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  21. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  22. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  23. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  24. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  25. package/dist/components/waka-container-list/index.d.ts +51 -0
  26. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  27. package/dist/components/waka-database-card/index.d.ts +46 -0
  28. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  29. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  30. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  31. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  32. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  33. package/dist/components/waka-migration-list/index.d.ts +36 -0
  34. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  35. package/dist/components/waka-pod-card/index.d.ts +73 -0
  36. package/dist/components/waka-query-explain/index.d.ts +48 -0
  37. package/dist/components/waka-secret-card/index.d.ts +43 -0
  38. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  39. package/dist/components/waka-service-graph/index.d.ts +44 -0
  40. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  41. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  42. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  43. package/dist/components/waka-test-report/index.d.ts +60 -0
  44. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  45. package/dist/components/waka-video-ad/index.d.ts +32 -0
  46. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  47. package/dist/index.cjs.js +251 -200
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.es.js +47315 -35823
  50. package/dist/utils/security.d.ts +96 -0
  51. package/package.json +4 -4
  52. package/src/blocks/apm-overview/index.tsx +672 -0
  53. package/src/blocks/cicd-builder/index.tsx +738 -0
  54. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  55. package/src/blocks/container-orchestrator/index.tsx +729 -0
  56. package/src/blocks/database-admin/index.tsx +679 -0
  57. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  58. package/src/blocks/incident-manager/index.tsx +586 -0
  59. package/src/blocks/index.ts +119 -0
  60. package/src/blocks/infrastructure-map/index.tsx +638 -0
  61. package/src/blocks/on-call-schedule/index.tsx +615 -0
  62. package/src/blocks/release-notes/index.tsx +643 -0
  63. package/src/blocks/sidebar/index.tsx +6 -6
  64. package/src/components/DataTable/templates/index.tsx +3 -2
  65. package/src/components/index.ts +283 -0
  66. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  67. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  68. package/src/components/waka-ad-banner/index.tsx +275 -0
  69. package/src/components/waka-ad-fallback/index.tsx +181 -0
  70. package/src/components/waka-ad-inline/index.tsx +103 -0
  71. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  72. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  73. package/src/components/waka-ad-provider/index.tsx +329 -0
  74. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  75. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  76. package/src/components/waka-alert-panel/index.tsx +493 -0
  77. package/src/components/waka-artifact-list/index.tsx +416 -0
  78. package/src/components/waka-badge-showcase/index.tsx +12 -11
  79. package/src/components/waka-build-matrix/index.tsx +396 -0
  80. package/src/components/waka-command-bar/index.tsx +2 -1
  81. package/src/components/waka-config-comparator/index.tsx +416 -0
  82. package/src/components/waka-container-list/index.tsx +475 -0
  83. package/src/components/waka-content-recommendation/index.tsx +294 -0
  84. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  85. package/src/components/waka-database-card/index.tsx +473 -0
  86. package/src/components/waka-dependency-tree/index.tsx +542 -0
  87. package/src/components/waka-env-var-editor/index.tsx +417 -0
  88. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  89. package/src/components/waka-funnel-chart/index.tsx +8 -8
  90. package/src/components/waka-health-pulse/index.tsx +6 -6
  91. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  92. package/src/components/waka-leaderboard/index.tsx +9 -9
  93. package/src/components/waka-log-viewer/index.tsx +386 -0
  94. package/src/components/waka-loot-box/index.tsx +20 -20
  95. package/src/components/waka-migration-list/index.tsx +487 -0
  96. package/src/components/waka-outstream-video/index.tsx +240 -0
  97. package/src/components/waka-player-card/index.tsx +5 -5
  98. package/src/components/waka-pod-card/index.tsx +528 -0
  99. package/src/components/waka-query-explain/index.tsx +657 -0
  100. package/src/components/waka-quota-bar/index.tsx +4 -4
  101. package/src/components/waka-radar-score/index.tsx +10 -10
  102. package/src/components/waka-scratch-card/index.tsx +5 -4
  103. package/src/components/waka-secret-card/index.tsx +371 -0
  104. package/src/components/waka-security-scan-result/index.tsx +473 -0
  105. package/src/components/waka-server-rack/index.tsx +28 -27
  106. package/src/components/waka-service-graph/index.tsx +445 -0
  107. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  108. package/src/components/waka-sponsored-card/index.tsx +275 -0
  109. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  110. package/src/components/waka-spotlight/index.tsx +2 -1
  111. package/src/components/waka-success-explosion/index.tsx +4 -4
  112. package/src/components/waka-test-report/index.tsx +469 -0
  113. package/src/components/waka-trace-viewer/index.tsx +490 -0
  114. package/src/components/waka-video-ad/index.tsx +406 -0
  115. package/src/components/waka-video-overlay/index.tsx +257 -0
  116. package/src/components/waka-xp-bar/index.tsx +13 -13
  117. package/src/styles/base.css +16 -0
  118. package/src/styles/tailwind.preset.js +12 -0
  119. package/src/styles/themes/forest.css +16 -0
  120. package/src/styles/themes/monochrome.css +16 -0
  121. package/src/styles/themes/perpetuity.css +16 -0
  122. package/src/styles/themes/sunset.css +16 -0
  123. package/src/styles/themes/twilight.css +16 -0
@@ -0,0 +1,490 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../badge"
6
+ import { Button } from "../button"
7
+ import { ScrollArea } from "../scroll-area"
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from "../tooltip"
14
+ import {
15
+ ChevronDown,
16
+ ChevronRight,
17
+ Clock,
18
+ AlertCircle,
19
+ CheckCircle2,
20
+ Loader2,
21
+ ExternalLink,
22
+ Copy,
23
+ } from "lucide-react"
24
+
25
+ export type SpanStatus = "ok" | "error" | "unset"
26
+
27
+ export interface TraceSpan {
28
+ id: string
29
+ traceId: string
30
+ parentId?: string
31
+ operationName: string
32
+ serviceName: string
33
+ startTime: number // timestamp in ms
34
+ duration: number // duration in ms
35
+ status: SpanStatus
36
+ tags?: Record<string, string | number | boolean>
37
+ logs?: Array<{
38
+ timestamp: number
39
+ message: string
40
+ level?: "info" | "warn" | "error"
41
+ }>
42
+ children?: TraceSpan[]
43
+ }
44
+
45
+ export interface WakaTraceViewerProps {
46
+ /** Root spans of the trace */
47
+ spans: TraceSpan[]
48
+ /** Trace ID */
49
+ traceId?: string
50
+ /** Total trace duration */
51
+ totalDuration?: number
52
+ /** Callback when clicking on a span */
53
+ onSpanClick?: (span: TraceSpan) => void
54
+ /** Callback when copying trace ID */
55
+ onCopyTraceId?: (traceId: string) => void
56
+ /** Show service colors */
57
+ showServiceColors?: boolean
58
+ /** Custom class name */
59
+ className?: string
60
+ }
61
+
62
+ const serviceColors = [
63
+ "bg-blue-500",
64
+ "bg-green-500",
65
+ "bg-purple-500",
66
+ "bg-orange-500",
67
+ "bg-pink-500",
68
+ "bg-cyan-500",
69
+ "bg-yellow-500",
70
+ "bg-red-500",
71
+ ]
72
+
73
+ function getServiceColor(serviceName: string, serviceMap: Map<string, string>): string {
74
+ if (!serviceMap.has(serviceName)) {
75
+ serviceMap.set(serviceName, serviceColors[serviceMap.size % serviceColors.length])
76
+ }
77
+ return serviceMap.get(serviceName)!
78
+ }
79
+
80
+ function SpanRow({
81
+ span,
82
+ depth,
83
+ totalDuration,
84
+ minTime,
85
+ serviceMap,
86
+ isExpanded,
87
+ onToggle,
88
+ onSpanClick,
89
+ }: {
90
+ span: TraceSpan
91
+ depth: number
92
+ totalDuration: number
93
+ minTime: number
94
+ serviceMap: Map<string, string>
95
+ isExpanded: boolean
96
+ onToggle: () => void
97
+ onSpanClick?: (span: TraceSpan) => void
98
+ }) {
99
+ const hasChildren = span.children && span.children.length > 0
100
+ const serviceColor = getServiceColor(span.serviceName, serviceMap)
101
+
102
+ // Calculate position and width on timeline
103
+ const startPercent = ((span.startTime - minTime) / totalDuration) * 100
104
+ const widthPercent = Math.max((span.duration / totalDuration) * 100, 0.5)
105
+
106
+ const statusIcon = {
107
+ ok: <CheckCircle2 className="h-4 w-4 text-green-500" />,
108
+ error: <AlertCircle className="h-4 w-4 text-red-500" />,
109
+ unset: <Clock className="h-4 w-4 text-muted-foreground" />,
110
+ }
111
+
112
+ return (
113
+ <div className="group">
114
+ <div
115
+ className={cn(
116
+ "flex items-center gap-2 px-2 py-1.5 hover:bg-muted/50 cursor-pointer border-b border-border/30",
117
+ span.status === "error" && "bg-red-500/5"
118
+ )}
119
+ onClick={() => onSpanClick?.(span)}
120
+ >
121
+ {/* Expand/Collapse */}
122
+ <div style={{ width: depth * 20 }} />
123
+ <button
124
+ onClick={(e) => {
125
+ e.stopPropagation()
126
+ onToggle()
127
+ }}
128
+ className="w-5 h-5 flex items-center justify-center"
129
+ disabled={!hasChildren}
130
+ >
131
+ {hasChildren ? (
132
+ isExpanded ? (
133
+ <ChevronDown className="h-4 w-4" />
134
+ ) : (
135
+ <ChevronRight className="h-4 w-4" />
136
+ )
137
+ ) : (
138
+ <span className="w-4" />
139
+ )}
140
+ </button>
141
+
142
+ {/* Service indicator */}
143
+ <div className={cn("w-3 h-3 rounded-full shrink-0", serviceColor)} />
144
+
145
+ {/* Service name */}
146
+ <span className="text-xs text-muted-foreground w-24 truncate shrink-0">
147
+ {span.serviceName}
148
+ </span>
149
+
150
+ {/* Operation name */}
151
+ <span className="text-sm font-medium truncate w-48 shrink-0">
152
+ {span.operationName}
153
+ </span>
154
+
155
+ {/* Status */}
156
+ <div className="shrink-0">{statusIcon[span.status]}</div>
157
+
158
+ {/* Timeline bar */}
159
+ <div className="flex-1 h-6 bg-muted/30 rounded relative min-w-[200px]">
160
+ <TooltipProvider>
161
+ <Tooltip>
162
+ <TooltipTrigger asChild>
163
+ <div
164
+ className={cn(
165
+ "absolute h-full rounded transition-all",
166
+ span.status === "error" ? "bg-red-500" : serviceColor,
167
+ "opacity-80 hover:opacity-100"
168
+ )}
169
+ style={{
170
+ left: `${startPercent}%`,
171
+ width: `${widthPercent}%`,
172
+ minWidth: "4px",
173
+ }}
174
+ />
175
+ </TooltipTrigger>
176
+ <TooltipContent>
177
+ <div className="text-xs space-y-1">
178
+ <div className="font-semibold">{span.operationName}</div>
179
+ <div>Service: {span.serviceName}</div>
180
+ <div>Duration: {span.duration.toFixed(2)}ms</div>
181
+ <div>Start: +{(span.startTime - minTime).toFixed(2)}ms</div>
182
+ </div>
183
+ </TooltipContent>
184
+ </Tooltip>
185
+ </TooltipProvider>
186
+ </div>
187
+
188
+ {/* Duration */}
189
+ <span className="text-xs text-muted-foreground w-20 text-right shrink-0">
190
+ {span.duration.toFixed(2)}ms
191
+ </span>
192
+ </div>
193
+ </div>
194
+ )
195
+ }
196
+
197
+ function SpanTree({
198
+ spans,
199
+ depth,
200
+ totalDuration,
201
+ minTime,
202
+ serviceMap,
203
+ expandedIds,
204
+ onToggle,
205
+ onSpanClick,
206
+ }: {
207
+ spans: TraceSpan[]
208
+ depth: number
209
+ totalDuration: number
210
+ minTime: number
211
+ serviceMap: Map<string, string>
212
+ expandedIds: Set<string>
213
+ onToggle: (id: string) => void
214
+ onSpanClick?: (span: TraceSpan) => void
215
+ }) {
216
+ return (
217
+ <>
218
+ {spans.map((span) => {
219
+ const isExpanded = expandedIds.has(span.id)
220
+ return (
221
+ <React.Fragment key={span.id}>
222
+ <SpanRow
223
+ span={span}
224
+ depth={depth}
225
+ totalDuration={totalDuration}
226
+ minTime={minTime}
227
+ serviceMap={serviceMap}
228
+ isExpanded={isExpanded}
229
+ onToggle={() => onToggle(span.id)}
230
+ onSpanClick={onSpanClick}
231
+ />
232
+ {isExpanded && span.children && (
233
+ <SpanTree
234
+ spans={span.children}
235
+ depth={depth + 1}
236
+ totalDuration={totalDuration}
237
+ minTime={minTime}
238
+ serviceMap={serviceMap}
239
+ expandedIds={expandedIds}
240
+ onToggle={onToggle}
241
+ onSpanClick={onSpanClick}
242
+ />
243
+ )}
244
+ </React.Fragment>
245
+ )
246
+ })}
247
+ </>
248
+ )
249
+ }
250
+
251
+ export function WakaTraceViewer({
252
+ spans,
253
+ traceId,
254
+ totalDuration: propTotalDuration,
255
+ onSpanClick,
256
+ onCopyTraceId,
257
+ showServiceColors = true,
258
+ className,
259
+ }: WakaTraceViewerProps) {
260
+ const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set())
261
+ const serviceMap = React.useMemo(() => new Map<string, string>(), [])
262
+
263
+ // Calculate min time and total duration
264
+ const { minTime, totalDuration } = React.useMemo(() => {
265
+ const allSpans: TraceSpan[] = []
266
+ const collectSpans = (spans: TraceSpan[]) => {
267
+ spans.forEach((span) => {
268
+ allSpans.push(span)
269
+ if (span.children) collectSpans(span.children)
270
+ })
271
+ }
272
+ collectSpans(spans)
273
+
274
+ if (allSpans.length === 0) return { minTime: 0, totalDuration: 1 }
275
+
276
+ const minTime = Math.min(...allSpans.map((s) => s.startTime))
277
+ const maxTime = Math.max(...allSpans.map((s) => s.startTime + s.duration))
278
+ return {
279
+ minTime,
280
+ totalDuration: propTotalDuration || maxTime - minTime || 1,
281
+ }
282
+ }, [spans, propTotalDuration])
283
+
284
+ const toggleExpand = (id: string) => {
285
+ setExpandedIds((prev) => {
286
+ const next = new Set(prev)
287
+ if (next.has(id)) {
288
+ next.delete(id)
289
+ } else {
290
+ next.add(id)
291
+ }
292
+ return next
293
+ })
294
+ }
295
+
296
+ const expandAll = () => {
297
+ const allIds = new Set<string>()
298
+ const collectIds = (spans: TraceSpan[]) => {
299
+ spans.forEach((span) => {
300
+ allIds.add(span.id)
301
+ if (span.children) collectIds(span.children)
302
+ })
303
+ }
304
+ collectIds(spans)
305
+ setExpandedIds(allIds)
306
+ }
307
+
308
+ const collapseAll = () => {
309
+ setExpandedIds(new Set())
310
+ }
311
+
312
+ // Get unique services
313
+ const services = React.useMemo(() => {
314
+ const serviceSet = new Set<string>()
315
+ const collectServices = (spans: TraceSpan[]) => {
316
+ spans.forEach((span) => {
317
+ serviceSet.add(span.serviceName)
318
+ if (span.children) collectServices(span.children)
319
+ })
320
+ }
321
+ collectServices(spans)
322
+ return Array.from(serviceSet)
323
+ }, [spans])
324
+
325
+ // Count spans
326
+ const spanCount = React.useMemo(() => {
327
+ let count = 0
328
+ const countSpans = (spans: TraceSpan[]) => {
329
+ spans.forEach((span) => {
330
+ count++
331
+ if (span.children) countSpans(span.children)
332
+ })
333
+ }
334
+ countSpans(spans)
335
+ return count
336
+ }, [spans])
337
+
338
+ return (
339
+ <div className={cn("flex flex-col border rounded-lg bg-background", className)}>
340
+ {/* Header */}
341
+ <div className="flex items-center justify-between gap-4 p-3 border-b">
342
+ <div className="flex items-center gap-3">
343
+ <h3 className="font-semibold">Trace</h3>
344
+ {traceId && (
345
+ <div className="flex items-center gap-1">
346
+ <code className="text-xs bg-muted px-2 py-0.5 rounded font-mono">
347
+ {traceId.slice(0, 16)}...
348
+ </code>
349
+ <Button
350
+ variant="ghost"
351
+ size="sm"
352
+ className="h-6 w-6 p-0"
353
+ onClick={() => onCopyTraceId?.(traceId)}
354
+ >
355
+ <Copy className="h-3 w-3" />
356
+ </Button>
357
+ </div>
358
+ )}
359
+ <Badge variant="secondary">{spanCount} spans</Badge>
360
+ <Badge variant="outline">{totalDuration.toFixed(2)}ms</Badge>
361
+ </div>
362
+
363
+ <div className="flex items-center gap-2">
364
+ <Button variant="ghost" size="sm" onClick={expandAll}>
365
+ Expand All
366
+ </Button>
367
+ <Button variant="ghost" size="sm" onClick={collapseAll}>
368
+ Collapse All
369
+ </Button>
370
+ </div>
371
+ </div>
372
+
373
+ {/* Service legend */}
374
+ {showServiceColors && services.length > 0 && (
375
+ <div className="flex items-center gap-3 px-3 py-2 border-b bg-muted/30 flex-wrap">
376
+ <span className="text-xs text-muted-foreground">Services:</span>
377
+ {services.map((service) => (
378
+ <div key={service} className="flex items-center gap-1.5">
379
+ <div className={cn("w-2.5 h-2.5 rounded-full", getServiceColor(service, serviceMap))} />
380
+ <span className="text-xs">{service}</span>
381
+ </div>
382
+ ))}
383
+ </div>
384
+ )}
385
+
386
+ {/* Timeline header */}
387
+ <div className="flex items-center gap-2 px-2 py-1 border-b bg-muted/20 text-xs text-muted-foreground">
388
+ <div className="w-5" />
389
+ <div className="w-5" />
390
+ <div className="w-3" />
391
+ <div className="w-24">Service</div>
392
+ <div className="w-48">Operation</div>
393
+ <div className="w-4" />
394
+ <div className="flex-1 flex justify-between min-w-[200px]">
395
+ <span>0ms</span>
396
+ <span>{(totalDuration / 2).toFixed(0)}ms</span>
397
+ <span>{totalDuration.toFixed(0)}ms</span>
398
+ </div>
399
+ <div className="w-20 text-right">Duration</div>
400
+ </div>
401
+
402
+ {/* Spans */}
403
+ <ScrollArea className="flex-1">
404
+ {spans.length === 0 ? (
405
+ <div className="flex items-center justify-center h-32 text-muted-foreground">
406
+ No spans to display
407
+ </div>
408
+ ) : (
409
+ <SpanTree
410
+ spans={spans}
411
+ depth={0}
412
+ totalDuration={totalDuration}
413
+ minTime={minTime}
414
+ serviceMap={serviceMap}
415
+ expandedIds={expandedIds}
416
+ onToggle={toggleExpand}
417
+ onSpanClick={onSpanClick}
418
+ />
419
+ )}
420
+ </ScrollArea>
421
+ </div>
422
+ )
423
+ }
424
+
425
+ // Default sample trace for demo
426
+ export const defaultTraceSpans: TraceSpan[] = [
427
+ {
428
+ id: "span-1",
429
+ traceId: "abc123def456",
430
+ operationName: "HTTP GET /api/users",
431
+ serviceName: "api-gateway",
432
+ startTime: 0,
433
+ duration: 245,
434
+ status: "ok",
435
+ children: [
436
+ {
437
+ id: "span-2",
438
+ traceId: "abc123def456",
439
+ parentId: "span-1",
440
+ operationName: "authenticate",
441
+ serviceName: "auth-service",
442
+ startTime: 5,
443
+ duration: 35,
444
+ status: "ok",
445
+ },
446
+ {
447
+ id: "span-3",
448
+ traceId: "abc123def456",
449
+ parentId: "span-1",
450
+ operationName: "SELECT * FROM users",
451
+ serviceName: "user-service",
452
+ startTime: 45,
453
+ duration: 120,
454
+ status: "ok",
455
+ children: [
456
+ {
457
+ id: "span-4",
458
+ traceId: "abc123def456",
459
+ parentId: "span-3",
460
+ operationName: "PostgreSQL Query",
461
+ serviceName: "postgres",
462
+ startTime: 50,
463
+ duration: 95,
464
+ status: "ok",
465
+ },
466
+ ],
467
+ },
468
+ {
469
+ id: "span-5",
470
+ traceId: "abc123def456",
471
+ parentId: "span-1",
472
+ operationName: "cache.get",
473
+ serviceName: "redis",
474
+ startTime: 170,
475
+ duration: 8,
476
+ status: "ok",
477
+ },
478
+ {
479
+ id: "span-6",
480
+ traceId: "abc123def456",
481
+ parentId: "span-1",
482
+ operationName: "serialize response",
483
+ serviceName: "api-gateway",
484
+ startTime: 185,
485
+ duration: 55,
486
+ status: "ok",
487
+ },
488
+ ],
489
+ },
490
+ ]