(workflow: {
graph: { nodes: BackendNode[]; edges?: BackendEdge[] };
})
| 155 | * Frontend needs: { id, type: 'workflow', position, data: { componentId, componentSlug, label, status, config } } |
| 156 | */ |
| 157 | export function deserializeNodes(workflow: { |
| 158 | graph: { nodes: BackendNode[]; edges?: BackendEdge[] }; |
| 159 | }): ReactFlowNode<NodeData>[] { |
| 160 | const nodes = workflow.graph.nodes; |
| 161 | const edges = workflow.graph.edges || []; |
| 162 | |
| 163 | const inputMappingsByNode = new Map<string, Record<string, { source: string; output: string }>>(); |
| 164 | |
| 165 | if (Array.isArray(edges)) { |
| 166 | for (const edge of edges) { |
| 167 | if (!edge.targetHandle) continue; |
| 168 | |
| 169 | const targetNodeId = edge.target; |
| 170 | const existing = inputMappingsByNode.get(targetNodeId) ?? {}; |
| 171 | |
| 172 | existing[edge.targetHandle] = { |
| 173 | source: edge.source, |
| 174 | output: edge.sourceHandle ?? '', |
| 175 | }; |
| 176 | |
| 177 | inputMappingsByNode.set(targetNodeId, existing); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | return nodes.map((node) => { |
| 182 | // Extract config from backend node data |
| 183 | const backendConfig = node.data.config || {}; |
| 184 | const { __ui, params, inputOverrides, ...restOfConfig } = backendConfig as { |
| 185 | __ui?: any; |
| 186 | params?: Record<string, unknown>; |
| 187 | inputOverrides?: Record<string, unknown>; |
| 188 | [key: string]: any; |
| 189 | }; |
| 190 | |
| 191 | // Ensure config has the required structure |
| 192 | const config: NodeConfig = { |
| 193 | params: params || {}, |
| 194 | inputOverrides: inputOverrides || {}, |
| 195 | ...restOfConfig, |
| 196 | } as NodeConfig; |
| 197 | |
| 198 | // Extract dynamic ports from backend node data (if present) |
| 199 | const backendNodeData = node.data as any; |
| 200 | const dynamicInputs = backendNodeData.dynamicInputs; |
| 201 | const dynamicOutputs = backendNodeData.dynamicOutputs; |
| 202 | |
| 203 | return { |
| 204 | id: node.id, |
| 205 | type: 'workflow', // All nodes use the same React Flow type |
| 206 | position: node.position, |
| 207 | data: { |
| 208 | // Backend's data.label and data.config (required) |
| 209 | label: node.data.label, |
| 210 | config, |
| 211 | // Frontend extensions |
| 212 | componentId: node.type, |
| 213 | componentSlug: node.type, |
| 214 | componentVersion: '1.0.0', // Default version if not specified |
no test coverage detected