@gadmin2n/schematics 0.0.146 → 0.0.148

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 (95) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.local.md +8 -0
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/CODEBUDDY.md +70 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/gitignore +1 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +67 -15
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +17 -4
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +5 -0
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.spec.ts +24 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +15 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +2 -6
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +28 -29
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +16 -34
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +3 -3
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +61 -6
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +2 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +11 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +126 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +136 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.controller.ts +24 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.dto.ts +8 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +62 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.ts +94 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.types.ts +6 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staffDirectory.module.ts +10 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user.provider.ts +6 -2
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +20 -12
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +21 -19
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +11 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +97 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +150 -2
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.controller.ts +13 -1
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.dto.ts +13 -1
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.errors.ts +1 -0
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.spec.ts +125 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.ts +118 -5
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +20 -3
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +6 -9
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +2 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +4 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +5 -1
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +44 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +15 -1
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +2 -3
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +33 -22
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +10 -23
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.test.tsx +69 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.tsx +145 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/index.ts +2 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/staff-directory.api.ts +16 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +20 -2
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +6 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +49 -11
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +49 -11
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/PeopleGroupModal.tsx +181 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +203 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/people-groups.api.ts +145 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/PersonModal.tsx +112 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +214 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/person-directory.api.ts +79 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +7 -11
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +7 -7
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +20 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +97 -28
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +87 -119
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +22 -1
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +8 -2
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +8 -46
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +7 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +32 -3
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +163 -106
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +8 -1
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +26 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -6
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +13 -16
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +185 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +224 -105
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +1 -1
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +21 -12
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +1 -2
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +160 -8
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +125 -55
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +47 -3
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.test.tsx +33 -1
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +28 -46
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +16 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/subject-provider.api.ts +0 -6
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +24 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +14 -17
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
  93. package/package.json +1 -1
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +0 -162
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +0 -72
@@ -4,7 +4,33 @@ import i18n from '../../../../i18n';
4
4
  import { HumanApprovalConfigEditor } from './HumanApprovalConfigEditor';
5
5
 
6
6
  vi.mock('./SubjectPicker', () => ({
7
- SubjectPicker: () => <div data-testid="subject-picker" />,
7
+ SubjectPicker: (props: any) => {
8
+ const provider = props.allowedProviders?.[0];
9
+ const isGroup = provider === 'USER_GROUP';
10
+ return (
11
+ <div data-testid={`subject-picker-${provider}`}>
12
+ <button
13
+ aria-label={isGroup ? '选择固定人员组' : '选择固定人员'}
14
+ onClick={() =>
15
+ props.onChange([
16
+ {
17
+ provider,
18
+ refs: [isGroup ? 'finance' : 'alice'],
19
+ },
20
+ ])
21
+ }
22
+ />
23
+ {!isGroup ? (
24
+ <button
25
+ aria-label="继续添加固定人员"
26
+ onClick={() =>
27
+ props.onChange([{ provider: 'USER', refs: ['alice', 'bob'] }])
28
+ }
29
+ />
30
+ ) : null}
31
+ </div>
32
+ );
33
+ },
8
34
  }));
9
35
 
