Files
cameleer-server/ui/src/utils/diagram-mapping.ts
hsiegeln 78e12f5cf9
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 57s
CI / docker (push) Successful in 52s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s
fix: separate onException/errorHandler into distinct RouteFlow segments
ON_EXCEPTION and ERROR_HANDLER nodes are now treated as compound containers
in the ELK diagram renderer, nesting their children. The frontend
diagram-mapping builds separate FlowSegments for each error handler,
displayed as distinct sections in the RouteFlow component.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:15:06 +01:00

120 lines
3.9 KiB
TypeScript

import type { RouteNode, FlowSegment } from '@cameleer/design-system';
import type { DiagramNode } from '../api/queries/diagrams';
// Map NodeType strings to RouteNode types
function mapNodeType(type: string): RouteNode['type'] {
const lower = type?.toLowerCase() || '';
if (lower.includes('from') || lower === 'endpoint') return 'from';
if (lower.includes('exception') || lower.includes('error_handler')
|| lower.includes('dead_letter')) return 'error-handler';
if (lower === 'to' || lower === 'to_dynamic' || lower === 'direct'
|| lower === 'seda') return 'to';
if (lower.includes('choice') || lower.includes('when')
|| lower.includes('otherwise')) return 'choice';
return 'process';
}
function mapStatus(status: string | undefined): RouteNode['status'] {
if (!status) return 'ok';
const s = status.toUpperCase();
if (s === 'FAILED') return 'fail';
if (s === 'RUNNING') return 'slow';
return 'ok';
}
type ProcessorNode = { processorId?: string; status?: string; durationMs?: number; children?: ProcessorNode[] };
function buildProcMap(processors: ProcessorNode[]): Map<string, ProcessorNode> {
const map = new Map<string, ProcessorNode>();
function walk(nodes: ProcessorNode[]) {
for (const n of nodes) {
if (n.processorId) map.set(n.processorId, n);
if (n.children) walk(n.children);
}
}
walk(processors || []);
return map;
}
function toRouteNode(node: DiagramNode, procMap: Map<string, ProcessorNode>): RouteNode {
const proc = procMap.get(node.id ?? '');
return {
name: node.label || node.id || '',
type: mapNodeType(node.type ?? ''),
durationMs: proc?.durationMs ?? 0,
status: mapStatus(proc?.status),
isBottleneck: false,
};
}
/**
* Builds FlowSegment[] from diagram nodes, properly separating error handler
* compounds (ON_EXCEPTION, ERROR_HANDLER) into distinct flow sections.
*
* Returns:
* - flows: FlowSegment[] with main route + error handler sections
* - nodeIds: flat array of diagram node IDs aligned across all flows
* (main flow IDs first, then error handler children IDs)
*/
export function buildFlowSegments(
diagramNodes: DiagramNode[],
processors: ProcessorNode[],
): { flows: FlowSegment[]; nodeIds: string[] } {
const procMap = buildProcMap(processors);
const mainNodes: RouteNode[] = [];
const mainIds: string[] = [];
const errorFlows: { label: string; nodes: RouteNode[]; ids: string[] }[] = [];
for (const node of diagramNodes) {
const type = mapNodeType(node.type ?? '');
if (type === 'error-handler' && node.children && node.children.length > 0) {
// Error handler compound → separate flow segment with its children
const children: RouteNode[] = [];
const childIds: string[] = [];
for (const child of node.children) {
children.push(toRouteNode(child, procMap));
childIds.push(child.id ?? '');
}
errorFlows.push({
label: node.label || 'Error Handler',
nodes: children,
ids: childIds,
});
} else {
// Regular node → main flow
mainNodes.push(toRouteNode(node, procMap));
mainIds.push(node.id ?? '');
}
}
const flows: FlowSegment[] = [{ label: 'Main Route', nodes: mainNodes }];
const nodeIds = [...mainIds];
for (const ef of errorFlows) {
flows.push({ label: ef.label, nodes: ef.nodes, variant: 'error' });
nodeIds.push(...ef.ids);
}
return { flows, nodeIds };
}
/**
* Legacy: splits a flat RouteNode[] into FlowSegment[] by type.
* Used as fallback when no diagram data is available.
*/
export function toFlowSegments(nodes: RouteNode[]): { flows: FlowSegment[] } {
const mainNodes = nodes.filter(n => n.type !== 'error-handler');
const errorNodes = nodes.filter(n => n.type === 'error-handler');
const flows: FlowSegment[] = [
{ label: 'Main Route', nodes: mainNodes },
];
if (errorNodes.length > 0) {
flows.push({ label: 'Error Handler', nodes: errorNodes, variant: 'error' });
}
return { flows };
}