@gadmin2n/schematics 0.0.147 → 0.0.149
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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +67 -15
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +83 -53
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.spec.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/structured-all-exceptions.filter.ts +15 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +31 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.spec.ts +2 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.spec.ts +28 -29
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/approval-subject-source.util.ts +16 -34
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.spec.ts +61 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.spec.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.controller.ts +11 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.spec.ts +126 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/integrationConnection/integration-connection.service.ts +136 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.controller.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.dto.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.spec.ts +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.service.ts +94 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staff-directory.types.ts +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/staffDirectory/staffDirectory.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/subjectProvider/providers/user.provider.ts +6 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +20 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +21 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +11 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +97 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +150 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.controller.ts +13 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.dto.ts +13 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.errors.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.spec.ts +125 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/userGroup/user-group.service.ts +118 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +20 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +6 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +166 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +82 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-runtime.facade.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +4 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +5 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +7 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +106 -37
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +59 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.spec.ts +33 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/subject-config-validator.service.ts +10 -23
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.test.tsx +69 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/StaffSelect.tsx +145 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/StaffSelect/staff-directory.api.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +20 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +60 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +60 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +41 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/PeopleGroupModal.tsx +181 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +203 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/people-groups.api.ts +145 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/PersonModal.tsx +112 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +214 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/person-directory.api.ts +79 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +7 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +7 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.test.tsx +46 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.tsx +14 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.test.tsx +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +97 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +87 -119
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.test.tsx +22 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/WorkflowCodeEditor.tsx +8 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connections.css +8 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/connectionsApi.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.test.tsx +32 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +163 -106
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +8 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.test.tsx +26 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +34 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +13 -16
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-instance-show.css +185 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +224 -105
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +21 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.test.tsx +140 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +124 -62
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +160 -8
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +125 -55
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +78 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.test.tsx +33 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubjectPicker.tsx +28 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/event-catalog.api.ts +4 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/human-approval-config.css +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/subject-provider.api.ts +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +14 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +1 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CreateNodeInstanceDrawer.tsx +0 -162
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/UserGroupCreateModal.tsx +0 -72
|
@@ -257,7 +257,8 @@ export default function BusinessEventDetailPage() {
|
|
|
257
257
|
label={t('eventCatalog.identifier')}
|
|
258
258
|
value={
|
|
259
259
|
<Typography.Text code>
|
|
260
|
-
{event.schemaName}.{event.tableName} ·
|
|
260
|
+
{event.schemaName}.{event.tableName} ·{' '}
|
|
261
|
+
{event.operations.join(' / ')}
|
|
261
262
|
</Typography.Text>
|
|
262
263
|
}
|
|
263
264
|
/>
|
|
@@ -38,7 +38,7 @@ describe('BusinessEventCatalogPage', () => {
|
|
|
38
38
|
label: 'public.t_hardware_asset · UPDATE',
|
|
39
39
|
schemaName: 'public',
|
|
40
40
|
tableName: 't_hardware_asset',
|
|
41
|
-
|
|
41
|
+
operations: ['UPDATE'],
|
|
42
42
|
watchedFields: ['status'],
|
|
43
43
|
primaryKeyFields: ['id'],
|
|
44
44
|
isEnabled: true,
|
|
@@ -74,7 +74,7 @@ export default function BusinessEventCatalogPage() {
|
|
|
74
74
|
event.label,
|
|
75
75
|
event.schemaName,
|
|
76
76
|
event.tableName,
|
|
77
|
-
event.
|
|
77
|
+
...event.operations,
|
|
78
78
|
...event.fields.flatMap((field) => [
|
|
79
79
|
field.path,
|
|
80
80
|
field.label,
|
|
@@ -105,7 +105,7 @@ export default function BusinessEventCatalogPage() {
|
|
|
105
105
|
{event.label}
|
|
106
106
|
</Typography.Link>
|
|
107
107
|
<Typography.Text code>
|
|
108
|
-
{event.
|
|
108
|
+
{event.operations.includes('UPDATE')
|
|
109
109
|
? event.watchedFields.join(', ')
|
|
110
110
|
: t('eventCatalog.registration.allFields')}
|
|
111
111
|
</Typography.Text>
|
|
@@ -83,6 +83,13 @@ describe('NodeOutputSummary', () => {
|
|
|
83
83
|
type: 'object',
|
|
84
84
|
properties: {
|
|
85
85
|
operation: { type: 'string' },
|
|
86
|
+
table: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
schema: { type: 'string' },
|
|
90
|
+
name: { type: 'string' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
86
93
|
primaryKey: { type: 'object' },
|
|
87
94
|
before: { type: ['object', 'null'] },
|
|
88
95
|
after: { type: ['object', 'null'] },
|
|
@@ -90,6 +97,7 @@ describe('NodeOutputSummary', () => {
|
|
|
90
97
|
},
|
|
91
98
|
required: [
|
|
92
99
|
'operation',
|
|
100
|
+
'table',
|
|
93
101
|
'primaryKey',
|
|
94
102
|
'before',
|
|
95
103
|
'after',
|
|
@@ -102,8 +110,20 @@ describe('NodeOutputSummary', () => {
|
|
|
102
110
|
render(<NodeOutputSummary node={event} nodeType={nodeType} />);
|
|
103
111
|
|
|
104
112
|
expect(screen.getByText('input.operation')).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByText('操作类型')).toBeInTheDocument();
|
|
114
|
+
expect(
|
|
115
|
+
screen.getByText(
|
|
116
|
+
'数据库操作类型:INSERT(新增)、UPDATE(更新)或 DELETE(删除)',
|
|
117
|
+
),
|
|
118
|
+
).toBeInTheDocument();
|
|
119
|
+
expect(screen.getByText('input.table')).toBeInTheDocument();
|
|
120
|
+
expect(screen.getByText('数据表')).toBeInTheDocument();
|
|
105
121
|
expect(screen.getByText('input.primaryKey')).toBeInTheDocument();
|
|
122
|
+
expect(screen.getByText('主键')).toBeInTheDocument();
|
|
123
|
+
expect(screen.getByText('变更前的数据')).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByText('变更后的数据')).toBeInTheDocument();
|
|
106
125
|
expect(screen.getByText('input.changedFields')).toBeInTheDocument();
|
|
126
|
+
expect(screen.getByText('发生变化的字段')).toBeInTheDocument();
|
|
107
127
|
expect(
|
|
108
128
|
screen.queryByText('触发本次运行的事件数据'),
|
|
109
129
|
).not.toBeInTheDocument();
|
|
@@ -188,11 +188,10 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
188
188
|
expect(
|
|
189
189
|
screen.queryByText('Connection is required'),
|
|
190
190
|
).not.toBeInTheDocument();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
);
|
|
191
|
+
expect(
|
|
192
|
+
screen.queryByRole('button', { name: 'Save changes' }),
|
|
193
|
+
).not.toBeInTheDocument();
|
|
194
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
196
195
|
expect(
|
|
197
196
|
screen.queryByText('Connection is required'),
|
|
198
197
|
).not.toBeInTheDocument();
|
|
@@ -307,7 +306,6 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
307
306
|
/>,
|
|
308
307
|
);
|
|
309
308
|
|
|
310
|
-
await waitFor(() => expect(customRequest).toHaveBeenCalled());
|
|
311
309
|
await act(async () => {
|
|
312
310
|
await Promise.resolve();
|
|
313
311
|
await Promise.resolve();
|
|
@@ -346,8 +344,8 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
346
344
|
screen.getByText('Includes the approver and completion time'),
|
|
347
345
|
).toBeInTheDocument();
|
|
348
346
|
expect(
|
|
349
|
-
screen.
|
|
350
|
-
).toBeInTheDocument();
|
|
347
|
+
screen.queryByRole('button', { name: 'Save changes' }),
|
|
348
|
+
).not.toBeInTheDocument();
|
|
351
349
|
expect(customRequest).not.toHaveBeenCalledWith(
|
|
352
350
|
'workflowNodeInstance/findMany',
|
|
353
351
|
expect.anything(),
|
|
@@ -386,13 +384,14 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
386
384
|
/>,
|
|
387
385
|
);
|
|
388
386
|
|
|
389
|
-
expect(screen.getByRole('button', { name: 'Save changes' })).toBeEnabled();
|
|
390
387
|
expect(
|
|
391
388
|
screen.getByText('Use a template or configure this node directly'),
|
|
392
389
|
).toBeInTheDocument();
|
|
393
|
-
expect(
|
|
394
|
-
'
|
|
395
|
-
|
|
390
|
+
expect(
|
|
391
|
+
screen.getByRole('button', {
|
|
392
|
+
name: /Save current configuration as template/,
|
|
393
|
+
}),
|
|
394
|
+
).toHaveClass('workflow-panel-action');
|
|
396
395
|
expect(screen.getByText('Who can approve')).toBeInTheDocument();
|
|
397
396
|
expect(screen.queryByText('Parameters')).not.toBeInTheDocument();
|
|
398
397
|
expect(
|
|
@@ -419,7 +418,83 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
419
418
|
);
|
|
420
419
|
});
|
|
421
420
|
|
|
422
|
-
it('
|
|
421
|
+
it('saves the current configuration as a template and uses it immediately', async () => {
|
|
422
|
+
const user = userEvent.setup();
|
|
423
|
+
const onSave = vi.fn();
|
|
424
|
+
const inlineNode = {
|
|
425
|
+
...node,
|
|
426
|
+
instanceRef: undefined,
|
|
427
|
+
config: { subjects: [{ refs: ['alice'] }] },
|
|
428
|
+
};
|
|
429
|
+
vi.mocked(customRequest).mockImplementation((path) => {
|
|
430
|
+
if (path === 'workflowNodeInstance/findMany') {
|
|
431
|
+
return Promise.resolve({ data: [] }) as never;
|
|
432
|
+
}
|
|
433
|
+
if (path === 'workflowNodeInstance/createOne') {
|
|
434
|
+
return Promise.resolve({
|
|
435
|
+
id: '21',
|
|
436
|
+
code: 'FINANCE_APPROVAL_V2',
|
|
437
|
+
name: 'Finance approval',
|
|
438
|
+
nodeTypeId: '7',
|
|
439
|
+
isEnabled: true,
|
|
440
|
+
config: inlineNode.config,
|
|
441
|
+
}) as never;
|
|
442
|
+
}
|
|
443
|
+
return Promise.resolve([]) as never;
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
render(
|
|
447
|
+
<NodePropertyPanel
|
|
448
|
+
node={inlineNode}
|
|
449
|
+
nodeTypes={[nodeType]}
|
|
450
|
+
onSave={onSave}
|
|
451
|
+
onLabelChange={vi.fn()}
|
|
452
|
+
onAiEdit={vi.fn()}
|
|
453
|
+
aiLoading={false}
|
|
454
|
+
/>,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
await user.click(
|
|
458
|
+
screen.getByRole('button', {
|
|
459
|
+
name: /Save current configuration as template/,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
expect(
|
|
463
|
+
screen.getByText(/Create a template from the current node configuration/),
|
|
464
|
+
).toBeInTheDocument();
|
|
465
|
+
const templateName = screen.getByRole('textbox', {
|
|
466
|
+
name: 'Template name',
|
|
467
|
+
});
|
|
468
|
+
await user.clear(templateName);
|
|
469
|
+
await user.type(templateName, 'Reusable finance approval');
|
|
470
|
+
await user.click(
|
|
471
|
+
screen.getByRole('button', { name: 'Save and use template' }),
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
await waitFor(() =>
|
|
475
|
+
expect(customRequest).toHaveBeenCalledWith(
|
|
476
|
+
'workflowNodeInstance/createOne',
|
|
477
|
+
'POST',
|
|
478
|
+
expect.objectContaining({
|
|
479
|
+
name: 'Reusable finance approval',
|
|
480
|
+
config: inlineNode.config,
|
|
481
|
+
}),
|
|
482
|
+
),
|
|
483
|
+
);
|
|
484
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
485
|
+
'node-1',
|
|
486
|
+
expect.objectContaining({
|
|
487
|
+
config: inlineNode.config,
|
|
488
|
+
configOverride: null,
|
|
489
|
+
instanceRef: {
|
|
490
|
+
instanceId: '21',
|
|
491
|
+
instanceCode: 'FINANCE_APPROVAL_V2',
|
|
492
|
+
},
|
|
493
|
+
}),
|
|
494
|
+
);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('keeps an inline approval configuration in the Workflow draft', async () => {
|
|
423
498
|
const user = userEvent.setup();
|
|
424
499
|
const onSave = vi.fn();
|
|
425
500
|
const onConfigDraftChange = vi.fn();
|
|
@@ -531,10 +606,8 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
531
606
|
},
|
|
532
607
|
});
|
|
533
608
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const savedConfig = onSave.mock.lastCall?.[1].config;
|
|
537
|
-
expect(savedConfig).toMatchObject({
|
|
609
|
+
const pendingConfig = panelRef.current?.getPendingUpdate()?.updates.config;
|
|
610
|
+
expect(pendingConfig).toMatchObject({
|
|
538
611
|
external: {
|
|
539
612
|
request: {
|
|
540
613
|
connectionRef: 'ticketing',
|
|
@@ -549,6 +622,7 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
549
622
|
rejectionRule: { mode: 'ANY' },
|
|
550
623
|
timeout: { durationSeconds: 0, action: 'REJECTED' },
|
|
551
624
|
});
|
|
625
|
+
expect(onSave).not.toHaveBeenCalled();
|
|
552
626
|
|
|
553
627
|
await waitFor(() =>
|
|
554
628
|
expect(screen.getByLabelText('Task title field path')).toHaveValue(
|
|
@@ -616,7 +690,7 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
616
690
|
|
|
617
691
|
it('uses nested UI Schema widgets for Callable Trigger parameters', async () => {
|
|
618
692
|
const user = userEvent.setup();
|
|
619
|
-
const
|
|
693
|
+
const onConfigDraftChange = vi.fn();
|
|
620
694
|
const callableNodeType = {
|
|
621
695
|
id: '9',
|
|
622
696
|
type: 'callable_trigger',
|
|
@@ -655,7 +729,8 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
655
729
|
<NodePropertyPanel
|
|
656
730
|
node={callableNode}
|
|
657
731
|
nodeTypes={[callableNodeType]}
|
|
658
|
-
onSave={
|
|
732
|
+
onSave={vi.fn()}
|
|
733
|
+
onConfigDraftChange={onConfigDraftChange}
|
|
659
734
|
onLabelChange={vi.fn()}
|
|
660
735
|
onAiEdit={vi.fn()}
|
|
661
736
|
aiLoading={false}
|
|
@@ -672,16 +747,10 @@ describe('NodePropertyPanel instance references', () => {
|
|
|
672
747
|
screen.getByRole('textbox', { name: 'Parameter name' }),
|
|
673
748
|
'departmentId',
|
|
674
749
|
);
|
|
675
|
-
await user.click(screen.getByRole('button', { name: 'Save changes' }));
|
|
676
750
|
|
|
677
|
-
expect(
|
|
678
|
-
'
|
|
679
|
-
|
|
680
|
-
config: {
|
|
681
|
-
inputParameters: [{ key: 'departmentId', label: '', required: true }],
|
|
682
|
-
},
|
|
683
|
-
}),
|
|
684
|
-
);
|
|
751
|
+
expect(onConfigDraftChange).toHaveBeenCalledWith('callable-1', {
|
|
752
|
+
inputParameters: [{ key: 'departmentId', label: '', required: true }],
|
|
753
|
+
});
|
|
685
754
|
});
|
|
686
755
|
|
|
687
756
|
it('filters the node palette by translated name and technical type', async () => {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
DisconnectOutlined,
|
|
3
3
|
MinusCircleOutlined,
|
|
4
4
|
PlusOutlined,
|
|
5
|
+
SaveOutlined,
|
|
5
6
|
SearchOutlined,
|
|
6
7
|
SendOutlined,
|
|
7
8
|
} from '@ant-design/icons';
|
|
@@ -9,11 +10,11 @@ import {
|
|
|
9
10
|
Alert,
|
|
10
11
|
Button,
|
|
11
12
|
Card,
|
|
12
|
-
Checkbox,
|
|
13
13
|
Form,
|
|
14
14
|
Input,
|
|
15
15
|
InputNumber,
|
|
16
16
|
message,
|
|
17
|
+
Modal,
|
|
17
18
|
Segmented,
|
|
18
19
|
Select,
|
|
19
20
|
Space,
|
|
@@ -44,7 +45,6 @@ import type {
|
|
|
44
45
|
WorkflowNodeType,
|
|
45
46
|
} from '../types';
|
|
46
47
|
import { NodeOutputSummary } from './NodeOutputSummary';
|
|
47
|
-
import { CreateNodeInstanceDrawer } from './CreateNodeInstanceDrawer';
|
|
48
48
|
import { WorkflowCodeEditor } from './WorkflowCodeEditor';
|
|
49
49
|
import '../node-instances/node-config.css';
|
|
50
50
|
import type { ReferencedConfigStatus } from '../hooks/useReferencedNodeInstanceConfigs';
|
|
@@ -186,12 +186,11 @@ function NodePropertyPanelImpl(
|
|
|
186
186
|
const [instances, setInstances] = useState<WorkflowNodeInstance[]>([]);
|
|
187
187
|
const [instanceLoading, setInstanceLoading] = useState(false);
|
|
188
188
|
const [instanceLoadError, setInstanceLoadError] = useState(false);
|
|
189
|
-
const [
|
|
189
|
+
const [saveTemplateOpen, setSaveTemplateOpen] = useState(false);
|
|
190
190
|
const [paletteQuery, setPaletteQuery] = useState('');
|
|
191
|
-
const [
|
|
192
|
-
const [
|
|
193
|
-
const [
|
|
194
|
-
const [alsoSaveAsInstance, setAlsoSaveAsInstance] = useState(false);
|
|
191
|
+
const [templateName, setTemplateName] = useState('');
|
|
192
|
+
const [templateDescription, setTemplateDescription] = useState('');
|
|
193
|
+
const [templateSaving, setTemplateSaving] = useState(false);
|
|
195
194
|
const [configForm] = Form.useForm();
|
|
196
195
|
|
|
197
196
|
// Find configSchema for current node type
|
|
@@ -326,7 +325,6 @@ function NodePropertyPanelImpl(
|
|
|
326
325
|
]);
|
|
327
326
|
setDirty(false);
|
|
328
327
|
setPrompt('');
|
|
329
|
-
setAlsoSaveAsInstance(false);
|
|
330
328
|
}
|
|
331
329
|
}, [node?.id, currentNodeType]);
|
|
332
330
|
|
|
@@ -414,8 +412,7 @@ function NodePropertyPanelImpl(
|
|
|
414
412
|
|
|
415
413
|
// Reset selection when node changes
|
|
416
414
|
useEffect(() => {
|
|
417
|
-
|
|
418
|
-
setCreateInstanceOpen(false);
|
|
415
|
+
setSaveTemplateOpen(false);
|
|
419
416
|
}, [node?.id]);
|
|
420
417
|
|
|
421
418
|
const buildCurrentUpdates = useCallback((): NodePropertyUpdates | null => {
|
|
@@ -590,57 +587,51 @@ function NodePropertyPanelImpl(
|
|
|
590
587
|
}
|
|
591
588
|
}
|
|
592
589
|
|
|
593
|
-
|
|
594
|
-
if (!node) return;
|
|
590
|
+
function openSaveTemplate() {
|
|
591
|
+
if (!node || node.instanceRef) return;
|
|
592
|
+
setTemplateName(node.label.trim());
|
|
593
|
+
setTemplateDescription('');
|
|
594
|
+
setSaveTemplateOpen(true);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async function handleSaveAsTemplate() {
|
|
598
|
+
if (!node || node.instanceRef || !currentNodeType || !templateName.trim())
|
|
599
|
+
return;
|
|
595
600
|
const updates = buildCurrentUpdates();
|
|
596
601
|
if (!updates?.config) return;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
setInstances((prev) => [...prev, created]);
|
|
630
|
-
setAlsoSaveAsInstance(false);
|
|
631
|
-
setNewInstanceName('');
|
|
632
|
-
setNewInstanceDesc('');
|
|
633
|
-
message.success('已保存并创建新实例');
|
|
634
|
-
} catch (e: any) {
|
|
635
|
-
message.error(e?.message ?? '创建实例失败');
|
|
636
|
-
} finally {
|
|
637
|
-
setSaveAsLoading(false);
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
onSave(node.id, updates);
|
|
641
|
-
message.success('节点已保存');
|
|
602
|
+
setTemplateSaving(true);
|
|
603
|
+
try {
|
|
604
|
+
const created = await customRequest<WorkflowNodeInstance>(
|
|
605
|
+
'workflowNodeInstance/createOne',
|
|
606
|
+
'POST',
|
|
607
|
+
{
|
|
608
|
+
nodeTypeId: Number(currentNodeType.id),
|
|
609
|
+
name: templateName.trim(),
|
|
610
|
+
description: templateDescription.trim() || null,
|
|
611
|
+
config: updates.config,
|
|
612
|
+
isEnabled: true,
|
|
613
|
+
},
|
|
614
|
+
);
|
|
615
|
+
onSave(node.id, {
|
|
616
|
+
label,
|
|
617
|
+
config: created.config,
|
|
618
|
+
configOverride: null,
|
|
619
|
+
instanceRef: {
|
|
620
|
+
instanceId: String(created.id),
|
|
621
|
+
instanceCode: created.code,
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
setInstances((current) => [
|
|
625
|
+
created,
|
|
626
|
+
...current.filter((item) => item.id !== created.id),
|
|
627
|
+
]);
|
|
628
|
+
setSaveTemplateOpen(false);
|
|
629
|
+
message.success(t('editor.templateSavedAndApplied'));
|
|
630
|
+
} catch (error: any) {
|
|
631
|
+
message.error(error?.message ?? t('editor.templateSaveFailed'));
|
|
632
|
+
} finally {
|
|
633
|
+
setTemplateSaving(false);
|
|
642
634
|
}
|
|
643
|
-
setDirty(false);
|
|
644
635
|
}
|
|
645
636
|
|
|
646
637
|
function handleAiSubmit() {
|
|
@@ -1017,10 +1008,11 @@ function NodePropertyPanelImpl(
|
|
|
1017
1008
|
<Button
|
|
1018
1009
|
size="small"
|
|
1019
1010
|
className="workflow-panel-action"
|
|
1020
|
-
icon={<
|
|
1021
|
-
|
|
1011
|
+
icon={<SaveOutlined />}
|
|
1012
|
+
disabled={referencedConfigUnavailable}
|
|
1013
|
+
onClick={openSaveTemplate}
|
|
1022
1014
|
>
|
|
1023
|
-
{t('editor.
|
|
1015
|
+
{t('editor.saveCurrentAsTemplate')}
|
|
1024
1016
|
</Button>
|
|
1025
1017
|
</div>
|
|
1026
1018
|
<Select
|
|
@@ -1225,64 +1217,40 @@ function NodePropertyPanelImpl(
|
|
|
1225
1217
|
</Card>
|
|
1226
1218
|
)}
|
|
1227
1219
|
</div>
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
)}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
referencedConfigUnavailable ||
|
|
1263
|
-
(!node.instanceRef &&
|
|
1264
|
-
alsoSaveAsInstance &&
|
|
1265
|
-
!newInstanceName.trim())
|
|
1266
|
-
}
|
|
1267
|
-
onClick={handleSave}
|
|
1268
|
-
>
|
|
1269
|
-
{t('editor.saveChanges')}
|
|
1270
|
-
</Button>
|
|
1271
|
-
</footer>
|
|
1272
|
-
)}
|
|
1273
|
-
<CreateNodeInstanceDrawer
|
|
1274
|
-
open={createInstanceOpen}
|
|
1275
|
-
nodeType={currentNodeType}
|
|
1276
|
-
onCancel={() => setCreateInstanceOpen(false)}
|
|
1277
|
-
onCreated={(instance) => {
|
|
1278
|
-
setCreateInstanceOpen(false);
|
|
1279
|
-
setInstances((current) => [
|
|
1280
|
-
instance,
|
|
1281
|
-
...current.filter((item) => item.id !== instance.id),
|
|
1282
|
-
]);
|
|
1283
|
-
referenceInstance(instance);
|
|
1284
|
-
}}
|
|
1285
|
-
/>
|
|
1220
|
+
<Modal
|
|
1221
|
+
open={saveTemplateOpen}
|
|
1222
|
+
title={t('editor.saveCurrentAsTemplate')}
|
|
1223
|
+
okText={t('editor.saveAndUseTemplate')}
|
|
1224
|
+
cancelText={t('common.cancel')}
|
|
1225
|
+
confirmLoading={templateSaving}
|
|
1226
|
+
okButtonProps={{ disabled: !templateName.trim() }}
|
|
1227
|
+
onOk={() => void handleSaveAsTemplate()}
|
|
1228
|
+
onCancel={() => setSaveTemplateOpen(false)}
|
|
1229
|
+
destroyOnHidden
|
|
1230
|
+
>
|
|
1231
|
+
<Typography.Paragraph type="secondary">
|
|
1232
|
+
{t('editor.saveCurrentAsTemplateHint')}
|
|
1233
|
+
</Typography.Paragraph>
|
|
1234
|
+
<Form layout="vertical">
|
|
1235
|
+
<Form.Item label={t('editor.templateName')} required>
|
|
1236
|
+
<Input
|
|
1237
|
+
aria-label={t('editor.templateName')}
|
|
1238
|
+
value={templateName}
|
|
1239
|
+
onChange={(event) => setTemplateName(event.target.value)}
|
|
1240
|
+
autoFocus
|
|
1241
|
+
/>
|
|
1242
|
+
</Form.Item>
|
|
1243
|
+
<Form.Item label={t('common.description')}>
|
|
1244
|
+
<Input.TextArea
|
|
1245
|
+
aria-label={t('common.description')}
|
|
1246
|
+
value={templateDescription}
|
|
1247
|
+
onChange={(event) => setTemplateDescription(event.target.value)}
|
|
1248
|
+
placeholder={t('common.optionalDescription')}
|
|
1249
|
+
rows={3}
|
|
1250
|
+
/>
|
|
1251
|
+
</Form.Item>
|
|
1252
|
+
</Form>
|
|
1253
|
+
</Modal>
|
|
1286
1254
|
</div>
|
|
1287
1255
|
);
|
|
1288
1256
|
}
|
|
@@ -5,14 +5,19 @@ import { WorkflowCodeEditor } from './WorkflowCodeEditor';
|
|
|
5
5
|
|
|
6
6
|
vi.mock('@monaco-editor/react', () => ({
|
|
7
7
|
default: ({
|
|
8
|
+
language,
|
|
8
9
|
value,
|
|
9
10
|
onChange,
|
|
11
|
+
options,
|
|
10
12
|
}: {
|
|
13
|
+
language?: string;
|
|
11
14
|
value?: string;
|
|
12
15
|
onChange?(value: string): void;
|
|
16
|
+
options?: { ariaLabel?: string };
|
|
13
17
|
}) => (
|
|
14
18
|
<textarea
|
|
15
|
-
aria-label=
|
|
19
|
+
aria-label={options?.ariaLabel ?? 'shared editor'}
|
|
20
|
+
data-language={language}
|
|
16
21
|
value={value}
|
|
17
22
|
onChange={(event) => onChange?.(event.target.value)}
|
|
18
23
|
/>
|
|
@@ -39,4 +44,20 @@ describe('WorkflowCodeEditor', () => {
|
|
|
39
44
|
});
|
|
40
45
|
expect(onChange).toHaveBeenCalledWith('{"ok":true}');
|
|
41
46
|
});
|
|
47
|
+
|
|
48
|
+
it('enables Monaco SQL mode and labels the status bar', () => {
|
|
49
|
+
render(
|
|
50
|
+
<WorkflowCodeEditor
|
|
51
|
+
language="sql"
|
|
52
|
+
value="SELECT * FROM users"
|
|
53
|
+
ariaLabel="SQL 语句"
|
|
54
|
+
/>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByRole('textbox', { name: 'SQL 语句' })).toHaveAttribute(
|
|
58
|
+
'data-language',
|
|
59
|
+
'sql',
|
|
60
|
+
);
|
|
61
|
+
expect(screen.getByText('SQL')).toBeInTheDocument();
|
|
62
|
+
});
|
|
42
63
|
});
|
|
@@ -2,7 +2,13 @@ import Editor from '@monaco-editor/react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import './workflow-code-editor.css';
|
|
4
4
|
|
|
5
|
-
type WorkflowEditorLanguage = 'json' | 'javascript';
|
|
5
|
+
type WorkflowEditorLanguage = 'json' | 'javascript' | 'sql';
|
|
6
|
+
|
|
7
|
+
const LANGUAGE_LABELS: Record<WorkflowEditorLanguage, string> = {
|
|
8
|
+
json: 'JSON',
|
|
9
|
+
javascript: 'JavaScript',
|
|
10
|
+
sql: 'SQL',
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
export function WorkflowCodeEditor({
|
|
8
14
|
language,
|
|
@@ -67,7 +73,7 @@ export function WorkflowCodeEditor({
|
|
|
67
73
|
}}
|
|
68
74
|
/>
|
|
69
75
|
<div className="workflow-code-editor__status" aria-hidden="true">
|
|
70
|
-
<span>{language
|
|
76
|
+
<span>{LANGUAGE_LABELS[language]}</span>
|
|
71
77
|
<span>UTF-8</span>
|
|
72
78
|
<span>{t('editor.indentSpaces')}: 2</span>
|
|
73
79
|
</div>
|