@nocobase/plugin-workflow 0.10.1-alpha.1 → 0.11.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/client.d.ts +2 -3
  2. package/client.js +1 -30
  3. package/lib/client/AddButton.js +13 -11
  4. package/lib/client/Branch.js +10 -8
  5. package/lib/client/CanvasContent.js +12 -10
  6. package/lib/client/ExecutionCanvas.js +37 -33
  7. package/lib/client/ExecutionPage.js +4 -9
  8. package/lib/client/WorkflowCanvas.js +18 -15
  9. package/lib/client/WorkflowPage.js +4 -9
  10. package/lib/client/WorkflowProvider.js +1 -40
  11. package/lib/client/components/CollectionBlockInitializer.js +3 -3
  12. package/lib/client/components/CollectionFieldset.d.ts +1 -1
  13. package/lib/client/components/CollectionFieldset.js +15 -16
  14. package/lib/client/components/Duration.js +5 -5
  15. package/lib/client/components/DynamicExpression.d.ts +3 -3
  16. package/lib/client/components/FieldsSelect.d.ts +1 -1
  17. package/lib/client/components/FieldsSelect.js +10 -7
  18. package/lib/client/components/NodeDescription.js +45 -31
  19. package/lib/client/components/RadioWithTooltip.js +13 -20
  20. package/lib/client/components/ValueBlock.js +14 -21
  21. package/lib/client/components/renderEngineReference.js +1 -8
  22. package/lib/client/index.d.ts +12 -4
  23. package/lib/client/index.js +78 -15
  24. package/lib/client/locale/zh-CN.d.ts +5 -1
  25. package/lib/client/locale/zh-CN.js +6 -2
  26. package/lib/client/nodes/aggregate.d.ts +8 -3
  27. package/lib/client/nodes/aggregate.js +5 -4
  28. package/lib/client/nodes/calculation.d.ts +6 -4
  29. package/lib/client/nodes/calculation.js +22 -28
  30. package/lib/client/nodes/condition.d.ts +2 -10
  31. package/lib/client/nodes/condition.js +19 -37
  32. package/lib/client/nodes/create.d.ts +5 -6
  33. package/lib/client/nodes/create.js +1 -3
  34. package/lib/client/nodes/destroy.d.ts +1 -1
  35. package/lib/client/nodes/index.d.ts +2 -3
  36. package/lib/client/nodes/index.js +95 -102
  37. package/lib/client/nodes/loop.d.ts +1 -1
  38. package/lib/client/nodes/loop.js +46 -54
  39. package/lib/client/nodes/manual/FormBlockInitializer.js +6 -5
  40. package/lib/client/nodes/manual/ModeConfig.js +23 -30
  41. package/lib/client/nodes/manual/SchemaConfig.d.ts +4 -5
  42. package/lib/client/nodes/manual/SchemaConfig.js +180 -25
  43. package/lib/client/nodes/manual/WorkflowTodo.js +95 -110
  44. package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.d.ts +2 -5
  45. package/lib/client/nodes/manual/WorkflowTodoBlockInitializer.js +6 -5
  46. package/lib/client/nodes/manual/forms/create.js +8 -1
  47. package/lib/client/nodes/manual/forms/custom.js +22 -22
  48. package/lib/client/nodes/manual/forms/update.js +8 -1
  49. package/lib/client/nodes/manual/index.d.ts +6 -1
  50. package/lib/client/nodes/manual/index.js +5 -4
  51. package/lib/client/nodes/parallel.js +23 -20
  52. package/lib/client/nodes/query.d.ts +3 -5
  53. package/lib/client/nodes/query.js +1 -3
  54. package/lib/client/nodes/request.d.ts +2 -2
  55. package/lib/client/nodes/request.js +7 -7
  56. package/lib/client/nodes/sql.d.ts +26 -0
  57. package/lib/client/{triggers/schedule/DateFieldsSelect.js → nodes/sql.js} +37 -46
  58. package/lib/client/nodes/update.d.ts +2 -2
  59. package/lib/client/nodes/update.js +1 -1
  60. package/lib/client/schemas/collection.d.ts +3 -4
  61. package/lib/client/schemas/collection.js +11 -17
  62. package/lib/client/style.d.ts +18 -13
  63. package/lib/client/style.js +315 -292
  64. package/lib/client/triggers/collection.d.ts +13 -13
  65. package/lib/client/triggers/collection.js +5 -1
  66. package/lib/client/triggers/index.d.ts +3 -4
  67. package/lib/client/triggers/index.js +51 -53
  68. package/lib/client/triggers/schedule/EndsByField.js +11 -11
  69. package/lib/client/triggers/schedule/OnField.js +45 -33
  70. package/lib/client/triggers/schedule/RepeatField.js +4 -4
  71. package/lib/client/triggers/schedule/ScheduleConfig.js +24 -31
  72. package/lib/client/triggers/schedule/index.d.ts +1 -1
  73. package/lib/client/triggers/schedule/index.js +32 -20
  74. package/lib/client/variable.d.ts +31 -13
  75. package/lib/client/variable.js +44 -29
  76. package/lib/server/Plugin.d.ts +3 -6
  77. package/lib/server/Plugin.js +15 -12
  78. package/lib/server/Processor.d.ts +3 -5
  79. package/lib/server/Processor.js +2 -2
  80. package/lib/server/actions/nodes.js +7 -7
  81. package/lib/server/fields/expression-field.d.ts +1 -2
  82. package/lib/server/fields/expression-field.js +1 -8
  83. package/lib/server/functions/index.d.ts +2 -3
  84. package/lib/server/index.d.ts +1 -0
  85. package/lib/server/index.js +12 -0
  86. package/lib/server/instructions/aggregate.d.ts +1 -1
  87. package/lib/server/instructions/aggregate.js +5 -5
  88. package/lib/server/instructions/condition.d.ts +2 -1
  89. package/lib/server/instructions/create.d.ts +2 -2
  90. package/lib/server/instructions/create.js +13 -13
  91. package/lib/server/instructions/delay.d.ts +3 -3
  92. package/lib/server/instructions/delay.js +66 -64
  93. package/lib/server/instructions/destroy.d.ts +1 -1
  94. package/lib/server/instructions/index.d.ts +5 -5
  95. package/lib/server/instructions/index.js +1 -1
  96. package/lib/server/instructions/loop.d.ts +1 -2
  97. package/lib/server/instructions/manual/actions.js +19 -7
  98. package/lib/server/instructions/manual/forms/create.js +7 -1
  99. package/lib/server/instructions/manual/forms/index.d.ts +1 -1
  100. package/lib/server/instructions/manual/forms/update.js +7 -1
  101. package/lib/server/instructions/manual/index.d.ts +1 -1
  102. package/lib/server/instructions/parallel.d.ts +1 -2
  103. package/lib/server/instructions/query.d.ts +1 -1
  104. package/lib/server/instructions/query.js +8 -1
  105. package/lib/server/instructions/request.d.ts +3 -3
  106. package/lib/server/instructions/request.js +5 -2
  107. package/lib/server/instructions/sql.d.ts +12 -0
  108. package/lib/server/instructions/sql.js +34 -0
  109. package/lib/server/instructions/update.d.ts +1 -1
  110. package/lib/server/migrations/20230221071831-calculation-expression.js +1 -1
  111. package/lib/server/migrations/20230221121203-condition-calculation.js +1 -1
  112. package/lib/server/migrations/20230221162902-jsonb-to-json.js +7 -7
  113. package/lib/server/migrations/20230411034722-manual-multi-form.js +1 -8
  114. package/lib/server/migrations/20230710115902-manual-action-values.d.ts +4 -0
  115. package/lib/server/migrations/20230710115902-manual-action-values.js +97 -0
  116. package/lib/server/triggers/collection.d.ts +1 -1
  117. package/lib/server/triggers/collection.js +15 -13
  118. package/lib/server/triggers/index.d.ts +1 -1
  119. package/lib/server/triggers/schedule.d.ts +1 -1
  120. package/lib/server/triggers/schedule.js +18 -18
  121. package/lib/server/{models → types}/Execution.d.ts +2 -3
  122. package/lib/server/{models → types}/FlowNode.d.ts +1 -2
  123. package/lib/server/{models → types}/Job.d.ts +1 -2
  124. package/lib/server/{models → types}/Workflow.d.ts +1 -2
  125. package/lib/server/types/index.d.ts +4 -0
  126. package/lib/server/types/index.js +5 -0
  127. package/lib/server/utils.d.ts +2 -0
  128. package/lib/server/utils.js +21 -0
  129. package/package.json +39 -18
  130. package/server.d.ts +2 -3
  131. package/server.js +1 -30
  132. package/src/client/AddButton.tsx +111 -0
  133. package/src/client/Branch.tsx +37 -0
  134. package/src/client/CanvasContent.tsx +25 -0
  135. package/src/client/ExecutionCanvas.tsx +166 -0
  136. package/src/client/ExecutionLink.tsx +16 -0
  137. package/src/client/ExecutionPage.tsx +45 -0
  138. package/src/client/ExecutionResourceProvider.tsx +21 -0
  139. package/src/client/FlowContext.ts +7 -0
  140. package/src/client/WorkflowCanvas.tsx +221 -0
  141. package/src/client/WorkflowLink.tsx +16 -0
  142. package/src/client/WorkflowPage.tsx +52 -0
  143. package/src/client/WorkflowProvider.tsx +84 -0
  144. package/src/client/components/CollectionBlockInitializer.tsx +71 -0
  145. package/src/client/components/CollectionFieldset.tsx +160 -0
  146. package/src/client/components/Duration.tsx +45 -0
  147. package/src/client/components/DynamicExpression.tsx +53 -0
  148. package/src/client/components/FieldsSelect.tsx +32 -0
  149. package/src/client/components/FilterDynamicComponent.tsx +15 -0
  150. package/src/client/components/NodeDescription.tsx +51 -0
  151. package/src/client/components/NullRender.tsx +3 -0
  152. package/src/client/components/OpenDrawer.tsx +24 -0
  153. package/src/client/components/RadioWithTooltip.tsx +38 -0
  154. package/src/client/components/ValueBlock.tsx +67 -0
  155. package/src/client/components/renderEngineReference.tsx +30 -0
  156. package/src/client/constants.tsx +91 -0
  157. package/src/client/index.tsx +51 -0
  158. package/src/client/interfaces/expression.tsx +25 -0
  159. package/src/client/locale/en-US.ts +136 -0
  160. package/src/client/locale/es-ES.ts +129 -0
  161. package/src/client/locale/index.ts +18 -0
  162. package/src/client/locale/ja-JP.ts +90 -0
  163. package/src/client/locale/pt-BR.ts +136 -0
  164. package/src/client/locale/ru-RU.ts +90 -0
  165. package/src/client/locale/tr-TR.ts +90 -0
  166. package/src/client/locale/zh-CN.ts +248 -0
  167. package/src/client/nodes/aggregate.tsx +327 -0
  168. package/src/client/nodes/calculation.tsx +216 -0
  169. package/src/client/nodes/condition.tsx +463 -0
  170. package/src/client/nodes/create.tsx +85 -0
  171. package/src/client/nodes/delay.tsx +37 -0
  172. package/src/client/nodes/destroy.tsx +34 -0
  173. package/src/client/nodes/index.tsx +485 -0
  174. package/src/client/nodes/loop.tsx +144 -0
  175. package/src/client/nodes/manual/AssigneesSelect.tsx +33 -0
  176. package/src/client/nodes/manual/DetailsBlockProvider.tsx +80 -0
  177. package/src/client/nodes/manual/FormBlockInitializer.tsx +69 -0
  178. package/src/client/nodes/manual/FormBlockProvider.tsx +75 -0
  179. package/src/client/nodes/manual/ModeConfig.tsx +84 -0
  180. package/src/client/nodes/manual/SchemaConfig.tsx +509 -0
  181. package/src/client/nodes/manual/WorkflowTodo.tsx +607 -0
  182. package/src/client/nodes/manual/WorkflowTodoBlockInitializer.tsx +28 -0
  183. package/src/client/nodes/manual/forms/create.tsx +92 -0
  184. package/src/client/nodes/manual/forms/custom.tsx +392 -0
  185. package/src/client/nodes/manual/forms/update.tsx +134 -0
  186. package/src/client/nodes/manual/index.tsx +162 -0
  187. package/src/client/nodes/manual/utils.ts +28 -0
  188. package/src/client/nodes/parallel.tsx +138 -0
  189. package/src/client/nodes/query.tsx +88 -0
  190. package/src/client/nodes/request.tsx +185 -0
  191. package/src/client/nodes/sql.tsx +37 -0
  192. package/src/client/nodes/update.tsx +99 -0
  193. package/src/client/schemas/collection.ts +75 -0
  194. package/src/client/schemas/executions.tsx +169 -0
  195. package/src/client/schemas/workflows.ts +364 -0
  196. package/src/client/style.tsx +347 -0
  197. package/src/client/triggers/collection.tsx +190 -0
  198. package/src/client/triggers/index.tsx +311 -0
  199. package/src/client/triggers/schedule/EndsByField.tsx +40 -0
  200. package/src/client/triggers/schedule/OnField.tsx +64 -0
  201. package/src/client/triggers/schedule/RepeatField.tsx +116 -0
  202. package/src/client/triggers/schedule/ScheduleConfig.tsx +227 -0
  203. package/src/client/triggers/schedule/constants.ts +4 -0
  204. package/src/client/triggers/schedule/index.tsx +78 -0
  205. package/src/client/triggers/schedule/locale/Cron.zh-CN.ts +79 -0
  206. package/src/client/utils.ts +36 -0
  207. package/src/client/variable.tsx +318 -0
  208. package/src/index.ts +1 -0
  209. package/src/server/Plugin.ts +355 -0
  210. package/src/server/Processor.ts +354 -0
  211. package/src/server/__tests__/Plugin.test.ts +398 -0
  212. package/src/server/__tests__/Processor.test.ts +474 -0
  213. package/src/server/__tests__/actions/workflows.test.ts +419 -0
  214. package/src/server/__tests__/collections/categories.ts +27 -0
  215. package/src/server/__tests__/collections/comments.ts +24 -0
  216. package/src/server/__tests__/collections/posts.ts +42 -0
  217. package/src/server/__tests__/collections/replies.ts +9 -0
  218. package/src/server/__tests__/collections/tags.ts +15 -0
  219. package/src/server/__tests__/index.ts +89 -0
  220. package/src/server/__tests__/instructions/aggregate.test.ts +294 -0
  221. package/src/server/__tests__/instructions/calculation.test.ts +265 -0
  222. package/src/server/__tests__/instructions/condition.test.ts +335 -0
  223. package/src/server/__tests__/instructions/create.test.ts +129 -0
  224. package/src/server/__tests__/instructions/delay.test.ts +182 -0
  225. package/src/server/__tests__/instructions/destroy.test.ts +58 -0
  226. package/src/server/__tests__/instructions/loop.test.ts +331 -0
  227. package/src/server/__tests__/instructions/manual.test.ts +1173 -0
  228. package/src/server/__tests__/instructions/parallel.test.ts +445 -0
  229. package/src/server/__tests__/instructions/query.test.ts +359 -0
  230. package/src/server/__tests__/instructions/request.test.ts +247 -0
  231. package/src/server/__tests__/instructions/sql.test.ts +162 -0
  232. package/src/server/__tests__/instructions/update.test.ts +189 -0
  233. package/src/server/__tests__/triggers/collection.test.ts +333 -0
  234. package/src/server/__tests__/triggers/schedule.test.ts +369 -0
  235. package/src/server/actions/index.ts +25 -0
  236. package/src/server/actions/nodes.ts +214 -0
  237. package/src/server/actions/workflows.ts +178 -0
  238. package/src/server/collections/executions.ts +35 -0
  239. package/src/server/collections/flow_nodes.ts +54 -0
  240. package/src/server/collections/jobs.ts +31 -0
  241. package/src/server/collections/workflows.ts +88 -0
  242. package/src/server/constants.ts +26 -0
  243. package/src/server/fields/expression-field.ts +11 -0
  244. package/src/server/fields/index.ts +7 -0
  245. package/src/server/functions/index.ts +16 -0
  246. package/src/server/index.ts +6 -0
  247. package/src/server/instructions/aggregate.ts +42 -0
  248. package/src/server/instructions/calculation.ts +41 -0
  249. package/src/server/instructions/condition.ts +172 -0
  250. package/src/server/instructions/create.ts +39 -0
  251. package/src/server/instructions/delay.ts +105 -0
  252. package/src/server/instructions/destroy.ts +23 -0
  253. package/src/server/instructions/index.ts +64 -0
  254. package/src/server/instructions/loop.ts +99 -0
  255. package/src/server/instructions/manual/actions.ts +91 -0
  256. package/src/server/instructions/manual/collecions/jobs.ts +17 -0
  257. package/src/server/instructions/manual/collecions/users.ts +15 -0
  258. package/src/server/instructions/manual/collecions/users_jobs.ts +50 -0
  259. package/src/server/instructions/manual/forms/create.ts +23 -0
  260. package/src/server/instructions/manual/forms/index.ts +12 -0
  261. package/src/server/instructions/manual/forms/update.ts +23 -0
  262. package/src/server/instructions/manual/index.ts +184 -0
  263. package/src/server/instructions/parallel.ts +121 -0
  264. package/src/server/instructions/query.ts +42 -0
  265. package/src/server/instructions/request.ts +88 -0
  266. package/src/server/instructions/sql.ts +25 -0
  267. package/src/server/instructions/update.ts +24 -0
  268. package/src/server/migrations/20221129153547-calculation-variables.ts +64 -0
  269. package/src/server/migrations/20230221032941-change-request-body-type.ts +76 -0
  270. package/src/server/migrations/20230221071831-calculation-expression.ts +102 -0
  271. package/src/server/migrations/20230221121203-condition-calculation.ts +82 -0
  272. package/src/server/migrations/20230221162902-jsonb-to-json.ts +51 -0
  273. package/src/server/migrations/20230411034722-manual-multi-form.ts +282 -0
  274. package/src/server/migrations/20230612021134-manual-collection-block.ts +138 -0
  275. package/src/server/migrations/20230710115902-manual-action-values.ts +78 -0
  276. package/src/server/triggers/collection.ts +146 -0
  277. package/src/server/triggers/index.ts +22 -0
  278. package/src/server/triggers/schedule.ts +567 -0
  279. package/src/server/types/Execution.ts +26 -0
  280. package/src/server/types/FlowNode.ts +21 -0
  281. package/src/server/types/Job.ts +18 -0
  282. package/src/server/types/Workflow.ts +36 -0
  283. package/src/server/types/index.ts +4 -0
  284. package/src/server/utils.ts +17 -0
  285. package/lib/client/triggers/schedule/DateFieldsSelect.d.ts +0 -2
  286. /package/lib/server/{models → types}/Execution.js +0 -0
  287. /package/lib/server/{models → types}/FlowNode.js +0 -0
  288. /package/lib/server/{models → types}/Job.js +0 -0
  289. /package/lib/server/{models → types}/Workflow.js +0 -0
