fix(plugin-workflow): break cycling trigger through transaction id (#341)

This commit is contained in:
Junyi 2022-04-29 22:21:58 +08:00 committed by GitHub
parent 1fb2dd884c
commit 5652d11b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 9 deletions

View File

@ -509,4 +509,28 @@ describe('execution', () => {
expect(jobs.length).toEqual(5); expect(jobs.length).toEqual(5);
}); });
}); });
describe('cycling trigger', () => {
it('trigger should not be triggered more than once in same execution', async () => {
const n1 = await workflow.createNode({
type: 'create',
config: {
collection: 'posts',
params: {
values: {
title: 't2'
}
}
}
});
const post = await PostModel.create({ title: 't1' });
const posts = await PostModel.findAll();
expect(posts.length).toBe(2);
const [execution] = await workflow.getExecutions();
expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
});
});
}); });

View File

@ -17,6 +17,11 @@ export default {
title: '使用事务', title: '使用事务',
defaultValue: false defaultValue: false
}, },
{
type: 'uuid',
name: 'transaction',
defaultValue: null
},
{ {
interface: 'linkTo', interface: 'linkTo',
type: 'hasMany', type: 'hasMany',

View File

@ -73,9 +73,9 @@ export default class ExecutionModel extends Model {
}); });
} }
getTransaction() { async getTransaction() {
const { sequelize } = (<typeof WorkflowModel>this.constructor).database; const { sequelize } = (<typeof ExecutionModel>this.constructor).database;
// @ts-ignore
if (!this.useTransaction) { if (!this.useTransaction) {
return undefined; return undefined;
} }
@ -83,15 +83,21 @@ export default class ExecutionModel extends Model {
const { options } = this; const { options } = this;
// @ts-ignore // @ts-ignore
return options.transaction && !options.transaction.finished const transaction = options.transaction && !options.transaction.finished
? options.transaction ? options.transaction
: sequelize.transaction(); : sequelize.transaction();
// @ts-ignore
if (this.transaction !== transaction.id) {
// @ts-ignore
await this.update({ transaction: transaction.id }, { transaction });
}
return transaction;
} }
async prepare(options, commit = false) { async prepare(options, commit = false) {
this.options = options || {}; this.options = options || {};
// @ts-ignore const transaction = await this.getTransaction();
const transaction = await this.getTransaction()
this.transaction = transaction; this.transaction = transaction;
if (!this.workflow) { if (!this.workflow) {
@ -230,7 +236,7 @@ export default class ExecutionModel extends Model {
// TODO(optimize) // TODO(optimize)
async saveJob(payload) { async saveJob(payload) {
const { database } = <typeof WorkflowModel>this.constructor; const { database } = <typeof ExecutionModel>this.constructor;
const { model } = database.getCollection('jobs'); const { model } = database.getCollection('jobs');
const [job] = (await model.upsert( const [job] = (await model.upsert(
{ {

View File

@ -1,5 +1,5 @@
import { Database, Model } from '@nocobase/database'; import { Database, Model } from '@nocobase/database';
import { HasManyCreateAssociationMixin, HasManyGetAssociationsMixin } from 'sequelize'; import { HasManyCountAssociationsMixin, HasManyCreateAssociationMixin, HasManyGetAssociationsMixin } from 'sequelize';
import triggers from '../triggers'; import triggers from '../triggers';
import { EXECUTION_STATUS } from '../constants'; import { EXECUTION_STATUS } from '../constants';
@ -25,6 +25,7 @@ export default class WorkflowModel extends Model {
declare createNode: HasManyCreateAssociationMixin<FlowNodeModel>; declare createNode: HasManyCreateAssociationMixin<FlowNodeModel>;
declare executions: ExecutionModel[]; declare executions: ExecutionModel[];
declare countExecutions: HasManyCountAssociationsMixin;
declare getExecutions: HasManyGetAssociationsMixin<ExecutionModel>; declare getExecutions: HasManyGetAssociationsMixin<ExecutionModel>;
declare createExecution: HasManyCreateAssociationMixin<ExecutionModel>; declare createExecution: HasManyCreateAssociationMixin<ExecutionModel>;
@ -75,10 +76,25 @@ export default class WorkflowModel extends Model {
const transaction = await this.getTransaction(options); const transaction = await this.getTransaction(options);
if (this.useTransaction) {
const existed = await this.countExecutions({
where: {
transaction: transaction.id
},
transaction
});
if (existed) {
console.warn(`workflow ${this.id} has already been triggered in same execution (${transaction.id}), and newly triggering will be skipped.`);
return;
}
}
const execution = await this.createExecution({ const execution = await this.createExecution({
context, context,
status: EXECUTION_STATUS.STARTED, status: EXECUTION_STATUS.STARTED,
useTransaction: this.useTransaction useTransaction: this.useTransaction,
transaction: transaction.id
}, { transaction }); }, { transaction });
execution.workflow = this; execution.workflow = this;