@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
|
@@ -12,7 +12,7 @@ const registration = {
|
|
|
12
12
|
registrationKey: 'a'.repeat(64),
|
|
13
13
|
schemaName: 'public',
|
|
14
14
|
tableName: 't_hardware_asset',
|
|
15
|
-
|
|
15
|
+
operations: ['UPDATE'],
|
|
16
16
|
watchedFields: ['status'],
|
|
17
17
|
primaryKeyFields: ['id'],
|
|
18
18
|
isEnabled: true,
|
|
@@ -67,6 +67,7 @@ describe('PostgreSQL business event registration', () => {
|
|
|
67
67
|
type: 'event_trigger',
|
|
68
68
|
config: {
|
|
69
69
|
registrationCode: registration.code,
|
|
70
|
+
operationRules: [{ operation: 'UPDATE' }],
|
|
70
71
|
processingMode: 'SERIAL_BY_KEY',
|
|
71
72
|
concurrencyKeyPath: 'primaryKey.id',
|
|
72
73
|
},
|
|
@@ -98,6 +99,7 @@ describe('PostgreSQL business event registration', () => {
|
|
|
98
99
|
type: 'event_trigger',
|
|
99
100
|
config: {
|
|
100
101
|
registrationCode: registration.code,
|
|
102
|
+
operationRules: [{ operation: 'UPDATE' }],
|
|
101
103
|
processingMode: 'SERIAL_BY_KEY',
|
|
102
104
|
concurrencyKeyPath: 'missing',
|
|
103
105
|
},
|
|
@@ -116,17 +118,22 @@ describe('PostgreSQL business event registration', () => {
|
|
|
116
118
|
config: {
|
|
117
119
|
registrationCode: registration.code,
|
|
118
120
|
processingMode: 'PARALLEL',
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
operationRules: [
|
|
122
|
+
{
|
|
123
|
+
operation: 'UPDATE',
|
|
124
|
+
match: {
|
|
125
|
+
combineOperation: 'AND',
|
|
126
|
+
conditions: [
|
|
127
|
+
{
|
|
128
|
+
id: 'condition-1',
|
|
129
|
+
left: { source: 'WORKFLOW', path: leftPath },
|
|
130
|
+
operator: 'EQ',
|
|
131
|
+
right: { source: 'FIXED', valueType, value: 'APPROVED' },
|
|
132
|
+
},
|
|
133
|
+
],
|
|
127
134
|
},
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
},
|
|
136
|
+
],
|
|
130
137
|
},
|
|
131
138
|
});
|
|
132
139
|
|
|
@@ -179,7 +186,7 @@ describe('PostgreSQL business event registration', () => {
|
|
|
179
186
|
{
|
|
180
187
|
schemaName: 'public',
|
|
181
188
|
tableName: 't_hardware_asset',
|
|
182
|
-
|
|
189
|
+
operations: ['UPDATE'],
|
|
183
190
|
watchedFields: ['status'],
|
|
184
191
|
},
|
|
185
192
|
'admin',
|
|
@@ -224,7 +231,7 @@ describe('PostgreSQL business event registration', () => {
|
|
|
224
231
|
{
|
|
225
232
|
schemaName: 'another_schema',
|
|
226
233
|
tableName: 't_hardware_asset',
|
|
227
|
-
|
|
234
|
+
operations: ['UPDATE'],
|
|
228
235
|
watchedFields: ['status'],
|
|
229
236
|
},
|
|
230
237
|
'admin',
|
|
@@ -236,8 +243,8 @@ describe('PostgreSQL business event registration', () => {
|
|
|
236
243
|
it('registers INSERT for the whole row without requiring selected fields', async () => {
|
|
237
244
|
const insertRegistration = {
|
|
238
245
|
...registration,
|
|
239
|
-
|
|
240
|
-
watchedFields: [
|
|
246
|
+
operations: ['INSERT'],
|
|
247
|
+
watchedFields: [],
|
|
241
248
|
};
|
|
242
249
|
const executeRawUnsafe = jest.fn().mockResolvedValue(0);
|
|
243
250
|
const tx = {
|
|
@@ -266,14 +273,14 @@ describe('PostgreSQL business event registration', () => {
|
|
|
266
273
|
{
|
|
267
274
|
schemaName: 'public',
|
|
268
275
|
tableName: 't_hardware_asset',
|
|
269
|
-
|
|
276
|
+
operations: ['INSERT'],
|
|
270
277
|
watchedFields: [],
|
|
271
278
|
},
|
|
272
279
|
'admin',
|
|
273
280
|
);
|
|
274
281
|
|
|
275
282
|
expect(tx.workflowBusinessEventRegistration.create).toHaveBeenCalledWith({
|
|
276
|
-
data: expect.objectContaining({ watchedFields: [
|
|
283
|
+
data: expect.objectContaining({ watchedFields: [] }),
|
|
277
284
|
});
|
|
278
285
|
expect(executeRawUnsafe).toHaveBeenCalledWith(
|
|
279
286
|
expect.stringContaining('AFTER INSERT ON'),
|
|
@@ -321,3 +328,145 @@ describe('PostgreSQL business event registration', () => {
|
|
|
321
328
|
);
|
|
322
329
|
});
|
|
323
330
|
});
|
|
331
|
+
|
|
332
|
+
describe('multi-operation event rules', () => {
|
|
333
|
+
const [event] = buildBusinessEventCatalog(
|
|
334
|
+
[{ ...registration, operations: ['INSERT', 'UPDATE', 'DELETE'] }],
|
|
335
|
+
columns,
|
|
336
|
+
);
|
|
337
|
+
const condition = (path: string, values: string[]) => ({
|
|
338
|
+
id: path,
|
|
339
|
+
left: { source: 'WORKFLOW', path },
|
|
340
|
+
operator: 'IN',
|
|
341
|
+
right: {
|
|
342
|
+
source: 'FIXED',
|
|
343
|
+
valueType: 'STRING',
|
|
344
|
+
value: JSON.stringify(values),
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
const validate = (operationRules: unknown) =>
|
|
348
|
+
validateBusinessEventTriggers(
|
|
349
|
+
{
|
|
350
|
+
nodes: [
|
|
351
|
+
{
|
|
352
|
+
type: 'event_trigger',
|
|
353
|
+
config: {
|
|
354
|
+
registrationCode: event.registrationCode,
|
|
355
|
+
operationRules,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
[event],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
it('requires stable primary keys for serial processing when before/after can be null', () => {
|
|
364
|
+
expect(
|
|
365
|
+
event.fields.find((field) => field.path === 'primaryKey.id')?.nullable,
|
|
366
|
+
).toBe(false);
|
|
367
|
+
expect(
|
|
368
|
+
event.fields.find((field) => field.path === 'after.id')?.nullable,
|
|
369
|
+
).toBe(true);
|
|
370
|
+
expect(
|
|
371
|
+
event.fields.find((field) => field.path === 'before.id')?.nullable,
|
|
372
|
+
).toBe(true);
|
|
373
|
+
expect(() =>
|
|
374
|
+
validateBusinessEventTriggers(
|
|
375
|
+
{
|
|
376
|
+
nodes: [
|
|
377
|
+
{
|
|
378
|
+
type: 'event_trigger',
|
|
379
|
+
config: {
|
|
380
|
+
registrationCode: event.registrationCode,
|
|
381
|
+
operationRules: [{ operation: 'INSERT' }],
|
|
382
|
+
processingMode: 'SERIAL_BY_KEY',
|
|
383
|
+
concurrencyKeyPath: 'after.id',
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
},
|
|
388
|
+
[event],
|
|
389
|
+
),
|
|
390
|
+
).toThrow(BadRequestException);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('accepts independent insert and status transition rules', () => {
|
|
394
|
+
expect(() =>
|
|
395
|
+
validate([
|
|
396
|
+
{
|
|
397
|
+
operation: 'INSERT',
|
|
398
|
+
match: {
|
|
399
|
+
combineOperation: 'AND',
|
|
400
|
+
conditions: [
|
|
401
|
+
condition('after.status', ['PENDING', 'PENDING_SUPERIOR']),
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
operation: 'UPDATE',
|
|
407
|
+
match: {
|
|
408
|
+
combineOperation: 'AND',
|
|
409
|
+
conditions: [
|
|
410
|
+
condition('before.status', ['DRAFT', 'REJECTED']),
|
|
411
|
+
condition('after.status', ['PENDING', 'PENDING_SUPERIOR']),
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
]),
|
|
416
|
+
).not.toThrow();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it.each([
|
|
420
|
+
[],
|
|
421
|
+
[{ operation: 'UPSERT' }],
|
|
422
|
+
[{ operation: 'INSERT' }, { operation: 'INSERT' }],
|
|
423
|
+
[
|
|
424
|
+
{
|
|
425
|
+
operation: 'INSERT',
|
|
426
|
+
match: {
|
|
427
|
+
combineOperation: 'AND',
|
|
428
|
+
conditions: [condition('before.status', ['DRAFT'])],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
[
|
|
433
|
+
{
|
|
434
|
+
operation: 'DELETE',
|
|
435
|
+
match: {
|
|
436
|
+
combineOperation: 'AND',
|
|
437
|
+
conditions: [condition('after.status', ['DRAFT'])],
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
[
|
|
442
|
+
{
|
|
443
|
+
operation: 'UPDATE',
|
|
444
|
+
match: {
|
|
445
|
+
combineOperation: 'AND',
|
|
446
|
+
conditions: [condition('after.status', [])],
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
])('rejects invalid or unavailable rules: %j', (...rules) => {
|
|
451
|
+
expect(() => validate(rules)).toThrow(BadRequestException);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('rejects operations outside the registered set', () => {
|
|
455
|
+
expect(() =>
|
|
456
|
+
validateBusinessEventTriggers(
|
|
457
|
+
{
|
|
458
|
+
nodes: [
|
|
459
|
+
{
|
|
460
|
+
type: 'event_trigger',
|
|
461
|
+
config: {
|
|
462
|
+
registrationCode: event.registrationCode,
|
|
463
|
+
operationRules: [{ operation: 'DELETE' }],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
buildBusinessEventCatalog([registration], columns),
|
|
469
|
+
),
|
|
470
|
+
).toThrow(BadRequestException);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { WorkflowService } from './workflow.service';
|
|
2
|
+
|
|
3
|
+
// Opt-in, isolated database check. All fixtures and DDL are rolled back.
|
|
4
|
+
const pgTest = process.env.WORKFLOW_EVENT_PG_TEST === '1' ? it : it.skip;
|
|
5
|
+
|
|
6
|
+
pgTest(
|
|
7
|
+
'captures insert/delete rows and only real watched UPDATE changes, atomically',
|
|
8
|
+
async () => {
|
|
9
|
+
const { Client } = require('pg');
|
|
10
|
+
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
|
11
|
+
const schemaName = `wf_event_test_${process.pid}`;
|
|
12
|
+
await client.connect();
|
|
13
|
+
try {
|
|
14
|
+
await client.query('BEGIN');
|
|
15
|
+
await client.query(`CREATE SCHEMA "${schemaName}"`);
|
|
16
|
+
await client.query(`SET LOCAL search_path TO "${schemaName}"`);
|
|
17
|
+
await client.query(
|
|
18
|
+
`CREATE TYPE "WorkflowEventOperation" AS ENUM ('INSERT', 'UPDATE', 'DELETE')`,
|
|
19
|
+
);
|
|
20
|
+
await client.query(
|
|
21
|
+
`CREATE TABLE request (id integer PRIMARY KEY, status text NOT NULL, title text)`,
|
|
22
|
+
);
|
|
23
|
+
await client.query(`CREATE TABLE t_workflow_event_outbox (
|
|
24
|
+
id serial PRIMARY KEY, registration_id bigint, operation "WorkflowEventOperation",
|
|
25
|
+
schema_name text, table_name text, primary_key jsonb, before jsonb, after jsonb,
|
|
26
|
+
changed_fields jsonb, status text, retry_count integer,
|
|
27
|
+
next_attempt_at timestamptz, created_at timestamptz, updated_at timestamptz
|
|
28
|
+
)`);
|
|
29
|
+
const tx = {
|
|
30
|
+
$queryRaw: async () => [{ schemaName }],
|
|
31
|
+
$executeRawUnsafe: (sql: string) => client.query(sql),
|
|
32
|
+
};
|
|
33
|
+
const service = new WorkflowService(
|
|
34
|
+
{} as any,
|
|
35
|
+
{} as any,
|
|
36
|
+
{} as any,
|
|
37
|
+
{} as any,
|
|
38
|
+
);
|
|
39
|
+
await (service as any).installBusinessEventTrigger(tx, {
|
|
40
|
+
id: BigInt(1),
|
|
41
|
+
schemaName,
|
|
42
|
+
tableName: 'request',
|
|
43
|
+
operations: ['INSERT', 'UPDATE', 'DELETE'],
|
|
44
|
+
watchedFields: ['status'],
|
|
45
|
+
primaryKeyFields: ['id'],
|
|
46
|
+
});
|
|
47
|
+
await client.query(`INSERT INTO request VALUES (1, 'DRAFT', 'original')`);
|
|
48
|
+
await client.query(`UPDATE request SET title = 'edited' WHERE id = 1`);
|
|
49
|
+
await client.query(`UPDATE request SET status = 'DRAFT' WHERE id = 1`);
|
|
50
|
+
await client.query(`UPDATE request SET status = 'PENDING' WHERE id = 1`);
|
|
51
|
+
await client.query('SAVEPOINT submission');
|
|
52
|
+
await client.query(`UPDATE request SET status = 'APPROVED' WHERE id = 1`);
|
|
53
|
+
await client.query('ROLLBACK TO SAVEPOINT submission');
|
|
54
|
+
await client.query(`DELETE FROM request WHERE id = 1`);
|
|
55
|
+
const { rows } = await client.query(
|
|
56
|
+
'SELECT * FROM t_workflow_event_outbox ORDER BY id',
|
|
57
|
+
);
|
|
58
|
+
expect(rows.map((row: any) => row.operation)).toEqual([
|
|
59
|
+
'INSERT',
|
|
60
|
+
'UPDATE',
|
|
61
|
+
'DELETE',
|
|
62
|
+
]);
|
|
63
|
+
expect(rows[0]).toMatchObject({
|
|
64
|
+
before: null,
|
|
65
|
+
after: { status: 'DRAFT' },
|
|
66
|
+
primary_key: { id: 1 },
|
|
67
|
+
});
|
|
68
|
+
expect(rows[1]).toMatchObject({
|
|
69
|
+
before: { status: 'DRAFT' },
|
|
70
|
+
after: { status: 'PENDING' },
|
|
71
|
+
changed_fields: ['status'],
|
|
72
|
+
});
|
|
73
|
+
expect(rows[2]).toMatchObject({
|
|
74
|
+
before: { status: 'PENDING' },
|
|
75
|
+
after: null,
|
|
76
|
+
});
|
|
77
|
+
} finally {
|
|
78
|
+
await client.query('ROLLBACK');
|
|
79
|
+
await client.end();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
);
|
|
@@ -22,6 +22,7 @@ export interface WorkflowCancellationSummary {
|
|
|
22
22
|
export interface CancelWorkflowExecutionInput {
|
|
23
23
|
tx: Prisma.TransactionClient;
|
|
24
24
|
instanceId: bigint;
|
|
25
|
+
requestedBy: string;
|
|
25
26
|
reason?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -60,6 +61,7 @@ export class WorkflowRuntimeFacade {
|
|
|
60
61
|
await this.workflows.cancelExecutionInTransaction(
|
|
61
62
|
input.tx,
|
|
62
63
|
input.instanceId,
|
|
64
|
+
input.requestedBy,
|
|
63
65
|
input.reason,
|
|
64
66
|
);
|
|
65
67
|
return { instanceId: input.instanceId.toString(), status: 'CANCELLED' };
|
|
@@ -81,7 +81,9 @@ describe('WorkflowController runtime contracts', () => {
|
|
|
81
81
|
prisma as never,
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
-
await expect(
|
|
84
|
+
await expect(
|
|
85
|
+
controller.cancelInstance(42, { user: { userid: 'admin-user' } }),
|
|
86
|
+
).resolves.toEqual({
|
|
85
87
|
instanceId: '42',
|
|
86
88
|
status: 'CANCELLED',
|
|
87
89
|
});
|
|
@@ -89,6 +91,7 @@ describe('WorkflowController runtime contracts', () => {
|
|
|
89
91
|
expect(runtime.cancelExecution).toHaveBeenCalledWith({
|
|
90
92
|
tx,
|
|
91
93
|
instanceId: BigInt(42),
|
|
94
|
+
requestedBy: 'admin-user',
|
|
92
95
|
});
|
|
93
96
|
});
|
|
94
97
|
});
|
|
@@ -272,11 +272,15 @@ export class WorkflowController {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
@Post('instance/:instanceId/cancel')
|
|
275
|
-
async cancelInstance(
|
|
275
|
+
async cancelInstance(
|
|
276
|
+
@Param('instanceId', ParseIntPipe) instanceId: number,
|
|
277
|
+
@Req() req,
|
|
278
|
+
) {
|
|
276
279
|
return this.prisma.$transaction((tx) =>
|
|
277
280
|
this.workflowRuntime.cancelExecution({
|
|
278
281
|
tx,
|
|
279
282
|
instanceId: BigInt(instanceId),
|
|
283
|
+
requestedBy: req.user.userid,
|
|
280
284
|
}),
|
|
281
285
|
);
|
|
282
286
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Prisma } from '@prisma/client';
|
|
2
2
|
import {
|
|
3
|
+
ArrayMinSize,
|
|
4
|
+
ArrayUnique,
|
|
3
5
|
IsArray,
|
|
4
6
|
IsBoolean,
|
|
5
7
|
IsEnum,
|
|
@@ -32,8 +34,11 @@ export class RegisterBusinessEventDto {
|
|
|
32
34
|
@Matches(/^[A-Za-z_][A-Za-z0-9_$]*$/)
|
|
33
35
|
tableName: string;
|
|
34
36
|
|
|
35
|
-
@
|
|
36
|
-
|
|
37
|
+
@IsArray()
|
|
38
|
+
@ArrayMinSize(1)
|
|
39
|
+
@ArrayUnique()
|
|
40
|
+
@IsEnum(WorkflowEventOperationEnum, { each: true })
|
|
41
|
+
operations: WorkflowEventOperationEnum[];
|
|
37
42
|
|
|
38
43
|
@IsArray()
|
|
39
44
|
@IsString({ each: true })
|
|
@@ -7,6 +7,50 @@ import { signWebhookPayload } from './webhook-signature.util';
|
|
|
7
7
|
import { WorkflowCompilerService } from './workflow-compiler.service';
|
|
8
8
|
import { WorkflowService } from './workflow.service';
|
|
9
9
|
|
|
10
|
+
describe('WorkflowService — workflow list run counts', () => {
|
|
11
|
+
it('includes cancelled instances in the run summary', async () => {
|
|
12
|
+
const prisma = {
|
|
13
|
+
workflow: {
|
|
14
|
+
findMany: jest.fn().mockResolvedValue([
|
|
15
|
+
{
|
|
16
|
+
id: BigInt(42),
|
|
17
|
+
dsl: null,
|
|
18
|
+
versions: [],
|
|
19
|
+
_count: { instances: 2 },
|
|
20
|
+
},
|
|
21
|
+
]),
|
|
22
|
+
count: jest.fn().mockResolvedValue(1),
|
|
23
|
+
},
|
|
24
|
+
workflowInstance: {
|
|
25
|
+
groupBy: jest.fn().mockResolvedValue([
|
|
26
|
+
{
|
|
27
|
+
workflowId: BigInt(42),
|
|
28
|
+
status: 'CANCELLED',
|
|
29
|
+
_count: 2,
|
|
30
|
+
},
|
|
31
|
+
]),
|
|
32
|
+
findMany: jest.fn().mockResolvedValue([]),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const service = new WorkflowService(
|
|
36
|
+
prisma as any,
|
|
37
|
+
{} as any,
|
|
38
|
+
{} as any,
|
|
39
|
+
{} as any,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const result = await service.findMany({});
|
|
43
|
+
|
|
44
|
+
expect(result.items[0].runs).toEqual({
|
|
45
|
+
pending: 0,
|
|
46
|
+
running: 0,
|
|
47
|
+
completed: 0,
|
|
48
|
+
failed: 0,
|
|
49
|
+
cancelled: 2,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
10
54
|
describe('WorkflowService — cron lifecycle hooks', () => {
|
|
11
55
|
let service: WorkflowService;
|
|
12
56
|
let prisma: any;
|