feat: show tracing badges on processor nodes
All checks were successful
CI / build (push) Successful in 1m18s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 1m12s
CI / deploy (push) Successful in 40s
CI / deploy-feature (push) Has been skipped

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:
hsiegeln
2026-03-24 23:10:37 +01:00
parent bf57fd139b
commit 488a32f319
3 changed files with 34 additions and 10 deletions

8
ui/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "ui",
"version": "0.0.0",
"dependencies": {
"@cameleer/design-system": "^0.1.7",
"@cameleer/design-system": "^0.1.8",
"@tanstack/react-query": "^5.90.21",
"openapi-fetch": "^0.17.0",
"react": "^19.2.4",
@@ -276,9 +276,9 @@
}
},
"node_modules/@cameleer/design-system": {
"version": "0.1.7",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.7/design-system-0.1.7.tgz",
"integrity": "sha512-Q+CFFVuMqnF4xKB0Rtz4VZRQeaHqXjfK/M0DMCE0FIfW6q/bpTFBioPI5r7fDjBjIByG2hDB5XztL//I8o2RNA==",
"version": "0.1.8",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.8/design-system-0.1.8.tgz",
"integrity": "sha512-mc7IQOYYez0UItvwiNbbYFrJehG3JtdVlOUsdLXcN8zmgtpImleVro4MsPxCX4/OOGI4EGoX1oIVpFi91qEI6A==",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",

View File

@@ -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"
},
"dependencies": {
"@cameleer/design-system": "^0.1.7",
"@cameleer/design-system": "^0.1.8",
"@tanstack/react-query": "^5.90.21",
"openapi-fetch": "^0.17.0",
"react": "^19.2.4",

View File

@@ -4,7 +4,7 @@ import {
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow, useToast,
} 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 { useCorrelationChain } from '../../api/queries/correlation'
import { useDiagramLayout } from '../../api/queries/diagrams'
@@ -74,25 +74,35 @@ export default function ExchangeDetail() {
? (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[]
const processors: ProcessorStep[] = useMemo(() => {
if (!procList.length) return []
const result: ProcessorStep[] = []
let offset = 0
function walk(node: any) {
const pid = node.processorId || node.processorType
result.push({
name: node.processorId || node.processorType,
name: pid,
type: node.processorType,
durationMs: node.durationMs ?? 0,
status: procStatusToStep(node.status ?? ''),
startMs: offset,
badges: badgesFor(node.processorId || ''),
})
offset += node.durationMs ?? 0
if (node.children) node.children.forEach(walk)
}
procList.forEach(walk)
return result
}, [procList])
}, [procList, tracedMap])
// Default selected processor: first failed, or 0
const defaultIndex = useMemo(() => {
@@ -121,7 +131,20 @@ export default function ExchangeDetail() {
// Build RouteFlow nodes from diagram + execution data
const routeNodes: RouteNode[] = useMemo(() => {
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
return processors.map((p) => ({
@@ -129,8 +152,9 @@ export default function ExchangeDetail() {
type: 'process' as RouteNode['type'],
durationMs: p.durationMs,
status: p.status,
badges: badgesFor(p.name),
}))
}, [diagram, processors, procList])
}, [diagram, processors, procList, tracedMap])
// ProcessorId lookup: timeline index → processorId
const processorIds: string[] = useMemo(() => {