feat: resolved URI display and drill-down for TO/TO_DYNAMIC nodes
- 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:
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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?.('');
|
||||||
|
|||||||
Reference in New Issue
Block a user