feat: resolved URI display and drill-down for TO/TO_DYNAMIC nodes
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m2s
CI / docker (push) Successful in 55s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s

- Show resolved endpoint URI as teal italic line on diagram nodes
  when execution overlay is active
- Enable drill-down for TO and TO_DYNAMIC nodes (not just DIRECT/SEDA)
- Use runtime resolvedEndpointUri from execution overlay for drill-down
  when static endpointUri doesn't match
- Increase node height from 50px to 56px to accommodate the third line

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-29 16:30:11 +02:00
parent 32cde5363f
commit 090c51c809
5 changed files with 47 additions and 17 deletions

View File

@@ -53,7 +53,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
} }
private static final int PADDING = 20; private static final int PADDING = 20;
private static final int NODE_HEIGHT = 50; private static final int NODE_HEIGHT = 56;
private static final int NODE_WIDTH = 220; private static final int NODE_WIDTH = 220;
private static final int COMPOUND_TOP_PADDING = 30; private static final int COMPOUND_TOP_PADDING = 30;
private static final int COMPOUND_SIDE_PADDING = 10; private static final int COMPOUND_SIDE_PADDING = 10;

View File

@@ -10,6 +10,8 @@ export interface NodeExecutionState {
subRouteFailed?: boolean; subRouteFailed?: boolean;
/** True if trace data is available for this processor */ /** True if trace data is available for this processor */
hasTraceData?: boolean; hasTraceData?: boolean;
/** Runtime-resolved endpoint URI (for TO_DYNAMIC, etc.) */
resolvedEndpointUri?: string;
} }
export interface IterationInfo { export interface IterationInfo {

View File

@@ -60,6 +60,7 @@ function buildOverlay(
durationMs: proc.durationMs ?? 0, durationMs: proc.durationMs ?? 0,
subRouteFailed: subRouteFailed || undefined, subRouteFailed: subRouteFailed || undefined,
hasTraceData: !!proc.hasTraceData, hasTraceData: !!proc.hasTraceData,
resolvedEndpointUri: proc.resolvedEndpointUri || undefined,
}); });
// Recurse into children // Recurse into children

View File

@@ -42,6 +42,7 @@ export function DiagramNode({
// Extract label parts: type name and detail // Extract label parts: type name and detail
const typeName = node.type?.replace(/^EIP_/, '').replace(/_/g, ' ') ?? ''; const typeName = node.type?.replace(/^EIP_/, '').replace(/_/g, ' ') ?? '';
const detail = node.label || ''; const detail = node.label || '';
const resolvedUri = executionState?.resolvedEndpointUri;
// Overlay state derivation // Overlay state derivation
const isCompleted = executionState?.status === 'COMPLETED'; const isCompleted = executionState?.status === 'COMPLETED';
@@ -125,15 +126,33 @@ export function DiagramNode({
)} )}
</g> </g>
{/* Type name + detail (clipped to available width) */} {/* Type name + detail + resolved URI (clipped to available width) */}
<g clipPath={`url(#clip-${node.id})`}> <g clipPath={`url(#clip-${node.id})`}>
<text x={TEXT_LEFT} y={h / 2 - 1} fill={labelColor} fontSize={11} fontWeight={600}> {resolvedUri ? (
{typeName} <>
</text> <text x={TEXT_LEFT} y={TOP_BAR_HEIGHT + 12} fill={labelColor} fontSize={11} fontWeight={600}>
{detail && detail !== typeName && ( {typeName}
<text x={TEXT_LEFT} y={h / 2 + 12} fill={isFailed ? '#C0392B' : '#5C5347'} fontSize={10}> </text>
{detail} {detail && detail !== typeName && (
</text> <text x={TEXT_LEFT} y={TOP_BAR_HEIGHT + 24} fill={isFailed ? '#C0392B' : '#5C5347'} fontSize={10}>
{detail}
</text>
)}
<text x={TEXT_LEFT} y={h - 5} fill="#1A7F8E" fontSize={9} fontStyle="italic">
{resolvedUri.split('?')[0]}
</text>
</>
) : (
<>
<text x={TEXT_LEFT} y={h / 2 - 1} fill={labelColor} fontSize={11} fontWeight={600}>
{typeName}
</text>
{detail && detail !== typeName && (
<text x={TEXT_LEFT} y={h / 2 + 12} fill={isFailed ? '#C0392B' : '#5C5347'} fontSize={10}>
{detail}
</text>
)}
</>
)} )}
</g> </g>

View File

@@ -16,7 +16,7 @@ import styles from './ProcessDiagram.module.css';
const PADDING = 40; const PADDING = 40;
/** Types that support drill-down — double-click navigates to the target route */ /** Types that support drill-down — double-click navigates to the target route */
const DRILLDOWN_TYPES = new Set(['DIRECT', 'SEDA']); const DRILLDOWN_TYPES = new Set(['DIRECT', 'SEDA', 'TO', 'TO_DYNAMIC']);
export function ProcessDiagram({ export function ProcessDiagram({
application, application,
@@ -168,17 +168,26 @@ export function ProcessDiagram({
const node = findNodeById(sections, nodeId); const node = findNodeById(sections, nodeId);
if (!node || !DRILLDOWN_TYPES.has(node.type ?? '')) return; if (!node || !DRILLDOWN_TYPES.has(node.type ?? '')) return;
// Resolve via endpointUri → endpointRouteMap (exact match, no heuristics) // Try static endpointUri first, then runtime-resolved URI from execution overlay
const uri = node.endpointUri; const staticUri = node.endpointUri;
if (!uri) return; const runtimeUri = executionOverlay?.get(nodeId)?.resolvedEndpointUri;
const stripped = uri.split('?')[0];
const resolved = endpointRouteMap?.get(stripped); function normalize(uri: string): string {
return uri.split('?')[0];
}
function tryResolve(uri: string | undefined): string | undefined {
if (!uri) return undefined;
return endpointRouteMap?.get(normalize(uri));
}
const resolved = tryResolve(staticUri) ?? tryResolve(runtimeUri);
if (resolved) { if (resolved) {
onNodeSelect?.(''); onNodeSelect?.('');
setRouteStack(prev => [...prev, resolved]); setRouteStack(prev => [...prev, resolved]);
} }
}, },
[sections, onNodeSelect, endpointRouteMap], [sections, onNodeSelect, endpointRouteMap, executionOverlay],
); );
const handleBreadcrumbClick = useCallback( const handleBreadcrumbClick = useCallback(
@@ -201,7 +210,6 @@ export function ProcessDiagram({
(e: React.KeyboardEvent) => { (e: React.KeyboardEvent) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
if (routeStack.length > 1) { if (routeStack.length > 1) {
// Go back one level
setRouteStack(prev => prev.slice(0, -1)); setRouteStack(prev => prev.slice(0, -1));
} else { } else {
onNodeSelect?.(''); onNodeSelect?.('');