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 { const map = new Map(); 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): 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 }; }