All files flow.ts

100% Statements 27/27
100% Branches 20/20
100% Functions 12/12
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78                    28x 42x           29x           134x 191x   84x 84x 54x   84x             13x 13x 13x 2x   13x         2x         2x 1x   2x       2x             339x 403x   323x 323x 323x   323x 339x 339x    
import type { Connection, Edge, Node } from '@xyflow/react';
import type { Graph, GraphEdge } from './graph';
import type { NodeTypeInfo } from './types';
 
/** Data on every React Flow node: the workflow node type and its parameters. */
export type FlowNodeData = { nodetype: string; params: Record<string, unknown> };
export type FlowNode = Node<FlowNodeData, 'autoflow'>;
 
/** Saved graph to React Flow nodes and edges. */
export function toFlow(graph: Graph): { nodes: FlowNode[]; edges: Edge[] } {
  return {
    nodes: graph.nodes.map((n) => ({
      id: n.id,
      type: 'autoflow' as const,
      position: n.position ?? { x: 0, y: 0 },
      data: { nodetype: n.type, params: (n.data.params as Record<string, unknown>) ?? {} },
    })),
    edges: graph.edges.map((e) => ({ ...e })),
  };
}
 
/** React Flow nodes and edges back to the saved graph shape. */
export function fromFlow(nodes: FlowNode[], edges: Edge[]): Graph {
  return {
    nodes: nodes.map((n) => ({ id: n.id, type: n.data.nodetype, data: { params: n.data.params }, position: n.position })),
    edges: edges.map((e) => {
      const edge: GraphEdge = { id: e.id, source: e.source, target: e.target };
      if (e.sourceHandle) {
        edge.sourceHandle = e.sourceHandle;
      }
      return edge;
    }),
  };
}
 
/** A new node of a type, with an id no other node has, placed below the last one. */
export function newNode(type: string, existingIds: string[], count: number): FlowNode {
  const base = type.replace(/\./g, '_');
  let n = 1;
  while (existingIds.includes(`${base}_${n}`)) {
    n++;
  }
  return { id: `${base}_${n}`, type: 'autoflow', position: { x: 120, y: count * 110 + 40 }, data: { nodetype: type, params: {} } };
}
 
/** An edge for a connection the user drew, keeping the branch it leaves from. */
export function connectionToEdge(c: Connection): Edge {
  const edge: Edge = {
    id: c.sourceHandle ? `${c.source}-${c.sourceHandle}-${c.target}` : `${c.source}-${c.target}`,
    source: c.source,
    target: c.target,
  };
  if (c.sourceHandle) {
    edge.sourceHandle = c.sourceHandle;
  }
  return edge;
}
 
/** Longest summary shown on a node, in characters. */
const SUMMARY_LENGTH = 48;
 
/**
 * One line saying what a node will do: its filled settings in order, with option labels,
 * leaving out JSON settings. Empty when nothing is filled in yet.
 */
export function nodeSummary(info: NodeTypeInfo | undefined, params: Record<string, unknown>): string {
  const parts = (info?.params ?? [])
    .filter((p) => p.kind !== 'json')
    .map((p) => {
      const value = params[p.name];
      const text = typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '';
      return p.optionlabels[text] ?? text;
    })
    .filter((text) => text !== '');
  const summary = parts.join(' ');
  return summary.length > SUMMARY_LENGTH ? `${summary.slice(0, SUMMARY_LENGTH - 1)}…` : summary;
}