refactor: move config badges inline, fix trace config from server
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m1s
CI / docker (push) Successful in 56s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 40s

- Render hasTrace/hasTap/status badges inside the node card in both
  raw diagram and overlay modes (consistent positioning)
- Pulse only on trace badge in overlay mode when hasTraceData is true
- Fix nodeConfigs to read tracedProcessors from appConfig instead of
  never-synced tracing store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-30 18:08:40 +02:00
parent 2f2f93f37e
commit 26de222884
2 changed files with 90 additions and 37 deletions

View File

@@ -1,8 +1,8 @@
import React from 'react';
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
import type { NodeConfig, LatencyHeatmapEntry } from './types';
import type { NodeExecutionState } from '../ExecutionDiagram/types';
import { colorForType, iconForType, type IconElement } from './node-colors';
import { ConfigBadge } from './ConfigBadge';
const TOP_BAR_HEIGHT = 6;
const TEXT_LEFT = 32;
@@ -176,38 +176,92 @@ export function DiagramNode({
)}
</g>
{/* Config badges */}
{(config || executionState?.hasTraceData) && (
<ConfigBadge nodeWidth={w} config={config ?? {}} hasTraceData={executionState?.hasTraceData} />
)}
{/* Inline badges row: hasTrace, hasTap, status — inside card, top-right */}
{(() => {
const BADGE_R = 6;
const BADGE_D = BADGE_R * 2;
const BADGE_GAP = 3;
const cy = TOP_BAR_HEIGHT + BADGE_R + 2;
const showTrace = config?.traceEnabled || executionState?.hasTraceData;
const showTap = !!config?.tapExpression;
if (!showTrace && !showTap && !isCompleted && !isFailed) return null;
const badges: React.ReactNode[] = [];
let slot = 0;
{/* Execution overlay: status badge inside card, top-right corner */}
{isCompleted && (
<>
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#3D7C47" />
<path
d={`M${w - 13} ${TOP_BAR_HEIGHT + 8} l2 2 4-4`}
fill="none" stroke="white" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round"
/>
</>
)}
{isFailed && (
<>
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="none" stroke="#C0392B" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values="6;14" dur="1.5s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" repeatCount="indefinite" />
</circle>
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="none" stroke="#C0392B" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values="6;14" dur="1.5s" begin="0.75s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" begin="0.75s" repeatCount="indefinite" />
</circle>
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#C0392B" />
<path
d={`M${w - 10} ${TOP_BAR_HEIGHT + 5} v4 M${w - 10} ${TOP_BAR_HEIGHT + 10.5} v0.5`}
fill="none" stroke="white" strokeWidth={1.5} strokeLinecap="round"
/>
</>
)}
// Status badge (rightmost, only during overlay)
const statusCx = w - BADGE_R - 4;
if (isCompleted) {
badges.push(
<g key="status">
<circle cx={statusCx} cy={cy} r={BADGE_R} fill="#3D7C47" />
<path d={`M${statusCx - 3} ${cy} l2 2 4-4`} fill="none" stroke="white" strokeWidth={1.5} strokeLinecap="round" strokeLinejoin="round" />
</g>
);
slot++;
} else if (isFailed) {
badges.push(
<g key="status">
<circle cx={statusCx} cy={cy} r={BADGE_R} fill="none" stroke="#C0392B" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values="6;14" dur="1.5s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" repeatCount="indefinite" />
</circle>
<circle cx={statusCx} cy={cy} r={BADGE_R} fill="none" stroke="#C0392B" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values="6;14" dur="1.5s" begin="0.75s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" begin="0.75s" repeatCount="indefinite" />
</circle>
<circle cx={statusCx} cy={cy} r={BADGE_R} fill="#C0392B" />
<path d={`M${statusCx} ${cy - 3} v4 M${statusCx} ${cy + 2.5} v0.5`} fill="none" stroke="white" strokeWidth={1.5} strokeLinecap="round" />
</g>
);
slot++;
}
// Tap badge (before status)
if (showTap) {
const tapCx = statusCx - slot * (BADGE_D + BADGE_GAP);
badges.push(
<g key="tap">
<circle cx={tapCx} cy={cy} r={BADGE_R} fill="#7C3AED" />
<g transform={`translate(${tapCx - 5}, ${cy - 5})`} stroke="white" strokeWidth={1.4} fill="none" strokeLinecap="round" strokeLinejoin="round">
<path d="M5 1 C5 1 2 4.5 2 6.5a3 3 0 006 0C8 4.5 5 1 5 1z" />
</g>
</g>
);
slot++;
}
// Trace badge (leftmost)
if (showTrace) {
const traceCx = statusCx - slot * (BADGE_D + BADGE_GAP);
const tracePulse = overlayActive && executionState?.hasTraceData;
const traceHasData = executionState?.hasTraceData;
badges.push(
<g key="trace">
{tracePulse && (
<>
<circle cx={traceCx} cy={cy} r={BADGE_R} fill="none" stroke="#1A7F8E" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values={`${BADGE_R};${BADGE_R + 8}`} dur="1.5s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" repeatCount="indefinite" />
</circle>
<circle cx={traceCx} cy={cy} r={BADGE_R} fill="none" stroke="#1A7F8E" strokeWidth={2} opacity={0.5}>
<animate attributeName="r" values={`${BADGE_R};${BADGE_R + 8}`} dur="1.5s" begin="0.75s" repeatCount="indefinite" />
<animate attributeName="opacity" values="0.5;0" dur="1.5s" begin="0.75s" repeatCount="indefinite" />
</circle>
</>
)}
<circle cx={traceCx} cy={cy} r={BADGE_R} fill={traceHasData ? '#1A7F8E' : '#1A7F8E'} opacity={traceHasData ? 1 : 0.2} />
<g transform={`translate(${traceCx - 5}, ${cy - 5}) scale(${10/24})`} stroke={traceHasData ? 'white' : '#1A7F8E'} strokeWidth={2.4} fill="none" strokeLinecap="round" strokeLinejoin="round">
<path d="M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z" />
<path d="M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z" />
<path d="M16 17h4" />
<path d="M4 13h4" />
</g>
</g>
);
}
return <>{badges}</>;
})()}
{/* Execution overlay: duration text at bottom-right */}
{executionState && statusColor && (

View File

@@ -152,13 +152,12 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
return map;
}, [catalog]);
// Build nodeConfigs from tracing store + app config (for TRACE/TAP badges)
// Build nodeConfigs from app config (for TRACE/TAP badges)
const { data: appConfig } = useApplicationConfig(appId);
const tracedMap = useTracingStore((s) => s.tracedProcessors[appId]);
const nodeConfigs = useMemo(() => {
const map = new Map<string, NodeConfig>();
if (tracedMap) {
for (const pid of Object.keys(tracedMap)) {
if (appConfig?.tracedProcessors) {
for (const pid of Object.keys(appConfig.tracedProcessors)) {
map.set(pid, { traceEnabled: true });
}
}
@@ -171,7 +170,7 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
}
}
return map;
}, [tracedMap, appConfig]);
}, [appConfig]);
// Processor options for tap modal dropdown
const processorOptions = useMemo(() => {