balibabu commited on
Commit
8828184
·
1 Parent(s): 9c89a70

feat: Build the edges of Switch by form data #1739 (#2022)

Browse files

### What problem does this PR solve?

feat: Build the edges of Switch by form data #1739

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/pages/flow/canvas/node/hooks.ts CHANGED
@@ -4,7 +4,11 @@ import { useEffect, useMemo, useState } from 'react';
4
  import { useUpdateNodeInternals } from 'reactflow';
5
  import { Operator } from '../../constant';
6
  import { IPosition, NodeData } from '../../interface';
7
- import { buildNewPositionMap, isKeysEqual } from '../../utils';
 
 
 
 
8
 
9
  export const useBuildCategorizeHandlePositions = ({
10
  data,
@@ -32,7 +36,7 @@ export const useBuildCategorizeHandlePositions = ({
32
  const position = positionMap[x];
33
  let text = x;
34
  if (operatorName === Operator.Switch) {
35
- text = `Item ${idx + 1}`;
36
  }
37
  return { text, ...position };
38
  })
 
4
  import { useUpdateNodeInternals } from 'reactflow';
5
  import { Operator } from '../../constant';
6
  import { IPosition, NodeData } from '../../interface';
7
+ import {
8
+ buildNewPositionMap,
9
+ generateSwitchHandleText,
10
+ isKeysEqual,
11
+ } from '../../utils';
12
 
13
  export const useBuildCategorizeHandlePositions = ({
14
  data,
 
36
  const position = positionMap[x];
37
  let text = x;
38
  if (operatorName === Operator.Switch) {
39
+ text = generateSwitchHandleText(idx);
40
  }
41
  return { text, ...position };
42
  })
web/src/pages/flow/categorize-form/dynamic-categorize.tsx CHANGED
@@ -28,14 +28,15 @@ interface INameInputProps {
28
 
29
  const getOtherFieldValues = (
30
  form: FormInstance,
 
31
  field: FormListFieldData,
32
  latestField: string,
33
  ) =>
34
- (form.getFieldValue(['items']) ?? [])
35
  .map((x: any) => x[latestField])
36
  .filter(
37
  (x: string) =>
38
- x !== form.getFieldValue(['items', field.name, latestField]),
39
  );
40
 
41
  const NameInput = ({
@@ -132,7 +133,12 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
132
  ]}
133
  >
134
  <NameInput
135
- otherNames={getOtherFieldValues(form, field, 'name')}
 
 
 
 
 
136
  validate={(errors: string[]) =>
137
  form.setFields([
138
  {
@@ -159,7 +165,7 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
159
  <Select
160
  allowClear
161
  options={buildCategorizeToOptions(
162
- getOtherFieldValues(form, field, 'to'),
163
  )}
164
  />
165
  </Form.Item>
@@ -173,14 +179,6 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
173
  );
174
  }}
175
  </Form.List>
176
-
177
- {/* <Form.Item noStyle shouldUpdate>
178
- {() => (
179
- <Typography>
180
- <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
181
- </Typography>
182
- )}
183
- </Form.Item> */}
184
  </>
185
  );
186
  };
 
28
 
29
  const getOtherFieldValues = (
30
  form: FormInstance,
31
+ formListName: string = 'items',
32
  field: FormListFieldData,
33
  latestField: string,
34
  ) =>
35
+ (form.getFieldValue([formListName]) ?? [])
36
  .map((x: any) => x[latestField])
37
  .filter(
38
  (x: string) =>
39
+ x !== form.getFieldValue([formListName, field.name, latestField]),
40
  );
41
 
42
  const NameInput = ({
 
133
  ]}
134
  >
135
  <NameInput
136
+ otherNames={getOtherFieldValues(
137
+ form,
138
+ 'items',
139
+ field,
140
+ 'name',
141
+ )}
142
  validate={(errors: string[]) =>
143
  form.setFields([
144
  {
 
165
  <Select
166
  allowClear
167
  options={buildCategorizeToOptions(
168
+ getOtherFieldValues(form, 'items', field, 'to'),
169
  )}
170
  />
171
  </Form.Item>
 
179
  );
180
  }}
181
  </Form.List>
 
 
 
 
 
 
 
 
182
  </>
183
  );
184
  };
web/src/pages/flow/hooks.ts CHANGED
@@ -53,10 +53,11 @@ import {
53
  initialSwitchValues,
54
  initialWikipediaValues,
55
  } from './constant';
56
- import { ICategorizeForm, IRelevantForm } from './interface';
57
  import useGraphStore, { RFState } from './store';
58
  import {
59
  buildDslComponentsByGraph,
 
60
  receiveMessageError,
61
  replaceIdWithText,
62
  } from './utils';
@@ -498,6 +499,31 @@ export const useWatchNodeFormDataChange = () => {
498
  [setEdgesByNodeId],
499
  );
500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  useEffect(() => {
502
  nodes.forEach((node) => {
503
  const currentNode = getNode(node.id);
@@ -510,6 +536,9 @@ export const useWatchNodeFormDataChange = () => {
510
  case Operator.Categorize:
511
  buildCategorizeEdgesByFormData(node.id, form as ICategorizeForm);
512
  break;
 
 
 
513
  default:
514
  break;
515
  }
@@ -519,5 +548,6 @@ export const useWatchNodeFormDataChange = () => {
519
  buildCategorizeEdgesByFormData,
520
  getNode,
521
  buildRelevantEdgesByFormData,
 
522
  ]);
523
  };
 
53
  initialSwitchValues,
54
  initialWikipediaValues,
55
  } from './constant';
56
+ import { ICategorizeForm, IRelevantForm, ISwitchForm } from './interface';
57
  import useGraphStore, { RFState } from './store';
58
  import {
59
  buildDslComponentsByGraph,
60
+ generateSwitchHandleText,
61
  receiveMessageError,
62
  replaceIdWithText,
63
  } from './utils';
 
499
  [setEdgesByNodeId],
500
  );
501
 
502
+ const buildSwitchEdgesByFormData = useCallback(
503
+ (nodeId: string, form: ISwitchForm) => {
504
+ // add
505
+ // delete
506
+ // edit
507
+ const conditions = form.conditions;
508
+ const downstreamEdges = conditions.reduce<Edge[]>((pre, _, idx) => {
509
+ const target = conditions[idx]?.to;
510
+ if (target) {
511
+ pre.push({
512
+ id: uuid(),
513
+ source: nodeId,
514
+ target,
515
+ sourceHandle: generateSwitchHandleText(idx),
516
+ });
517
+ }
518
+
519
+ return pre;
520
+ }, []);
521
+
522
+ setEdgesByNodeId(nodeId, downstreamEdges);
523
+ },
524
+ [setEdgesByNodeId],
525
+ );
526
+
527
  useEffect(() => {
528
  nodes.forEach((node) => {
529
  const currentNode = getNode(node.id);
 
536
  case Operator.Categorize:
537
  buildCategorizeEdgesByFormData(node.id, form as ICategorizeForm);
538
  break;
539
+ case Operator.Switch:
540
+ buildSwitchEdgesByFormData(node.id, form as ISwitchForm);
541
+ break;
542
  default:
543
  break;
544
  }
 
548
  buildCategorizeEdgesByFormData,
549
  getNode,
550
  buildRelevantEdgesByFormData,
551
+ buildSwitchEdgesByFormData,
552
  ]);
553
  };
web/src/pages/flow/store.ts CHANGED
@@ -23,7 +23,7 @@ import { devtools } from 'zustand/middleware';
23
  import { immer } from 'zustand/middleware/immer';
24
  import { Operator } from './constant';
25
  import { NodeData } from './interface';
26
- import { isEdgeEqual } from './utils';
27
 
28
  export type RFState = {
29
  nodes: Node<NodeData>[];
@@ -184,14 +184,19 @@ const useGraphStore = create<RFState>()(
184
  'to',
185
  ]);
186
  break;
187
- // case Operator.Switch:
188
- // if (sourceHandle)
189
- // updateNodeForm(source, target, [
190
- // 'conditions',
191
- // sourceHandle,
192
- // 'to',
193
- // ]);
194
- // break;
 
 
 
 
 
195
  default:
196
  break;
197
  }
