fix: use graph root + edge walk to separate main flow from handlers
Root cause: graph.getNodes() is a flat list with duplicates — handler compound children appear both nested inside their parent AND as top-level entries. The previous separation tried to filter the flat list but missed the duplicates, leaving handler children in rootNode. New approach: walk from graph.getRoot() following non-ERROR edges to discover main flow nodes. Edges targeting handler compounds (ON_EXCEPTION, ON_COMPLETION) are not followed. This cleanly separates main flow from handler sections using the graph's own structure. Falls back to flat list filtering (old behavior) when graph.getRoot() is null (legacy/test graphs). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -206,24 +206,59 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
|||||||
Map<String, Color> nodeColors = new HashMap<>();
|
Map<String, Color> nodeColors = new HashMap<>();
|
||||||
Set<String> compoundNodeIds = new HashSet<>();
|
Set<String> compoundNodeIds = new HashSet<>();
|
||||||
|
|
||||||
// Separate handler sections from main flow nodes.
|
// Build a lookup from the flat nodes list (graph.getNodes() contains
|
||||||
// graph.getNodes() is a FLAT list that includes handler compound children
|
// duplicates — children appear both nested AND top-level). Use this for
|
||||||
// as top-level entries. We must identify and exclude them.
|
// resolving edge endpoints to RouteNode objects.
|
||||||
|
Map<String, RouteNode> nodeById = new HashMap<>();
|
||||||
|
if (graph.getNodes() != null) {
|
||||||
|
for (RouteNode rn : graph.getNodes()) {
|
||||||
|
nodeById.put(rn.getId(), rn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate main flow from handler sections using graph structure:
|
||||||
|
// - Start from graph.getRoot() and walk FLOW edges for main flow
|
||||||
|
// - Handler sections are top-level nodes of HANDLER_SECTION_TYPES
|
||||||
|
// This avoids the problem of the flat nodes list containing duplicates.
|
||||||
|
Set<String> mainNodeIds = new HashSet<>();
|
||||||
List<RouteNode> mainNodes = new ArrayList<>();
|
List<RouteNode> mainNodes = new ArrayList<>();
|
||||||
List<RouteNode> handlerNodes = new ArrayList<>();
|
List<RouteNode> handlerNodes = new ArrayList<>();
|
||||||
Set<String> handlerDescendantIds = new HashSet<>();
|
|
||||||
|
// Collect main flow node IDs by walking from root along non-ERROR edges.
|
||||||
|
// graph.getRoot() provides the entry point; ERROR edges lead to handler sections.
|
||||||
|
if (graph.getRoot() != null && graph.getEdges() != null) {
|
||||||
|
mainNodeIds.add(graph.getRoot().getId());
|
||||||
|
boolean changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (RouteEdge re : graph.getEdges()) {
|
||||||
|
if (re.getEdgeType() == RouteEdge.EdgeType.ERROR) continue;
|
||||||
|
if (mainNodeIds.contains(re.getSource()) && !mainNodeIds.contains(re.getTarget())) {
|
||||||
|
// Don't follow edges INTO handler compounds
|
||||||
|
RouteNode target = nodeById.get(re.getTarget());
|
||||||
|
if (target != null && target.getType() != null
|
||||||
|
&& HANDLER_SECTION_TYPES.contains(target.getType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mainNodeIds.add(re.getTarget());
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build node lists — deduplicate (flat list has duplicates)
|
||||||
|
Set<String> seen = new HashSet<>();
|
||||||
if (graph.getNodes() != null) {
|
if (graph.getNodes() != null) {
|
||||||
// First pass: identify handler compounds and collect their descendant IDs
|
|
||||||
for (RouteNode rn : graph.getNodes()) {
|
for (RouteNode rn : graph.getNodes()) {
|
||||||
|
if (seen.contains(rn.getId())) continue;
|
||||||
|
seen.add(rn.getId());
|
||||||
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
if (rn.getType() != null && HANDLER_SECTION_TYPES.contains(rn.getType())
|
||||||
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
&& rn.getChildren() != null && !rn.getChildren().isEmpty()) {
|
||||||
handlerNodes.add(rn);
|
handlerNodes.add(rn);
|
||||||
collectDescendantIds(rn.getChildren(), handlerDescendantIds);
|
} else if (mainNodeIds.isEmpty() || mainNodeIds.contains(rn.getId())) {
|
||||||
}
|
// When no root is available (legacy/test graphs), include all
|
||||||
}
|
// non-handler nodes as main flow (fallback to old behavior)
|
||||||
// Second pass: main flow = everything that isn't a handler or handler descendant
|
|
||||||
for (RouteNode rn : graph.getNodes()) {
|
|
||||||
if (!handlerNodes.contains(rn) && !handlerDescendantIds.contains(rn.getId())) {
|
|
||||||
mainNodes.add(rn);
|
mainNodes.add(rn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user