@gadmin2n/schematics 0.0.152 → 0.0.153
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/CODEBUDDY.local.md +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.seed-timestamp +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permission.seed.ts +23 -44
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +25 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +74 -64
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +0 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-cancellation.spec.ts +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +58 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +66 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution-idempotency.spec.ts +61 -149
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.spec.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle-runtime.controller.ts +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-lifecycle.service.spec.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +38 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +18 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +131 -242
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +7 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +6 -36
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +0 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +29 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +29 -7
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/people-groups/index.tsx +20 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/person-directory/index.tsx +38 -35
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +6 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +24 -22
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/EnhancedFlowRenderer.tsx +0 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +24 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +20 -18
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +28 -79
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/DbExecuteConfigEditor.tsx +227 -97
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +62 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/settings/index.tsx +73 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +21 -46
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +1 -0
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@ describe('Workflow execution idempotency', () => {
|
|
|
47
47
|
temporalRunId: 'wf-7-21',
|
|
48
48
|
}),
|
|
49
49
|
cancelWorkflow: jest.fn().mockResolvedValue(undefined),
|
|
50
|
+
signalWorkflow: jest.fn().mockResolvedValue(undefined),
|
|
50
51
|
inspectWorkflow: jest.fn(),
|
|
51
52
|
};
|
|
52
53
|
const lifecycle: any = {
|
|
@@ -362,7 +363,7 @@ describe('Workflow execution idempotency', () => {
|
|
|
362
363
|
);
|
|
363
364
|
});
|
|
364
365
|
|
|
365
|
-
it('
|
|
366
|
+
it('rejects node retry after the Temporal run has already failed', async () => {
|
|
366
367
|
const { prisma, temporal, service } = setup();
|
|
367
368
|
prisma.workflowInstance.findUnique.mockResolvedValueOnce({
|
|
368
369
|
id: BigInt(22),
|
|
@@ -372,100 +373,82 @@ describe('Workflow execution idempotency', () => {
|
|
|
372
373
|
context: { workflow: { orderId: 'O-1' }, nodes: {} },
|
|
373
374
|
nodeExecutions: [],
|
|
374
375
|
});
|
|
375
|
-
prisma.workflowInstance.findFirst.mockResolvedValueOnce({
|
|
376
|
-
id: BigInt(23),
|
|
377
|
-
workflowId,
|
|
378
|
-
versionId: version.id,
|
|
379
|
-
status: 'COMPLETED',
|
|
380
|
-
temporalRunId: 'wf-7-23',
|
|
381
|
-
});
|
|
382
|
-
const execute = jest.spyOn(service, 'executeWorkflow');
|
|
383
376
|
|
|
384
377
|
await expect(
|
|
385
|
-
service.
|
|
378
|
+
service.retryFailedNode(BigInt(22), 'request', temporal),
|
|
386
379
|
).resolves.toEqual({
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
workflowVersionId: '11',
|
|
390
|
-
status: 'COMPLETED',
|
|
380
|
+
kind: 'conflict',
|
|
381
|
+
message: '只有等待重试的 Workflow 才能重试失败节点',
|
|
391
382
|
});
|
|
392
|
-
expect(
|
|
383
|
+
expect(temporal.signalWorkflow).not.toHaveBeenCalled();
|
|
393
384
|
});
|
|
394
385
|
|
|
395
|
-
it('
|
|
386
|
+
it('signals the same Temporal run when a waiting node is retried', async () => {
|
|
396
387
|
const { prisma, temporal, service } = setup();
|
|
397
|
-
const
|
|
398
|
-
previous: { eventId: 'event-1' },
|
|
399
|
-
result: { requestId: '6', result: 'APPROVED', approved: true },
|
|
400
|
-
};
|
|
388
|
+
const failedAt = new Date('2026-09-16T08:00:00.000Z');
|
|
401
389
|
prisma.workflowInstance.findUnique.mockResolvedValueOnce({
|
|
402
390
|
id: BigInt(22),
|
|
403
391
|
workflowId,
|
|
404
392
|
versionId: version.id,
|
|
405
|
-
status: '
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
nodes: {},
|
|
409
|
-
system: { operationScopeId: '21' },
|
|
410
|
-
},
|
|
393
|
+
status: 'WAITING_RETRY',
|
|
394
|
+
temporalRunId: 'wf-7-22',
|
|
395
|
+
context: { workflow: { orderId: 'O-1' }, nodes: {} },
|
|
411
396
|
nodeExecutions: [
|
|
412
397
|
{
|
|
413
|
-
nodeId: '
|
|
414
|
-
status: '
|
|
415
|
-
output:
|
|
398
|
+
nodeId: 'parallel',
|
|
399
|
+
status: 'FAILED',
|
|
400
|
+
output: null,
|
|
401
|
+
updatedAt: failedAt,
|
|
416
402
|
},
|
|
417
|
-
{ nodeId: 'request', status: 'FAILED', output: null },
|
|
418
403
|
],
|
|
419
404
|
});
|
|
420
405
|
prisma.workflowVersion.findUnique.mockResolvedValueOnce({
|
|
421
406
|
dsl: {
|
|
422
407
|
nodes: [
|
|
423
|
-
{ id: 'start', type: '
|
|
424
|
-
{ id: '
|
|
425
|
-
{ id: 'request', type: 'http_request', label: '
|
|
408
|
+
{ id: 'start', type: 'manual_trigger', label: '手动触发' },
|
|
409
|
+
{ id: 'parallel', type: 'parallel', label: '并行执行' },
|
|
410
|
+
{ id: 'branch-request', type: 'http_request', label: '分支请求' },
|
|
426
411
|
],
|
|
427
412
|
edges: [
|
|
428
|
-
{ source: 'start', target: '
|
|
413
|
+
{ source: 'start', target: 'parallel', kind: 'FLOW' },
|
|
414
|
+
{
|
|
415
|
+
source: 'parallel',
|
|
416
|
+
target: 'branch-request',
|
|
417
|
+
kind: 'CALL',
|
|
418
|
+
pairId: 'branch-a',
|
|
419
|
+
},
|
|
429
420
|
{
|
|
430
|
-
source: '
|
|
431
|
-
target: '
|
|
432
|
-
|
|
433
|
-
|
|
421
|
+
source: 'branch-request',
|
|
422
|
+
target: 'parallel',
|
|
423
|
+
kind: 'RETURN',
|
|
424
|
+
pairId: 'branch-a',
|
|
434
425
|
},
|
|
435
426
|
],
|
|
436
427
|
},
|
|
437
428
|
});
|
|
438
|
-
const execute = jest
|
|
439
|
-
.spyOn(service, 'executeWorkflow')
|
|
440
|
-
.mockResolvedValue({ instanceId: '23' } as any);
|
|
441
|
-
|
|
442
|
-
await service.retryInstanceFromNode(
|
|
443
|
-
BigInt(22),
|
|
444
|
-
'request',
|
|
445
|
-
'alice',
|
|
446
|
-
temporal,
|
|
447
|
-
);
|
|
429
|
+
const execute = jest.spyOn(service, 'executeWorkflow');
|
|
448
430
|
|
|
449
|
-
expect(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
431
|
+
await expect(
|
|
432
|
+
service.retryFailedNode(BigInt(22), 'parallel', temporal),
|
|
433
|
+
).resolves.toEqual({
|
|
434
|
+
kind: 'ok',
|
|
435
|
+
value: {
|
|
436
|
+
instanceId: '22',
|
|
437
|
+
temporalRunId: 'wf-7-22',
|
|
438
|
+
workflowVersionId: '11',
|
|
439
|
+
status: 'WAITING_RETRY',
|
|
455
440
|
},
|
|
456
|
-
|
|
457
|
-
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(temporal.signalWorkflow).toHaveBeenCalledWith(
|
|
444
|
+
'wf-7-22',
|
|
445
|
+
'retryNode',
|
|
458
446
|
{
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
startNodeId: 'request',
|
|
462
|
-
completedNodeIds: ['approval'],
|
|
463
|
-
outputs: { approval: approvalOutput },
|
|
464
|
-
},
|
|
465
|
-
retryOfInstanceId: BigInt(22),
|
|
466
|
-
retryFromNodeId: 'request',
|
|
447
|
+
nodeId: 'parallel',
|
|
448
|
+
requestId: 'retry-node:22:parallel:2026-09-16T08:00:00.000Z',
|
|
467
449
|
},
|
|
468
450
|
);
|
|
451
|
+
expect(execute).not.toHaveBeenCalled();
|
|
469
452
|
});
|
|
470
453
|
|
|
471
454
|
it('does not retry from inside a private branch pipeline', async () => {
|
|
@@ -474,10 +457,16 @@ describe('Workflow execution idempotency', () => {
|
|
|
474
457
|
id: BigInt(22),
|
|
475
458
|
workflowId,
|
|
476
459
|
versionId: version.id,
|
|
477
|
-
status: '
|
|
460
|
+
status: 'WAITING_RETRY',
|
|
461
|
+
temporalRunId: 'wf-7-22',
|
|
478
462
|
context: { workflow: {}, nodes: {} },
|
|
479
463
|
nodeExecutions: [
|
|
480
|
-
{
|
|
464
|
+
{
|
|
465
|
+
nodeId: 'branch-request',
|
|
466
|
+
status: 'FAILED',
|
|
467
|
+
output: null,
|
|
468
|
+
updatedAt: new Date('2026-09-16T08:00:00.000Z'),
|
|
469
|
+
},
|
|
481
470
|
],
|
|
482
471
|
});
|
|
483
472
|
prisma.workflowVersion.findUnique.mockResolvedValueOnce({
|
|
@@ -504,88 +493,11 @@ describe('Workflow execution idempotency', () => {
|
|
|
504
493
|
});
|
|
505
494
|
|
|
506
495
|
await expect(
|
|
507
|
-
service.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
temporal,
|
|
512
|
-
),
|
|
513
|
-
).rejects.toThrow('暂不支持从循环或并行分支内部重试');
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
it('keeps inherited checkpoint outputs when a resumed run fails farther downstream', async () => {
|
|
517
|
-
const { prisma, temporal, service } = setup();
|
|
518
|
-
const approvalOutput = {
|
|
519
|
-
result: { requestId: '6', result: 'APPROVED', approved: true },
|
|
520
|
-
};
|
|
521
|
-
const requestOutput = { status: 200, data: { resolved: true } };
|
|
522
|
-
prisma.workflowInstance.findUnique.mockResolvedValueOnce({
|
|
523
|
-
id: BigInt(23),
|
|
524
|
-
workflowId,
|
|
525
|
-
versionId: version.id,
|
|
526
|
-
status: 'FAILED',
|
|
527
|
-
context: {
|
|
528
|
-
workflow: { eventId: 'event-1' },
|
|
529
|
-
nodes: {},
|
|
530
|
-
system: {
|
|
531
|
-
operationScopeId: '21',
|
|
532
|
-
retryCheckpoint: {
|
|
533
|
-
startNodeId: 'request',
|
|
534
|
-
completedNodeIds: ['approval'],
|
|
535
|
-
outputs: { approval: approvalOutput },
|
|
536
|
-
},
|
|
537
|
-
},
|
|
538
|
-
},
|
|
539
|
-
nodeExecutions: [
|
|
540
|
-
{ nodeId: 'request', status: 'COMPLETED', output: requestOutput },
|
|
541
|
-
{ nodeId: 'notify', status: 'FAILED', output: null },
|
|
542
|
-
],
|
|
543
|
-
});
|
|
544
|
-
prisma.workflowVersion.findUnique.mockResolvedValueOnce({
|
|
545
|
-
dsl: {
|
|
546
|
-
nodes: [
|
|
547
|
-
{ id: 'start', type: 'event_trigger', label: '业务事件触发' },
|
|
548
|
-
{ id: 'approval', type: 'approval', label: '人工审批' },
|
|
549
|
-
{ id: 'request', type: 'http_request', label: '确认报废' },
|
|
550
|
-
{ id: 'notify', type: 'http_request', label: '发送通知' },
|
|
551
|
-
],
|
|
552
|
-
edges: [
|
|
553
|
-
{ source: 'start', target: 'approval', kind: 'FLOW' },
|
|
554
|
-
{ source: 'approval', target: 'request', kind: 'FLOW' },
|
|
555
|
-
{ source: 'request', target: 'notify', kind: 'FLOW' },
|
|
556
|
-
],
|
|
557
|
-
},
|
|
496
|
+
service.retryFailedNode(BigInt(22), 'branch-request', temporal),
|
|
497
|
+
).resolves.toEqual({
|
|
498
|
+
kind: 'invalid_input',
|
|
499
|
+
message: '请从外层循环或并行节点重试',
|
|
558
500
|
});
|
|
559
|
-
const execute = jest
|
|
560
|
-
.spyOn(service, 'executeWorkflow')
|
|
561
|
-
.mockResolvedValue({ instanceId: '24' } as any);
|
|
562
|
-
|
|
563
|
-
await service.retryInstanceFromNode(
|
|
564
|
-
BigInt(23),
|
|
565
|
-
'notify',
|
|
566
|
-
'alice',
|
|
567
|
-
temporal,
|
|
568
|
-
);
|
|
569
|
-
|
|
570
|
-
expect(execute).toHaveBeenCalledWith(
|
|
571
|
-
workflowId,
|
|
572
|
-
expect.objectContaining({
|
|
573
|
-
idempotencyKey: 'retry:23',
|
|
574
|
-
}),
|
|
575
|
-
'alice',
|
|
576
|
-
temporal,
|
|
577
|
-
expect.objectContaining({
|
|
578
|
-
operationScopeId: '21',
|
|
579
|
-
retryCheckpoint: {
|
|
580
|
-
startNodeId: 'notify',
|
|
581
|
-
completedNodeIds: ['approval', 'request'],
|
|
582
|
-
outputs: {
|
|
583
|
-
approval: approvalOutput,
|
|
584
|
-
request: requestOutput,
|
|
585
|
-
},
|
|
586
|
-
},
|
|
587
|
-
}),
|
|
588
|
-
);
|
|
589
501
|
});
|
|
590
502
|
|
|
591
503
|
it('restarts a stale reserved instance when Temporal has no execution', async () => {
|
|
@@ -785,7 +697,7 @@ describe('Workflow execution idempotency', () => {
|
|
|
785
697
|
expect(lifecycle.markFailed).toHaveBeenCalledWith(
|
|
786
698
|
expect.objectContaining({
|
|
787
699
|
instanceId: BigInt(21),
|
|
788
|
-
expectedStatuses: ['PENDING', 'RUNNING'],
|
|
700
|
+
expectedStatuses: ['PENDING', 'RUNNING', 'WAITING_RETRY'],
|
|
789
701
|
temporalRunId: 'wf-7-21',
|
|
790
702
|
}),
|
|
791
703
|
);
|
|
@@ -813,7 +725,7 @@ describe('Workflow execution idempotency', () => {
|
|
|
813
725
|
expect(lifecycle.markFailed).toHaveBeenCalledWith(
|
|
814
726
|
expect.objectContaining({
|
|
815
727
|
instanceId: BigInt(21),
|
|
816
|
-
expectedStatuses: ['RUNNING'],
|
|
728
|
+
expectedStatuses: ['RUNNING', 'WAITING_RETRY'],
|
|
817
729
|
temporalRunId: 'wf-7-21',
|
|
818
730
|
result: expect.objectContaining({
|
|
819
731
|
code: 'TEMPORAL_EXECUTION_MISSING',
|
|
@@ -26,7 +26,7 @@ describe('WorkflowLifecycleRuntimeController', () => {
|
|
|
26
26
|
expect(runtimeConfig.assertTenant).toHaveBeenCalledWith('gadmin-test');
|
|
27
27
|
expect(lifecycle.markFailed).toHaveBeenCalledWith({
|
|
28
28
|
instanceId: BigInt(45),
|
|
29
|
-
expectedStatuses: ['PENDING', 'RUNNING'],
|
|
29
|
+
expectedStatuses: ['PENDING', 'RUNNING', 'WAITING_RETRY'],
|
|
30
30
|
result: { error: 'dynamic_users is required' },
|
|
31
31
|
});
|
|
32
32
|
});
|
|
@@ -42,7 +42,7 @@ export class WorkflowLifecycleRuntimeController {
|
|
|
42
42
|
const instanceId = BigInt(input.workflowInstanceId);
|
|
43
43
|
await this.lifecycle.markFailed({
|
|
44
44
|
instanceId,
|
|
45
|
-
expectedStatuses: ['PENDING', 'RUNNING'],
|
|
45
|
+
expectedStatuses: ['PENDING', 'RUNNING', 'WAITING_RETRY'],
|
|
46
46
|
result: (input.result ?? {
|
|
47
47
|
code: 'WORKFLOW_EXECUTION_FAILED',
|
|
48
48
|
}) as Prisma.InputJsonObject,
|
|
@@ -61,7 +61,7 @@ describe('WorkflowLifecycleService', () => {
|
|
|
61
61
|
await expect(
|
|
62
62
|
service.markFailed({
|
|
63
63
|
instanceId: BigInt(45),
|
|
64
|
-
expectedStatuses: ['PENDING', 'RUNNING'],
|
|
64
|
+
expectedStatuses: ['PENDING', 'RUNNING', 'WAITING_RETRY'],
|
|
65
65
|
temporalRunId: 'wf-45',
|
|
66
66
|
result: { code: 'TEMPORAL_TERMINAL_STATUS_RECONCILED' },
|
|
67
67
|
finishedAt: failedAt,
|
|
@@ -72,7 +72,7 @@ describe('WorkflowLifecycleService', () => {
|
|
|
72
72
|
expect(tx.workflowInstance.updateMany).toHaveBeenCalledWith({
|
|
73
73
|
where: {
|
|
74
74
|
id: BigInt(45),
|
|
75
|
-
status: { in: ['PENDING', 'RUNNING'] },
|
|
75
|
+
status: { in: ['PENDING', 'RUNNING', 'WAITING_RETRY'] },
|
|
76
76
|
temporalRunId: 'wf-45',
|
|
77
77
|
},
|
|
78
78
|
data: {
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
ConflictException,
|
|
4
|
+
NotFoundException,
|
|
5
|
+
} from '@nestjs/common';
|
|
1
6
|
import { WorkflowController } from './workflow.controller';
|
|
2
7
|
|
|
3
8
|
describe('WorkflowController runtime contracts', () => {
|
|
@@ -94,4 +99,37 @@ describe('WorkflowController runtime contracts', () => {
|
|
|
94
99
|
requestedBy: 'admin-user',
|
|
95
100
|
});
|
|
96
101
|
});
|
|
102
|
+
|
|
103
|
+
it.each([
|
|
104
|
+
['not_found', NotFoundException],
|
|
105
|
+
['conflict', ConflictException],
|
|
106
|
+
['invalid_input', BadRequestException],
|
|
107
|
+
] as const)(
|
|
108
|
+
'maps retry-node %s outcomes at the HTTP boundary',
|
|
109
|
+
async (kind, ExceptionType) => {
|
|
110
|
+
const temporalService = {};
|
|
111
|
+
const workflowService = {
|
|
112
|
+
retryFailedNode: jest.fn().mockResolvedValue({
|
|
113
|
+
kind,
|
|
114
|
+
message: 'retry rejected',
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
const controller = new WorkflowController(
|
|
118
|
+
workflowService as never,
|
|
119
|
+
temporalService as never,
|
|
120
|
+
{} as never,
|
|
121
|
+
{} as never,
|
|
122
|
+
{} as never,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await expect(
|
|
126
|
+
controller.retryFailedNode(42, { nodeId: 'request' }),
|
|
127
|
+
).rejects.toBeInstanceOf(ExceptionType);
|
|
128
|
+
expect(workflowService.retryFailedNode).toHaveBeenCalledWith(
|
|
129
|
+
BigInt(42),
|
|
130
|
+
'request',
|
|
131
|
+
temporalService,
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
);
|
|
97
135
|
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ACGuard } from '@gadmin2n/nest-common';
|
|
2
2
|
import {
|
|
3
|
+
BadRequestException,
|
|
3
4
|
Body,
|
|
5
|
+
ConflictException,
|
|
4
6
|
Controller,
|
|
5
7
|
Delete,
|
|
6
8
|
Get,
|
|
7
9
|
Headers,
|
|
8
10
|
HttpCode,
|
|
11
|
+
NotFoundException,
|
|
9
12
|
Param,
|
|
10
13
|
ParseIntPipe,
|
|
11
14
|
Patch,
|
|
@@ -26,7 +29,7 @@ import { listWorkflowDatabaseFunctions } from './workflow-database-function';
|
|
|
26
29
|
import {
|
|
27
30
|
ApproveInstanceDto,
|
|
28
31
|
ExecuteWorkflowDto,
|
|
29
|
-
|
|
32
|
+
RetryFailedNodeDto,
|
|
30
33
|
} from './workflow-execution.dto';
|
|
31
34
|
import { ImportPayloadDto } from './workflow-export.dto';
|
|
32
35
|
import { WorkflowExportService } from './workflow-export.service';
|
|
@@ -431,23 +434,31 @@ export class WorkflowController {
|
|
|
431
434
|
);
|
|
432
435
|
}
|
|
433
436
|
|
|
434
|
-
@Post('instance/:instanceId/retry-
|
|
437
|
+
@Post('instance/:instanceId/retry-failed-node')
|
|
435
438
|
@UseRoles({
|
|
436
439
|
resource: 'workflowExecution',
|
|
437
440
|
action: 'update',
|
|
438
441
|
possession: 'any',
|
|
439
442
|
})
|
|
440
|
-
async
|
|
443
|
+
async retryFailedNode(
|
|
441
444
|
@Param('instanceId', ParseIntPipe) instanceId: number,
|
|
442
|
-
@Body() dto:
|
|
443
|
-
@Req() req,
|
|
445
|
+
@Body() dto: RetryFailedNodeDto,
|
|
444
446
|
) {
|
|
445
|
-
|
|
447
|
+
const outcome = await this.workflowService.retryFailedNode(
|
|
446
448
|
BigInt(instanceId),
|
|
447
449
|
dto.nodeId,
|
|
448
|
-
req.user.userid,
|
|
449
450
|
this.temporalService,
|
|
450
451
|
);
|
|
452
|
+
switch (outcome.kind) {
|
|
453
|
+
case 'ok':
|
|
454
|
+
return outcome.value;
|
|
455
|
+
case 'not_found':
|
|
456
|
+
throw new NotFoundException(outcome.message);
|
|
457
|
+
case 'conflict':
|
|
458
|
+
throw new ConflictException(outcome.message);
|
|
459
|
+
case 'invalid_input':
|
|
460
|
+
throw new BadRequestException(outcome.message);
|
|
461
|
+
}
|
|
451
462
|
}
|
|
452
463
|
|
|
453
464
|
@Post('instance/:instanceId/approve')
|