@@ -201,7 +206,11 @@ const useGraphStore = create<RFState>()(
201
  // Delete the edge on the classification node or relevant node anchor when the anchor is connected to other nodes
202
  const { edges, getOperatorTypeFromId, deleteEdgeById } = get();
203
  // the node containing the anchor
204
- const anchoredNodes = [Operator.Categorize, Operator.Relevant];
 
 
 
 
205
  if (
206
  anchoredNodes.some(
207
  (x) => x === getOperatorTypeFromId(connection.source),
@@ -265,6 +274,19 @@ const useGraphStore = create<RFState>()(
265
  'to',
266
  ]);
267
  break;
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  default:
269
  break;
270
  }
 
23
  import { immer } from 'zustand/middleware/immer';
24
  import { Operator } from './constant';
25
  import { NodeData } from './interface';
26
+ import { getOperatorIndex, isEdgeEqual } from './utils';
27
 
28
  export type RFState = {
29
  nodes: Node<NodeData>[];
 
184
  'to',
185
  ]);
186
  break;
187
+ case Operator.Switch: {
188
+ if (sourceHandle) {
189
+ const operatorIndex = getOperatorIndex(sourceHandle);
190
+ if (operatorIndex) {
191
+ updateNodeForm(source, target, [
192
+ 'conditions',
193
+ operatorIndex,
194
+ 'to',
195
+ ]);
196
+ }
197
+ }
198
+ break;
199
+ }
200
  default:
