@nocobase/plugin-workflow 1.5.0-beta.9 → 1.5.1
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/client/112c72b90000c81b.js +10 -0
- package/dist/client/87dff3ee7e6061ee.js +10 -0
- package/dist/client/9d4370267ad5749d.js +10 -0
- package/dist/client/FlowContext.d.ts +2 -0
- package/dist/client/index.js +1 -1
- package/dist/client/nodes/calculation.d.ts +3 -2
- package/dist/client/nodes/condition.d.ts +1 -0
- package/dist/client/nodes/create.d.ts +3 -2
- package/dist/client/nodes/destroy.d.ts +2 -0
- package/dist/client/nodes/end.d.ts +2 -0
- package/dist/client/nodes/index.d.ts +1 -0
- package/dist/client/nodes/query.d.ts +5 -4
- package/dist/client/nodes/update.d.ts +3 -2
- package/dist/client/triggers/schedule/ScheduleModes.d.ts +5 -2
- package/dist/client/triggers/schedule/index.d.ts +2 -0
- package/dist/client/variable.d.ts +16 -1
- package/dist/externalVersion.js +11 -11
- package/dist/locale/en-US.json +3 -1
- package/dist/locale/zh-CN.json +8 -6
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/lru-cache/package.json +1 -1
- package/dist/server/Plugin.d.ts +9 -6
- package/dist/server/Plugin.js +113 -43
- package/dist/server/actions/workflows.js +5 -2
- package/dist/server/collections/executions.js +8 -0
- package/dist/server/instructions/CreateInstruction.js +1 -1
- package/dist/server/instructions/DestroyInstruction.js +1 -1
- package/dist/server/instructions/UpdateInstruction.js +1 -1
- package/dist/server/triggers/CollectionTrigger.d.ts +6 -6
- package/dist/server/triggers/CollectionTrigger.js +51 -31
- package/dist/server/triggers/ScheduleTrigger/DateFieldScheduleTrigger.d.ts +5 -1
- package/dist/server/triggers/ScheduleTrigger/DateFieldScheduleTrigger.js +53 -14
- package/dist/server/triggers/ScheduleTrigger/StaticScheduleTrigger.d.ts +1 -0
- package/dist/server/triggers/ScheduleTrigger/StaticScheduleTrigger.js +3 -0
- package/dist/server/triggers/ScheduleTrigger/index.d.ts +2 -2
- package/dist/server/triggers/ScheduleTrigger/index.js +21 -3
- package/dist/server/triggers/index.d.ts +4 -1
- package/package.json +3 -3
- package/dist/client/5ed8ff0f70ed5911.js +0 -10
- package/dist/client/92877729dbcede8f.js +0 -10
- package/dist/client/e7b9d67c6a964bec.js +0 -10
package/dist/server/Plugin.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(Plugin_exports, {
|
|
|
41
41
|
module.exports = __toCommonJS(Plugin_exports);
|
|
42
42
|
var import_path = __toESM(require("path"));
|
|
43
43
|
var import_crypto = require("crypto");
|
|
44
|
+
var import_sequelize = require("sequelize");
|
|
44
45
|
var import_lru_cache = __toESM(require("lru-cache"));
|
|
45
46
|
var import_database = require("@nocobase/database");
|
|
46
47
|
var import_server = require("@nocobase/server");
|
|
@@ -126,7 +127,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
126
127
|
/**
|
|
127
128
|
* @experimental
|
|
128
129
|
*/
|
|
129
|
-
getLogger(workflowId) {
|
|
130
|
+
getLogger(workflowId = "dispatcher") {
|
|
130
131
|
const now = /* @__PURE__ */ new Date();
|
|
131
132
|
const date = `${now.getFullYear()}-${`0${now.getMonth() + 1}`.slice(-2)}-${`0${now.getDate()}`.slice(-2)}`;
|
|
132
133
|
const key = `${date}-${workflowId}}`;
|
|
@@ -256,7 +257,6 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
256
257
|
(model, { transaction }) => this.toggle(model, false, { transaction })
|
|
257
258
|
);
|
|
258
259
|
this.app.on("afterStart", async () => {
|
|
259
|
-
this.app.setMaintainingMessage("check for not started executions");
|
|
260
260
|
this.ready = true;
|
|
261
261
|
const collection = db.getCollection("workflows");
|
|
262
262
|
const workflows = await collection.repository.find({
|
|
@@ -266,8 +266,14 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
266
266
|
this.toggle(workflow, true, { silent: true });
|
|
267
267
|
});
|
|
268
268
|
this.checker = setInterval(() => {
|
|
269
|
+
this.getLogger("dispatcher").info(`(cycling) check for queueing executions`);
|
|
269
270
|
this.dispatch();
|
|
270
271
|
}, 3e5);
|
|
272
|
+
this.app.on("workflow:dispatch", () => {
|
|
273
|
+
this.app.logger.info("workflow:dispatch");
|
|
274
|
+
this.dispatch();
|
|
275
|
+
});
|
|
276
|
+
this.getLogger("dispatcher").info("(starting) check for queueing executions");
|
|
271
277
|
this.dispatch();
|
|
272
278
|
});
|
|
273
279
|
this.app.on("beforeStop", async () => {
|
|
@@ -323,10 +329,19 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
323
329
|
logger.debug(`ignored event data:`, context);
|
|
324
330
|
return;
|
|
325
331
|
}
|
|
326
|
-
if (!options.manually && !workflow.enabled) {
|
|
332
|
+
if (!options.force && !options.manually && !workflow.enabled) {
|
|
327
333
|
logger.warn(`workflow ${workflow.id} is not enabled, event will be ignored`);
|
|
328
334
|
return;
|
|
329
335
|
}
|
|
336
|
+
const duplicated = this.events.find(([w, c, { eventKey }]) => {
|
|
337
|
+
if (eventKey && options.eventKey) {
|
|
338
|
+
return eventKey === options.eventKey;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
if (duplicated) {
|
|
342
|
+
logger.warn(`event of workflow ${workflow.id} is duplicated (${options.eventKey}), event will be ignored`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
330
345
|
if (context == null) {
|
|
331
346
|
logger.warn(`workflow ${workflow.id} event data context is null, event will be ignored`);
|
|
332
347
|
return;
|
|
@@ -340,9 +355,10 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
340
355
|
logger.info(`new event triggered, now events: ${this.events.length}`);
|
|
341
356
|
logger.debug(`event data:`, { context });
|
|
342
357
|
if (this.events.length > 1) {
|
|
358
|
+
logger.info(`new event is pending to be prepared after previous preparation is finished`);
|
|
343
359
|
return;
|
|
344
360
|
}
|
|
345
|
-
|
|
361
|
+
setImmediate(this.prepare);
|
|
346
362
|
}
|
|
347
363
|
async triggerSync(workflow, context, { deferred, ...options } = {}) {
|
|
348
364
|
let execution;
|
|
@@ -367,33 +383,63 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
367
383
|
`execution (${job.execution.id}) resuming from job (${job.id}) added to pending list`
|
|
368
384
|
);
|
|
369
385
|
this.pending.push([job.execution, job]);
|
|
386
|
+
if (this.executing) {
|
|
387
|
+
await this.executing;
|
|
388
|
+
}
|
|
370
389
|
this.dispatch();
|
|
371
390
|
}
|
|
372
391
|
/**
|
|
373
392
|
* Start a deferred execution
|
|
374
393
|
* @experimental
|
|
375
394
|
*/
|
|
376
|
-
start(execution) {
|
|
395
|
+
async start(execution) {
|
|
377
396
|
if (execution.status !== import_constants.EXECUTION_STATUS.STARTED) {
|
|
378
397
|
return;
|
|
379
398
|
}
|
|
399
|
+
this.getLogger(execution.workflowId).info(`starting deferred execution (${execution.id})`);
|
|
380
400
|
this.pending.push([execution]);
|
|
401
|
+
if (this.executing) {
|
|
402
|
+
await this.executing;
|
|
403
|
+
}
|
|
381
404
|
this.dispatch();
|
|
382
405
|
}
|
|
383
|
-
|
|
384
|
-
|
|
406
|
+
async validateEvent(workflow, context, options) {
|
|
407
|
+
const trigger = this.triggers.get(workflow.type);
|
|
408
|
+
const triggerValid = await trigger.validateEvent(workflow, context, options);
|
|
409
|
+
if (!triggerValid) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
const { stack } = options;
|
|
413
|
+
let valid = true;
|
|
414
|
+
if ((stack == null ? void 0 : stack.length) > 0) {
|
|
415
|
+
const existed = await workflow.countExecutions({
|
|
416
|
+
where: {
|
|
417
|
+
id: stack
|
|
418
|
+
},
|
|
419
|
+
transaction: options.transaction
|
|
420
|
+
});
|
|
421
|
+
const limitCount = workflow.options.stackLimit || 1;
|
|
422
|
+
if (existed >= limitCount) {
|
|
423
|
+
this.getLogger(workflow.id).warn(
|
|
424
|
+
`workflow ${workflow.id} has already been triggered in stacks executions (${stack}), and max call coont is ${limitCount}, newly triggering will be skipped.`
|
|
425
|
+
);
|
|
426
|
+
valid = false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return valid;
|
|
385
430
|
}
|
|
386
431
|
async createExecution(workflow, context, options) {
|
|
432
|
+
var _a;
|
|
387
433
|
const { deferred } = options;
|
|
388
434
|
const transaction = await this.useDataSourceTransaction("main", options.transaction, true);
|
|
389
435
|
const sameTransaction = options.transaction === transaction;
|
|
390
|
-
const
|
|
391
|
-
const valid = await trigger.validateEvent(workflow, context, { ...options, transaction });
|
|
436
|
+
const valid = await this.validateEvent(workflow, context, { ...options, transaction });
|
|
392
437
|
if (!valid) {
|
|
393
438
|
if (!sameTransaction) {
|
|
394
439
|
await transaction.commit();
|
|
395
440
|
}
|
|
396
|
-
|
|
441
|
+
(_a = options.onTriggerFail) == null ? void 0 : _a.call(options, workflow, context, options);
|
|
442
|
+
return Promise.reject(new Error("event is not valid"));
|
|
397
443
|
}
|
|
398
444
|
let execution;
|
|
399
445
|
try {
|
|
@@ -402,6 +448,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
402
448
|
context,
|
|
403
449
|
key: workflow.key,
|
|
404
450
|
eventKey: options.eventKey ?? (0, import_crypto.randomUUID)(),
|
|
451
|
+
stack: options.stack,
|
|
405
452
|
status: deferred ? import_constants.EXECUTION_STATUS.STARTED : import_constants.EXECUTION_STATUS.QUEUEING
|
|
406
453
|
},
|
|
407
454
|
{ transaction }
|
|
@@ -441,7 +488,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
441
488
|
const event = this.events.shift();
|
|
442
489
|
this.eventsCount = this.events.length;
|
|
443
490
|
if (!event) {
|
|
444
|
-
this.getLogger("dispatcher").
|
|
491
|
+
this.getLogger("dispatcher").info(`events queue is empty, no need to prepare`);
|
|
445
492
|
return;
|
|
446
493
|
}
|
|
447
494
|
const logger = this.getLogger(event[0].id);
|
|
@@ -451,12 +498,16 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
451
498
|
if ((execution == null ? void 0 : execution.status) === import_constants.EXECUTION_STATUS.QUEUEING && !this.executing && !this.pending.length) {
|
|
452
499
|
this.pending.push([execution]);
|
|
453
500
|
}
|
|
454
|
-
} catch (
|
|
455
|
-
logger.error(`failed to create execution
|
|
501
|
+
} catch (error) {
|
|
502
|
+
logger.error(`failed to create execution:`, { error });
|
|
456
503
|
}
|
|
457
504
|
if (this.events.length) {
|
|
458
505
|
await this.prepare();
|
|
459
506
|
} else {
|
|
507
|
+
this.getLogger("dispatcher").info("no more events need to be prepared, dispatching...");
|
|
508
|
+
if (this.executing) {
|
|
509
|
+
await this.executing;
|
|
510
|
+
}
|
|
460
511
|
this.dispatch();
|
|
461
512
|
}
|
|
462
513
|
};
|
|
@@ -474,45 +525,64 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
474
525
|
}
|
|
475
526
|
this.executing = (async () => {
|
|
476
527
|
let next = null;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
"workflow.enabled": true,
|
|
486
|
-
"workflow.id": {
|
|
487
|
-
[import_database.Op.not]: null
|
|
488
|
-
}
|
|
528
|
+
if (this.pending.length) {
|
|
529
|
+
next = this.pending.shift();
|
|
530
|
+
this.getLogger(next[0].workflowId).info(`pending execution (${next[0].id}) ready to process`);
|
|
531
|
+
} else {
|
|
532
|
+
try {
|
|
533
|
+
await this.db.sequelize.transaction(
|
|
534
|
+
{
|
|
535
|
+
isolationLevel: this.db.options.dialect === "sqlite" ? [][0] : import_sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ
|
|
489
536
|
},
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
537
|
+
async (transaction) => {
|
|
538
|
+
const execution = await this.db.getRepository("executions").findOne({
|
|
539
|
+
filter: {
|
|
540
|
+
status: import_constants.EXECUTION_STATUS.QUEUEING,
|
|
541
|
+
"workflow.enabled": true
|
|
542
|
+
},
|
|
543
|
+
sort: "id",
|
|
544
|
+
transaction
|
|
545
|
+
});
|
|
546
|
+
if (execution) {
|
|
547
|
+
this.getLogger(execution.workflowId).info(`execution (${execution.id}) fetched from db`);
|
|
548
|
+
await execution.update(
|
|
549
|
+
{
|
|
550
|
+
status: import_constants.EXECUTION_STATUS.STARTED
|
|
551
|
+
},
|
|
552
|
+
{ transaction }
|
|
553
|
+
);
|
|
554
|
+
execution.workflow = this.enabledCache.get(execution.workflowId);
|
|
555
|
+
next = [execution];
|
|
556
|
+
} else {
|
|
557
|
+
this.getLogger("dispatcher").info(`no execution in db queued to process`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
this.getLogger("dispatcher").error(`fetching execution from db failed: ${error.message}`, { error });
|
|
505
563
|
}
|
|
506
564
|
}
|
|
565
|
+
if (next) {
|
|
566
|
+
await this.process(...next);
|
|
567
|
+
}
|
|
568
|
+
this.executing = null;
|
|
569
|
+
if (next || this.pending.length) {
|
|
570
|
+
this.getLogger("dispatcher").info(`last process finished, will do another dispatch`);
|
|
571
|
+
this.dispatch();
|
|
572
|
+
}
|
|
507
573
|
})();
|
|
508
574
|
}
|
|
575
|
+
createProcessor(execution, options = {}) {
|
|
576
|
+
return new import_Processor.default(execution, { ...options, plugin: this });
|
|
577
|
+
}
|
|
509
578
|
async process(execution, job, options = {}) {
|
|
510
579
|
var _a, _b;
|
|
580
|
+
const logger = this.getLogger(execution.workflowId);
|
|
511
581
|
if (execution.status === import_constants.EXECUTION_STATUS.QUEUEING) {
|
|
512
582
|
const transaction = await this.useDataSourceTransaction("main", options.transaction);
|
|
513
583
|
await execution.update({ status: import_constants.EXECUTION_STATUS.STARTED }, { transaction });
|
|
584
|
+
logger.info(`queueing execution (${execution.id}) from pending list updated to started`);
|
|
514
585
|
}
|
|
515
|
-
const logger = this.getLogger(execution.workflowId);
|
|
516
586
|
const processor = this.createProcessor(execution, options);
|
|
517
587
|
logger.info(`execution (${execution.id}) ${job ? "resuming" : "starting"}...`);
|
|
518
588
|
try {
|
|
@@ -526,7 +596,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
526
596
|
}
|
|
527
597
|
return processor;
|
|
528
598
|
}
|
|
529
|
-
async execute(workflow,
|
|
599
|
+
async execute(workflow, values, options = {}) {
|
|
530
600
|
const trigger = this.triggers.get(workflow.type);
|
|
531
601
|
if (!trigger) {
|
|
532
602
|
throw new Error(`trigger type "${workflow.type}" of workflow ${workflow.id} is not registered`);
|
|
@@ -534,7 +604,7 @@ class PluginWorkflowServer extends import_server.Plugin {
|
|
|
534
604
|
if (!trigger.execute) {
|
|
535
605
|
throw new Error(`"execute" method of trigger ${workflow.type} is not implemented`);
|
|
536
606
|
}
|
|
537
|
-
return trigger.execute(workflow,
|
|
607
|
+
return trigger.execute(workflow, values, options);
|
|
538
608
|
}
|
|
539
609
|
/**
|
|
540
610
|
* @experimental
|
|
@@ -121,7 +121,10 @@ async function trigger(context, next) {
|
|
|
121
121
|
}
|
|
122
122
|
async function execute(context, next) {
|
|
123
123
|
const plugin = context.app.pm.get(import_Plugin.default);
|
|
124
|
-
const { filterByTk, autoRevision } = context.action.params;
|
|
124
|
+
const { filterByTk, values, autoRevision } = context.action.params;
|
|
125
|
+
if (!values) {
|
|
126
|
+
return context.throw(400, "values is required");
|
|
127
|
+
}
|
|
125
128
|
if (!filterByTk) {
|
|
126
129
|
return context.throw(400, "filterByTk is required");
|
|
127
130
|
}
|
|
@@ -137,7 +140,7 @@ async function execute(context, next) {
|
|
|
137
140
|
const { executed } = workflow;
|
|
138
141
|
let processor;
|
|
139
142
|
try {
|
|
140
|
-
processor = await plugin.execute(workflow,
|
|
143
|
+
processor = await plugin.execute(workflow, values, { manually: true });
|
|
141
144
|
if (!processor) {
|
|
142
145
|
return context.throw(400, "workflow not triggered");
|
|
143
146
|
}
|
|
@@ -44,7 +44,7 @@ class CreateInstruction extends import__.Instruction {
|
|
|
44
44
|
const created = await repository.create({
|
|
45
45
|
...options,
|
|
46
46
|
context: {
|
|
47
|
-
stack: Array.from(new Set((processor.execution.
|
|
47
|
+
stack: Array.from(new Set((processor.execution.stack ?? []).concat(processor.execution.id)))
|
|
48
48
|
},
|
|
49
49
|
transaction
|
|
50
50
|
});
|
|
@@ -42,7 +42,7 @@ class DestroyInstruction extends import__.Instruction {
|
|
|
42
42
|
const result = await repository.destroy({
|
|
43
43
|
...options,
|
|
44
44
|
context: {
|
|
45
|
-
stack: Array.from(new Set((processor.execution.
|
|
45
|
+
stack: Array.from(new Set((processor.execution.stack ?? []).concat(processor.execution.id)))
|
|
46
46
|
},
|
|
47
47
|
transaction: this.workflow.useDataSourceTransaction(dataSourceName, processor.transaction)
|
|
48
48
|
});
|
|
@@ -42,7 +42,7 @@ class UpdateInstruction extends import__.Instruction {
|
|
|
42
42
|
const result = await repository.update({
|
|
43
43
|
...options,
|
|
44
44
|
context: {
|
|
45
|
-
stack: Array.from(new Set((processor.execution.
|
|
45
|
+
stack: Array.from(new Set((processor.execution.stack ?? []).concat(processor.execution.id)))
|
|
46
46
|
},
|
|
47
47
|
transaction: this.workflow.useDataSourceTransaction(dataSourceName, processor.transaction)
|
|
48
48
|
});
|
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import { Model
|
|
9
|
+
import { Model } from '@nocobase/database';
|
|
10
10
|
import Trigger from '.';
|
|
11
11
|
import type { WorkflowModel } from '../types';
|
|
12
12
|
import type { EventOptions } from '../Plugin';
|
|
13
|
-
import { Context } from '@nocobase/actions';
|
|
14
13
|
export interface CollectionChangeTriggerConfig {
|
|
15
14
|
collection: string;
|
|
16
15
|
mode: number;
|
|
@@ -19,12 +18,13 @@ export interface CollectionChangeTriggerConfig {
|
|
|
19
18
|
export default class CollectionTrigger extends Trigger {
|
|
20
19
|
events: Map<any, any>;
|
|
21
20
|
private static handler;
|
|
22
|
-
prepare(workflow: WorkflowModel, data: Model | Record<string, any
|
|
21
|
+
prepare(workflow: WorkflowModel, data: Model | Record<string, any> | string | number, options: any): Promise<{
|
|
23
22
|
data: any;
|
|
24
|
-
stack: any;
|
|
25
23
|
}>;
|
|
26
24
|
on(workflow: WorkflowModel): void;
|
|
27
25
|
off(workflow: WorkflowModel): void;
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
execute(workflow: WorkflowModel, values: any, options: EventOptions): Promise<void | import("..").Processor>;
|
|
27
|
+
validateContext(values: any): {
|
|
28
|
+
data: string;
|
|
29
|
+
};
|
|
30
30
|
}
|
|
@@ -74,17 +74,19 @@ class CollectionTrigger extends import__.default {
|
|
|
74
74
|
if (!ctx) {
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
+
const { stack } = options.context ?? {};
|
|
77
78
|
if (workflow.sync) {
|
|
78
79
|
await this.workflow.trigger(workflow, ctx, {
|
|
79
|
-
transaction
|
|
80
|
+
transaction,
|
|
81
|
+
stack
|
|
80
82
|
});
|
|
81
83
|
} else {
|
|
82
84
|
if (transaction) {
|
|
83
85
|
transaction.afterCommit(() => {
|
|
84
|
-
this.workflow.trigger(workflow, ctx);
|
|
86
|
+
this.workflow.trigger(workflow, ctx, { stack });
|
|
85
87
|
});
|
|
86
88
|
} else {
|
|
87
|
-
this.workflow.trigger(workflow, ctx);
|
|
89
|
+
this.workflow.trigger(workflow, ctx, { stack });
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
}
|
|
@@ -95,13 +97,24 @@ class CollectionTrigger extends import__.default {
|
|
|
95
97
|
const collection = collectionManager.getCollection(collectionName);
|
|
96
98
|
const { transaction, context } = options;
|
|
97
99
|
const { repository, filterTargetKey } = collection;
|
|
98
|
-
|
|
100
|
+
let target = data;
|
|
101
|
+
let filterByTk;
|
|
102
|
+
let loadNeeded = false;
|
|
103
|
+
if (target && typeof target === "object") {
|
|
104
|
+
filterByTk = Array.isArray(filterTargetKey) ? (0, import_lodash.pick)(
|
|
105
|
+
target,
|
|
106
|
+
filterTargetKey.sort((a, b) => a.localeCompare(b))
|
|
107
|
+
) : target[filterTargetKey];
|
|
108
|
+
} else {
|
|
109
|
+
filterByTk = target;
|
|
110
|
+
loadNeeded = true;
|
|
111
|
+
}
|
|
112
|
+
if (target instanceof import_database.Model && changed && changed.length && changed.filter((name) => {
|
|
99
113
|
const field = collection.getField(name);
|
|
100
114
|
return field && !["linkTo", "hasOne", "hasMany", "belongsToMany"].includes(field.options.type);
|
|
101
|
-
}).every((name) => !
|
|
115
|
+
}).every((name) => !target.changedWithAssociations(getFieldRawName(collection, name)))) {
|
|
102
116
|
return null;
|
|
103
117
|
}
|
|
104
|
-
const filterByTk = Array.isArray(filterTargetKey) ? (0, import_lodash.pick)(data, filterTargetKey) : { [filterTargetKey]: data[filterTargetKey] };
|
|
105
118
|
if ((0, import_utils.isValidFilter)(condition) && !(mode & MODE_BITMAP.DESTROY)) {
|
|
106
119
|
const count = await repository.count({
|
|
107
120
|
filterByTk,
|
|
@@ -113,22 +126,20 @@ class CollectionTrigger extends import__.default {
|
|
|
113
126
|
return null;
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
|
-
|
|
117
|
-
if ((appends == null ? void 0 : appends.length) && !(mode & MODE_BITMAP.DESTROY)) {
|
|
129
|
+
if (loadNeeded || (appends == null ? void 0 : appends.length) && !(mode & MODE_BITMAP.DESTROY)) {
|
|
118
130
|
const includeFields = appends.reduce((set, field) => {
|
|
119
131
|
set.add(field.split(".")[0]);
|
|
120
132
|
set.add(field);
|
|
121
133
|
return set;
|
|
122
134
|
}, /* @__PURE__ */ new Set());
|
|
123
|
-
|
|
135
|
+
target = await repository.findOne({
|
|
124
136
|
filterByTk,
|
|
125
137
|
appends: Array.from(includeFields),
|
|
126
138
|
transaction
|
|
127
139
|
});
|
|
128
140
|
}
|
|
129
141
|
return {
|
|
130
|
-
data: (0, import_utils2.toJSON)(
|
|
131
|
-
stack: context == null ? void 0 : context.stack
|
|
142
|
+
data: (0, import_utils2.toJSON)(target)
|
|
132
143
|
};
|
|
133
144
|
}
|
|
134
145
|
on(workflow) {
|
|
@@ -182,26 +193,27 @@ class CollectionTrigger extends import__.default {
|
|
|
182
193
|
}
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
|
-
async validateEvent(workflow, context, options) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
196
|
+
// async validateEvent(workflow: WorkflowModel, context: any, options: Transactionable): Promise<boolean> {
|
|
197
|
+
// if (context.stack) {
|
|
198
|
+
// const existed = await workflow.countExecutions({
|
|
199
|
+
// where: {
|
|
200
|
+
// id: context.stack,
|
|
201
|
+
// },
|
|
202
|
+
// transaction: options.transaction,
|
|
203
|
+
// });
|
|
204
|
+
// if (existed) {
|
|
205
|
+
// this.workflow
|
|
206
|
+
// .getLogger(workflow.id)
|
|
207
|
+
// .warn(
|
|
208
|
+
// `workflow ${workflow.id} has already been triggered in stack executions (${context.stack}), and newly triggering will be skipped.`,
|
|
209
|
+
// );
|
|
210
|
+
// return false;
|
|
211
|
+
// }
|
|
212
|
+
// }
|
|
213
|
+
// return true;
|
|
214
|
+
// }
|
|
215
|
+
async execute(workflow, values, options) {
|
|
216
|
+
const ctx = await this.prepare(workflow, values == null ? void 0 : values.data, options);
|
|
205
217
|
const [dataSourceName] = (0, import_data_source_manager.parseCollectionName)(workflow.config.collection);
|
|
206
218
|
const { transaction } = options;
|
|
207
219
|
return this.workflow.trigger(workflow, ctx, {
|
|
@@ -209,4 +221,12 @@ class CollectionTrigger extends import__.default {
|
|
|
209
221
|
transaction: this.workflow.useDataSourceTransaction(dataSourceName, transaction)
|
|
210
222
|
});
|
|
211
223
|
}
|
|
224
|
+
validateContext(values) {
|
|
225
|
+
if (!values.data) {
|
|
226
|
+
return {
|
|
227
|
+
data: "Data is required"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
212
232
|
}
|
|
@@ -30,10 +30,14 @@ export default class DateFieldScheduleTrigger {
|
|
|
30
30
|
constructor(workflow: Plugin);
|
|
31
31
|
reload(): Promise<void>;
|
|
32
32
|
inspect(workflows: WorkflowModel[]): void;
|
|
33
|
-
loadRecordsToSchedule({ config: { collection, limit, startsOn, repeat, endsOn }, allExecuted }: WorkflowModel, currentDate: Date): Promise<import("@nocobase/database").Model<any, any>[]>;
|
|
33
|
+
loadRecordsToSchedule({ id, config: { collection, limit, startsOn, repeat, endsOn }, allExecuted }: WorkflowModel, currentDate: Date): Promise<import("@nocobase/database").Model<any, any>[]>;
|
|
34
34
|
getRecordNextTime(workflow: WorkflowModel, record: any, nextSecond?: boolean): any;
|
|
35
35
|
schedule(workflow: WorkflowModel, record: any, nextTime: any, toggle?: boolean, options?: {}): Promise<void>;
|
|
36
36
|
trigger(workflow: WorkflowModel, record: any, nextTime: any, { transaction }?: Transactionable): Promise<void>;
|
|
37
37
|
on(workflow: WorkflowModel): void;
|
|
38
38
|
off(workflow: WorkflowModel): void;
|
|
39
|
+
execute(workflow: any, values: any, options: any): Promise<void | import("../..").Processor>;
|
|
40
|
+
validateContext(values: any): {
|
|
41
|
+
data: string;
|
|
42
|
+
};
|
|
39
43
|
}
|
|
@@ -43,6 +43,7 @@ var import_database = require("@nocobase/database");
|
|
|
43
43
|
var import_cron_parser = __toESM(require("cron-parser"));
|
|
44
44
|
var import_utils = require("./utils");
|
|
45
45
|
var import_data_source_manager = require("@nocobase/data-source-manager");
|
|
46
|
+
var import_lodash = require("lodash");
|
|
46
47
|
function getOnTimestampWithOffset({ field, offset = 0, unit = 1e3 }, now) {
|
|
47
48
|
if (!field) {
|
|
48
49
|
return null;
|
|
@@ -64,7 +65,7 @@ function getDataOptionTime(record, on, dir = 1) {
|
|
|
64
65
|
if (!field || !record.get(field)) {
|
|
65
66
|
return null;
|
|
66
67
|
}
|
|
67
|
-
const second = new Date(record.get(field)
|
|
68
|
+
const second = new Date(record.get(field));
|
|
68
69
|
second.setMilliseconds(0);
|
|
69
70
|
return second.getTime() + offset * unit * dir;
|
|
70
71
|
}
|
|
@@ -131,6 +132,7 @@ class DateFieldScheduleTrigger {
|
|
|
131
132
|
const now = /* @__PURE__ */ new Date();
|
|
132
133
|
workflows.forEach(async (workflow) => {
|
|
133
134
|
const records = await this.loadRecordsToSchedule(workflow, now);
|
|
135
|
+
this.workflow.getLogger(workflow.id).info(`[Schedule on date field] ${records.length} records to schedule`);
|
|
134
136
|
records.forEach((record) => {
|
|
135
137
|
const nextTime = this.getRecordNextTime(workflow, record);
|
|
136
138
|
this.schedule(workflow, record, nextTime, Boolean(nextTime));
|
|
@@ -144,17 +146,20 @@ class DateFieldScheduleTrigger {
|
|
|
144
146
|
// b. repeat in range (number or cron):
|
|
145
147
|
// i. endsOn after now -> yes
|
|
146
148
|
// ii. endsOn before now -> no
|
|
147
|
-
async loadRecordsToSchedule({ config: { collection, limit, startsOn, repeat, endsOn }, allExecuted }, currentDate) {
|
|
149
|
+
async loadRecordsToSchedule({ id, config: { collection, limit, startsOn, repeat, endsOn }, allExecuted }, currentDate) {
|
|
148
150
|
const { dataSourceManager } = this.workflow.app;
|
|
149
151
|
if (limit && allExecuted >= limit) {
|
|
152
|
+
this.workflow.getLogger(id).warn(`[Schedule on date field] limit reached (all executed ${allExecuted})`);
|
|
150
153
|
return [];
|
|
151
154
|
}
|
|
152
155
|
if (!startsOn) {
|
|
156
|
+
this.workflow.getLogger(id).warn(`[Schedule on date field] "startsOn" is not configured`);
|
|
153
157
|
return [];
|
|
154
158
|
}
|
|
155
159
|
const timestamp = currentDate.getTime();
|
|
156
160
|
const startTimestamp = getOnTimestampWithOffset(startsOn, currentDate);
|
|
157
161
|
if (!startTimestamp) {
|
|
162
|
+
this.workflow.getLogger(id).warn(`[Schedule on date field] "startsOn.field" is not configured`);
|
|
158
163
|
return [];
|
|
159
164
|
}
|
|
160
165
|
const [dataSourceName, collectionName] = (0, import_data_source_manager.parseCollectionName)(collection);
|
|
@@ -181,7 +186,7 @@ class DateFieldScheduleTrigger {
|
|
|
181
186
|
const modExp = (0, import_database.fn)(
|
|
182
187
|
"MOD",
|
|
183
188
|
(0, import_database.literal)(
|
|
184
|
-
`${Math.round(timestamp / 1e3)} - ${db.sequelize.getQueryInterface().quoteIdentifiers(
|
|
189
|
+
`${Math.round(timestamp / 1e3)} - ${tsFn(db.sequelize.getQueryInterface().quoteIdentifiers(field))}`
|
|
185
190
|
),
|
|
186
191
|
Math.round(repeat / 1e3)
|
|
187
192
|
);
|
|
@@ -193,21 +198,21 @@ class DateFieldScheduleTrigger {
|
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
if (endsOn) {
|
|
196
|
-
const now = /* @__PURE__ */ new Date();
|
|
197
|
-
const endTimestamp = getOnTimestampWithOffset(endsOn, now);
|
|
198
|
-
if (!endTimestamp) {
|
|
199
|
-
return [];
|
|
200
|
-
}
|
|
201
201
|
if (typeof endsOn === "string") {
|
|
202
|
-
if (
|
|
202
|
+
if ((0, import_utils.parseDateWithoutMs)(endsOn) <= timestamp) {
|
|
203
203
|
return [];
|
|
204
204
|
}
|
|
205
205
|
} else {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
const endTimestamp = getOnTimestampWithOffset(endsOn, currentDate);
|
|
207
|
+
if (endTimestamp) {
|
|
208
|
+
conditions.push({
|
|
209
|
+
[endsOn.field]: {
|
|
210
|
+
[import_database.Op.gte]: new Date(endTimestamp)
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
this.workflow.getLogger(id).warn(`[Schedule on date field] "endsOn.field" is not configured`);
|
|
215
|
+
}
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
} else {
|
|
@@ -217,6 +222,7 @@ class DateFieldScheduleTrigger {
|
|
|
217
222
|
}
|
|
218
223
|
});
|
|
219
224
|
}
|
|
225
|
+
this.workflow.getLogger(id).debug(`[Schedule on date field] conditions: `, { conditions });
|
|
220
226
|
return model.findAll({
|
|
221
227
|
where: {
|
|
222
228
|
[import_database.Op.and]: conditions
|
|
@@ -363,4 +369,37 @@ class DateFieldScheduleTrigger {
|
|
|
363
369
|
this.events.delete(name);
|
|
364
370
|
}
|
|
365
371
|
}
|
|
372
|
+
async execute(workflow, values, options) {
|
|
373
|
+
var _a;
|
|
374
|
+
const [dataSourceName, collectionName] = (0, import_data_source_manager.parseCollectionName)(workflow.config.collection);
|
|
375
|
+
const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName);
|
|
376
|
+
const { filterTargetKey, repository } = collectionManager.getCollection(collectionName);
|
|
377
|
+
let { data } = values;
|
|
378
|
+
let filterByTk;
|
|
379
|
+
let loadNeeded = false;
|
|
380
|
+
if (data && typeof data === "object") {
|
|
381
|
+
filterByTk = Array.isArray(filterTargetKey) ? (0, import_lodash.pick)(
|
|
382
|
+
data,
|
|
383
|
+
filterTargetKey.sort((a, b) => a.localeCompare(b))
|
|
384
|
+
) : data[filterTargetKey];
|
|
385
|
+
} else {
|
|
386
|
+
filterByTk = data;
|
|
387
|
+
loadNeeded = true;
|
|
388
|
+
}
|
|
389
|
+
if (loadNeeded || ((_a = workflow.config.appends) == null ? void 0 : _a.length)) {
|
|
390
|
+
data = await repository.findOne({
|
|
391
|
+
filterByTk,
|
|
392
|
+
appends: workflow.config.appends
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return this.workflow.trigger(workflow, { ...values, data, date: (values == null ? void 0 : values.date) ?? /* @__PURE__ */ new Date() }, options);
|
|
396
|
+
}
|
|
397
|
+
validateContext(values) {
|
|
398
|
+
if (!(values == null ? void 0 : values.data)) {
|
|
399
|
+
return {
|
|
400
|
+
data: "Data is required"
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
366
405
|
}
|