@nocobase/plugin-workflow-manual 0.19.0-alpha.3 → 0.19.0-alpha.5

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.
@@ -0,0 +1,233 @@
1
+ import { faker } from '@faker-js/faker';
2
+ import {
3
+ CollectionTriggerNode,
4
+ apiCreateWorkflow,
5
+ apiDeleteWorkflow,
6
+ apiGetWorkflow,
7
+ apiUpdateWorkflowTrigger,
8
+ appendJsonCollectionName,
9
+ generalWithNoRelationalFields,
10
+ ManualNode,
11
+ } from '@nocobase/plugin-workflow-test/e2e';
12
+ import { expect, test } from '@nocobase/test/e2e';
13
+ import { dayjs } from '@nocobase/utils';
14
+
15
+ test('filter task node', async ({ page, mockPage, mockCollections, mockRecords }) => {
16
+ //数据表后缀标识
17
+ const triggerNodeAppendText = 'a' + faker.string.alphanumeric(4);
18
+ const manualNodeAppendText = 'b' + dayjs().format('HHmmss').toString();
19
+
20
+ // 创建触发器节点数据表
21
+ const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`;
22
+ const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`;
23
+ const triggerNodeFieldName = 'orgname';
24
+ const triggerNodeFieldDisplayName = '公司名称(单行文本)';
25
+ await mockCollections(
26
+ appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText)
27
+ .collections,
28
+ );
29
+ // 创建Manual节点数据表
30
+ const manualNodeCollectionDisplayName = `自动>组织[普通表]${manualNodeAppendText}`;
31
+ const manualNodeCollectionName = `tt_amt_org${manualNodeAppendText}`;
32
+ const manualNodeFieldName = 'orgname';
33
+ const manualNodeFieldDisplayName = '公司名称(单行文本)';
34
+ await mockCollections(
35
+ appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), manualNodeAppendText)
36
+ .collections,
37
+ );
38
+ //添加工作流
39
+ const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText;
40
+ const workflowData = {
41
+ current: true,
42
+ options: { deleteExecutionOnStatus: [] },
43
+ title: workFlowName,
44
+ type: 'collection',
45
+ enabled: true,
46
+ };
47
+ const workflow = await apiCreateWorkflow(workflowData);
48
+ const workflowObj = JSON.parse(JSON.stringify(workflow));
49
+ const workflowId = workflowObj.id;
50
+ //配置工作流触发器
51
+ const triggerNodeData = {
52
+ config: { appends: [], collection: triggerNodeCollectionName, changed: [], condition: { $and: [] }, mode: 1 },
53
+ };
54
+ const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData);
55
+ const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode));
56
+ //配置Manual节点
57
+ await page.goto(`admin/workflow/workflows/${workflowId}`);
58
+ await page.waitForLoadState('networkidle');
59
+ const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName);
60
+ await collectionTriggerNode.addNodeButton.click();
61
+ await page.getByRole('button', { name: 'manual', exact: true }).click();
62
+ const manualNodeName = 'Manual' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
63
+ await page.getByLabel('Manual-Manual', { exact: true }).getByRole('textbox').fill(manualNodeName);
64
+ const manualNode = new ManualNode(page, manualNodeName);
65
+ const manualNodeId = await manualNode.node.locator('.workflow-node-id').innerText();
66
+ await manualNode.nodeConfigure.click();
67
+ await manualNode.assigneesDropDown.click();
68
+ await page.getByRole('option', { name: 'Super Admin' }).click();
69
+ await manualNode.configureUserInterfaceButton.click();
70
+ await manualNode.addBlockButton.hover();
71
+ await manualNode.createRecordFormMenu.hover();
72
+ await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
73
+ await page.mouse.move(300, 0, { steps: 100 });
74
+ await page
75
+ .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
76
+ .hover();
77
+ await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
78
+ await page.getByRole('menuitem', { name: 'Edit block title' }).click();
79
+ const blockTitle = 'Create record' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
80
+ await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
81
+ await page.getByRole('button', { name: 'OK', exact: true }).click();
82
+ await page
83
+ .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
84
+ .hover();
85
+ await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
86
+ await page.mouse.move(300, 0, { steps: 100 });
87
+ await page.mouse.click(300, 0);
88
+ await manualNode.submitButton.click();
89
+ await page.waitForLoadState('networkidle');
90
+
91
+ // 2、测试步骤:添加数据触发工作流
92
+ const triggerNodeCollectionRecordOne = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
93
+ const triggerNodeCollectionRecords = await mockRecords(triggerNodeCollectionName, [
94
+ { orgname: triggerNodeCollectionRecordOne },
95
+ ]);
96
+ await page.waitForTimeout(1000);
97
+ // 3、预期结果:工作流成功触发,待办弹窗表单中显示数据
98
+ const getWorkflow = await apiGetWorkflow(workflowId);
99
+ const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow));
100
+ const getWorkflowExecuted = getWorkflowObj.executed;
101
+ expect(getWorkflowExecuted).toBe(1);
102
+
103
+ const newPage = mockPage();
104
+ await newPage.goto();
105
+ await page.waitForLoadState('networkidle');
106
+ await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
107
+ await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
108
+ await page.mouse.move(300, 0, { steps: 100 });
109
+ await page.waitForTimeout(300);
110
+ await page.getByLabel('action-Filter.Action-Filter-filter-users_jobs-workflow-todo').click();
111
+ await page.getByText('Add condition', { exact: true }).click();
112
+ await page.getByTestId('select-filter-field').click();
113
+ await page.getByRole('menuitemcheckbox', { name: 'Task right' }).click();
114
+ await page.getByRole('menuitemcheckbox', { name: 'Title' }).click();
115
+ await page.getByRole('textbox').fill(manualNodeName);
116
+ await page.getByRole('button', { name: 'Submit' }).click();
117
+
118
+ // 3、预期结果:列表中出现筛选的工作流
119
+ await expect(page.getByText(manualNodeName)).toBeAttached();
120
+
121
+ // 4、后置处理:删除工作流
122
+ await apiDeleteWorkflow(workflowId);
123
+ });
124
+
125
+ test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecords }) => {
126
+ //数据表后缀标识
127
+ const triggerNodeAppendText = 'a' + faker.string.alphanumeric(4);
128
+ const manualNodeAppendText = 'b' + dayjs().format('HHmmss').toString();
129
+
130
+ // 创建触发器节点数据表
131
+ const triggerNodeCollectionDisplayName = `自动>组织[普通表]${triggerNodeAppendText}`;
132
+ const triggerNodeCollectionName = `tt_amt_org${triggerNodeAppendText}`;
133
+ const triggerNodeFieldName = 'orgname';
134
+ const triggerNodeFieldDisplayName = '公司名称(单行文本)';
135
+ await mockCollections(
136
+ appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), triggerNodeAppendText)
137
+ .collections,
138
+ );
139
+ // 创建Manual节点数据表
140
+ const manualNodeCollectionDisplayName = `自动>组织[普通表]${manualNodeAppendText}`;
141
+ const manualNodeCollectionName = `tt_amt_org${manualNodeAppendText}`;
142
+ const manualNodeFieldName = 'orgname';
143
+ const manualNodeFieldDisplayName = '公司名称(单行文本)';
144
+ await mockCollections(
145
+ appendJsonCollectionName(JSON.parse(JSON.stringify(generalWithNoRelationalFields)), manualNodeAppendText)
146
+ .collections,
147
+ );
148
+ //添加工作流
149
+ const workFlowName = faker.string.alphanumeric(5) + triggerNodeAppendText;
150
+ const workflowData = {
151
+ current: true,
152
+ options: { deleteExecutionOnStatus: [] },
153
+ title: workFlowName,
154
+ type: 'collection',
155
+ enabled: true,
156
+ };
157
+ const workflow = await apiCreateWorkflow(workflowData);
158
+ const workflowObj = JSON.parse(JSON.stringify(workflow));
159
+ const workflowId = workflowObj.id;
160
+ //配置工作流触发器
161
+ const triggerNodeData = {
162
+ config: { appends: [], collection: triggerNodeCollectionName, changed: [], condition: { $and: [] }, mode: 1 },
163
+ };
164
+ const triggerNode = await apiUpdateWorkflowTrigger(workflowId, triggerNodeData);
165
+ const triggerNodeObj = JSON.parse(JSON.stringify(triggerNode));
166
+ //配置Manual节点
167
+ await page.goto(`admin/workflow/workflows/${workflowId}`);
168
+ await page.waitForLoadState('networkidle');
169
+ const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName);
170
+ await collectionTriggerNode.addNodeButton.click();
171
+ await page.getByRole('button', { name: 'manual', exact: true }).click();
172
+ const manualNodeName = 'Manual' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
173
+ await page.getByLabel('Manual-Manual', { exact: true }).getByRole('textbox').fill(manualNodeName);
174
+ const manualNode = new ManualNode(page, manualNodeName);
175
+ const manualNodeId = await manualNode.node.locator('.workflow-node-id').innerText();
176
+ await manualNode.nodeConfigure.click();
177
+ await manualNode.assigneesDropDown.click();
178
+ await page.getByRole('option', { name: 'Super Admin' }).click();
179
+ await manualNode.configureUserInterfaceButton.click();
180
+ await manualNode.addBlockButton.hover();
181
+ await manualNode.createRecordFormMenu.hover();
182
+ await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click();
183
+ await page.mouse.move(300, 0, { steps: 100 });
184
+ await page
185
+ .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
186
+ .hover();
187
+ await page.getByLabel(`designer-schema-settings-CardItem-CreateFormDesigner-${manualNodeCollectionName}`).hover();
188
+ await page.getByRole('menuitem', { name: 'Edit block title' }).click();
189
+ const blockTitle = 'Create record' + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
190
+ await page.getByLabel('Edit block title').getByRole('textbox').fill(blockTitle);
191
+ await page.getByRole('button', { name: 'OK', exact: true }).click();
192
+ await page
193
+ .locator(`button[aria-label^="schema-initializer-Grid-FormItemInitializers-${manualNodeCollectionName}"]`)
194
+ .hover();
195
+ await page.getByRole('menuitem', { name: manualNodeFieldDisplayName }).getByRole('switch').click();
196
+ await page.mouse.move(300, 0, { steps: 100 });
197
+ await page.mouse.click(300, 0);
198
+ await manualNode.submitButton.click();
199
+ await page.waitForLoadState('networkidle');
200
+
201
+ // 2、测试步骤:添加数据触发工作流
202
+ const triggerNodeCollectionRecordOne = triggerNodeFieldDisplayName + dayjs().format('YYYYMMDDHHmmss.SSS').toString();
203
+ const triggerNodeCollectionRecords = await mockRecords(triggerNodeCollectionName, [
204
+ { orgname: triggerNodeCollectionRecordOne },
205
+ ]);
206
+ await page.waitForTimeout(1000);
207
+ // 3、预期结果:工作流成功触发,待办弹窗表单中显示数据
208
+ const getWorkflow = await apiGetWorkflow(workflowId);
209
+ const getWorkflowObj = JSON.parse(JSON.stringify(getWorkflow));
210
+ const getWorkflowExecuted = getWorkflowObj.executed;
211
+ expect(getWorkflowExecuted).toBe(1);
212
+
213
+ const newPage = mockPage();
214
+ await newPage.goto();
215
+ await page.waitForLoadState('networkidle');
216
+ await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover();
217
+ await page.getByRole('menuitem', { name: 'check-square Workflow todos' }).click();
218
+ await page.mouse.move(300, 0, { steps: 100 });
219
+ await page.waitForTimeout(300);
220
+ await page.getByLabel('action-Filter.Action-Filter-filter-users_jobs-workflow-todo').click();
221
+ await page.getByText('Add condition', { exact: true }).click();
222
+ await page.getByTestId('select-filter-field').click();
223
+ await page.getByRole('menuitemcheckbox', { name: 'Workflow right' }).click();
224
+ await page.getByRole('menuitemcheckbox', { name: 'Name' }).click();
225
+ await page.getByRole('textbox').fill(workFlowName);
226
+ await page.getByRole('button', { name: 'Submit' }).click();
227
+
228
+ // 3、预期结果:列表中出现筛选的工作流
229
+ await expect(page.getByText(manualNodeName)).toBeAttached();
230
+
231
+ // 4、后置处理:删除工作流
232
+ await apiDeleteWorkflow(workflowId);
233
+ });
@@ -20,8 +20,7 @@ export default class extends Plugin {
20
20
 
21
21
  // this.app.addProvider(Provider);
22
22
  const workflow = this.app.pm.get('workflow') as WorkflowPlugin;
23
- const manualInstruction = new Manual();
24
- workflow.instructions.register(manualInstruction.type, manualInstruction);
23
+ workflow.registerInstruction('manual', Manual);
25
24
 
26
25
  this.app.schemaInitializerManager.add(addBlockButton);
27
26
  this.app.schemaInitializerManager.add(addActionButton);
@@ -416,7 +416,6 @@ export function SchemaConfig({ value, onChange }) {
416
416
  const nodeComponents = {};
417
417
  nodes.forEach((item) => {
418
418
  const instruction = workflowPlugin.instructions.get(item.type);
419
- Object.assign(nodeInitializers, instruction.initializers);
420
419
  Object.assign(nodeComponents, instruction.components);
421
420
  });
422
421
 
@@ -1,6 +1,6 @@
1
- import { SchemaInitializerItemType, useCollectionManager, useCompile } from '@nocobase/client';
1
+ import { SchemaInitializerItemType, useCollectionManager, useCompile, usePlugin } from '@nocobase/client';
2
2
 
3
- import {
3
+ import WorkflowPlugin, {
4
4
  defaultFieldNames,
5
5
  getCollectionFieldOptions,
6
6
  CollectionBlockInitializer,
@@ -154,4 +154,7 @@ export default class extends Instruction {
154
154
  }
155
155
  : null;
156
156
  }
157
+ isAvailable({ engine, workflow, upstream, branchIndex }) {
158
+ return !engine.isWorkflowSync(workflow);
159
+ }
157
160
  }
@@ -0,0 +1,32 @@
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
+ }
@@ -26,5 +26,6 @@
26
26
  "Update record form": "更新数据表单",
27
27
  "Filter settings": "筛选设置",
28
28
  "Workflow todos": "工作流待办",
29
- "Task node": "任务节点"
29
+ "Task node": "任务节点",
30
+ "Unprocessed": "未处理"
30
31
  }
@@ -99,6 +99,7 @@ export default class extends Instruction {
99
99
  status: JOB_STATUS.PENDING,
100
100
  result: mode ? [] : null,
101
101
  nodeId: node.id,
102
+ nodeKey: node.key,
102
103
  upstreamId: prevJob?.id ?? null,
103
104
  });
104
105
 
@@ -37,7 +37,7 @@ export default class extends Plugin {
37
37
 
38
38
  this.app.acl.allow('users_jobs', ['list', 'get', 'submit'], 'loggedIn');
39
39
 
40
- const workflowPlugin = this.app.getPlugin<WorkflowPlugin>(WorkflowPlugin);
40
+ const workflowPlugin = this.app.pm.get(WorkflowPlugin) as WorkflowPlugin;
41
41
  workflowPlugin.registerInstruction('manual', ManualInstruction);
42
42
  }
43
43
  }