201
  break;
202
  }
 
206
  // Delete the edge on the classification node or relevant node anchor when the anchor is connected to other nodes
207
  const { edges, getOperatorTypeFromId, deleteEdgeById } = get();
208
  // the node containing the anchor
209
+ const anchoredNodes = [
210
+ Operator.Categorize,
211
+ Operator.Relevant,
212
+ Operator.Switch,
213
+ ];
214
  if (
215
  anchoredNodes.some(
216
  (x) => x === getOperatorTypeFromId(connection.source),
 
274
  'to',
275
  ]);
276
  break;
277
+ case Operator.Switch: {
278
+ if (sourceHandle) {
279
+ const operatorIndex = getOperatorIndex(sourceHandle);
280
+ if (operatorIndex) {
281
+ updateNodeForm(source, undefined, [
282
+ 'conditions',
283
+ operatorIndex,
284
+ 'to',
285
+ ]);
286
+ }
287
+ }
288
+ break;
289
+ }
290
  default:
291
  break;
292
  }
web/src/pages/flow/switch-form/index.tsx CHANGED
@@ -1,10 +1,10 @@
1
  import { CloseOutlined } from '@ant-design/icons';
2
  import { Button, Card, Form, Input, Select, Typography } from 'antd';
3
- import React from 'react';
4
  import { useTranslation } from 'react-i18next';
5
  import { Operator } from '../constant';
6
  import { useBuildFormSelectOptions } from '../form-hooks';
7
- import { IOperatorForm } from '../interface';
 
8
 
