@nocobase/plugin-workflow-manual 1.0.0-alpha.2 → 1.0.0-alpha.3

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 (88) hide show
  1. package/dist/client/WorkflowTodo.d.ts +8 -0
  2. package/dist/client/WorkflowTodoBlockInitializer.d.ts +8 -0
  3. package/dist/client/index.d.ts +8 -0
  4. package/dist/client/index.js +9 -0
  5. package/dist/client/instruction/AssigneesSelect.d.ts +8 -0
  6. package/dist/client/instruction/FormBlockInitializer.d.ts +8 -0
  7. package/dist/client/instruction/FormBlockProvider.d.ts +8 -0
  8. package/dist/client/instruction/ModeConfig.d.ts +8 -0
  9. package/dist/client/instruction/SchemaConfig.d.ts +8 -0
  10. package/dist/client/instruction/createManualFormBlockUISchema.d.ts +8 -0
  11. package/dist/client/instruction/forms/create.d.ts +8 -0
  12. package/dist/client/instruction/forms/custom.d.ts +8 -0
  13. package/dist/client/instruction/forms/update.d.ts +8 -0
  14. package/dist/client/instruction/index.d.ts +8 -0
  15. package/dist/client/instruction/utils.d.ts +8 -0
  16. package/dist/externalVersion.js +18 -9
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.js +9 -0
  19. package/dist/locale/index.d.ts +8 -0
  20. package/dist/locale/index.js +9 -0
  21. package/dist/server/ManualInstruction.d.ts +8 -0
  22. package/dist/server/ManualInstruction.js +9 -0
  23. package/dist/server/Plugin.d.ts +8 -0
  24. package/dist/server/Plugin.js +9 -0
  25. package/dist/server/actions.d.ts +8 -0
  26. package/dist/server/actions.js +9 -0
  27. package/dist/server/collections/1-users_jobs.d.ts +8 -0
  28. package/dist/server/collections/1-users_jobs.js +9 -0
  29. package/dist/server/collections/2-jobs.d.ts +8 -0
  30. package/dist/server/collections/2-jobs.js +9 -0
  31. package/dist/server/collections/3-users.d.ts +8 -0
  32. package/dist/server/collections/3-users.js +9 -0
  33. package/dist/server/forms/create.d.ts +8 -0
  34. package/dist/server/forms/create.js +9 -0
  35. package/dist/server/forms/index.d.ts +8 -0
  36. package/dist/server/forms/index.js +9 -0
  37. package/dist/server/forms/update.d.ts +8 -0
  38. package/dist/server/forms/update.js +9 -0
  39. package/dist/server/index.d.ts +8 -0
  40. package/dist/server/index.js +9 -0
  41. package/dist/server/migrations/20240325213145-fix-schema.d.ts +8 -0
  42. package/dist/server/migrations/20240325213145-fix-schema.js +9 -0
  43. package/package.json +2 -2
  44. package/src/client/WorkflowTodo.tsx +0 -647
  45. package/src/client/WorkflowTodoBlockInitializer.tsx +0 -32
  46. package/src/client/__e2e__/assignees.test.ts +0 -0
  47. package/src/client/__e2e__/createRecordForm.test.ts +0 -2287
  48. package/src/client/__e2e__/customFormBlocks.test.ts +0 -1933
  49. package/src/client/__e2e__/datablocks.test.ts +0 -1208
  50. package/src/client/__e2e__/updateRecordForm.test.ts +0 -2338
  51. package/src/client/__e2e__/workflowTodo.test.ts +0 -242
  52. package/src/client/index.ts +0 -51
  53. package/src/client/instruction/AssigneesSelect.tsx +0 -39
  54. package/src/client/instruction/FormBlockInitializer.tsx +0 -79
  55. package/src/client/instruction/FormBlockProvider.tsx +0 -92
  56. package/src/client/instruction/ModeConfig.tsx +0 -85
  57. package/src/client/instruction/SchemaConfig.tsx +0 -659
  58. package/src/client/instruction/createManualFormBlockUISchema.ts +0 -5
  59. package/src/client/instruction/forms/create.tsx +0 -123
  60. package/src/client/instruction/forms/custom.tsx +0 -439
  61. package/src/client/instruction/forms/update.tsx +0 -167
  62. package/src/client/instruction/index.tsx +0 -160
  63. package/src/client/instruction/utils.ts +0 -19
  64. package/src/index.ts +0 -2
  65. package/src/locale/en-US.json +0 -30
  66. package/src/locale/index.ts +0 -14
  67. package/src/locale/ko_KR.json +0 -32
  68. package/src/locale/zh-CN.json +0 -32
  69. package/src/server/ManualInstruction.ts +0 -157
  70. package/src/server/Plugin.ts +0 -43
  71. package/src/server/__tests__/assignees.test.ts +0 -153
  72. package/src/server/__tests__/collections/categories.ts +0 -15
  73. package/src/server/__tests__/collections/comments.ts +0 -24
  74. package/src/server/__tests__/collections/posts.ts +0 -40
  75. package/src/server/__tests__/collections/replies.ts +0 -9
  76. package/src/server/__tests__/collections/tags.ts +0 -15
  77. package/src/server/__tests__/data-source.test.ts +0 -223
  78. package/src/server/__tests__/form.test.ts +0 -637
  79. package/src/server/__tests__/mode.test.ts +0 -561
  80. package/src/server/actions.ts +0 -103
  81. package/src/server/collections/1-users_jobs.ts +0 -52
  82. package/src/server/collections/2-jobs.ts +0 -19
  83. package/src/server/collections/3-users.ts +0 -17
  84. package/src/server/forms/create.ts +0 -30
  85. package/src/server/forms/index.ts +0 -13
  86. package/src/server/forms/update.ts +0 -30
  87. package/src/server/index.ts +0 -1
  88. package/src/server/migrations/20240325213145-fix-schema.ts +0 -82
