Spaces:
Sleeping
Sleeping
| const LAYER_ORDER = ['frontend', 'api', 'backend', 'middleware', 'auth', 'database', 'utility']; | |
| export function toArray(value) { | |
| if (Array.isArray(value)) return value; | |
| if (value === null || value === undefined || value === '') return []; | |
| return [value]; | |
| } | |
| export function normalizeSteps(value) { | |
| if (Array.isArray(value)) return value.map((step) => String(step)).filter(Boolean); | |
| if (typeof value === 'string') { | |
| const parts = value.split(/\s*(?:->|→|=>)\s*/).map((step) => step.trim()).filter(Boolean); | |
| return parts.length > 1 ? parts : [value]; | |
| } | |
| return toArray(value).map((step) => String(step)).filter(Boolean); | |
| } | |
| export function normalizeWorkflows(workflows = []) { | |
| return toArray(workflows) | |
| .map((workflow, index) => { | |
| if (workflow && typeof workflow === 'object') { | |
| return { | |
| ...workflow, | |
| name: workflow.name || workflow.title || `Workflow ${index + 1}`, | |
| steps: normalizeSteps(workflow.steps || workflow.flow || workflow.sequence || workflow.description), | |
| services_involved: toArray(workflow.services_involved || workflow.services || workflow.components), | |
| }; | |
| } | |
| return { | |
| name: `Workflow ${index + 1}`, | |
| steps: normalizeSteps(workflow), | |
| services_involved: [], | |
| }; | |
| }) | |
| .filter((workflow) => workflow.steps.length > 0); | |
| } | |
| export function transformGraphData(rawGraph) { | |
| if (!rawGraph) return { nodes: [], edges: [], metadata: {}, workflows: [] }; | |
| const { nodes = [], edges = [], metadata = {}, workflows = [] } = rawGraph; | |
| const layers = {}; | |
| nodes.forEach((node) => { | |
| const type = node.data?.type || 'utility'; | |
| const layer = LAYER_ORDER.indexOf(type); | |
| const key = layer >= 0 ? layer : LAYER_ORDER.length; | |
| layers[key] = layers[key] || []; | |
| layers[key].push(node); | |
| }); | |
| const transformedNodes = []; | |
| Object.keys(layers) | |
| .sort((a, b) => Number(a) - Number(b)) | |
| .forEach((layerKey, layerIndex) => { | |
| const layerNodes = layers[layerKey]; | |
| const offset = ((layerNodes.length - 1) * 150) / 2; | |
| layerNodes.forEach((node, nodeIndex) => { | |
| transformedNodes.push({ | |
| ...node, | |
| data: { | |
| ...node.data, | |
| files: toArray(node.data?.files), | |
| }, | |
| position: { | |
| x: layerIndex * 310 + 60, | |
| y: nodeIndex * 170 + 260 - offset, | |
| }, | |
| }); | |
| }); | |
| }); | |
| return { nodes: transformedNodes, edges, metadata, workflows: normalizeWorkflows(workflows) }; | |
| } | |
| export function highlightImpactedNodes(nodes, impactedServiceNames = []) { | |
| return nodes.map((node) => ({ | |
| ...node, | |
| data: { | |
| ...node.data, | |
| highlighted: impactedServiceNames.some((name) => | |
| node.data?.label?.toLowerCase().includes(String(name).toLowerCase()), | |
| ), | |
| }, | |
| })); | |
| } | |
| export function workflowsToReactFlow(workflows = []) { | |
| const nodes = []; | |
| const edges = []; | |
| normalizeWorkflows(workflows).forEach((workflow, workflowIndex) => { | |
| const steps = workflow.steps; | |
| steps.forEach((step, stepIndex) => { | |
| const id = `wf_${workflowIndex}_${stepIndex}`; | |
| nodes.push({ | |
| id, | |
| type: 'workflowNode', | |
| position: { x: stepIndex * 220 + 60, y: workflowIndex * 190 + 90 }, | |
| data: { | |
| label: step, | |
| nodeType: stepIndex === 0 ? 'start' : stepIndex === steps.length - 1 ? 'process' : 'process', | |
| color: stepIndex === 0 ? '#10B981' : '#3B82F6', | |
| description: workflow.name, | |
| }, | |
| }); | |
| if (stepIndex > 0) { | |
| edges.push({ | |
| id: `wf_e_${workflowIndex}_${stepIndex}`, | |
| source: `wf_${workflowIndex}_${stepIndex - 1}`, | |
| target: id, | |
| type: 'workflowEdge', | |
| animated: true, | |
| }); | |
| } | |
| }); | |
| }); | |
| return { nodes, edges }; | |
| } | |