9
  const subLabelCol = {
10
  span: 7,
@@ -14,17 +14,20 @@ const subWrapperCol = {
14
  span: 17,
15
  };
16
 
17
- const SwitchForm: React.FC = ({
18
- form,
19
- onValuesChange,
20
- nodeId,
21
- }: IOperatorForm) => {
22
  const { t } = useTranslation();
23
  const buildCategorizeToOptions = useBuildFormSelectOptions(
24
- Operator.Categorize,
25
- nodeId,
26
  );
27
 
 
 
 
 
 
 
 
28
  return (
29
  <Form
30
  labelCol={{ span: 8 }}
@@ -36,7 +39,10 @@ const SwitchForm: React.FC = ({
36
  onValuesChange={onValuesChange}
37
  >
38
  <Form.Item label={t('flow.to')} name={['end_cpn_id']}>
39
- <Select options={buildCategorizeToOptions([])} />
 
 
 
40
  </Form.Item>
41
  <Form.Item label={t('flow.no')} name={['no']}>
42
  <Input />
@@ -65,7 +71,13 @@ const SwitchForm: React.FC = ({
65
  </Form.Item>
66
 
67
  <Form.Item label={t('flow.to')} name={[field.name, 'to']}>
68
- <Select options={buildCategorizeToOptions([])} />
 
 
 
 
 
 
69
  </Form.Item>
70
  <Form.Item label="Items">
71
  <Form.List name={[field.name, 'items']}>
 
1
  import { CloseOutlined } from '@ant-design/icons';
2
  import { Button, Card, Form, Input, Select, Typography } from 'antd';
 
3
  import { useTranslation } from 'react-i18next';
4
  import { Operator } from '../constant';
5
  import { useBuildFormSelectOptions } from '../form-hooks';
6
+ import { IOperatorForm, ISwitchForm } from '../interface';
7
+ import { getOtherFieldValues } from '../utils';
8
 
9
  const subLabelCol = {
10
  span: 7,
 
14
  span: 17,
15
  };
16
 
17
+ const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
 
 
 
 
18
  const { t } = useTranslation();
19
  const buildCategorizeToOptions = useBuildFormSelectOptions(
20
+ Operator.Switch,
21
+ node?.id,
22
  );
23
 
24
+ const getSelectedConditionTos = () => {
25
+ const conditions: ISwitchForm['conditions'] =
26
+ form?.getFieldValue('conditions');
27
+
28
+ return conditions?.filter((x) => !!x).map((x) => x?.to) ?? [];
29
+ };
30
+
31
  return (
32
  <Form
33
  labelCol={{ span: 8 }}
 
39
  onValuesChange={onValuesChange}
40
  >
41
  <Form.Item label={t('flow.to')} name={['end_cpn_id']}>
42
+ <Select
43
+ allowClear
44
+ options={buildCategorizeToOptions(getSelectedConditionTos())}
45
+ />
46
  </Form.Item>
47
  <Form.Item label={t('flow.no')} name={['no']}>
48
  <Input />
 
71
  </Form.Item>
72
 
73
  <Form.Item label={t('flow.to')} name={[field.name, 'to']}>
74
+ <Select
75
+ allowClear
76
+ options={buildCategorizeToOptions([
77
+ form?.getFieldValue('end_cpn_id'),
78
+ ...getOtherFieldValues(form!, 'conditions', field, 'to'),
79
+ ])}
80
+ />
81
  </Form.Item>
82
  <Form.Item label="Items">
83
  <Form.List name={[field.name, 'items']}>
web/src/pages/flow/utils.ts CHANGED
@@ -1,7 +1,8 @@
1
  import { DSLComponents } from '@/interfaces/database/flow';
2
  import { removeUselessFieldsFromValues } from '@/utils/form';
 
3
  import { humanId } from 'human-id';
4
- import { curry, intersectionWith, isEqual, sample } from 'lodash';
5
  import pipe from 'lodash/fp/pipe';
6
  import isObject from 'lodash/isObject';
7
  import { Edge, Node, Position } from 'reactflow';
@@ -209,3 +210,27 @@ export const buildNewPositionMap = (
209
  export const isKeysEqual = (currentKeys: string[], previousKeys: string[]) => {
210
  return isEqual(currentKeys.sort(), previousKeys.sort());
211
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { DSLComponents } from '@/interfaces/database/flow';
2
  import { removeUselessFieldsFromValues } from '@/utils/form';
3
+ import { FormInstance, FormListFieldData } from 'antd';
4
  import { humanId } from 'human-id';
5
+ import { curry, get, intersectionWith, isEqual, sample } from 'lodash';
6
  import pipe from 'lodash/fp/pipe';
7
  import isObject from 'lodash/isObject';
8
  import { Edge, Node, Position } from 'reactflow';
 
210
  export const isKeysEqual = (currentKeys: string[], previousKeys: string[]) => {
211
  return isEqual(currentKeys.sort(), previousKeys.sort());
212
  };
213
+
214
+ export const getOperatorIndex = (handleTitle: string) => {
215
+ return handleTitle.split(' ').at(-1);
216
+ };
217
+
218
+ // Get the value of other forms except itself
219
+ export const getOtherFieldValues = (
220
+ form: FormInstance,
221
+ formListName: string = 'items',
222
+ field: FormListFieldData,
223
+ latestField: string,
224
+ ) =>
225
+ (form.getFieldValue([formListName]) ?? [])
226
+ .map((x: any) => {
227
+ return get(x, latestField);
228
+ })
229
+ .filter(
230
+ (x: string) =>
231
+ x !== form.getFieldValue([formListName, field.name, latestField]),
232
+ );
233
+
234
+ export const generateSwitchHandleText = (idx: number) => {
235
+ return `Item ${idx + 1}`;
236
+ };