feat: add processor tracing toggle to exchange detail views
All checks were successful
CI / build (push) Successful in 1m22s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 52s
CI / deploy (push) Successful in 39s
CI / deploy-feature (push) Has been skipped

Wire getActions on ProcessorTimeline and RouteFlow to send
SET_TRACED_PROCESSORS commands to all agents of the same application.
Tracing state managed via Zustand store with optimistic UI and rollback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 22:30:26 +01:00
parent 7532cc9d59
commit f4dd2b3415
3 changed files with 120 additions and 2 deletions

View File

@@ -1,14 +1,16 @@
import { useState, useMemo } from 'react'
import { useState, useMemo, useCallback } from 'react'
import { useParams, useNavigate } from 'react-router'
import {
Badge, StatusDot, MonoText, CodeBlock, InfoCallout,
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow,
ProcessorTimeline, Breadcrumb, Spinner, RouteFlow, useToast,
} from '@cameleer/design-system'
import type { ProcessorStep, RouteNode } from '@cameleer/design-system'
import { useExecutionDetail, useProcessorSnapshot } from '../../api/queries/executions'
import { useCorrelationChain } from '../../api/queries/correlation'
import { useDiagramLayout } from '../../api/queries/diagrams'
import { mapDiagramToRouteNodes } from '../../utils/diagram-mapping'
import { useTracingStore } from '../../stores/tracing-store'
import { useSendGroupCommand } from '../../api/queries/commands'
import styles from './ExchangeDetail.module.css'
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -130,6 +132,59 @@ export default function ExchangeDetail() {
}))
}, [diagram, processors, procList])
// ProcessorId lookup: timeline index → processorId
const processorIds: string[] = useMemo(() => {
const ids: string[] = []
function walk(node: any) {
ids.push(node.processorId || '')
if (node.children) node.children.forEach(walk)
}
procList.forEach(walk)
return ids
}, [procList])
// ProcessorId lookup: flow node index → processorId (diagram order)
const flowProcessorIds: string[] = useMemo(() => {
if (!diagram?.nodes) return processorIds
const flatProcs: Array<{ diagramNodeId?: string; processorId?: string }> = []
function flatten(nodes: any[]) {
for (const n of nodes) {
flatProcs.push(n)
if (n.children) flatten(n.children)
}
}
flatten(procList)
const lookup = new Map(flatProcs
.filter(p => p.diagramNodeId && p.processorId)
.map(p => [p.diagramNodeId!, p.processorId!]))
return diagram.nodes.map(node => lookup.get(node.id ?? '') ?? '')
}, [diagram, procList, processorIds])
// ── Tracing toggle ──────────────────────────────────────────────────────
const { toast } = useToast()
const tracingStore = useTracingStore()
const sendCommand = useSendGroupCommand()
const appRoute = detail ? `${detail.applicationName}:${detail.routeId}` : ''
const handleToggleTracing = useCallback((processorId: string) => {
if (!processorId || !detail?.applicationName || !detail?.routeId) return
const newSet = tracingStore.toggleProcessor(appRoute, processorId)
sendCommand.mutate({
group: detail.applicationName,
type: 'set-traced-processors',
payload: { routeId: detail.routeId, processorIds: Array.from(newSet) },
}, {
onSuccess: (data) => {
const action = newSet.has(processorId) ? 'enabled' : 'disabled'
toast({ title: `Tracing ${action}`, description: `${processorId} — sent to ${data?.targetCount ?? 0} agent(s)`, variant: 'success' })
},
onError: () => {
tracingStore.toggleProcessor(appRoute, processorId)
toast({ title: 'Command failed', description: 'Could not send tracing command', variant: 'error' })
},
})
}, [detail, appRoute, tracingStore, sendCommand, toast])
// Correlation chain
const correlatedExchanges = useMemo(() => {
if (!correlationData?.data || correlationData.data.length <= 1) return []
@@ -287,6 +342,15 @@ export default function ExchangeDetail() {
totalMs={detail.durationMs}
onProcessorClick={(_proc, index) => setSelectedProcessorIndex(index)}
selectedIndex={activeIndex}
getActions={(_proc, index) => {
const pid = processorIds[index]
if (!pid || !detail?.applicationName) return []
return [{
label: tracingStore.isTraced(appRoute, pid) ? 'Disable Tracing' : 'Enable Tracing',
onClick: () => handleToggleTracing(pid),
disabled: sendCommand.isPending,
}]
}}
/>
) : (
<InfoCallout>No processor data available</InfoCallout>
@@ -297,6 +361,15 @@ export default function ExchangeDetail() {
nodes={routeNodes}
onNodeClick={(_node, index) => setSelectedProcessorIndex(index)}
selectedIndex={activeIndex}
getActions={(_node, index) => {
const pid = flowProcessorIds[index]
if (!pid || !detail?.applicationName) return []
return [{
label: tracingStore.isTraced(appRoute, pid) ? 'Disable Tracing' : 'Enable Tracing',
onClick: () => handleToggleTracing(pid),
disabled: sendCommand.isPending,
}]
}}
/>
) : (
<Spinner />