feat: show tracing badges on processor nodes
Update design system to 0.1.8 and pass NodeBadge[] to both ProcessorTimeline and RouteFlow. Traced processors display a blue "TRACED" badge that updates reactively via Zustand store. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.7",
|
"@cameleer/design-system": "^0.1.8",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
@@ -276,9 +276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cameleer/design-system": {
|
"node_modules/@cameleer/design-system": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.7/design-system-0.1.7.tgz",
|
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.8/design-system-0.1.8.tgz",
|
||||||
"integrity": "sha512-Q+CFFVuMqnF4xKB0Rtz4VZRQeaHqXjfK/M0DMCE0FIfW6q/bpTFBioPI5r7fDjBjIByG2hDB5XztL//I8o2RNA==",
|
"integrity": "sha512-mc7IQOYYez0UItvwiNbbYFrJehG3JtdVlOUsdLXcN8zmgtpImleVro4MsPxCX4/OOGI4EGoX1oIVpFi91qEI6A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.7",
|
"@cameleer/design-system": "^0.1.8",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
|
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
|
||||||
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow, useToast,
|
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow, useToast,
|
||||||
} from '@cameleer/design-system'
|
} from '@cameleer/design-system'
|
||||||
import type { ProcessorStep, RouteNode } from '@cameleer/design-system'
|
import type { ProcessorStep, RouteNode, NodeBadge } from '@cameleer/design-system'
|
||||||
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
|
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
|
||||||
import { useCorrelationChain } from '../../api/queries/correlation'
|
import { useCorrelationChain } from '../../api/queries/correlation'
|
||||||
import { useDiagramLayout } from '../../api/queries/diagrams'
|
import { useDiagramLayout } from '../../api/queries/diagrams'
|
||||||
@@ -74,25 +74,35 @@ export default function ExchangeDetail() {
|
|||||||
? (detail.processors?.length ? detail.processors : (detail.children ?? []))
|
? (detail.processors?.length ? detail.processors : (detail.children ?? []))
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
// Subscribe to tracing state for badge rendering
|
||||||
|
const tracedMap = useTracingStore((s) => s.tracedProcessors[detail?.applicationName ?? ''])
|
||||||
|
|
||||||
|
function badgesFor(processorId: string): NodeBadge[] | undefined {
|
||||||
|
if (!tracedMap || !(processorId in tracedMap)) return undefined
|
||||||
|
return [{ label: 'Traced', variant: 'info' }]
|
||||||
|
}
|
||||||
|
|
||||||
// Flatten processor tree into ProcessorStep[]
|
// Flatten processor tree into ProcessorStep[]
|
||||||
const processors: ProcessorStep[] = useMemo(() => {
|
const processors: ProcessorStep[] = useMemo(() => {
|
||||||
if (!procList.length) return []
|
if (!procList.length) return []
|
||||||
const result: ProcessorStep[] = []
|
const result: ProcessorStep[] = []
|
||||||
let offset = 0
|
let offset = 0
|
||||||
function walk(node: any) {
|
function walk(node: any) {
|
||||||
|
const pid = node.processorId || node.processorType
|
||||||
result.push({
|
result.push({
|
||||||
name: node.processorId || node.processorType,
|
name: pid,
|
||||||
type: node.processorType,
|
type: node.processorType,
|
||||||
durationMs: node.durationMs ?? 0,
|
durationMs: node.durationMs ?? 0,
|
||||||
status: procStatusToStep(node.status ?? ''),
|
status: procStatusToStep(node.status ?? ''),
|
||||||
startMs: offset,
|
startMs: offset,
|
||||||
|
badges: badgesFor(node.processorId || ''),
|
||||||
})
|
})
|
||||||
offset += node.durationMs ?? 0
|
offset += node.durationMs ?? 0
|
||||||
if (node.children) node.children.forEach(walk)
|
if (node.children) node.children.forEach(walk)
|
||||||
}
|
}
|
||||||
procList.forEach(walk)
|
procList.forEach(walk)
|
||||||
return result
|
return result
|
||||||
}, [procList])
|
}, [procList, tracedMap])
|
||||||
|
|
||||||
// Default selected processor: first failed, or 0
|
// Default selected processor: first failed, or 0
|
||||||
const defaultIndex = useMemo(() => {
|
const defaultIndex = useMemo(() => {
|
||||||
@@ -121,7 +131,20 @@ export default function ExchangeDetail() {
|
|||||||
// Build RouteFlow nodes from diagram + execution data
|
// Build RouteFlow nodes from diagram + execution data
|
||||||
const routeNodes: RouteNode[] = useMemo(() => {
|
const routeNodes: RouteNode[] = useMemo(() => {
|
||||||
if (diagram?.nodes) {
|
if (diagram?.nodes) {
|
||||||
return mapDiagramToRouteNodes(diagram.nodes, procList)
|
// Flatten processors to build diagramNodeId → processorId lookup
|
||||||
|
const flatProcs: Array<{ diagramNodeId?: string; processorId?: string }> = []
|
||||||
|
function flattenProcs(nodes: any[]) {
|
||||||
|
for (const n of nodes) { flatProcs.push(n); if (n.children) flattenProcs(n.children) }
|
||||||
|
}
|
||||||
|
flattenProcs(procList)
|
||||||
|
const pidLookup = new Map(flatProcs
|
||||||
|
.filter(p => p.diagramNodeId && p.processorId)
|
||||||
|
.map(p => [p.diagramNodeId!, p.processorId!]))
|
||||||
|
|
||||||
|
return mapDiagramToRouteNodes(diagram.nodes, procList).map((node, i) => ({
|
||||||
|
...node,
|
||||||
|
badges: badgesFor(pidLookup.get(diagram.nodes[i]?.id ?? '') ?? diagram.nodes[i]?.id ?? ''),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
// Fallback: build from processor list
|
// Fallback: build from processor list
|
||||||
return processors.map((p) => ({
|
return processors.map((p) => ({
|
||||||
@@ -129,8 +152,9 @@ export default function ExchangeDetail() {
|
|||||||
type: 'process' as RouteNode['type'],
|
type: 'process' as RouteNode['type'],
|
||||||
durationMs: p.durationMs,
|
durationMs: p.durationMs,
|
||||||
status: p.status,
|
status: p.status,
|
||||||
|
badges: badgesFor(p.name),
|
||||||
}))
|
}))
|
||||||
}, [diagram, processors, procList])
|
}, [diagram, processors, procList, tracedMap])
|
||||||
|
|
||||||
// ProcessorId lookup: timeline index → processorId
|
// ProcessorId lookup: timeline index → processorId
|
||||||
const processorIds: string[] = useMemo(() => {
|
const processorIds: string[] = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user