@@ -1,167 +0,0 @@
1
- import { useFieldSchema } from '@formily/react';
2
- import React, { useMemo, useState } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
- import _ from 'lodash';
5
-
6
- import {
7
- GeneralSchemaDesigner,
8
- SchemaSettingsActionModalItem,
9
- SchemaSettingsBlockTitleItem,
10
- SchemaSettingsDivider,
11
- SchemaSettingsLinkageRules,
12
- SchemaSettingsRemove,
13
- useCollection_deprecated,
14
- useCollectionFilterOptions,
15
- useDesignable,
16
- useMenuSearch,
17
- } from '@nocobase/client';
18
- import { isValidFilter } from '@nocobase/utils/client';
19
- import { FilterDynamicComponent } from '@nocobase/plugin-workflow/client';
20
-
21
- import { NAMESPACE } from '../../../locale';
22
- import { FormBlockInitializer } from '../FormBlockInitializer';
23
- import { ManualFormType } from '../SchemaConfig';
24
- import { findSchema } from '../utils';
25
-
26
- function UpdateFormDesigner() {
27
- const { name, title } = useCollection_deprecated();
28
- const fieldSchema = useFieldSchema();
29
- const { t } = useTranslation();
30
- const { dn } = useDesignable();
31
-
32
- return (
33
- <GeneralSchemaDesigner title={title || name}>
34
- <SchemaSettingsBlockTitleItem />
35
- <SchemaSettingsActionModalItem
36
- title={t('Filter settings', { ns: NAMESPACE })}
37
- schema={{
38
- name: 'filter',
39
- type: 'object',
40
- title: `{{t("Filter")}}`,
41
- 'x-component': 'Filter',
42
- 'x-use-component-props': () => {
43
- // eslint-disable-next-line react-hooks/rules-of-hooks
44
- const options = useCollectionFilterOptions(fieldSchema?.['x-decorator-props']?.collection);
45
- return {
46
- options,
47
- };
48
- },
49
- 'x-component-props': {
50
- dynamicComponent: 'FilterDynamicComponent',
51
- },
52
- }}
53
- initialValues={fieldSchema?.['x-decorator-props']}
54
- onSubmit={({ filter }) => {
55
- fieldSchema['x-decorator-props'].filter = filter;
56
- dn.emit('patch', {
57
- schema: {
58
- // ['x-uid']: fieldSchema['x-uid'],
59
- 'x-decorator-props': fieldSchema['x-decorator-props'],
60
- },
61
- });
62
- dn.refresh();
63
- }}
64
- />
65
- <SchemaSettingsLinkageRules collectionName={name} />
66
- <SchemaSettingsDivider />
67
- <SchemaSettingsRemove
68
- removeParentsIfNoChildren
69
- breakRemoveOn={{
70
- 'x-component': 'Grid',
71
- }}
72
- />
73
- </GeneralSchemaDesigner>
74
- );
75
- }
76
-
77
- export default {
78
- title: `{{t("Update record form", { ns: "${NAMESPACE}" })}}`,
79
- config: {
80
- useInitializer({ allCollections }) {
81
- const childItems = useMemo(
82
- () =>
83
- allCollections.map(({ key, displayName, collections }) => ({
84
- key: key,
85
- name: key,
86
- label: displayName,
87
- type: 'subMenu',
88
- children: collections.map((item) => ({
89
- name: _.camelCase(`updateRecordForm-child-${item.name}`),
90
- type: 'item',
91
- title: item.title || item.tableName,
92
- schema: {
93
- collection: item.name,
94
- dataSource: key,
95
- title: `{{t("Update record", { ns: "${NAMESPACE}" })}}`,
96
- formType: 'update',
97
- 'x-designer': 'UpdateFormDesigner',
98
- },
99
- Component: FormBlockInitializer,
100
- })),
101
- })),
102
- [allCollections],
103
- );
104
- const [openMenuKeys, setOpenMenuKeys] = useState([]);
105
- const searchedChildren = useMenuSearch({ data: childItems, openKeys: openMenuKeys });
106
- return {
107
- name: 'updateRecordForm',
108
- key: 'updateRecordForm',
109
- type: 'subMenu',
110
- title: `{{t("Update record form", { ns: "${NAMESPACE}" })}}`,
111
- componentProps: {
112
- onOpenChange(keys) {
113
- setOpenMenuKeys(keys);
114
- },
115
- },
116
- children: searchedChildren,
117
- } as any;
118
- },
119
- initializers: {
120
- // AddCustomFormField
121
- },
122
- components: {
123
- FilterDynamicComponent,
124
- UpdateFormDesigner,
125
- },
126
- parseFormOptions(root) {
127
- const forms = {};
128
- const formBlocks: any[] = findSchema(
129
- root,
130
- (item) => item['x-decorator'] === 'FormBlockProvider' && item['x-decorator-props'].formType === 'update',
131
- );
132
- formBlocks.forEach((formBlock) => {
133
- const [formKey] = Object.keys(formBlock.properties);
134
- const formSchema = formBlock.properties[formKey];
135
- //获取actionBar的schemakey
136
- const actionKey =
137
- Object.entries(formSchema.properties).find(([key, f]) => f['x-component'] === 'ActionBar')?.[0] || 'actions';
138
- forms[formKey] = {
139
- ...formBlock['x-decorator-props'],
140
- type: 'update',
141
- title: formBlock['x-component-props']?.title || formKey,
142
- actions: findSchema(formSchema.properties[actionKey], (item) => item['x-component'] === 'Action').map(
143
- (item) => ({
144
- status: item['x-decorator-props'].value,
145
- values: item['x-action-settings']?.assignedValues?.values,
146
- key: item.name,
147
- }),
148
- ),
149
- };
150
- });
151
- return forms;
152
- },
153
- },
154
- block: {
155
- scope: {
156
- // useFormBlockProps
157
- },
158
- components: {},
159
- },
160
- validate({ filter }) {
161
- if (!filter || !isValidFilter(filter)) {
162
- return 'Please check one of your update record form, and add at least one filter condition in form settings.';
163
- }
164
-
165
- return null;
166
- },
167
- } as ManualFormType;
@@ -1,160 +0,0 @@
1
- import { SchemaInitializerItemType, useCollectionManager_deprecated, useCompile, usePlugin } from '@nocobase/client';
2
-
3
- import WorkflowPlugin, {
4
- defaultFieldNames,
5
- getCollectionFieldOptions,
6
- CollectionBlockInitializer,
7
- Instruction,
8
- } from '@nocobase/plugin-workflow/client';
9
-
10
- import { SchemaConfig, SchemaConfigButton } from './SchemaConfig';
11
- import { ModeConfig } from './ModeConfig';
12
- import { AssigneesSelect } from './AssigneesSelect';
13
- import { NAMESPACE } from '../../locale';
14
-
15
- const MULTIPLE_ASSIGNED_MODE = {
16
- SINGLE: Symbol('single'),
17
- ALL: Symbol('all'),
18
- ANY: Symbol('any'),
19
- ALL_PERCENTAGE: Symbol('all percentage'),
20
- ANY_PERCENTAGE: Symbol('any percentage'),
21
- };
22
-
23
- export default class extends Instruction {
24
- title = `{{t("Manual", { ns: "${NAMESPACE}" })}}`;
25
- type = 'manual';
26
- group = 'manual';
27
- description = `{{t("Could be used for manually submitting data, and determine whether to continue or exit. Workflow will generate a todo item for assigned user when it reaches a manual node, and continue processing after user submits the form.", { ns: "${NAMESPACE}" })}}`;
28
- fieldset = {
29
- assignees: {
30
- type: 'array',
31
- title: `{{t("Assignees", { ns: "${NAMESPACE}" })}}`,
32
- 'x-decorator': 'FormItem',
33
- 'x-component': 'AssigneesSelect',
34
- 'x-component-props': {
35
- // multiple: true,
36
- },
37
- required: true,
38
- default: [],
39
- },
40
- mode: {
41
- type: 'number',
42
- title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`,
43
- 'x-decorator': 'FormItem',
44
- 'x-component': 'ModeConfig',
45
- default: 1,
46
- 'x-reactions': {
47
- dependencies: ['assignees'],
48
- fulfill: {
49
- state: {
50
- visible: '{{$deps[0].length > 1}}',
51
- },
52
- },
53
- },
54
- },
55
- schema: {
56
- type: 'void',
57
- title: `{{t("User interface", { ns: "${NAMESPACE}" })}}`,
58
- 'x-decorator': 'FormItem',
59
- 'x-component': 'SchemaConfigButton',
60
- properties: {
61
- schema: {
62
- type: 'object',
63
- 'x-component': 'SchemaConfig',
64
- default: null,
65
- },
66
- },
67
- },
68
- forms: {
69
- type: 'object',
70
- default: {},
71
- },
72
- };
73
- components = {
74
- SchemaConfigButton,
75
- SchemaConfig,
76
- ModeConfig,
77
- AssigneesSelect,
78
- };
79
- useVariables({ key, title, config }, { types, fieldNames = defaultFieldNames }) {
80
- // eslint-disable-next-line react-hooks/rules-of-hooks
81
- const compile = useCompile();
82
- // eslint-disable-next-line react-hooks/rules-of-hooks
83
- const { getCollectionFields } = useCollectionManager_deprecated();
84
- const formKeys = Object.keys(config.forms ?? {});
85
- if (!formKeys.length) {
86
- return null;
87
- }
88
-
89
- const options = formKeys
90
- .map((formKey) => {
91
- const form = config.forms[formKey];
92
-
93
- const fieldsOptions = getCollectionFieldOptions({
94
- fields: form.collection?.fields,
95
- collection: form.collection,
96
- types,
97
- compile,
98
- getCollectionFields,
99
- });
100
- const label = compile(form.title) || formKey;
101
- return fieldsOptions.length
102
- ? {
103
- key: formKey,
104
- value: formKey,
105
- label,
106
- title: label,
107
- children: fieldsOptions,
108
- }
109
- : null;
110
- })
111
- .filter(Boolean);
112
-
113
- return options.length
114
- ? {
115
- [fieldNames.value]: key,
116
- [fieldNames.label]: title,
117
- [fieldNames.children]: options,
118
- }
119
- : null;
120
- }
121
- useInitializers(node): SchemaInitializerItemType | null {
122
- // eslint-disable-next-line react-hooks/rules-of-hooks
123
- const { getCollection } = useCollectionManager_deprecated();
124
- const formKeys = Object.keys(node.config.forms ?? {});
125
- if (!formKeys.length || node.config.mode) {
126
- return null;
127
- }
128
-
129
- const forms = formKeys
130
- .map((formKey) => {
131
- const form = node.config.forms[formKey];
132
- const { fields = [] } = getCollection(form.collection);
133
-
134
- return fields.length
135
- ? ({
136
- name: form.title ?? formKey,
137
- type: 'item',
138
- title: form.title ?? formKey,
139
- Component: CollectionBlockInitializer,
140
- collection: form.collection,
141
- dataPath: `$jobsMapByNodeKey.${node.key}.${formKey}`,
142
- } as SchemaInitializerItemType)
143
- : null;
144
- })
145
- .filter(Boolean);
146
-
147
- return forms.length
148
- ? {
149
- name: `#${node.id}`,
150
- key: 'forms',
151
- type: 'subMenu',
152
- title: node.title,
153
- children: forms,
154
- }
155
- : null;
156
- }
157
- isAvailable({ engine, workflow, upstream, branchIndex }) {
158
- return !engine.isWorkflowSync(workflow);
159
- }
160
- }
@@ -1,19 +0,0 @@
1
- export function findSchema(schema, filter, onlyLeaf = false) {
2
- const result = [];
3
-
4
- if (!schema) {
5
- return result;
6
- }
7
-
8
- if (filter(schema) && (!onlyLeaf || !schema.properties)) {
9
- result.push(schema);
10
- return result;
11
- }
12
-
13
- if (schema.properties) {
14
- Object.keys(schema.properties).forEach((key) => {
15
- result.push(...findSchema(schema.properties[key], filter));
16
- });
17
- }
18
- return result;
19
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './server';
2
- export { default } from './server';
@@ -1,30 +0,0 @@
1
- {
2
- "Manual": "Manual",
3
- "Could be used for manually submitting data, and determine whether to continue or exit. Workflow will generate a todo item for assigned user when it reaches a manual node, and continue processing after user submits the form.": "Could be used for manually submitting data, and determine whether to continue or exit. Workflow will generate a todo item for assigned user when it reaches a manual node, and continue processing after user submits the form.",
4
- "Values preset in this form will override user submitted ones when continue or reject.": "Values preset in this form will override user submitted ones when continue or reject.",
5
- "Assignee": "Assignee",
6
- "Assignees": "Assignees",
7
- "User interface": "User interface",
8
- "Configure user interface": "Configure user interface",
9
- "View user interface": "View user interface",
10
- "Separately": "Separately",
11
- "Each user has own task": "Each user has own task",
12
- "Collaboratively": "Collaboratively",
13
- "Everyone shares one task": "Everyone shares one task",
14
- "Negotiation": "Negotiation",
15
- "All pass": "All pass",
16
- "Everyone should pass": "Everyone should pass",
17
- "Any pass": "Any pass",
18
- "Anyone pass": "Anyone pass",
19
- "Field name existed in form": "Field name existed in form",
20
- "Continue the process": "Continue the process",
21
- "Terminate the process": "Terminate the process",
22
- "Save temporarily": "Save temporarily",
23
- "Custom form": "Custom form",
24
- "Data record": "Data record",
25
- "Create record form": "Create record form",
26
- "Update record form": "Update record form",
27
- "Filter settings": "Filter settings",
28
- "Workflow todos": "Workflow todos",
29
- "Task": "Task"
30
- }
@@ -1,14 +0,0 @@
1
- import { useTranslation } from 'react-i18next';
2
-
3
- export const NAMESPACE = 'workflow-manual';
4
-
5
- export function useLang(key: string, options = {}) {
6
- const { t } = usePluginTranslation(options);
7
- return t(key);
8
- }
9
-
10
- export const lang = useLang;
11
-
12
- export function usePluginTranslation(options?) {
13
- return useTranslation(NAMESPACE, options);
14
- }
@@ -1,32 +0,0 @@
1
- {
2
- "Manual": "수동",
3
- "Could be used for manually submitting data, and determine whether to continue or exit. Workflow will generate a todo item for assigned user when it reaches a manual node, and continue processing after user submits the form.":
4
- "수동으로 데이터를 제출하고 계속 진행할지 종료할지를 결정하는 데 사용될 수 있습니다. 워크플로우가 수동 노드에 도달하면 지정된 사용자에 대해 할 일 항목을 생성하고 사용자가 양식을 제출한 후 처리를 계속합니다.",
5
- "Values preset in this form will override user submitted ones when continue or reject.":
6
- "이 양식에 사전 설정된 값은 계속 또는 거부될 때 사용자가 제출한 값이 덮어씁니다.",
7
- "Assignee": "담당자",
8
- "Assignees": "담당자",
9
- "User interface": "사용자 인터페이스",
10
- "Configure user interface": "사용자 인터페이스 구성",
11
- "View user interface": "사용자 인터페이스 보기",
12
- "Separately": "별도로",
13
- "Each user has own task": "각 사용자는 고유한 작업을 갖습니다.",
14
- "Collaboratively": "협력하여",
15
- "Everyone shares one task": "모두가 하나의 작업을 공유합니다.",
16
- "Negotiation": "협상",
17
- "All pass": "모두 통과",
18
- "Everyone should pass": "모든 사람이 통과해야 합니다.",
19
- "Any pass": "어떤 통과",
20
- "Anyone pass": "누구든 통과하면 됩니다.",
21
- "Field name existed in form": "양식에 이미 존재하는 필드 이름",
22
- "Continue the process": "프로세스 계속 진행",
23
- "Terminate the process": "프로세스 종료",
24
- "Save temporarily": "임시로 저장",
25
- "Custom form": "사용자 정의 양식",
26
- "Data record": "데이터 레코드",
27
- "Create record form": "레코드 생성 양식",
28
- "Update record form": "레코드 업데이트 양식",
29
- "Filter settings": "필터 설정",
30
- "Workflow todos": "워크플로우 할 일",
31
- "Task node": "작업 노드"
32
- }
@@ -1,32 +0,0 @@
1
- {
2
- "Manual": "人工处理",
3
- "Could be used for manually submitting data, and determine whether to continue or exit. Workflow will generate a todo item for assigned user when it reaches a manual node, and continue processing after user submits the form.": "可用于人工提交数据,并决定是否继续或退出流程。工作流在执行到人工节点时会为被指派的用户生成待办事项,直到用户提交对应表单后继续处理该流程。",
4
- "Values preset in this form will override user submitted ones when continue or reject.": "表单中预设的字段值会在用户提交继续或拒绝时覆盖相应字段的值。",
5
- "Assignee": "负责人",
6
- "Assignees": "负责人",
7
- "User interface": "操作界面",
8
- "Configure user interface": "配置界面",
9
- "View user interface": "查看界面",
10
- "Separately": "分别处理",
11
- "Each user has own task": "每个人处理各自的任务",
12
- "Collaboratively": "协作处理",
13
- "Everyone shares one task": "所有人共享同一个任务",
14
- "Negotiation": "协商机制",
15
- "All pass": "全部通过",
16
- "Everyone should pass": "每个人通过才通过",
17
- "Any pass": "任意通过",
18
- "Anyone pass": "任何一人通过即通过",
19
- "Field name existed in form": "表单中已有对应标识的字段",
20
- "Continue the process": "继续流程",
21
- "Terminate the process": "终止流程",
22
- "Save temporarily": "暂存",
23
- "Custom form": "自定义表单",
24
- "Data record": "数据记录",
25
- "Create record form": "新增数据表单",
26
- "Update record form": "更新数据表单",
27
- "Filter settings": "筛选设置",
28
- "Workflow todos": "工作流待办",
29
- "Task node": "任务节点",
30
- "Unprocessed": "未处理",
31
- "Please check one of your update record form, and add at least one filter condition in form settings.": "请检查您的其中的更新数据表单,并在表单设置中至少添加一个筛选条件。"
32
- }
@@ -1,157 +0,0 @@
1
- import { Registry } from '@nocobase/utils';
2
- import WorkflowPlugin, { Processor, JOB_STATUS, Instruction } from '@nocobase/plugin-workflow';
3
-
4
- import initFormTypes, { FormHandler } from './forms';
5
-
6
- type FormType = {
7
- type: 'custom' | 'create' | 'update';
8
- actions: number[];
9
- options: {
10
- [key: string]: any;
11
- };
12
- };
13
-
14
- export interface ManualConfig {
15
- schema: { [key: string]: any };
16
- forms: { [key: string]: FormType };
17
- assignees?: (number | string)[];
18
- mode?: number;
19
- }
20
-
21
- const MULTIPLE_ASSIGNED_MODE = {
22
- SINGLE: Symbol('single'),
23
- ALL: Symbol('all'),
24
- ANY: Symbol('any'),
25
- ALL_PERCENTAGE: Symbol('all percentage'),
26
- ANY_PERCENTAGE: Symbol('any percentage'),
27
- };
28
-
29
- const Modes = {
30
- [MULTIPLE_ASSIGNED_MODE.SINGLE]: {
31
- getStatus(distribution, assignees) {
32
- const done = distribution.find((item) => item.status !== JOB_STATUS.PENDING && item.count > 0);
33
- return done ? done.status : null;
34
- },
35
- },
36
- [MULTIPLE_ASSIGNED_MODE.ALL]: {
37
- getStatus(distribution, assignees) {
38
- const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
39
- if (resolved && resolved.count === assignees.length) {
40
- return JOB_STATUS.RESOLVED;
41
- }
42
- const rejected = distribution.find((item) => item.status < JOB_STATUS.PENDING);
43
- if (rejected && rejected.count) {
44
- return rejected.status;
45
- }
46
-
47
- return null;
48
- },
49
- },
50
- [MULTIPLE_ASSIGNED_MODE.ANY]: {
51
- getStatus(distribution, assignees) {
52
- const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
53
- if (resolved && resolved.count) {
54
- return JOB_STATUS.RESOLVED;
55
- }
56
- const rejectedCount = distribution.reduce(
57
- (count, item) => (item.status < JOB_STATUS.PENDING ? count + item.count : count),
58
- 0,
59
- );
60
- // NOTE: all failures are considered as rejected for now
61
- if (rejectedCount === assignees.length) {
62
- return JOB_STATUS.REJECTED;
63
- }
64
-
65
- return null;
66
- },
67
- },
68
- };
69
-
70
- function getMode(mode) {
71
- switch (true) {
72
- case mode === 1:
73
- return Modes[MULTIPLE_ASSIGNED_MODE.ALL];
74
- case mode === -1:
75
- return Modes[MULTIPLE_ASSIGNED_MODE.ANY];
76
- case mode > 0:
77
- return Modes[MULTIPLE_ASSIGNED_MODE.ALL_PERCENTAGE];
78
- case mode < 0:
79
- return Modes[MULTIPLE_ASSIGNED_MODE.ANY_PERCENTAGE];
80
- default:
81
- return Modes[MULTIPLE_ASSIGNED_MODE.SINGLE];
82
- }
83
- }
84
-
85
- export default class extends Instruction {
86
- formTypes = new Registry<FormHandler>();
87
-
88
- constructor(public workflow: WorkflowPlugin) {
89
- super(workflow);
90
-
91
- initFormTypes(this);
92
- }
93
-
94
- async run(node, prevJob, processor: Processor) {
95
- const { mode, ...config } = node.config as ManualConfig;
96
- const assignees = [...new Set(processor.getParsedValue(config.assignees, node.id).flat().filter(Boolean))];
97
-
98
- const job = await processor.saveJob({
99
- status: assignees.length ? JOB_STATUS.PENDING : JOB_STATUS.RESOLVED,
100
- result: mode ? [] : null,
101
- nodeId: node.id,
102
- nodeKey: node.key,
103
- upstreamId: prevJob?.id ?? null,
104
- });
105
-
106
- if (!assignees.length) {
107
- return job;
108
- }
109
-
110
- // NOTE: batch create users jobs
111
- const UserJobModel = this.workflow.app.db.getModel('users_jobs');
112
- await UserJobModel.bulkCreate(
113
- assignees.map((userId) => ({
114
- userId,
115
- jobId: job.id,
116
- nodeId: node.id,
117
- executionId: job.executionId,
118
- workflowId: node.workflowId,
119
- status: JOB_STATUS.PENDING,
120
- })),
121
- {
122
- // transaction: processor.transaction,
123
- },
124
- );
125
-
126
- return job;
127
- }
128
-
129
- async resume(node, job, processor: Processor) {
130
- // NOTE: check all users jobs related if all done then continue as parallel
131
- const { mode } = node.config as ManualConfig;
132
- const assignees = [...new Set(processor.getParsedValue(node.config.assignees, node.id).flat().filter(Boolean))];
133
-
134
- const UserJobModel = this.workflow.app.db.getModel('users_jobs');
135
- const distribution = await UserJobModel.count({
136
- where: {
137
- jobId: job.id,
138
- },
139
- group: ['status'],
140
- // transaction: processor.transaction,
141
- });
142
-
143
- const submitted = distribution.reduce(
144
- (count, item) => (item.status !== JOB_STATUS.PENDING ? count + item.count : count),
145
- 0,
146
- );
147
- const status = job.status || (getMode(mode).getStatus(distribution, assignees) ?? JOB_STATUS.PENDING);
148
- const result = mode ? (submitted || 0) / assignees.length : job.latestUserJob?.result ?? job.result;
149
- processor.logger.debug(`manual resume job and next status: ${status}`);
150
- job.set({
151
- status,
152
- result,
153
- });
154
-
155
- return job;
156
- }
157
- }
@@ -1,43 +0,0 @@
1
- import { Plugin } from '@nocobase/server';
2
- import actions from '@nocobase/actions';
3
- import { HandlerType } from '@nocobase/resourcer';
4
- import WorkflowPlugin, { JOB_STATUS } from '@nocobase/plugin-workflow';
5
-
6
- import path from 'path';
7
- import { submit } from './actions';
8
-
9
- import ManualInstruction from './ManualInstruction';
10
-
11
- export default class extends Plugin {
12
- async load() {
13
- await this.importCollections(path.resolve(__dirname, 'collections'));
14
-
15
- this.app.resource({
16
- name: 'users_jobs',
17
- actions: {
18
- list: {
19
- filter: {
20
- $or: [
21
- {
22
- 'workflow.enabled': true,
23
- },
24
- {
25
- 'workflow.enabled': false,
26
- status: {
27
- $ne: JOB_STATUS.PENDING,
28
- },
29
- },
30
- ],
31
- },
32
- handler: actions.list as HandlerType,
33
- },
34
- submit,
35
- },
36
- });
37
-
38
- this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
39
-
40
- const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin;
41
- workflowPlugin.registerInstruction('manual', ManualInstruction);
42
- }
43
- }