@gadmin2n/schematics 0.0.142 → 0.0.143
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 +5 -23
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/lib/page-helpers.ts +4 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/page-manage.ts +36 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/agenda.seed.ts +14 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/bootstrap.ts +18 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/canvas.seed.ts +14 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +67 -404
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +16 -40
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +12 -124
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/config/index.ts +0 -21
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/code-runtime.controller.ts +3 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/codeRuntime/codeRuntime.module.ts +1 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.spec.ts +48 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-callback.controller.ts +96 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.spec.ts +1 -59
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.controller.ts +8 -43
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction-runtime.dto.ts +0 -23
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-interaction.service.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/external-wait.handler.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/externalInteraction.module.ts +7 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.controller.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.service.spec.ts +18 -123
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/externalInteraction/workflow-callback-url.service.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/http-request-runtime.controller.ts +3 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/httpRequest/httpRequest.module.ts +1 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.controller.ts +0 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.dto.ts +12 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.errors.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.external-decision.spec.ts +42 -209
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/human-approval.service.ts +108 -225
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/humanApproval.module.ts +0 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.controller.ts +27 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/human-approval-runtime.dto.ts +9 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/runtime-token.guard.spec.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/humanApproval/runtime/runtime-token.guard.ts +8 -15
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +0 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/runtime-connectivity-probe.service.spec.ts +14 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/runtime-connectivity-probe.service.ts +37 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +41 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +53 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +7 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/workflow-runtime-health.controller.ts +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/dsl-validate.util.spec.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.spec.ts +16 -88
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-callable.ts +23 -59
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +125 -39
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +95 -28
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-handle-validator.ts +0 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.spec.ts +23 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.ts +6 -52
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +26 -21
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.spec.ts +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/widget-catalog.service.ts +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflow-runtime-config.service.spec.ts +32 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflow-runtime-config.service.ts +41 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowRuntimeConfig/workflowRuntimeConfig.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.build.json +10 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +16 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/PageHeader.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/agentPanel/inspectorActions.ts +19 -19
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +15 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +0 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddDataModal.tsx +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/AddPageModal.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DeleteDataConfirm.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/EditDataModal.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +85 -95
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +85 -95
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +21 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/game/list.tsx +21 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/inbox.tsx +14 -66
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +4 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.test.tsx +29 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +4 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +4 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.test.tsx +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExecutionStatusNode.tsx +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +4 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NewNodeDefaults.test.tsx +23 -92
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeOutputSummary.tsx +200 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.test.tsx +61 -136
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +96 -195
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeTypeBadge.tsx +36 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/CreateNodeInstanceModal.tsx +5 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +47 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/create.tsx +7 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/edit.tsx +8 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/node-config.css +35 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/schema-form/default-widgets.tsx +1 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApproverSourceEditor.tsx +1 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/CodeConfigEditor.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalWaitConfigEditor.tsx +82 -259
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HttpRequestConfigEditor.tsx +1 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.test.tsx +7 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/HumanApprovalConfigEditor.tsx +77 -71
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.test.tsx +10 -57
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.tsx +8 -273
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +54 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +110 -20
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +4 -34
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.test.ts +3 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/resolveOutputs.ts +2 -37
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/list.tsx +26 -2
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.controller.ts +0 -89
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.dto.ts +0 -68
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.service.spec.ts +0 -177
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approval-channel.service.ts +0 -283
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/approvalChannel/approvalChannel.module.ts +0 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/ApprovalChannelModal.test.tsx +0 -84
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/ApprovalChannelModal.tsx +0 -141
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/approval-channel.types.ts +0 -16
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/index.tsx +0 -210
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ApprovalRequestTriggerConfigEditor.tsx +0 -39
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubmitApprovalResultConfigEditor.tsx +0 -104
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SubmitExternalResultConfigEditor.tsx +0 -98
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/AssignRolesModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/CreatePageModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/EditPageModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/PageDetailDrawer.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/{Components → components}/PageFormModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/CreateModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/EditModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/ResourceDetailDrawer.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/{Components → components}/modal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/CreateModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/EditModal.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/RoleDetailDrawer.tsx +0 -0
- /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/{Components → components}/modal.tsx +0 -0
|
@@ -2,7 +2,7 @@ import { hashNodeInstanceConfig } from '../workflowNodeInstance/node-instance-co
|
|
|
2
2
|
import { WorkflowCompilerService } from './workflow-compiler.service';
|
|
3
3
|
|
|
4
4
|
describe('WorkflowCompilerService', () => {
|
|
5
|
-
it('compiles a webhook function that returns
|
|
5
|
+
it('compiles a webhook function that returns its previous result', async () => {
|
|
6
6
|
const prisma = {
|
|
7
7
|
workflowNodeType: {
|
|
8
8
|
findMany: jest.fn().mockResolvedValue([
|
|
@@ -54,11 +54,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
54
54
|
{
|
|
55
55
|
id: 'return',
|
|
56
56
|
type: 'return_result',
|
|
57
|
-
config: {
|
|
58
|
-
resultType: 'JSON',
|
|
59
|
-
sourceMode: 'WORKFLOW',
|
|
60
|
-
path: 'body',
|
|
61
|
-
},
|
|
57
|
+
config: {},
|
|
62
58
|
},
|
|
63
59
|
],
|
|
64
60
|
edges: [
|
|
@@ -77,7 +73,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
77
73
|
expect.objectContaining({ type: 'webhook_trigger' }),
|
|
78
74
|
expect.objectContaining({
|
|
79
75
|
type: 'return_result',
|
|
80
|
-
config:
|
|
76
|
+
config: {},
|
|
81
77
|
}),
|
|
82
78
|
]);
|
|
83
79
|
});
|
|
@@ -145,7 +141,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
145
141
|
),
|
|
146
142
|
).rejects.toMatchObject({
|
|
147
143
|
response: {
|
|
148
|
-
message: '
|
|
144
|
+
message: '等待返回结果时,每个流程分支都必须以“返回调用”结束',
|
|
149
145
|
nodeId: 'webhook',
|
|
150
146
|
},
|
|
151
147
|
});
|
|
@@ -277,7 +273,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
277
273
|
expect(instance.config).toEqual({ timeoutSeconds: 0 });
|
|
278
274
|
});
|
|
279
275
|
|
|
280
|
-
it('rejects a Human Approval node that references a disabled
|
|
276
|
+
it('rejects a Human Approval node that references a disabled Workflow', async () => {
|
|
281
277
|
const prisma = {
|
|
282
278
|
workflowNodeType: {
|
|
283
279
|
findMany: jest.fn().mockResolvedValue([
|
|
@@ -299,12 +295,15 @@ describe('WorkflowCompilerService', () => {
|
|
|
299
295
|
code: 'FINANCE_APPROVAL',
|
|
300
296
|
nodeTypeId: BigInt(7),
|
|
301
297
|
isEnabled: true,
|
|
302
|
-
config: {
|
|
298
|
+
config: { approvalWorkflowId: '11' },
|
|
303
299
|
},
|
|
304
300
|
]),
|
|
305
301
|
},
|
|
306
|
-
|
|
307
|
-
findUnique: jest.fn().mockResolvedValue({
|
|
302
|
+
workflow: {
|
|
303
|
+
findUnique: jest.fn().mockResolvedValue({
|
|
304
|
+
isEnabled: false,
|
|
305
|
+
status: 'PUBLISHED',
|
|
306
|
+
}),
|
|
308
307
|
},
|
|
309
308
|
};
|
|
310
309
|
const service = new WorkflowCompilerService(
|
|
@@ -333,13 +332,13 @@ describe('WorkflowCompilerService', () => {
|
|
|
333
332
|
),
|
|
334
333
|
).rejects.toMatchObject({
|
|
335
334
|
response: {
|
|
336
|
-
message: 'Approval
|
|
335
|
+
message: 'Approval Workflow was not found, published, or enabled',
|
|
337
336
|
nodeId: 'approval-1',
|
|
338
337
|
},
|
|
339
338
|
});
|
|
340
339
|
});
|
|
341
340
|
|
|
342
|
-
it('rejects a Human Approval node missing a required
|
|
341
|
+
it('rejects a Human Approval node missing a required Workflow argument', async () => {
|
|
343
342
|
const prisma = {
|
|
344
343
|
workflowNodeType: {
|
|
345
344
|
findMany: jest.fn().mockResolvedValue([
|
|
@@ -362,40 +361,52 @@ describe('WorkflowCompilerService', () => {
|
|
|
362
361
|
nodeTypeId: BigInt(7),
|
|
363
362
|
isEnabled: true,
|
|
364
363
|
config: {
|
|
365
|
-
|
|
366
|
-
|
|
364
|
+
approvalWorkflowId: '11',
|
|
365
|
+
approvalWorkflowInputBindings: {
|
|
367
366
|
title: { source: 'WORKFLOW', path: 'requestTitle' },
|
|
368
367
|
},
|
|
369
368
|
},
|
|
370
369
|
},
|
|
371
370
|
]),
|
|
372
371
|
},
|
|
373
|
-
|
|
372
|
+
workflow: {
|
|
374
373
|
findUnique: jest.fn().mockResolvedValue({
|
|
375
374
|
isEnabled: true,
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
375
|
+
status: 'PUBLISHED',
|
|
376
|
+
versions: [
|
|
377
|
+
{
|
|
378
|
+
dsl: {
|
|
379
|
+
nodes: [
|
|
380
|
+
{
|
|
381
|
+
id: 'input',
|
|
382
|
+
type: 'callable_trigger',
|
|
383
|
+
config: {
|
|
384
|
+
inputParameters: [
|
|
385
|
+
{ key: 'title', required: true },
|
|
386
|
+
{
|
|
387
|
+
key: 'content',
|
|
388
|
+
label: '审批内容',
|
|
389
|
+
required: true,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
393
392
|
},
|
|
394
|
-
|
|
395
|
-
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
id: 'return',
|
|
396
|
+
type: 'return_result',
|
|
397
|
+
config: {},
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
edges: [
|
|
401
|
+
{
|
|
402
|
+
source: 'input',
|
|
403
|
+
target: 'return',
|
|
404
|
+
kind: 'FLOW',
|
|
405
|
+
},
|
|
406
|
+
],
|
|
396
407
|
},
|
|
397
|
-
|
|
398
|
-
|
|
408
|
+
},
|
|
409
|
+
],
|
|
399
410
|
}),
|
|
400
411
|
},
|
|
401
412
|
};
|
|
@@ -425,7 +436,7 @@ describe('WorkflowCompilerService', () => {
|
|
|
425
436
|
),
|
|
426
437
|
).rejects.toMatchObject({
|
|
427
438
|
response: {
|
|
428
|
-
message: '
|
|
439
|
+
message: '审批 Workflow 缺少必填参数:审批内容',
|
|
429
440
|
nodeId: 'approval-1',
|
|
430
441
|
},
|
|
431
442
|
});
|
|
@@ -509,4 +520,79 @@ describe('WorkflowCompilerService', () => {
|
|
|
509
520
|
},
|
|
510
521
|
});
|
|
511
522
|
});
|
|
523
|
+
|
|
524
|
+
it('validates required inputs when calling another Workflow', async () => {
|
|
525
|
+
const prisma = {
|
|
526
|
+
workflowNodeType: {
|
|
527
|
+
findMany: jest.fn().mockResolvedValue([
|
|
528
|
+
{
|
|
529
|
+
id: BigInt(8),
|
|
530
|
+
type: 'sub_workflow',
|
|
531
|
+
runtimeKey: 'sub_workflow',
|
|
532
|
+
instancePolicy: 'INLINE_ONLY',
|
|
533
|
+
schemaVersion: 2,
|
|
534
|
+
configSchema: { type: 'object' },
|
|
535
|
+
uiSchema: {},
|
|
536
|
+
},
|
|
537
|
+
]),
|
|
538
|
+
},
|
|
539
|
+
workflowNodeInstance: { findMany: jest.fn().mockResolvedValue([]) },
|
|
540
|
+
workflow: {
|
|
541
|
+
findUnique: jest.fn().mockResolvedValue({
|
|
542
|
+
status: 'PUBLISHED',
|
|
543
|
+
isEnabled: true,
|
|
544
|
+
versions: [
|
|
545
|
+
{
|
|
546
|
+
dsl: {
|
|
547
|
+
nodes: [
|
|
548
|
+
{
|
|
549
|
+
id: 'input',
|
|
550
|
+
type: 'callable_trigger',
|
|
551
|
+
config: {
|
|
552
|
+
inputParameters: [
|
|
553
|
+
{ key: 'orderId', label: '订单 ID', required: true },
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
{ id: 'work', type: 'code', config: {} },
|
|
558
|
+
{ id: 'return', type: 'return_result', config: {} },
|
|
559
|
+
],
|
|
560
|
+
edges: [
|
|
561
|
+
{ source: 'input', target: 'work', kind: 'FLOW' },
|
|
562
|
+
{ source: 'work', target: 'return', kind: 'FLOW' },
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
}),
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
const service = new WorkflowCompilerService(
|
|
571
|
+
prisma as any,
|
|
572
|
+
{ validateOrThrow: jest.fn() } as any,
|
|
573
|
+
{ assertUiSchemaSupported: jest.fn() } as any,
|
|
574
|
+
{ validateSubjects: jest.fn() } as any,
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
await expect(
|
|
578
|
+
service.compile(
|
|
579
|
+
{
|
|
580
|
+
nodes: [
|
|
581
|
+
{
|
|
582
|
+
id: 'call-1',
|
|
583
|
+
type: 'sub_workflow',
|
|
584
|
+
config: { workflowId: '42', inputBindings: {} },
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
edges: [],
|
|
588
|
+
},
|
|
589
|
+
{ userId: 'author' },
|
|
590
|
+
),
|
|
591
|
+
).rejects.toMatchObject({
|
|
592
|
+
response: {
|
|
593
|
+
message: '调用 Workflow 的参数来源无效:订单 ID',
|
|
594
|
+
nodeId: 'call-1',
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
});
|
|
512
598
|
});
|
|
@@ -9,8 +9,8 @@ import { WidgetCatalogService } from '../workflowConfig/widget-catalog.service';
|
|
|
9
9
|
import { hashNodeInstanceConfig } from '../workflowNodeInstance/node-instance-config.util';
|
|
10
10
|
import { validateParameterBinding } from './parameter-binding';
|
|
11
11
|
import {
|
|
12
|
-
describeApprovalChannelInputs,
|
|
13
12
|
describeCallableWorkflow,
|
|
13
|
+
describeReturningWorkflowInputs,
|
|
14
14
|
validateCallableWorkflow,
|
|
15
15
|
} from './workflow-callable';
|
|
16
16
|
import {
|
|
@@ -166,49 +166,119 @@ export class WorkflowCompilerService {
|
|
|
166
166
|
'HTTP Request Connection was not found or is disabled',
|
|
167
167
|
);
|
|
168
168
|
}
|
|
169
|
+
if (nodeType.runtimeKey === 'sub_workflow') {
|
|
170
|
+
const workflowId = String(
|
|
171
|
+
(config as Record<string, unknown>)?.workflowId ?? '',
|
|
172
|
+
);
|
|
173
|
+
if (!/^[1-9]\d*$/.test(workflowId))
|
|
174
|
+
this.fail('NODE_TYPE_MISMATCH', node, '调用的 Workflow ID 无效');
|
|
175
|
+
const workflow = await this.prisma.workflow.findUnique({
|
|
176
|
+
where: { id: BigInt(workflowId) },
|
|
177
|
+
select: {
|
|
178
|
+
isEnabled: true,
|
|
179
|
+
status: true,
|
|
180
|
+
versions: {
|
|
181
|
+
orderBy: { version: 'desc' },
|
|
182
|
+
take: 1,
|
|
183
|
+
select: { dsl: true },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
if (!workflow?.isEnabled || workflow.status !== 'PUBLISHED')
|
|
188
|
+
this.fail(
|
|
189
|
+
'NODE_TYPE_MISMATCH',
|
|
190
|
+
node,
|
|
191
|
+
'调用的 Workflow 未发布或已停用',
|
|
192
|
+
);
|
|
193
|
+
let descriptor;
|
|
194
|
+
try {
|
|
195
|
+
descriptor = describeCallableWorkflow(workflow.versions[0]?.dsl);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.fail(
|
|
198
|
+
'NODE_TYPE_MISMATCH',
|
|
199
|
+
node,
|
|
200
|
+
error instanceof Error ? error.message : '调用的 Workflow 配置无效',
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (!descriptor)
|
|
204
|
+
this.fail(
|
|
205
|
+
'NODE_TYPE_MISMATCH',
|
|
206
|
+
node,
|
|
207
|
+
'选择的 Workflow 不能被内部调用',
|
|
208
|
+
);
|
|
209
|
+
const bindings =
|
|
210
|
+
(config as Record<string, unknown>).inputBindings &&
|
|
211
|
+
typeof (config as Record<string, unknown>).inputBindings ===
|
|
212
|
+
'object' &&
|
|
213
|
+
!Array.isArray((config as Record<string, unknown>).inputBindings)
|
|
214
|
+
? ((config as Record<string, unknown>).inputBindings as Record<
|
|
215
|
+
string,
|
|
216
|
+
unknown
|
|
217
|
+
>)
|
|
218
|
+
: {};
|
|
219
|
+
const invalidParameter = descriptor.inputParameters.find(
|
|
220
|
+
(parameter) =>
|
|
221
|
+
(parameter.required || bindings[parameter.key] !== undefined) &&
|
|
222
|
+
!validateParameterBinding(bindings[parameter.key]),
|
|
223
|
+
);
|
|
224
|
+
if (invalidParameter)
|
|
225
|
+
this.fail(
|
|
226
|
+
'NODE_TYPE_MISMATCH',
|
|
227
|
+
node,
|
|
228
|
+
`调用 Workflow 的参数来源无效:${
|
|
229
|
+
invalidParameter.label ?? invalidParameter.key
|
|
230
|
+
}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
169
233
|
if (nodeType.runtimeKey === 'human_approval') {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
234
|
+
const approvalWorkflowId = (config as Record<string, unknown>)
|
|
235
|
+
?.approvalWorkflowId;
|
|
236
|
+
if (
|
|
237
|
+
typeof approvalWorkflowId === 'string' &&
|
|
238
|
+
/^[1-9]\d*$/.test(approvalWorkflowId)
|
|
239
|
+
) {
|
|
240
|
+
const approvalWorkflow = await this.prisma.workflow.findUnique({
|
|
241
|
+
where: { id: BigInt(approvalWorkflowId) },
|
|
174
242
|
select: {
|
|
175
243
|
isEnabled: true,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
select: { dsl: true },
|
|
182
|
-
},
|
|
183
|
-
},
|
|
244
|
+
status: true,
|
|
245
|
+
versions: {
|
|
246
|
+
orderBy: { version: 'desc' },
|
|
247
|
+
take: 1,
|
|
248
|
+
select: { dsl: true },
|
|
184
249
|
},
|
|
185
250
|
},
|
|
186
251
|
});
|
|
187
|
-
if (
|
|
252
|
+
if (
|
|
253
|
+
!approvalWorkflow?.isEnabled ||
|
|
254
|
+
approvalWorkflow.status !== 'PUBLISHED'
|
|
255
|
+
)
|
|
188
256
|
this.fail(
|
|
189
257
|
'NODE_TYPE_MISMATCH',
|
|
190
258
|
node,
|
|
191
|
-
'Approval
|
|
259
|
+
'Approval Workflow was not found, published, or enabled',
|
|
192
260
|
);
|
|
193
261
|
const bindings =
|
|
194
|
-
(config as Record<string, unknown>).
|
|
195
|
-
typeof (config as Record<string, unknown>)
|
|
196
|
-
'object' &&
|
|
262
|
+
(config as Record<string, unknown>).approvalWorkflowInputBindings &&
|
|
263
|
+
typeof (config as Record<string, unknown>)
|
|
264
|
+
.approvalWorkflowInputBindings === 'object' &&
|
|
197
265
|
!Array.isArray(
|
|
198
|
-
(config as Record<string, unknown>).
|
|
266
|
+
(config as Record<string, unknown>).approvalWorkflowInputBindings,
|
|
199
267
|
)
|
|
200
268
|
? ((config as Record<string, unknown>)
|
|
201
|
-
.
|
|
269
|
+
.approvalWorkflowInputBindings as Record<string, unknown>)
|
|
202
270
|
: {};
|
|
203
|
-
const missing =
|
|
204
|
-
|
|
271
|
+
const missing = describeReturningWorkflowInputs(
|
|
272
|
+
approvalWorkflow.versions[0]?.dsl,
|
|
205
273
|
).find((parameter) => parameter.required && !bindings[parameter.key]);
|
|
206
274
|
if (missing)
|
|
207
275
|
this.fail(
|
|
208
276
|
'NODE_TYPE_MISMATCH',
|
|
209
277
|
node,
|
|
210
|
-
|
|
278
|
+
`审批 Workflow 缺少必填参数:${missing.label ?? missing.key}`,
|
|
211
279
|
);
|
|
280
|
+
} else if (approvalWorkflowId !== undefined) {
|
|
281
|
+
this.fail('NODE_TYPE_MISMATCH', node, 'Approval Workflow ID 无效');
|
|
212
282
|
}
|
|
213
283
|
const approverSources = Array.isArray(
|
|
214
284
|
(config as Record<string, unknown>)?.approverSources,
|
|
@@ -256,14 +326,11 @@ export class WorkflowCompilerService {
|
|
|
256
326
|
: '自动找审批人的 Workflow 配置无效',
|
|
257
327
|
);
|
|
258
328
|
}
|
|
259
|
-
if (
|
|
260
|
-
descriptor?.mode !== 'RETURN' ||
|
|
261
|
-
descriptor.resultType !== 'APPROVAL_SUBJECTS'
|
|
262
|
-
)
|
|
329
|
+
if (descriptor?.mode !== 'RETURN')
|
|
263
330
|
this.fail(
|
|
264
331
|
'NODE_TYPE_MISMATCH',
|
|
265
332
|
node,
|
|
266
|
-
'选择的 Workflow
|
|
333
|
+
'选择的 Workflow 没有返回结果',
|
|
267
334
|
);
|
|
268
335
|
const inputBindings =
|
|
269
336
|
source.inputBindings &&
|
|
@@ -104,25 +104,5 @@ function deriveDynamicOutputs(node: DslNode): string[] {
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
if (node.type === 'external_wait') {
|
|
108
|
-
const outcomes = Array.isArray(node.config?.outcomes)
|
|
109
|
-
? node.config.outcomes
|
|
110
|
-
: [];
|
|
111
|
-
const handles = outcomes
|
|
112
|
-
.map((item) => item?.code)
|
|
113
|
-
.filter(
|
|
114
|
-
(code): code is string => typeof code === 'string' && code.length > 0,
|
|
115
|
-
);
|
|
116
|
-
const timeoutOutcome = node.config?.timeout?.outcome;
|
|
117
|
-
if (
|
|
118
|
-
typeof timeoutOutcome === 'string' &&
|
|
119
|
-
timeoutOutcome.length > 0 &&
|
|
120
|
-
!handles.includes(timeoutOutcome)
|
|
121
|
-
) {
|
|
122
|
-
handles.push(timeoutOutcome);
|
|
123
|
-
}
|
|
124
|
-
return handles;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
107
|
return [];
|
|
128
108
|
}
|
|
@@ -12,15 +12,11 @@ const trigger = {
|
|
|
12
12
|
const returnNode = (id: string) => ({
|
|
13
13
|
id,
|
|
14
14
|
type: 'return_result',
|
|
15
|
-
config: {
|
|
16
|
-
resultType: 'JSON',
|
|
17
|
-
sourceMode: 'INPUT',
|
|
18
|
-
httpStatusCode: 200,
|
|
19
|
-
},
|
|
15
|
+
config: {},
|
|
20
16
|
});
|
|
21
17
|
|
|
22
18
|
describe('webhook function response contract', () => {
|
|
23
|
-
it('accepts conditional branches that each return their
|
|
19
|
+
it('accepts conditional branches that each return their previous result', () => {
|
|
24
20
|
expect(() =>
|
|
25
21
|
validateWebhookResponseWorkflow({
|
|
26
22
|
nodes: [
|
|
@@ -38,6 +34,15 @@ describe('webhook function response contract', () => {
|
|
|
38
34
|
).not.toThrow();
|
|
39
35
|
});
|
|
40
36
|
|
|
37
|
+
it('accepts returning the external request data directly', () => {
|
|
38
|
+
expect(() =>
|
|
39
|
+
validateWebhookResponseWorkflow({
|
|
40
|
+
nodes: [trigger, returnNode('return')],
|
|
41
|
+
edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
|
|
42
|
+
}),
|
|
43
|
+
).not.toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
41
46
|
it('rejects a function-mode webhook with a terminal branch that does not return', () => {
|
|
42
47
|
expect(() =>
|
|
43
48
|
validateWebhookResponseWorkflow({
|
|
@@ -53,33 +58,25 @@ describe('webhook function response contract', () => {
|
|
|
53
58
|
{ source: 'branch', target: 'unfinished', kind: 'FLOW' },
|
|
54
59
|
],
|
|
55
60
|
}),
|
|
56
|
-
).toThrow('
|
|
61
|
+
).toThrow('等待返回结果时,每个流程分支都必须以“返回调用”结束');
|
|
57
62
|
});
|
|
58
63
|
|
|
59
|
-
it('rejects
|
|
64
|
+
it('rejects a return node with more than one previous node', () => {
|
|
60
65
|
expect(() =>
|
|
61
66
|
validateWebhookResponseWorkflow({
|
|
62
67
|
nodes: [
|
|
63
68
|
trigger,
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
69
|
+
{ id: 'left', type: 'code', config: {} },
|
|
70
|
+
{ id: 'right', type: 'code', config: {} },
|
|
71
|
+
returnNode('return'),
|
|
72
|
+
],
|
|
73
|
+
edges: [
|
|
74
|
+
{ source: 'webhook', target: 'left', kind: 'FLOW' },
|
|
75
|
+
{ source: 'webhook', target: 'right', kind: 'FLOW' },
|
|
76
|
+
{ source: 'left', target: 'return', kind: 'FLOW' },
|
|
77
|
+
{ source: 'right', target: 'return', kind: 'FLOW' },
|
|
71
78
|
],
|
|
72
|
-
edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
|
|
73
|
-
}),
|
|
74
|
-
).toThrow('HTTP 调用只能返回普通数据');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('rejects returning a previous result when only the trigger is connected', () => {
|
|
78
|
-
expect(() =>
|
|
79
|
-
validateWebhookResponseWorkflow({
|
|
80
|
-
nodes: [trigger, returnNode('return')],
|
|
81
|
-
edges: [{ source: 'webhook', target: 'return', kind: 'FLOW' }],
|
|
82
79
|
}),
|
|
83
|
-
).toThrow('
|
|
80
|
+
).toThrow('“返回调用”前需要连接一个节点');
|
|
84
81
|
});
|
|
85
82
|
});
|
|
@@ -69,7 +69,7 @@ function validateReturningBranches(
|
|
|
69
69
|
const queue = [...(outgoing.get(triggerId) ?? [])];
|
|
70
70
|
const visited = new Set<string>();
|
|
71
71
|
if (queue.length === 0) {
|
|
72
|
-
throw new Error('
|
|
72
|
+
throw new Error('等待返回结果时,每个流程分支都必须以“返回调用”结束');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
while (queue.length > 0) {
|
|
@@ -81,67 +81,21 @@ function validateReturningBranches(
|
|
|
81
81
|
const next = outgoing.get(nodeId) ?? [];
|
|
82
82
|
|
|
83
83
|
if (node.type === 'return_result') {
|
|
84
|
-
validateReturnNode(
|
|
84
|
+
validateReturnNode(next, incoming.get(nodeId) ?? []);
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
if (next.length === 0) {
|
|
88
|
-
throw new Error('
|
|
88
|
+
throw new Error('等待返回结果时,每个流程分支都必须以“返回调用”结束');
|
|
89
89
|
}
|
|
90
90
|
queue.push(...next);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function validateReturnNode(
|
|
95
|
-
node: DslNode,
|
|
96
|
-
next: string[],
|
|
97
|
-
previous: string[],
|
|
98
|
-
nodeById: Map<string, DslNode>,
|
|
99
|
-
): void {
|
|
100
|
-
const config = asRecord(node.config);
|
|
101
|
-
if (config.resultType !== 'JSON') {
|
|
102
|
-
throw new Error('HTTP 调用只能返回普通数据');
|
|
103
|
-
}
|
|
94
|
+
function validateReturnNode(next: string[], previous: string[]): void {
|
|
104
95
|
if (next.length > 0) {
|
|
105
|
-
throw new Error('
|
|
106
|
-
}
|
|
107
|
-
if (config.sourceMode === 'INPUT') {
|
|
108
|
-
const processingNodes = previous.filter((nodeId) => {
|
|
109
|
-
const predecessorType = nodeById.get(nodeId)?.type;
|
|
110
|
-
return (
|
|
111
|
-
typeof predecessorType !== 'string' ||
|
|
112
|
-
!predecessorType.endsWith('_trigger')
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
if (processingNodes.length !== 1) {
|
|
116
|
-
throw new Error('返回上一步结果时,“返回结果”前需要连接一个处理节点');
|
|
117
|
-
}
|
|
118
|
-
} else if (config.sourceMode === 'WORKFLOW') {
|
|
119
|
-
if (typeof config.path !== 'string' || !config.path.trim()) {
|
|
120
|
-
throw new Error('返回 Workflow 数据时必须填写字段路径');
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
throw new Error('返回结果来源必须是上一个节点或 Workflow 数据');
|
|
124
|
-
}
|
|
125
|
-
if (
|
|
126
|
-
config.httpStatusCode !== undefined &&
|
|
127
|
-
(typeof config.httpStatusCode !== 'number' ||
|
|
128
|
-
!Number.isInteger(config.httpStatusCode) ||
|
|
129
|
-
config.httpStatusCode < 100 ||
|
|
130
|
-
config.httpStatusCode > 599)
|
|
131
|
-
) {
|
|
132
|
-
throw new Error('HTTP 状态码必须是 100 到 599 的整数');
|
|
133
|
-
}
|
|
134
|
-
if (config.httpHeaders !== undefined) {
|
|
135
|
-
const headers = asRecord(config.httpHeaders);
|
|
136
|
-
if (
|
|
137
|
-
!config.httpHeaders ||
|
|
138
|
-
typeof config.httpHeaders !== 'object' ||
|
|
139
|
-
Array.isArray(config.httpHeaders) ||
|
|
140
|
-
Object.values(headers).some((value) => typeof value !== 'string')
|
|
141
|
-
) {
|
|
142
|
-
throw new Error('HTTP 响应头必须是字符串键值');
|
|
143
|
-
}
|
|
96
|
+
throw new Error('“返回调用”必须是流程分支的最后一个节点');
|
|
144
97
|
}
|
|
98
|
+
if (previous.length !== 1) throw new Error('“返回调用”前需要连接一个节点');
|
|
145
99
|
}
|
|
146
100
|
|
|
147
101
|
function asDsl(value: unknown): DslLike {
|
|
@@ -64,8 +64,8 @@ export class WorkflowController {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
@Get('callable')
|
|
67
|
-
findCallable(
|
|
68
|
-
return this.workflowService.findCallableWorkflows(
|
|
67
|
+
findCallable() {
|
|
68
|
+
return this.workflowService.findCallableWorkflows();
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
@Get('external-interaction-options')
|
|
@@ -156,6 +156,30 @@ describe('WorkflowService — cron lifecycle hooks', () => {
|
|
|
156
156
|
expect(result).toMatchObject({ dsl: changedDsl, status: 'DRAFT' });
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
it('rejects saving an internally callable Workflow without a return node', async () => {
|
|
160
|
+
prisma.workflow.findUnique.mockResolvedValueOnce({
|
|
161
|
+
id: BigInt(42),
|
|
162
|
+
versions: [],
|
|
163
|
+
});
|
|
164
|
+
const callableDsl = {
|
|
165
|
+
nodes: [
|
|
166
|
+
{ id: 'input', type: 'callable_trigger', config: {} },
|
|
167
|
+
{ id: 'work', type: 'code', config: {} },
|
|
168
|
+
],
|
|
169
|
+
edges: [{ source: 'input', target: 'work', kind: 'FLOW' }],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
await expect(
|
|
173
|
+
service.saveDraft(BigInt(42), callableDsl),
|
|
174
|
+
).rejects.toMatchObject({
|
|
175
|
+
response: expect.objectContaining({
|
|
176
|
+
code: 'CALLABLE_WORKFLOW_INVALID',
|
|
177
|
+
message: '可调用 Workflow 必须且只能包含一个“返回调用”节点',
|
|
178
|
+
}),
|
|
179
|
+
});
|
|
180
|
+
expect(prisma.workflow.update).not.toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
|
|
159
183
|
it('does not create a version when the saved DSL is already published', async () => {
|
|
160
184
|
prisma.workflow.findUnique.mockResolvedValueOnce({
|
|
161
185
|
id: BigInt(42),
|