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>
120 lines
3.9 KiB
TypeScript
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 };
|
|
}
|