feat: render EIP_CIRCUIT_BREAKER as compound container with main/fallback lanes
Follow the DO_TRY pattern: virtual _CB_MAIN wrapper for main path children, onFallback rendered as _CB_FALLBACK section with purple dashed border. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
NodeType.DO_TRY, NodeType.DO_CATCH, NodeType.DO_FINALLY,
|
||||
NodeType.EIP_LOOP, NodeType.EIP_MULTICAST,
|
||||
NodeType.EIP_AGGREGATE, NodeType.ON_EXCEPTION, NodeType.ERROR_HANDLER,
|
||||
NodeType.ON_COMPLETION
|
||||
NodeType.ON_COMPLETION, NodeType.EIP_CIRCUIT_BREAKER
|
||||
);
|
||||
|
||||
/** Top-level handler types laid out in their own separate ELK graph. */
|
||||
@@ -621,6 +621,84 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
sectionOrder.add(handler.getId());
|
||||
}
|
||||
|
||||
ctx.doTrySectionOrder.put(rn.getId(), sectionOrder);
|
||||
} else if (isCompound && rn.getType() == NodeType.EIP_CIRCUIT_BREAKER) {
|
||||
// CIRCUIT_BREAKER: vertical container with _CB_MAIN for main path
|
||||
// and onFallback as a compound section below (like DO_TRY pattern)
|
||||
ctx.compoundNodeIds.add(rn.getId());
|
||||
elkNode.setWidth(200);
|
||||
elkNode.setHeight(100);
|
||||
elkNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
elkNode.setProperty(CoreOptions.DIRECTION, Direction.DOWN);
|
||||
elkNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.4);
|
||||
elkNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.3);
|
||||
elkNode.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(COMPOUND_TOP_PADDING,
|
||||
COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING, COMPOUND_SIDE_PADDING));
|
||||
|
||||
// Separate main path children from onFallback child
|
||||
List<RouteNode> mainChildren = new ArrayList<>();
|
||||
RouteNode fallbackNode = null;
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if ("onFallback".equals(child.getLabel())) {
|
||||
fallbackNode = child;
|
||||
} else {
|
||||
mainChildren.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> sectionOrder = new ArrayList<>();
|
||||
|
||||
// Virtual _CB_MAIN wrapper for main path (horizontal flow)
|
||||
if (!mainChildren.isEmpty()) {
|
||||
String wrapperId = rn.getId() + "._cb_main";
|
||||
ElkNode wrapper = ctx.factory.createElkNode();
|
||||
wrapper.setIdentifier(wrapperId);
|
||||
wrapper.setParent(elkNode);
|
||||
wrapper.setWidth(200);
|
||||
wrapper.setHeight(40);
|
||||
wrapper.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
wrapper.setProperty(CoreOptions.DIRECTION, Direction.RIGHT);
|
||||
wrapper.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
wrapper.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(8, 8, 8, 8));
|
||||
ctx.compoundNodeIds.add(wrapperId);
|
||||
ctx.elkNodeMap.put(wrapperId, wrapper);
|
||||
sectionOrder.add(wrapperId);
|
||||
|
||||
for (RouteNode child : mainChildren) {
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, wrapper, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// onFallback as compound section containing its children
|
||||
if (fallbackNode != null) {
|
||||
ctx.childNodeIds.add(fallbackNode.getId());
|
||||
ElkNode fallbackElk = ctx.factory.createElkNode();
|
||||
fallbackElk.setIdentifier(fallbackNode.getId());
|
||||
fallbackElk.setParent(elkNode);
|
||||
fallbackElk.setWidth(200);
|
||||
fallbackElk.setHeight(40);
|
||||
fallbackElk.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
fallbackElk.setProperty(CoreOptions.DIRECTION, Direction.RIGHT);
|
||||
fallbackElk.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING * 0.5);
|
||||
fallbackElk.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING * 0.5);
|
||||
fallbackElk.setProperty(CoreOptions.PADDING,
|
||||
new org.eclipse.elk.core.math.ElkPadding(18, 8, 8, 8));
|
||||
ctx.compoundNodeIds.add(fallbackNode.getId());
|
||||
ctx.elkNodeMap.put(fallbackNode.getId(), fallbackElk);
|
||||
sectionOrder.add(fallbackNode.getId());
|
||||
|
||||
if (fallbackNode.getChildren() != null) {
|
||||
for (RouteNode child : fallbackNode.getChildren()) {
|
||||
ctx.childNodeIds.add(child.getId());
|
||||
createElkNodeRecursive(child, fallbackElk, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.doTrySectionOrder.put(rn.getId(), sectionOrder);
|
||||
} else if (isCompound) {
|
||||
ctx.compoundNodeIds.add(rn.getId());
|
||||
@@ -690,6 +768,55 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
children.add(extractPositionedNode(handler, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
} else if (rn.getType() == NodeType.EIP_CIRCUIT_BREAKER) {
|
||||
// CIRCUIT_BREAKER: extract _CB_MAIN wrapper, then onFallback section
|
||||
String mainWrapperId = rn.getId() + "._cb_main";
|
||||
ElkNode mainWrapperElk = ctx.elkNodeMap.get(mainWrapperId);
|
||||
if (mainWrapperElk != null) {
|
||||
List<PositionedNode> wrapperChildren = new ArrayList<>();
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if (!"onFallback".equals(child.getLabel())) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
wrapperChildren.add(extractPositionedNode(child, childElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.add(new PositionedNode(
|
||||
mainWrapperId, "", "_CB_MAIN",
|
||||
getAbsoluteX(mainWrapperElk, rootNode),
|
||||
getAbsoluteY(mainWrapperElk, rootNode),
|
||||
mainWrapperElk.getWidth(), mainWrapperElk.getHeight(),
|
||||
wrapperChildren, null));
|
||||
ctx.compoundInfos.put(mainWrapperId, new CompoundInfo(mainWrapperId, Color.WHITE));
|
||||
}
|
||||
// onFallback section with its children, type overridden to _CB_FALLBACK
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
if ("onFallback".equals(child.getLabel())) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
if (childElk != null) {
|
||||
List<PositionedNode> fallbackChildren = new ArrayList<>();
|
||||
if (child.getChildren() != null) {
|
||||
for (RouteNode fc : child.getChildren()) {
|
||||
ElkNode fcElk = ctx.elkNodeMap.get(fc.getId());
|
||||
if (fcElk != null) {
|
||||
fallbackChildren.add(extractPositionedNode(fc, fcElk, rootNode, ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
children.add(new PositionedNode(
|
||||
child.getId(),
|
||||
child.getLabel() != null ? child.getLabel() : "",
|
||||
"_CB_FALLBACK",
|
||||
getAbsoluteX(childElk, rootNode),
|
||||
getAbsoluteY(childElk, rootNode),
|
||||
childElk.getWidth(), childElk.getHeight(),
|
||||
fallbackChildren, null));
|
||||
ctx.compoundInfos.put(child.getId(),
|
||||
new CompoundInfo(child.getId(), colorForType(rn.getType())));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (RouteNode child : rn.getChildren()) {
|
||||
ElkNode childElk = ctx.elkNodeMap.get(child.getId());
|
||||
|
||||
@@ -65,8 +65,8 @@ export function CompoundNode({
|
||||
onNodeClick, onNodeDoubleClick, onNodeEnter, onNodeLeave,
|
||||
};
|
||||
|
||||
// _TRY_BODY: transparent wrapper — no header, no border, just layout
|
||||
if (node.type === '_TRY_BODY') {
|
||||
// _TRY_BODY / _CB_MAIN: transparent wrapper — no header, no border, just layout
|
||||
if (node.type === '_TRY_BODY' || node.type === '_CB_MAIN') {
|
||||
return (
|
||||
<g transform={`translate(${x}, ${y})`}>
|
||||
{renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
|
||||
@@ -75,6 +75,24 @@ export function CompoundNode({
|
||||
);
|
||||
}
|
||||
|
||||
// _CB_FALLBACK: section styling with EIP purple
|
||||
if (node.type === '_CB_FALLBACK') {
|
||||
const fallbackColor = '#7C3AED'; // EIP purple
|
||||
return (
|
||||
<g data-node-id={node.id} transform={`translate(${x}, ${y})`}>
|
||||
<rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
|
||||
fill={fallbackColor} fillOpacity={0.06} />
|
||||
<rect x={0} y={0} width={w} height={h} rx={CORNER_RADIUS}
|
||||
fill="none" stroke={fallbackColor} strokeWidth={1} strokeOpacity={0.4} strokeDasharray="4 2" />
|
||||
<text x={8} y={12} fill={fallbackColor} fontSize={10} fontWeight={600}>
|
||||
fallback
|
||||
</text>
|
||||
{renderInternalEdges(internalEdges, absX, absY, executionOverlay)}
|
||||
{renderChildren(node, absX, absY, childProps)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
// DO_CATCH / DO_FINALLY: section-like styling (tinted bg, thin border, label)
|
||||
if (node.type === 'DO_CATCH' || node.type === 'DO_FINALLY') {
|
||||
const sectionLabel = node.type === 'DO_CATCH'
|
||||
|
||||
@@ -64,6 +64,7 @@ const COMPOUND_TYPES = new Set([
|
||||
'EIP_LOOP', 'EIP_MULTICAST', 'EIP_AGGREGATE',
|
||||
'ON_EXCEPTION', 'ERROR_HANDLER',
|
||||
'ON_COMPLETION',
|
||||
'EIP_CIRCUIT_BREAKER', '_CB_MAIN', '_CB_FALLBACK',
|
||||
]);
|
||||
|
||||
const ERROR_COMPOUND_TYPES = new Set([
|
||||
|
||||
Reference in New Issue
Block a user