@@ -0,0 +1,485 @@
1
+ import { CloseOutlined, DeleteOutlined } from '@ant-design/icons';
2
+ import { ISchema, useForm } from '@formily/react';
3
+ import {
4
+ ActionContextProvider,
5
+ SchemaComponent,
6
+ SchemaInitializerItemOptions,
7
+ css,
8
+ cx,
9
+ useAPIClient,
10
+ useActionContext,
11
+ useCompile,
12
+ useRequest,
13
+ useResourceActionContext,
14
+ } from '@nocobase/client';
15
+ import { Registry, parse, str2moment } from '@nocobase/utils/client';
16
+ import { Alert, App, Button, Dropdown, Input, Tag, message } from 'antd';
17
+ import React, { useContext, useState } from 'react';
18
+ import { useTranslation } from 'react-i18next';
19
+ import { AddButton } from '../AddButton';
20
+ import { useFlowContext } from '../FlowContext';
21
+ import { NodeDescription } from '../components/NodeDescription';
22
+ import { JobStatusOptionsMap } from '../constants';
23
+ import { NAMESPACE, lang } from '../locale';
24
+ import useStyles from '../style';
25
+ import { VariableOption, VariableOptions } from '../variable';
26
+ import aggregate from './aggregate';
27
+ import calculation from './calculation';
28
+ import condition from './condition';
29
+ import create from './create';
30
+ import delay from './delay';
31
+ import destroy from './destroy';
32
+ import loop from './loop';
33
+ import manual from './manual';
34
+ import parallel from './parallel';
35
+ import query from './query';
36
+ import request from './request';
37
+ import update from './update';
38
+ import sql from './sql';
39
+
40
+ export interface Instruction {
41
+ title: string;
42
+ type: string;
43
+ group: string;
44
+ description?: string;
45
+ options?: { label: string; value: any; key: string }[];
46
+ fieldset: { [key: string]: ISchema };
47
+ view?: ISchema;
48
+ scope?: { [key: string]: any };
49
+ components?: { [key: string]: any };
50
+ component?(props): JSX.Element;
51
+ endding?: boolean;
52
+ useVariables?(node, options?): VariableOption;
53
+ useScopeVariables?(node, options?): VariableOptions;
54
+ useInitializers?(node): SchemaInitializerItemOptions | null;
55
+ initializers?: { [key: string]: any };
56
+ }
57
+
58
+ export const instructions = new Registry<Instruction>();
59
+
60
+ instructions.register('calculation', calculation);
61
+ instructions.register('condition', condition);
62
+ instructions.register('parallel', parallel);
63
+ instructions.register('loop', loop);
64
+ instructions.register('delay', delay);
65
+
66
+ instructions.register('manual', manual);
67
+
68
+ instructions.register('query', query);
69
+ instructions.register('create', create);
70
+ instructions.register('update', update);
71
+ instructions.register('destroy', destroy);
72
+ instructions.register('aggregate', aggregate);
73
+
74
+ instructions.register('request', request);
75
+ instructions.register('sql', sql);
76
+
77
+ function useUpdateAction() {
78
+ const form = useForm();
79
+ const api = useAPIClient();
80
+ const ctx = useActionContext();
81
+ const { refresh } = useResourceActionContext();
82
+ const data = useNodeContext();
83
+ const { workflow } = useFlowContext();
84
+ return {
85
+ async run() {
86
+ if (workflow.executed) {
87
+ message.error(lang('Node in executed workflow cannot be modified'));
88
+ return;
89
+ }
90
+ await form.submit();
91
+ await api.resource('flow_nodes', data.id).update?.({
92
+ filterByTk: data.id,
93
+ values: {
94
+ config: form.values,
95
+ },
96
+ });
97
+ ctx.setVisible(false);
98
+ refresh();
99
+ },
100
+ };
101
+ }
102
+
103
+ export const NodeContext = React.createContext<any>({});
104
+
105
+ export function useNodeContext() {
106
+ return useContext(NodeContext);
107
+ }
108
+
109
+ export function useAvailableUpstreams(node) {
110
+ const stack: any[] = [];
111
+ if (!node) {
112
+ return [];
113
+ }
114
+ for (let current = node.upstream; current; current = current.upstream) {
115
+ stack.push(current);
116
+ }
117
+
118
+ return stack;
119
+ }
120
+
121
+ export function useUpstreamScopes(node) {
122
+ const stack: any[] = [];
123
+ if (!node) {
124
+ return [];
125
+ }
126
+
127
+ for (let current = node; current; current = current.upstream) {
128
+ if (current.upstream && current.branchIndex != null) {
129
+ stack.push(current.upstream);
130
+ }
131
+ }
132
+
133
+ return stack;
134
+ }
135
+
136
+ export function Node({ data }) {
137
+ const { styles } = useStyles();
138
+ const { component: Component = NodeDefaultView, endding } = instructions.get(data.type);
139
+
140
+ return (
141
+ <NodeContext.Provider value={data}>
142
+ <div className={cx(styles.nodeBlockClass)}>
143
+ <Component data={data} />
144
+ {!endding ? (
145
+ <AddButton upstream={data} />
146
+ ) : (
147
+ <div
148
+ className={css`
149
+ flex-grow: 1;
150
+ display: flex;
151
+ flex-direction: column;
152
+ align-items: center;
153
+ justify-content: center;
154
+ width: 1px;
155
+ height: 6em;
156
+ padding: 2em 0;
157
+ background-color: var(--nb-box-bg);
158
+
159
+ .anticon {
160
+ font-size: 1.5em;
161
+ line-height: 100%;
162
+ }
163
+ `}
164
+ >
165
+ <CloseOutlined />
166
+ </div>
167
+ )}
168
+ </div>
169
+ </NodeContext.Provider>
170
+ );
171
+ }
172
+
173
+ export function RemoveButton() {
174
+ const { t } = useTranslation();
175
+ const api = useAPIClient();
176
+ const { workflow, nodes, refresh } = useFlowContext() ?? {};
177
+ const current = useNodeContext();
178
+ const { modal } = App.useApp();
179
+
180
+ if (!workflow) {
181
+ return null;
182
+ }
183
+ const resource = api.resource('workflows.nodes', workflow.id);
184
+
185
+ async function onRemove() {
186
+ async function onOk() {
187
+ await resource.destroy?.({
188
+ filterByTk: current.id,
189
+ });
190
+ refresh();
191
+ }
192
+
193
+ const usingNodes = nodes.filter((node) => {
194
+ if (node === current) {
195
+ return false;
196
+ }
197
+
198
+ const template = parse(node.config);
199
+ const refs = template.parameters.filter(
200
+ ({ key }) => key.startsWith(`$jobsMapByNodeId.${current.id}.`) || key === `$jobsMapByNodeId.${current.id}`,
201
+ );
202
+ return refs.length;
203
+ });
204
+
205
+ if (usingNodes.length) {
206
+ modal.error({
207
+ title: lang('Can not delete'),
208
+ content: lang(
209
+ 'The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.',
210
+ { nodes: usingNodes.map((item) => `#${item.id}`).join(', ') },
211
+ ),
212
+ });
213
+ return;
214
+ }
215
+
216
+ const hasBranches = !nodes.find((item) => item.upstream === current && item.branchIndex != null);
217
+ const message = hasBranches
218
+ ? t('Are you sure you want to delete it?')
219
+ : lang('This node contains branches, deleting will also be preformed to them, are you sure?');
220
+
221
+ modal.confirm({
222
+ title: t('Delete'),
223
+ content: message,
224
+ onOk,
225
+ });
226
+ }
227
+
228
+ return workflow.executed ? null : (
229
+ <Button
230
+ type="text"
231
+ shape="circle"
232
+ icon={<DeleteOutlined />}
233
+ onClick={onRemove}
234
+ className="workflow-node-remove-button"
235
+ />
236
+ );
237
+ }
238
+
239
+ function InnerJobButton({ job, ...props }) {
240
+ const { styles } = useStyles();
241
+ const { icon, color } = JobStatusOptionsMap[job.status];
242
+
243
+ return (
244
+ <Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
245
+ <Tag color={color}>{icon}</Tag>
246
+ </Button>
247
+ );
248
+ }
249
+
250
+ export function JobButton() {
251
+ const { execution, setViewJob } = useFlowContext();
252
+ const { jobs } = useNodeContext() ?? {};
253
+ const { styles } = useStyles();
254
+
255
+ if (!execution) {
256
+ return null;
257
+ }
258
+
259
+ if (!jobs.length) {
260
+ return (
261
+ <span
262
+ className={cx(
263
+ styles.nodeJobButtonClass,
264
+ css`
265
+ border: 2px solid #d9d9d9;
266
+ border-radius: 50%;
267
+ cursor: not-allowed;
268
+ `,
269
+ )}
270
+ />
271
+ );
272
+ }
273
+
274
+ function onOpenJob({ key }) {
275
+ const job = jobs.find((item) => item.id == key);
276
+ setViewJob(job);
277
+ }
278
+
279
+ return jobs.length > 1 ? (
280
+ <Dropdown
281
+ menu={{
282
+ items: jobs.map((job) => {
283
+ const { icon, color } = JobStatusOptionsMap[job.status];
284
+ return {
285
+ key: job.id,
286
+ label: (
287
+ <div
288
+ className={css`
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 0.5em;
292
+
293
+ time {
294
+ color: #999;
295
+ font-size: 0.8em;
296
+ }
297
+ `}
298
+ >
299
+ <span className={cx(styles.nodeJobButtonClass, 'inner')}>
300
+ <Tag color={color}>{icon}</Tag>
301
+ </span>
302
+ <time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
303
+ </div>
304
+ ),
305
+ };
306
+ }),
307
+ onClick: onOpenJob,
308
+ }}
309
+ >
310
+ <InnerJobButton job={jobs[jobs.length - 1]} />
311
+ </Dropdown>
312
+ ) : (
313
+ <InnerJobButton job={jobs[0]} onClick={() => setViewJob(jobs[0])} />
314
+ );
315
+ }
316
+
317
+ export function NodeDefaultView(props) {
318
+ const { data, children } = props;
319
+ const compile = useCompile();
320
+ const api = useAPIClient();
321
+ const { workflow, refresh } = useFlowContext() ?? {};
322
+ const { styles } = useStyles();
323
+
324
+ const instruction = instructions.get(data.type);
325
+ const detailText = workflow.executed ? '{{t("View")}}' : '{{t("Configure")}}';
326
+ const typeTitle = compile(instruction.title);
327
+
328
+ const [editingTitle, setEditingTitle] = useState<string>(data.title ?? typeTitle);
329
+ const [editingConfig, setEditingConfig] = useState(false);
330
+
331
+ async function onChangeTitle(next) {
332
+ const title = next || typeTitle;
333
+ setEditingTitle(title);
334
+ if (title === data.title) {
335
+ return;
336
+ }
337
+ await api.resource('flow_nodes').update?.({
338
+ filterByTk: data.id,
339
+ values: {
340
+ title,
341
+ },
342
+ });
343
+ refresh();
344
+ }
345
+
346
+ function onOpenDrawer(ev) {
347
+ if (ev.target === ev.currentTarget) {
348
+ setEditingConfig(true);
349
+ return;
350
+ }
351
+ const whiteSet = new Set(['workflow-node-meta', 'workflow-node-config-button', 'ant-input-disabled']);
352
+ for (let el = ev.target; el && el !== ev.currentTarget && el !== document.documentElement; el = el.parentNode) {
353
+ if ((Array.from(el.classList) as string[]).some((name: string) => whiteSet.has(name))) {
354
+ setEditingConfig(true);
355
+ ev.stopPropagation();
356
+ return;
357
+ }
358
+ }
359
+ }
360
+
361
+ return (
362
+ <div className={cx(styles.nodeClass, `workflow-node-type-${data.type}`)}>
363
+ <div className={cx(styles.nodeCardClass, { configuring: editingConfig })} onClick={onOpenDrawer}>
364
+ <div className={cx(styles.nodeMetaClass, 'workflow-node-meta')}>
365
+ <Tag>{typeTitle}</Tag>
366
+ <span className="workflow-node-id">{data.id}</span>
367
+ </div>
368
+ <div>
369
+ <Input.TextArea
370
+ disabled={workflow.executed}
371
+ value={editingTitle}
372
+ onChange={(ev) => setEditingTitle(ev.target.value)}
373
+ onBlur={(ev) => onChangeTitle(ev.target.value)}
374
+ autoSize
375
+ />
376
+ </div>
377
+ <RemoveButton />
378
+ <JobButton />
379
+ <ActionContextProvider value={{ visible: editingConfig, setVisible: setEditingConfig }}>
380
+ <SchemaComponent
381
+ scope={instruction.scope}
382
+ components={instruction.components}
383
+ schema={{
384
+ type: 'void',
385
+ properties: {
386
+ ...(instruction.view ? { view: instruction.view } : {}),
387
+ button: {
388
+ type: 'void',
389
+ 'x-content': detailText,
390
+ 'x-component': Button,
391
+ 'x-component-props': {
392
+ type: 'link',
393
+ className: 'workflow-node-config-button',
394
+ },
395
+ },
396
+ [`${instruction.type}_${data.id}`]: {
397
+ type: 'void',
398
+ title: data.title,
399
+ 'x-component': 'Action.Drawer',
400
+ 'x-decorator': 'Form',
401
+ 'x-decorator-props': {
402
+ disabled: workflow.executed,
403
+ useValues(options) {
404
+ const { config } = useNodeContext();
405
+ return useRequest(() => {
406
+ return Promise.resolve({ data: config });
407
+ }, options);
408
+ },
409
+ },
410
+ properties: {
411
+ ...(workflow.executed
412
+ ? {
413
+ alert: {
414
+ type: 'void',
415
+ 'x-component': Alert,
416
+ 'x-component-props': {
417
+ type: 'warning',
418
+ showIcon: true,
419
+ message: `{{t("Node in executed workflow cannot be modified", { ns: "${NAMESPACE}" })}}`,
420
+ className: css`
421
+ width: 100%;
422
+ font-size: 85%;
423
+ margin-bottom: 2em;
424
+ `,
425
+ },
426
+ },
427
+ }
428
+ : instruction.description
429
+ ? {
430
+ description: {
431
+ type: 'void',
432
+ 'x-component': NodeDescription,
433
+ 'x-component-props': {
434
+ instruction,
435
+ },
436
+ },
437
+ }
438
+ : {}),
439
+ fieldset: {
440
+ type: 'void',
441
+ 'x-component': 'fieldset',
442
+ 'x-component-props': {
443
+ className: css`
444
+ .ant-select,
445
+ .ant-cascader-picker,
446
+ .ant-picker,
447
+ .ant-input-number,
448
+ `,
449
+ },
450
+ properties: instruction.fieldset,
451
+ },
452
+ actions: workflow.executed
453
+ ? null
454
+ : {
455
+ type: 'void',
456
+ 'x-component': 'Action.Drawer.Footer',
457
+ properties: {
458
+ cancel: {
459
+ title: '{{t("Cancel")}}',
460
+ 'x-component': 'Action',
461
+ 'x-component-props': {
462
+ useAction: '{{ cm.useCancelAction }}',
463
+ },
464
+ },
465
+ submit: {
466
+ title: '{{t("Submit")}}',
467
+ 'x-component': 'Action',
468
+ 'x-component-props': {
469
+ type: 'primary',
470
+ useAction: useUpdateAction,
471
+ },
472
+ },
473
+ },
474
+ },
475
+ },
476
+ } as ISchema,
477
+ },
478
+ }}
479
+ />
480
+ </ActionContextProvider>
481
+ </div>
482
+ {children}
483
+ </div>
484
+ );
485
+ }
@@ -0,0 +1,144 @@
1
+ import { ArrowUpOutlined } from '@ant-design/icons';
2
+ import { css, cx, useCompile } from '@nocobase/client';
3
+ import React from 'react';
4
+ import { NodeDefaultView } from '.';
5
+ import { Branch } from '../Branch';
6
+ import { useFlowContext } from '../FlowContext';
7
+ import { NAMESPACE, lang } from '../locale';
8
+ import useStyles from '../style';
9
+ import { VariableOption, defaultFieldNames, nodesOptions, triggerOptions, useWorkflowVariableOptions } from '../variable';
10
+
11
+ function findOption(options: VariableOption[], paths: string[]) {
12
+ let opts = options;
13
+ let option = null;
14
+ for (let i = 0; i < paths.length; i++) {
15
+ const path = paths[i];
16
+ const current = opts.find((item) => item.value === path);
17
+ if (!current) {
18
+ break;
19
+ }
20
+ option = current;
21
+ if (current.children) {
22
+ opts = current.children;
23
+ }
24
+ }
25
+ return option;
26
+ }
27
+
28
+ export default {
29
+ title: `{{t("Loop", { ns: "${NAMESPACE}" })}}`,
30
+ type: 'loop',
31
+ group: 'control',
32
+ description: `{{t("By using a loop node, you can perform the same operation on multiple sets of data. The source of these sets can be either multiple records from a query node or multiple associated records of a single record. Loop node can also be used for iterating a certain number of times or for looping through each character in a string. However, excessive looping may cause performance issues, so use with caution.", { ns: "${NAMESPACE}" })}}`,
33
+ fieldset: {
34
+ target: {
35
+ type: 'string',
36
+ title: `{{t("Loop target", { ns: "${NAMESPACE}" })}}`,
37
+ description: `{{t("A single number will be treated as a loop count, a single string will be treated as an array of characters, and other non-array values will be converted to arrays. The loop node ends when the loop count is reached, or when the array loop is completed. You can also add condition nodes to the loop to terminate it.", { ns: "${NAMESPACE}" })}}`,
38
+ 'x-decorator': 'FormItem',
39
+ 'x-component': 'Variable.Input',
40
+ 'x-component-props': {
41
+ scope: '{{useWorkflowVariableOptions()}}',
42
+ changeOnSelect: true,
43
+ useTypedConstant: ['string', 'number', 'null'],
44
+ className: css`
45
+ width: 100%;
46
+
47
+ .variable {
48
+ flex: 1;
49
+ }
50
+
51
+ .ant-input.null-value {
52
+ width: 100%;
53
+ }
54
+ `,
55
+ },
56
+ required: true,
57
+ },
58
+ },
59
+ view: {},
60
+ component: function Component({ data }) {
61
+ const { nodes } = useFlowContext();
62
+ const { styles } = useStyles();
63
+ const entry = nodes.find((node) => node.upstreamId === data.id && node.branchIndex != null);
64
+
65
+ return (
66
+ <NodeDefaultView data={data}>
67
+ <div className={styles.nodeSubtreeClass}>
68
+ <div
69
+ className={cx(
70
+ styles.branchBlockClass,
71
+ css`
72
+ padding-left: 20em;
73
+ `,
74
+ )}
75
+ >
76
+ <Branch from={data} entry={entry} branchIndex={entry?.branchIndex ?? 0} />
77
+
78
+ <div className={styles.branchClass}>
79
+ <div className="workflow-branch-lines" />
80
+ <div className={cx(styles.addButtonClass, styles.loopLineClass)}>
81
+ <ArrowUpOutlined />
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <div
86
+ className={css`
87
+ position: relative;
88
+ height: 2em;
89
+ `}
90
+ />
91
+ </div>
92
+ </NodeDefaultView>
93
+ );
94
+ },
95
+ scope: {
96
+ useWorkflowVariableOptions,
97
+ },
98
+ components: {},
99
+ useScopeVariables(node, options) {
100
+ const compile = useCompile();
101
+ const { target } = node.config;
102
+ if (!target) {
103
+ return null;
104
+ }
105
+
106
+ const { fieldNames = defaultFieldNames } = options;
107
+
108
+ // const { workflow } = useFlowContext();
109
+ // const current = useNodeContext();
110
+ // const upstreams = useAvailableUpstreams(current);
111
+ // find target data model by path described in `config.target`
112
+ // 1. get options from $context/$jobsMapByNodeId
113
+ // 2. route to sub-options and use as loop target options
114
+ let targetOption: VariableOption = { key: 'item', [fieldNames.value]: 'item', [fieldNames.label]: lang('Loop target') };
115
+
116
+ if (typeof target === 'string' && target.startsWith('{{') && target.endsWith('}}')) {
117
+ const paths = target
118
+ .slice(2, -2)
119
+ .split('.')
120
+ .map((path) => path.trim());
121
+
122
+ const targetOptions = [nodesOptions, triggerOptions].map((item: any) => {
123
+ const opts = item.useOptions(options).filter(Boolean);
124
+ return {
125
+ [fieldNames.label]: compile(item.title),
126
+ [fieldNames.value]: item.value,
127
+ key: item.value,
128
+ [fieldNames.children]: opts,
129
+ disabled: opts && !opts.length,
130
+ };
131
+ });
132
+
133
+ const found = findOption(targetOptions, paths);
134
+
135
+ targetOption = Object.assign({ ...targetOption }, found, targetOption);
136
+ }
137
+
138
+ return [
139
+ targetOption,
140
+ { key: 'index', [fieldNames.value]: 'index', [fieldNames.label]: lang('Loop index') },
141
+ { key: 'length', [fieldNames.value]: 'length', [fieldNames.label]: lang('Loop length') },
142
+ ];
143
+ },
144
+ };
@@ -0,0 +1,33 @@
1
+ import { RemoteSelect } from '@nocobase/client';
2
+ import React from 'react';
3
+ import { Variable } from '@nocobase/client';
4
+ import { useWorkflowVariableOptions } from '../../variable';
5
+
6
+ export function AssigneesSelect({ multiple = false, value = [], onChange }) {
7
+ const scope = useWorkflowVariableOptions({ types: [{ type: 'reference', options: { collection: 'users' } }] });
8
+
9
+ return (
10
+ <Variable.Input
11
+ scope={scope}
12
+ value={value[0]}
13
+ onChange={(next) => {
14
+ onChange([next]);
15
+ }}
16
+ >
17
+ <RemoteSelect
18
+ fieldNames={{
19
+ label: 'nickname',
20
+ value: 'id',
21
+ }}
22
+ service={{
23
+ resource: 'users',
24
+ }}
25
+ manual={false}
26
+ value={value[0]}
27
+ onChange={(v) => {
28
+ onChange(v != null ? [v] : []);
29
+ }}
30
+ />
31
+ </Variable.Input>
32
+ );
33
+ }