You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { DSLComponents } from '@/interfaces/database/flow';
  2. import { removeUselessFieldsFromValues } from '@/utils/form';
  3. import dagre from 'dagre';
  4. import { curry, isEmpty } from 'lodash';
  5. import pipe from 'lodash/fp/pipe';
  6. import { Edge, MarkerType, Node, Position } from 'reactflow';
  7. import { v4 as uuidv4 } from 'uuid';
  8. import { Operator, initialFormValuesMap } from './constant';
  9. import { NodeData } from './interface';
  10. const buildEdges = (
  11. operatorIds: string[],
  12. currentId: string,
  13. allEdges: Edge[],
  14. isUpstream = false,
  15. ) => {
  16. operatorIds.forEach((cur) => {
  17. const source = isUpstream ? cur : currentId;
  18. const target = isUpstream ? currentId : cur;
  19. if (!allEdges.some((e) => e.source === source && e.target === target)) {
  20. allEdges.push({
  21. id: uuidv4(),
  22. label: '',
  23. // type: 'step',
  24. source: source,
  25. target: target,
  26. markerEnd: {
  27. type: MarkerType.Arrow,
  28. },
  29. });
  30. }
  31. });
  32. };
  33. export const buildNodesAndEdgesFromDSLComponents = (data: DSLComponents) => {
  34. const nodes: Node[] = [];
  35. let edges: Edge[] = [];
  36. Object.entries(data).forEach(([key, value]) => {
  37. const downstream = [...value.downstream];
  38. const upstream = [...value.upstream];
  39. nodes.push({
  40. id: key,
  41. type: 'textUpdater',
  42. position: { x: 0, y: 0 },
  43. data: {
  44. label: value.obj.component_name,
  45. params: value.obj.params,
  46. downstream: downstream,
  47. upstream: upstream,
  48. },
  49. sourcePosition: Position.Left,
  50. targetPosition: Position.Right,
  51. });
  52. buildEdges(upstream, key, edges, true);
  53. buildEdges(downstream, key, edges, false);
  54. });
  55. return { nodes, edges };
  56. };
  57. const dagreGraph = new dagre.graphlib.Graph();
  58. dagreGraph.setDefaultEdgeLabel(() => ({}));
  59. const nodeWidth = 172;
  60. const nodeHeight = 36;
  61. export const getLayoutedElements = (
  62. nodes: Node[],
  63. edges: Edge[],
  64. direction = 'TB',
  65. ) => {
  66. const isHorizontal = direction === 'LR';
  67. dagreGraph.setGraph({ rankdir: direction });
  68. nodes.forEach((node) => {
  69. dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
  70. });
  71. edges.forEach((edge) => {
  72. dagreGraph.setEdge(edge.source, edge.target);
  73. });
  74. dagre.layout(dagreGraph);
  75. nodes.forEach((node) => {
  76. const nodeWithPosition = dagreGraph.node(node.id);
  77. node.targetPosition = isHorizontal ? Position.Left : Position.Top;
  78. node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
  79. // We are shifting the dagre node position (anchor=center center) to the top left
  80. // so it matches the React Flow node anchor point (top left).
  81. node.position = {
  82. x: nodeWithPosition.x - nodeWidth / 2,
  83. y: nodeWithPosition.y - nodeHeight / 2,
  84. };
  85. return node;
  86. });
  87. return { nodes, edges };
  88. };
  89. const buildComponentDownstreamOrUpstream = (
  90. edges: Edge[],
  91. nodeId: string,
  92. isBuildDownstream = true,
  93. ) => {
  94. return edges
  95. .filter((y) => y[isBuildDownstream ? 'source' : 'target'] === nodeId)
  96. .map((y) => y[isBuildDownstream ? 'target' : 'source']);
  97. };
  98. const removeUselessDataInTheOperator = curry(
  99. (operatorName: string, params: Record<string, unknown>) => {
  100. if (operatorName === Operator.Generate) {
  101. return removeUselessFieldsFromValues(params, '');
  102. }
  103. return params;
  104. },
  105. );
  106. // initialize data for operators without parameters
  107. const initializeOperatorParams = curry((operatorName: string, values: any) => {
  108. if (isEmpty(values)) {
  109. return initialFormValuesMap[operatorName as Operator];
  110. }
  111. return values;
  112. });
  113. const buildOperatorParams = (operatorName: string) =>
  114. pipe(
  115. removeUselessDataInTheOperator(operatorName),
  116. initializeOperatorParams(operatorName), // Final processing, for guarantee
  117. );
  118. // construct a dsl based on the node information of the graph
  119. export const buildDslComponentsByGraph = (
  120. nodes: Node<NodeData>[],
  121. edges: Edge[],
  122. ): DSLComponents => {
  123. const components: DSLComponents = {};
  124. nodes.forEach((x) => {
  125. const id = x.id;
  126. const operatorName = x.data.label;
  127. components[id] = {
  128. obj: {
  129. component_name: operatorName,
  130. // params:
  131. // removeUselessDataInTheOperator(
  132. // operatorName,
  133. // x.data.form as Record<string, unknown>,
  134. // ) ?? {},
  135. params:
  136. buildOperatorParams(operatorName)(
  137. x.data.form as Record<string, unknown>,
  138. ) ?? {},
  139. },
  140. downstream: buildComponentDownstreamOrUpstream(edges, id, true),
  141. upstream: buildComponentDownstreamOrUpstream(edges, id, false),
  142. };
  143. });
  144. return components;
  145. };