Merge branch 'next' into develop

This commit is contained in:
nocobase[bot] 2025-04-06 10:22:23 +00:00
commit 1eb036e599
5 changed files with 71 additions and 49 deletions

View File

@ -108,7 +108,7 @@ export default class extends Instruction {
// add to schedule // add to schedule
this.schedule(job); this.schedule(job);
return processor.exit(); return null;
} }
async resume(node, prevJob, processor: Processor) { async resume(node, prevJob, processor: Processor) {

View File

@ -24,6 +24,7 @@
"dayjs": "^1.11.8", "dayjs": "^1.11.8",
"lodash": "4.17.21", "lodash": "4.17.21",
"lru-cache": "8.0.5", "lru-cache": "8.0.5",
"nodejs-snowflake": "2.0.1",
"react": "18.x", "react": "18.x",
"react-i18next": "^11.15.1", "react-i18next": "^11.15.1",
"react-js-cron": "^3.1.0", "react-js-cron": "^3.1.0",

View File

@ -0,0 +1,12 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export default class Dispatcher {
constructor() {}
}

View File

@ -10,6 +10,7 @@
import path from 'path'; import path from 'path';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { Snowflake } from 'nodejs-snowflake';
import { Transaction, Transactionable } from 'sequelize'; import { Transaction, Transactionable } from 'sequelize';
import LRUCache from 'lru-cache'; import LRUCache from 'lru-cache';
@ -61,6 +62,7 @@ export default class PluginWorkflowServer extends Plugin {
triggers: Registry<Trigger> = new Registry(); triggers: Registry<Trigger> = new Registry();
functions: Registry<CustomFunction> = new Registry(); functions: Registry<CustomFunction> = new Registry();
enabledCache: Map<number, WorkflowModel> = new Map(); enabledCache: Map<number, WorkflowModel> = new Map();
snowflake: Snowflake;
private ready = false; private ready = false;
private executing: Promise<void> | null = null; private executing: Promise<void> | null = null;
@ -219,6 +221,14 @@ export default class PluginWorkflowServer extends Plugin {
WorkflowRepository, WorkflowRepository,
WorkflowTasksRepository, WorkflowTasksRepository,
}); });
const PluginRepo = this.db.getRepository<any>('applicationPlugins');
const pluginRecord = await PluginRepo.findOne({
filter: { name: this.name },
});
this.snowflake = new Snowflake({
custom_epoch: pluginRecord?.createdAt.getTime(),
});
} }
/** /**

View File

@ -56,15 +56,9 @@ export default class Processor {
*/ */
nodesMap = new Map<number, FlowNodeModel>(); nodesMap = new Map<number, FlowNodeModel>();
/** private jobsMapByNodeKey: { [key: string]: JobModel } = {};
* @experimental private jobResultsMapByNodeKey: { [key: string]: any } = {};
*/ private jobsToSave: Map<string, JobModel> = new Map();
jobsMap = new Map<number, JobModel>();
/**
* @experimental
*/
jobsMapByNodeKey: { [key: string]: any } = {};
/** /**
* @experimental * @experimental
@ -100,10 +94,9 @@ export default class Processor {
private makeJobs(jobs: Array<JobModel>) { private makeJobs(jobs: Array<JobModel>) {
jobs.forEach((job) => { jobs.forEach((job) => {
this.jobsMap.set(job.id, job);
const node = this.nodesMap.get(job.nodeId); const node = this.nodesMap.get(job.nodeId);
this.jobsMapByNodeKey[node.key] = job.result; this.jobsMapByNodeKey[node.key] = job;
this.jobResultsMapByNodeKey[node.key] = job.result;
}); });
} }
@ -192,11 +185,11 @@ export default class Processor {
} }
if (!(job instanceof Model)) { if (!(job instanceof Model)) {
job.upstreamId = prevJob instanceof Model ? prevJob.get('id') : null; // job.upstreamId = prevJob instanceof Model ? prevJob.get('id') : null;
job.nodeId = node.id; job.nodeId = node.id;
job.nodeKey = node.key; job.nodeKey = node.key;
} }
const savedJob = await this.saveJob(job); const savedJob = this.saveJob(job);
this.logger.info( this.logger.info(
`execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) finished as status: ${savedJob.status}`, `execution (${this.execution.id}) run instruction [${node.type}] for node (${node.id}) finished as status: ${savedJob.status}`,
@ -258,6 +251,27 @@ export default class Processor {
} }
public async exit(s?: number) { public async exit(s?: number) {
if (this.jobsToSave.size) {
const newJobs = [];
for (const job of this.jobsToSave.values()) {
if (job.isNewRecord) {
newJobs.push(job);
} else {
await job.save({ transaction: this.mainTransaction });
}
}
if (newJobs.length) {
const JobsModel = this.options.plugin.db.getModel('jobs');
await JobsModel.bulkCreate(
newJobs.map((job) => job.toJSON()),
{ transaction: this.mainTransaction },
);
for (const job of newJobs) {
job.isNewRecord = false;
}
}
this.jobsToSave.clear();
}
if (typeof s === 'number') { if (typeof s === 'number') {
const status = (<typeof Processor>this.constructor).StatusMap[s] ?? Math.sign(s); const status = (<typeof Processor>this.constructor).StatusMap[s] ?? Math.sign(s);
await this.execution.update({ status }, { transaction: this.mainTransaction }); await this.execution.update({ status }, { transaction: this.mainTransaction });
@ -269,33 +283,30 @@ export default class Processor {
return null; return null;
} }
// TODO(optimize)
/** /**
* @experimental * @experimental
*/ */
async saveJob(payload: JobModel | Record<string, any>): Promise<JobModel> { saveJob(payload: JobModel | Record<string, any>): JobModel {
const { database } = <typeof ExecutionModel>this.execution.constructor; const { database } = <typeof ExecutionModel>this.execution.constructor;
const { mainTransaction: transaction } = this;
const { model } = database.getCollection('jobs'); const { model } = database.getCollection('jobs');
let job; let job;
if (payload instanceof model) { if (payload instanceof model) {
job = await payload.save({ transaction }); job = payload;
} else if (payload.id) { job.set('updatedAt', new Date());
job = await model.findByPk(payload.id, { transaction });
await job.update(payload, { transaction });
} else { } else {
job = await model.create( job = model.build({
{
...payload, ...payload,
id: this.options.plugin.snowflake.getUniqueID().toString(),
createdAt: new Date(),
updatedAt: new Date(),
executionId: this.execution.id, executionId: this.execution.id,
}, });
{ transaction },
);
} }
this.jobsMap.set(job.id, job); this.jobsToSave.set(job.id, job);
this.lastSavedJob = job; this.lastSavedJob = job;
this.jobsMapByNodeKey[job.nodeKey] = job.result; this.jobsMapByNodeKey[job.nodeKey] = job;
this.jobResultsMapByNodeKey[job.nodeKey] = job.result;
return job; return job;
} }
@ -357,32 +368,20 @@ export default class Processor {
* @experimental * @experimental
*/ */
findBranchParentJob(job: JobModel, node: FlowNodeModel): JobModel | null { findBranchParentJob(job: JobModel, node: FlowNodeModel): JobModel | null {
for (let j: JobModel | undefined = job; j; j = this.jobsMap.get(j.upstreamId)) { return this.jobsMapByNodeKey[node.key];
if (j.nodeId === node.id) {
return j;
}
}
return null;
} }
/** /**
* @experimental * @experimental
*/ */
findBranchLastJob(node: FlowNodeModel, job: JobModel): JobModel | null { findBranchLastJob(node: FlowNodeModel, job: JobModel): JobModel | null {
const allJobs = Array.from(this.jobsMap.values()); const allJobs = Object.values(this.jobsMapByNodeKey);
const branchJobs = []; const branchJobs = [];
for (let n = this.findBranchEndNode(node); n && n !== node.upstream; n = n.upstream) { for (let n = this.findBranchEndNode(node); n && n !== node.upstream; n = n.upstream) {
branchJobs.push(...allJobs.filter((item) => item.nodeId === n.id)); branchJobs.push(...allJobs.filter((item) => item.nodeId === n.id));
} }
branchJobs.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); branchJobs.sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime());
for (let i = branchJobs.length - 1; i >= 0; i -= 1) { return branchJobs[branchJobs.length - 1] || null;
for (let j = branchJobs[i]; j && j.id !== job.id; j = this.jobsMap.get(j.upstreamId)) {
if (j.upstreamId === job.id) {
return branchJobs[i];
}
}
}
return null;
} }
/** /**
@ -403,13 +402,13 @@ export default class Processor {
for (let n = includeSelfScope ? node : this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) { for (let n = includeSelfScope ? node : this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) {
const instruction = this.options.plugin.instructions.get(n.type); const instruction = this.options.plugin.instructions.get(n.type);
if (typeof instruction?.getScope === 'function') { if (typeof instruction?.getScope === 'function') {
$scopes[n.id] = $scopes[n.key] = instruction.getScope(n, this.jobsMapByNodeKey[n.key], this); $scopes[n.id] = $scopes[n.key] = instruction.getScope(n, this.jobResultsMapByNodeKey[n.key], this);
} }
} }
return { return {
$context: this.execution.context, $context: this.execution.context,
$jobsMapByNodeKey: this.jobsMapByNodeKey, $jobsMapByNodeKey: this.jobResultsMapByNodeKey,
$system: systemFns, $system: systemFns,
$scopes, $scopes,
$env: this.options.plugin.app.environment.getVariables(), $env: this.options.plugin.app.environment.getVariables(),