fix: ENDPOINT node execution state, badge position, and edge traversal
- Synthesize COMPLETED state for ENDPOINT nodes when overlay is active (endpoints are route entry points, not in the processor execution tree) - Move status badge (check/error) inside the card (top-right, below top bar) to avoid collision with ConfigBadge (TRACE/TAP) badges - Include ENDPOINT nodes in edge traversal check so the edge from endpoint to first processor renders as green/traversed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -129,16 +129,16 @@ export function DiagramNode({
|
||||
{/* Config badges */}
|
||||
{config && <ConfigBadge nodeWidth={w} config={config} />}
|
||||
|
||||
{/* Execution overlay: status badge at top-right */}
|
||||
{/* Execution overlay: status badge inside card, top-right corner */}
|
||||
{isCompleted && (
|
||||
<>
|
||||
<circle cx={w - 8} cy={-8} r={8} fill="#3D7C47" />
|
||||
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#3D7C47" />
|
||||
<text
|
||||
x={w - 8}
|
||||
y={-4}
|
||||
x={w - 10}
|
||||
y={TOP_BAR_HEIGHT + 11}
|
||||
textAnchor="middle"
|
||||
fill="white"
|
||||
fontSize={11}
|
||||
fontSize={9}
|
||||
fontWeight={700}
|
||||
>
|
||||
✓
|
||||
@@ -147,13 +147,13 @@ export function DiagramNode({
|
||||
)}
|
||||
{isFailed && (
|
||||
<>
|
||||
<circle cx={w - 8} cy={-8} r={8} fill="#C0392B" />
|
||||
<circle cx={w - 10} cy={TOP_BAR_HEIGHT + 8} r={6} fill="#C0392B" />
|
||||
<text
|
||||
x={w - 8}
|
||||
y={-4}
|
||||
x={w - 10}
|
||||
y={TOP_BAR_HEIGHT + 11}
|
||||
textAnchor="middle"
|
||||
fill="white"
|
||||
fontSize={12}
|
||||
fontSize={9}
|
||||
fontWeight={700}
|
||||
>
|
||||
!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { ProcessDiagramProps } from './types';
|
||||
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
|
||||
import { useDiagramData } from './useDiagramData';
|
||||
@@ -58,6 +58,7 @@ export function ProcessDiagram({
|
||||
onIterationChange,
|
||||
}: ProcessDiagramProps) {
|
||||
const overlayActive = !!executionOverlay;
|
||||
|
||||
// Route stack for drill-down navigation
|
||||
const [routeStack, setRouteStack] = useState<string[]>([routeId]);
|
||||
|
||||
@@ -72,6 +73,19 @@ export function ProcessDiagram({
|
||||
application, currentRouteId, direction, diagramLayout,
|
||||
);
|
||||
|
||||
// Collect ENDPOINT node IDs — these are always "traversed" when overlay is active
|
||||
// because the endpoint is the route entry point (not in the processor execution tree).
|
||||
const endpointNodeIds = useMemo(() => {
|
||||
const ids = new Set<string>();
|
||||
if (!overlayActive || !sections.length) return ids;
|
||||
for (const section of sections) {
|
||||
for (const node of section.nodes) {
|
||||
if (node.type === 'ENDPOINT' && node.id) ids.add(node.id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}, [overlayActive, sections]);
|
||||
|
||||
const zoom = useZoomPan();
|
||||
const toolbar = useToolbarHover();
|
||||
|
||||
@@ -85,6 +99,23 @@ export function ProcessDiagram({
|
||||
}
|
||||
}, [totalWidth, totalHeight, currentRouteId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Resolve execution state for a node. ENDPOINT nodes (the route's "from:")
|
||||
// don't appear in the processor execution tree, but should be marked as
|
||||
// COMPLETED when the route executed (i.e., overlay has any entries).
|
||||
const getNodeExecutionState = useCallback(
|
||||
(nodeId: string | undefined, nodeType: string | undefined) => {
|
||||
if (!nodeId || !executionOverlay) return undefined;
|
||||
const state = executionOverlay.get(nodeId);
|
||||
if (state) return state;
|
||||
// Synthesize COMPLETED for ENDPOINT nodes when overlay is active
|
||||
if (nodeType === 'ENDPOINT' && executionOverlay.size > 0) {
|
||||
return { status: 'COMPLETED' as const, durationMs: 0, hasTraceData: false };
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[executionOverlay],
|
||||
);
|
||||
|
||||
const handleNodeClick = useCallback(
|
||||
(nodeId: string) => { onNodeSelect?.(nodeId); },
|
||||
[onNodeSelect],
|
||||
@@ -229,8 +260,10 @@ export function ProcessDiagram({
|
||||
{/* Main section top-level edges (not inside compounds) */}
|
||||
<g className="edges">
|
||||
{mainSection.edges.filter(e => topLevelEdge(e, mainSection.nodes)).map((edge, i) => {
|
||||
const sourceHasState = executionOverlay?.has(edge.sourceId) || endpointNodeIds.has(edge.sourceId);
|
||||
const targetHasState = executionOverlay?.has(edge.targetId) || endpointNodeIds.has(edge.targetId);
|
||||
const isTraversed = executionOverlay
|
||||
? (executionOverlay.has(edge.sourceId) && executionOverlay.has(edge.targetId))
|
||||
? (!!sourceHasState && !!targetHasState)
|
||||
: undefined;
|
||||
return (
|
||||
<DiagramEdge key={`${edge.sourceId}-${edge.targetId}-${i}`} edge={edge} traversed={isTraversed} />
|
||||
@@ -268,7 +301,7 @@ export function ProcessDiagram({
|
||||
isHovered={toolbar.hoveredNodeId === node.id}
|
||||
isSelected={selectedNodeId === node.id}
|
||||
config={node.id ? nodeConfigs?.get(node.id) : undefined}
|
||||
executionState={executionOverlay?.get(node.id ?? '')}
|
||||
executionState={getNodeExecutionState(node.id, node.type)}
|
||||
overlayActive={overlayActive}
|
||||
onClick={() => node.id && handleNodeClick(node.id)}
|
||||
onDoubleClick={() => node.id && handleNodeDoubleClick(node.id)}
|
||||
|
||||
Reference in New Issue
Block a user