10
36
  Object.defineProperty(window, 'matchMedia', {
@@ -116,9 +142,9 @@ describe('HumanApprovalConfigEditor', () => {
116
142
  );
117
143
  });
118
144
 
119
- it('adds an HTTP and Code lookup inside the approval node', () => {
145
+ it('can add multiple HTTP and Code lookups inside the approval node', () => {
120
146
  const onChange = vi.fn();
121
- const { container } = render(
147
+ const { container, rerender } = render(
122
148
  <HumanApprovalConfigEditor
123
149
  value={config}
124
150
  onChange={onChange}
@@ -126,30 +152,157 @@ describe('HumanApprovalConfigEditor', () => {
126
152
  />,
127
153
  );
128
154
 
129
- fireEvent.click(screen.getByRole('button', { name: '添加动态用户' }));
155
+ fireEvent.click(screen.getByRole('button', { name: '添加动态审批人' }));
130
156
 
131
157
  expect(onChange).toHaveBeenLastCalledWith(
132
158
  expect.objectContaining({
133
159
  subjects: [
134
160
  {
135
161
  source: 'DYNAMIC',
136
- provider: 'USER',
137
162
  inputKey: 'dynamic_users',
138
163
  label: '查询审批人',
139
164
  lookup: {
140
165
  request: expect.objectContaining({ method: 'GET', path: '/' }),
141
166
  result: expect.objectContaining({
142
167
  mode: 'runOnceForAll',
143
- script: expect.stringContaining("provider: 'USER'"),
168
+ script: expect.stringContaining('return approverIds;'),
144
169
  }),
145
170
  },
146
171
  },
147
172
  ],
148
173
  }),
149
174
  );
175
+ const dynamicConfig = onChange.mock.lastCall?.[0];
176
+ rerender(
177
+ <HumanApprovalConfigEditor
178
+ value={dynamicConfig}
179
+ onChange={onChange}
180
+ schema={{ type: 'object' }}
181
+ />,
182
+ );
183
+ fireEvent.click(screen.getByRole('button', { name: '添加动态审批人' }));
184
+ expect(onChange.mock.lastCall?.[0].subjects).toEqual([
185
+ dynamicConfig.subjects[0],
186
+ expect.objectContaining({
187
+ source: 'DYNAMIC',
188
+ inputKey: 'dynamic_users_2',
189
+ }),
190
+ ]);
150
191
  expect(container.querySelector('.human-approval-config__rail')).toBeNull();
151
192
  });
152
193
 
194
+ it('can add repeated fixed sources and a separate fixed group source', () => {
195
+ const onChange = vi.fn();
196
+ const { rerender } = render(
197
+ <HumanApprovalConfigEditor
198
+ value={config}
199
+ onChange={onChange}
200
+ schema={{ type: 'object' }}
201
+ />,
202
+ );
203
+
204
+ expect(screen.queryByTestId('subject-picker-USER')).not.toBeInTheDocument();
205
+ expect(screen.getByText('暂未添加固定人员或人员组')).toBeInTheDocument();
206
+
207
+ fireEvent.click(screen.getByRole('button', { name: '添加人员' }));
208
+ const peopleConfig = onChange.mock.lastCall?.[0];
209
+ expect(peopleConfig).toEqual(
210
+ expect.objectContaining({
211
+ subjects: [{ source: 'STATIC', provider: 'USER', refs: [] }],
212
+ }),
213
+ );
214
+
215
+ rerender(
216
+ <HumanApprovalConfigEditor
217
+ value={peopleConfig}
218
+ onChange={onChange}
219
+ schema={{ type: 'object' }}
220
+ />,
221
+ );
222
+
223
+ expect(screen.getByTestId('subject-picker-USER')).toBeInTheDocument();
224
+ expect(
225
+ screen.getByRole('button', { name: '添加人员组' }),
226
+ ).toBeInTheDocument();
227
+
228
+ fireEvent.click(screen.getByRole('button', { name: '选择固定人员' }));
229
+ const selectedPeopleConfig = onChange.mock.lastCall?.[0];
230
+ expect(selectedPeopleConfig).toEqual(
231
+ expect.objectContaining({
232
+ subjects: [{ source: 'STATIC', provider: 'USER', refs: ['alice'] }],
233
+ }),
234
+ );
235
+
236
+ rerender(
237
+ <HumanApprovalConfigEditor
238
+ value={selectedPeopleConfig}
239
+ onChange={onChange}
240
+ schema={{ type: 'object' }}
241
+ />,
242
+ );
243
+ fireEvent.click(screen.getByRole('button', { name: '继续添加固定人员' }));
244
+ const morePeopleConfig = onChange.mock.lastCall?.[0];
245
+ expect(morePeopleConfig.subjects[0].refs).toEqual(['alice', 'bob']);
246
+
247
+ rerender(
248
+ <HumanApprovalConfigEditor
249
+ value={morePeopleConfig}
250
+ onChange={onChange}
251
+ schema={{ type: 'object' }}
252
+ />,
253
+ );
254
+ expect(
255
+ screen.getByRole('button', { name: '添加人员' }),
256
+ ).toBeInTheDocument();
257
+ fireEvent.click(screen.getByRole('button', { name: '添加人员' }));
258
+ const repeatedPeopleConfig = onChange.mock.lastCall?.[0];
259
+ expect(repeatedPeopleConfig.subjects).toEqual([
260
+ { source: 'STATIC', provider: 'USER', refs: ['alice', 'bob'] },
261
+ { source: 'STATIC', provider: 'USER', refs: [] },
262
+ ]);
263
+
264
+ rerender(
265
+ <HumanApprovalConfigEditor
266
+ value={repeatedPeopleConfig}
267
+ onChange={onChange}
268
+ schema={{ type: 'object' }}
269
+ />,
270
+ );
271
+ expect(screen.getAllByTestId('subject-picker-USER')).toHaveLength(2);
272
+ fireEvent.click(screen.getAllByRole('button', { name: '选择固定人员' })[1]);
273
+ const selectedRepeatedPeopleConfig = onChange.mock.lastCall?.[0];
274
+
275
+ rerender(
276
+ <HumanApprovalConfigEditor
277
+ value={selectedRepeatedPeopleConfig}
278
+ onChange={onChange}
279
+ schema={{ type: 'object' }}
280
+ />,
281
+ );
282
+ fireEvent.click(screen.getByRole('button', { name: '添加人员组' }));
283
+ const bothSourcesConfig = onChange.mock.lastCall?.[0];
284
+ rerender(
285
+ <HumanApprovalConfigEditor
286
+ value={bothSourcesConfig}
287
+ onChange={onChange}
288
+ schema={{ type: 'object' }}
289
+ />,
290
+ );
291
+
292
+ expect(screen.getAllByTestId('subject-picker-USER')).toHaveLength(2);
293
+ expect(screen.getByTestId('subject-picker-USER_GROUP')).toBeInTheDocument();
294
+ fireEvent.click(screen.getByRole('button', { name: '选择固定人员组' }));
295
+ expect(onChange).toHaveBeenLastCalledWith(
296
+ expect.objectContaining({
297
+ subjects: expect.arrayContaining([
298
+ { source: 'STATIC', provider: 'USER', refs: ['alice', 'bob'] },
299
+ { source: 'STATIC', provider: 'USER', refs: ['alice'] },
300
+ { source: 'STATIC', provider: 'USER_GROUP', refs: ['finance'] },
301
+ ]),
302
+ }),
303
+ );
304
+ });
305
+
153
306
  it('uses the collapsible sections without repeating a lookup flow summary', () => {
154
307
  const { container } = render(
155
308
  <HumanApprovalConfigEditor
@@ -158,7 +311,6 @@ describe('HumanApprovalConfigEditor', () => {
158
311
  subjects: [
159
312
  {
160
313
  source: 'DYNAMIC',
161
- provider: 'USER',
162
314
  inputKey: 'direct_manager',
163
315
  label: '查询直属主管',
164
316
  lookup: {
@@ -174,7 +326,7 @@ describe('HumanApprovalConfigEditor', () => {
174
326
  result: {
175
327
  mode: 'runOnceForAll',
176
328
  timeout: 5000,
177
- script: 'return { subjects: [] };',
329
+ script: "return ['leader-a'];",
178
330
  },
179
331
  },
180
332
  },
@@ -15,7 +15,7 @@ import {
15
15
  Switch,
16
16
  Typography,
17
17
  } from 'antd';
18
- import { useMemo, useState } from 'react';
18
+ import { useState } from 'react';
19
19
  import { useTranslation } from 'react-i18next';
20
20
  import { ParameterBindingEditor } from '../../components/ParameterBindingEditor';
21
21
  import type { ParameterBinding } from '../../types/parameter-binding';
@@ -26,17 +26,19 @@ import { HttpRequestConfigEditor } from './HttpRequestConfigEditor';
26
26
  import { SubjectPicker, type SubjectSelection } from './SubjectPicker';
27
27
  import './human-approval-config.css';
28
28
 
29
- type Provider = 'USER' | 'USER_GROUP';
30
29
  type EmbeddedCodeConfig = {
31
30
  script: string;
32
31
  mode: 'runOnceForAll';
33
32
  timeout: number;
34
33
  };
35
34
  type SubjectSource =
36
- | { source: 'STATIC'; provider: Provider; refs: string[] }
35
+ | {
36
+ source: 'STATIC';
37
+ provider?: 'USER' | 'USER_GROUP';
38
+ refs: string[];
39
+ }
37
40
  | {
38
41
  source: 'DYNAMIC';
39
- provider: Provider;
40
42
  inputKey: string;
41
43
  label: string;
42
44
  lookup: {
@@ -49,6 +51,7 @@ type Rule = {
49
51
  count?: number | null;
50
52
  percentage?: number | null;
51
53
  };
54
+
52
55
  type TaskDetail = {
53
56
  key: string;
54
57
  label: string;
@@ -126,7 +129,7 @@ return {
126
129
  },
127
130
  };
128
131
 
129
- function defaultDynamicLookup(provider: Provider): {
132
+ function defaultDynamicLookup(): {
130
133
  request: Record<string, unknown>;
131
134
  result: EmbeddedCodeConfig;
132
135
  } {
@@ -145,13 +148,11 @@ function defaultDynamicLookup(provider: Provider): {
145
148
  timeout: 5000,
146
149
  script: `// response 是查询接口返回的 { status, data, headers }。
147
150
  // 从响应中提取审批人 ID,并返回统一格式。
148
- const refs = response?.data?.refs;
149
- if (!Array.isArray(refs) || refs.length === 0) {
151
+ const approverIds = response?.data?.approverIds;
152
+ if (!Array.isArray(approverIds) || approverIds.length === 0) {
150
153
  throw new Error('未查询到有效的审批人');
151
154
  }
152
- return {
153
- subjects: [{ provider: '${provider}', refs }],
154
- };`,
155
+ return approverIds;`,
155
156
  },
156
157
  };
157
158
  }
@@ -176,16 +177,19 @@ export function HumanApprovalConfigEditor({
176
177
  blockCancellationOnResolved: value?.blockCancellationOnResolved ?? false,
177
178
  timeout: value?.timeout ?? { durationSeconds: 0, action: 'REJECTED' },
178
179
  };
179
- const staticSelections = useMemo<SubjectSelection[]>(() => {
180
- const grouped = new Map<string, string[]>();
181
- for (const subject of config.subjects) {
182
- if (subject.source !== 'STATIC') continue;
183
- grouped.set(subject.provider, [
184
- ...new Set([...(grouped.get(subject.provider) ?? []), ...subject.refs]),
185
- ]);
186
- }
187
- return [...grouped].map(([provider, refs]) => ({ provider, refs }));
188
- }, [config.subjects]);
180
+ const staticSources = config.subjects.flatMap((subject, subjectIndex) =>
181
+ subject.source === 'STATIC'
182
+ ? [
183
+ {
184
+ subjectIndex,
185
+ selection: {
186
+ provider: subject.provider ?? 'USER',
187
+ refs: subject.refs,
188
+ } satisfies SubjectSelection,
189
+ },
190
+ ]
191
+ : [],
192
+ );
189
193
  const dynamicSources = config.subjects.filter(
190
194
  (subject): subject is Extract<SubjectSource, { source: 'DYNAMIC' }> =>
191
195
  subject.source === 'DYNAMIC',
@@ -219,19 +223,38 @@ export function HumanApprovalConfigEditor({
219
223
  onChange(gadminOnlyConfig);
220
224
  }
221
225
 
222
- function updateStatic(next: SubjectSelection[]) {
226
+ function updateStatic(
227
+ subjectIndex: number,
228
+ provider: 'USER' | 'USER_GROUP',
229
+ next: SubjectSelection[],
230
+ ) {
231
+ const refs =
232
+ next.find((subject) => subject.provider === provider)?.refs ?? [];
233
+ update({
234
+ subjects: config.subjects.map((subject, index) =>
235
+ index === subjectIndex
236
+ ? { source: 'STATIC' as const, provider, refs }
237
+ : subject,
238
+ ),
239
+ });
240
+ }
241
+
242
+ function addStatic(provider: 'USER' | 'USER_GROUP' = 'USER') {
223
243
  update({
224
244
  subjects: [
225
- ...next.map((subject) => ({
226
- source: 'STATIC' as const,
227
- provider: subject.provider as Provider,
228
- refs: subject.refs,
229
- })),
245
+ ...config.subjects.filter((subject) => subject.source === 'STATIC'),
246
+ { source: 'STATIC', provider, refs: [] },
230
247
  ...dynamicSources,
231
248
  ],
232
249
  });
233
250
  }
234
251
 
252
+ function removeStatic(subjectIndex: number) {
253
+ update({
254
+ subjects: config.subjects.filter((_, index) => index !== subjectIndex),
255
+ });
256
+ }
257
+
235
258
  function updateDetail(index: number, patch: Partial<TaskDetail>) {
236
259
  updateTask({
237
260
  details: config.task.details.map((detail, current) =>
@@ -253,8 +276,8 @@ export function HumanApprovalConfigEditor({
253
276
  });
254
277
  }
255
278
 
256
- function addDynamic(provider: Provider) {
257
- const base = provider === 'USER' ? 'dynamic_users' : 'dynamic_user_groups';
279
+ function addDynamic() {
280
+ const base = 'dynamic_users';
258
281
  const existing = new Set(dynamicSources.map((source) => source.inputKey));
259
282
  let inputKey = base;
260
283
  let suffix = 2;
@@ -264,13 +287,9 @@ export function HumanApprovalConfigEditor({
264
287
  ...config.subjects,
265
288
  {
266
289
  source: 'DYNAMIC',
267
- provider,
268
290
  inputKey,
269
- label:
270
- provider === 'USER'
271
- ? t('approval.dynamicUserDefaultName')
272
- : t('approval.dynamicGroupDefaultName'),
273
- lookup: defaultDynamicLookup(provider),
291
+ label: t('approval.dynamicUserDefaultName'),
292
+ lookup: defaultDynamicLookup(),
274
293
  },
275
294
  ],
276
295
  });
@@ -554,13 +573,76 @@ export function HumanApprovalConfigEditor({
554
573
  >
555
574
  {t('approval.description')}
556
575
  </Typography.Paragraph>
557
- <Typography.Text strong>{t('approval.fixedApprovers')}</Typography.Text>
558
- <SubjectPicker
559
- value={staticSelections}
560
- onChange={updateStatic}
561
- disabled={disabled}
562
- schema={schema}
563
- />
576
+ <div className="human-approval-config__fixed-heading">
577
+ <Typography.Text strong>
578
+ {t('approval.fixedApprovers')}
579
+ </Typography.Text>
580
+ <Space wrap size={8}>
581
+ <Button
582
+ size="small"
583
+ className="workflow-panel-action"
584
+ icon={<UserAddOutlined />}
585
+ aria-label={t('approval.addFixedUsers')}
586
+ disabled={disabled}
587
+ onClick={() => addStatic('USER')}
588
+ >
589
+ {t('approval.addFixedUsers')}
590
+ </Button>
591
+ <Button
592
+ size="small"
593
+ className="workflow-panel-action"
594
+ icon={<UsergroupAddOutlined />}
595
+ aria-label={t('approval.addFixedUserGroups')}
596
+ disabled={disabled}
597
+ onClick={() => addStatic('USER_GROUP')}
598
+ >
599
+ {t('approval.addFixedUserGroups')}
600
+ </Button>
601
+ </Space>
602
+ </div>
603
+ {staticSources.length === 0 ? (
604
+ <div className="human-approval-config__empty">
605
+ {t('approval.noFixedApprovers')}
606
+ </div>
607
+ ) : (
608
+ staticSources.map(({ subjectIndex, selection }) => {
609
+ const name =
610
+ selection.provider === 'USER_GROUP'
611
+ ? t('approval.fixedUserGroups')
612
+ : t('approval.fixedUsers');
613
+ return (
614
+ <div
615
+ className="human-approval-config__static-source"
616
+ key={`${selection.provider}-${subjectIndex}`}
617
+ >
618
+ <div className="human-approval-config__static-source-header">
619
+ <Typography.Text strong>{name}</Typography.Text>
620
+ <Button
621
+ type="text"
622
+ danger
623
+ icon={<DeleteOutlined />}
624
+ disabled={disabled}
625
+ aria-label={t('approval.removeFixedSource', { name })}
626
+ onClick={() => removeStatic(subjectIndex)}
627
+ />
628
+ </div>
629
+ <SubjectPicker
630
+ value={[selection]}
631
+ onChange={(next) =>
632
+ updateStatic(
633
+ subjectIndex,
634
+ selection.provider as 'USER' | 'USER_GROUP',
635
+ next,
636
+ )
637
+ }
638
+ disabled={disabled}
639
+ schema={schema}
640
+ allowedProviders={[selection.provider]}
641
+ />
642
+ </div>
643
+ );
644
+ })
645
+ )}
564
646
  <div className="human-approval-config__dynamic-heading">
565
647
  <div>
566
648
  <Typography.Text strong>
@@ -574,20 +656,10 @@ export function HumanApprovalConfigEditor({
574
656
  icon={<UserAddOutlined />}
575
657
  aria-label={t('approval.addUsers')}
576
658
  disabled={disabled}
577
- onClick={() => addDynamic('USER')}
659
+ onClick={addDynamic}
578
660
  >
579
661
  {t('approval.addUsers')}
580
662
  </Button>
581
- <Button
582
- size="small"
583
- className="workflow-panel-action"
584
- icon={<UsergroupAddOutlined />}
585
- aria-label={t('approval.addGroups')}
586
- disabled={disabled}
587
- onClick={() => addDynamic('USER_GROUP')}
588
- >
589
- {t('approval.addGroups')}
590
- </Button>
591
663
  </Space>
592
664
  </div>
593
665
  {dynamicSources.length === 0 ? (
@@ -603,9 +675,7 @@ export function HumanApprovalConfigEditor({
603
675
  <div className="human-approval-config__dynamic-source-header">
604
676
  <div>
605
677
  <Typography.Text strong>
606
- {source.provider === 'USER'
607
- ? t('approval.dynamicUsers')
608
- : t('approval.dynamicGroups')}
678
+ {t('approval.dynamicUsers')}
609
679
  </Typography.Text>
610
680
  <Typography.Text
611
681
  type="secondary"
@@ -9,6 +9,28 @@ import {
9
9
  import { EventTriggerConfigEditor } from './EventTriggerConfigEditor';
10
10
  import { ParallelConfigEditor } from './ParallelConfigEditor';
11
11
 
12
+ vi.mock('@monaco-editor/react', () => ({
13
+ default: ({
14
+ language,
15
+ value,
16
+ onChange,
17
+ options,
18
+ }: {
19
+ language?: string;
20
+ value?: string;
21
+ onChange?(value: string): void;
22
+ options?: { ariaLabel?: string; readOnly?: boolean };
23
+ }) => (
24
+ <textarea
25
+ aria-label={options?.ariaLabel}
26
+ data-language={language}
27
+ readOnly={options?.readOnly}
28
+ value={value}
29
+ onChange={(event) => onChange?.(event.target.value)}
30
+ />
31
+ ),
32
+ }));
33
+
12
34
  vi.mock('./event-catalog.api', () => ({
13
35
  eventCatalogApi: {
14
36
  list: vi.fn().mockResolvedValue([
@@ -429,6 +451,27 @@ describe('simplified workflow node editors', () => {
429
451
  });
430
452
  });
431
453
 
454
+ it('shows only the source table in the selected business event summary', async () => {
455
+ await i18n.changeLanguage('zh_CN');
456
+ render(
457
+ <EventTriggerConfigEditor
458
+ value={{
459
+ registrationCode: 'asset-disposal-submitted',
460
+ processingMode: 'PARALLEL',
461
+ }}
462
+ onChange={vi.fn()}
463
+ schema={schema}
464
+ />,
465
+ );
466
+
467
+ expect(
468
+ await screen.findByText('public.t_hardware_asset'),
469
+ ).toBeInTheDocument();
470
+ expect(
471
+ screen.queryByText('public.t_hardware_asset · status'),
472
+ ).not.toBeInTheDocument();
473
+ });
474
+
432
475
  it('adds a typed start condition for the selected event', async () => {
433
476
  await i18n.changeLanguage('zh_CN');
434
477
  const onChange = vi.fn();
@@ -477,9 +520,10 @@ describe('simplified workflow node editors', () => {
477
520
  />,
478
521
  );
479
522
 
480
- expect(screen.getByRole('textbox', { name: 'SQL 语句' })).toHaveValue(
481
- 'SELECT * FROM users WHERE id = $1',
482
- );
523
+ const sqlEditor = screen.getByRole('textbox', { name: 'SQL 语句' });
524
+ expect(sqlEditor).toHaveValue('SELECT * FROM users WHERE id = $1');
525
+ expect(sqlEditor).toHaveAttribute('data-language', 'sql');
526
+ expect(screen.getByText('只读 SQL')).toBeInTheDocument();
483
527
  expect(screen.getByRole('textbox', { name: '参数 $1' })).toBeDisabled();
484
528
  expect(screen.getByRole('textbox', { name: '参数 $1' })).toHaveValue('$1');
485
529
  expect(screen.getByText('参数')).toBeInTheDocument();
@@ -9,7 +9,6 @@ vi.mock('./subject-provider.api', () => ({
9
9
  descriptors: vi.fn(),
10
10
  search: vi.fn(),
11
11
  preview: vi.fn(),
12
- create: vi.fn(),
13
12
  userGroupDetail: vi.fn(),
14
13
  updateUserGroup: vi.fn(),
15
14
  replaceUserGroupMembers: vi.fn(),
@@ -58,4 +57,37 @@ describe('SubjectPicker', () => {
58
57
  await waitFor(() => expect(screen.getByText('Users')).toBeInTheDocument());
59
58
  expect(screen.queryByText('User groups')).not.toBeInTheDocument();
60
59
  });
60
+
61
+ it('can expose only the user picker without a provider choice', async () => {
62
+ render(
63
+ <SubjectPicker
64
+ value={[{ provider: 'USER', refs: [] }]}
65
+ onChange={vi.fn()}
66
+ schema={{ type: 'array' }}
67
+ allowedProviders={['USER']}
68
+ />,
69
+ );
70
+
71
+ expect(
72
+ (await screen.findAllByLabelText('Users selections')).length,
73
+ ).toBeGreaterThan(0);
74
+ expect(screen.queryByLabelText('Provider')).not.toBeInTheDocument();
75
+ expect(subjectProviderApi.search).toHaveBeenCalledWith('USER', '');
76
+ });
77
+
78
+ it('only selects existing groups and does not create groups inline', async () => {
79
+ render(
80
+ <SubjectPicker
81
+ value={[{ provider: 'USER_GROUP', refs: [] }]}
82
+ onChange={vi.fn()}
83
+ schema={{ type: 'array' }}
84
+ allowedProviders={['USER_GROUP']}
85
+ />,
86
+ );
87
+
88
+ expect(
89
+ (await screen.findAllByLabelText('User groups selections')).length,
90
+ ).toBeGreaterThan(0);
91
+ expect(screen.queryByRole('button', { name: 'Create' })).toBeNull();
92
+ });
61
93
  });