* refactor(plugin-workflow): multiple instances and event management (fix #384) * fix(plugin-workflow): fix test case
This commit is contained in:
parent
747851f2b1
commit
53482c5b22
@ -1,7 +1,7 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'approvals',
|
||||
name: 'comments',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsTo',
|
@ -16,6 +16,14 @@ export default {
|
||||
type: 'integer',
|
||||
name: 'read',
|
||||
defaultValue: 0
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'comments'
|
||||
},
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'tags'
|
||||
}
|
||||
]
|
||||
} as CollectionOptions;
|
||||
|
15
packages/plugins/workflow/src/__tests__/collections/tags.ts
Normal file
15
packages/plugins/workflow/src/__tests__/collections/tags.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'tags',
|
||||
fields: [
|
||||
{
|
||||
type: 'belongsToMany',
|
||||
name: 'posts',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'name'
|
||||
}
|
||||
],
|
||||
} as CollectionOptions;
|
@ -8,7 +8,7 @@ jest.setTimeout(300000);
|
||||
describe('execution', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -17,7 +17,7 @@ describe('execution', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -34,7 +34,7 @@ describe('execution', () => {
|
||||
|
||||
describe('base', () => {
|
||||
it('empty workflow without any nodes', async () => {
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.context.data.title).toEqual(post.title);
|
||||
@ -47,7 +47,7 @@ describe('execution', () => {
|
||||
type: 'echo'
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -64,7 +64,7 @@ describe('execution', () => {
|
||||
type: 'echo'
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.context.data.title).toEqual(post.title);
|
||||
@ -91,7 +91,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.context.data.title).toEqual(post.title);
|
||||
@ -110,7 +110,7 @@ describe('execution', () => {
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.REJECTED);
|
||||
@ -138,7 +138,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -170,7 +170,7 @@ describe('execution', () => {
|
||||
});
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -211,7 +211,7 @@ describe('execution', () => {
|
||||
upstreamId: n1.id
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -245,7 +245,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n3);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -280,7 +280,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n3);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -317,7 +317,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n3);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -353,7 +353,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n4);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -389,7 +389,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n4);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -440,7 +440,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n5);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -492,7 +492,7 @@ describe('execution', () => {
|
||||
|
||||
await n1.setDownstream(n5);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.STARTED);
|
||||
@ -524,9 +524,9 @@ describe('execution', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const posts = await PostModel.findAll();
|
||||
const posts = await PostRepo.find();
|
||||
expect(posts.length).toBe(2);
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
|
@ -7,7 +7,7 @@ import { getApp } from '..';
|
||||
describe('workflow > instructions > calculation', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -16,7 +16,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -46,7 +46,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(2);
|
||||
@ -66,7 +66,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(1);
|
||||
@ -86,7 +86,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(1);
|
||||
@ -113,7 +113,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [n1Job, n2Job] = await execution.getJobs({ order: [['id', 'ASC']]});
|
||||
expect(n2Job.result).toBe(1);
|
||||
@ -140,7 +140,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [n1Job, n2Job] = await execution.getJobs({ order: [['id', 'ASC']]});
|
||||
expect(n2Job.result).toBe(1);
|
||||
@ -160,7 +160,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(2);
|
||||
@ -191,7 +191,7 @@ describe('workflow > instructions > calculation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(-1);
|
||||
|
@ -8,7 +8,7 @@ import { EXECUTION_STATUS, BRANCH_INDEX } from '../../constants';
|
||||
describe('workflow > instructions > condition', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -17,7 +17,7 @@ describe('workflow > instructions > condition', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -65,7 +65,7 @@ describe('workflow > instructions > condition', () => {
|
||||
upstreamId: n1.id
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -102,7 +102,7 @@ describe('workflow > instructions > condition', () => {
|
||||
upstreamId: n1.id
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
expect(execution.status).toEqual(EXECUTION_STATUS.RESOLVED);
|
||||
@ -140,7 +140,7 @@ describe('workflow > instructions > condition', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -169,7 +169,7 @@ describe('workflow > instructions > condition', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -198,7 +198,7 @@ describe('workflow > instructions > condition', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions({ include: ['jobs'] });
|
||||
const [job] = execution.jobs;
|
||||
@ -227,7 +227,7 @@ describe('workflow > instructions > condition', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -261,7 +261,7 @@ describe('workflow > instructions > condition', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
|
@ -7,7 +7,7 @@ import { getApp } from '..';
|
||||
describe('workflow > instructions > create', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -16,7 +16,7 @@ describe('workflow > instructions > create', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -36,7 +36,7 @@ describe('workflow > instructions > create', () => {
|
||||
const n1 = await workflow.createNode({
|
||||
type: 'create',
|
||||
config: {
|
||||
collection: 'approvals',
|
||||
collection: 'comments',
|
||||
params: {
|
||||
values: {
|
||||
postId: '{{$context.data.id}}'
|
||||
@ -45,7 +45,7 @@ describe('workflow > instructions > create', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
|
@ -7,7 +7,7 @@ import { getApp } from '..';
|
||||
describe('workflow > instructions > destroy', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -16,7 +16,7 @@ describe('workflow > instructions > destroy', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -45,13 +45,13 @@ describe('workflow > instructions > destroy', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result).toBe(1);
|
||||
|
||||
const count = await PostModel.count();
|
||||
const count = await PostRepo.count();
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -7,7 +7,9 @@ import { getApp } from '..';
|
||||
describe('workflow > instructions > query', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostCollection;
|
||||
let PostRepo;
|
||||
let TagModel;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -16,7 +18,9 @@ describe('workflow > instructions > query', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostCollection = db.getCollection('posts');
|
||||
PostRepo = PostCollection.repository;
|
||||
TagModel = db.getCollection('tags').model;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -40,7 +44,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -60,7 +64,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -80,7 +84,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -100,7 +104,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -125,13 +129,58 @@ describe('workflow > instructions > query', () => {
|
||||
});
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const jobs = await execution.getJobs({ order: [['id', 'ASC']] });
|
||||
expect(jobs[1].result.title).toBe(post.title);
|
||||
});
|
||||
|
||||
it('params.filter: by association field', async () => {
|
||||
const n1 = await workflow.createNode({
|
||||
type: 'query',
|
||||
config: {
|
||||
collection: 'tags',
|
||||
params: {
|
||||
filter: {
|
||||
'posts.id': `{{$context.data.id}}`
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tag = await TagModel.create({ name: 'tag1' });
|
||||
const post = await PostCollection.repository.create({
|
||||
values: { title: 't1', tags: [tag.id] }
|
||||
});
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result.id).toBe(tag.id);
|
||||
});
|
||||
|
||||
it('params.appends: with associations', async () => {
|
||||
const n1 = await workflow.createNode({
|
||||
type: 'query',
|
||||
config: {
|
||||
collection: 'tags',
|
||||
params: {
|
||||
appends: ['posts']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const tag = await TagModel.create({ name: 'tag1' });
|
||||
const post = await PostCollection.repository.create({
|
||||
values: { title: 't1', tags: [tag.id] }
|
||||
});
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result.posts.length).toBe(1);
|
||||
expect(job.result.posts[0].id).toBe(post.id);
|
||||
});
|
||||
|
||||
it('params.sort', async () => {
|
||||
const n1 = await workflow.createNode({
|
||||
type: 'query',
|
||||
@ -143,8 +192,8 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const p1 = await PostModel.create({ title: 't1' });
|
||||
const p2 = await PostModel.create({ title: 't2' });
|
||||
const p1 = await PostRepo.create({ values: { title: 't1' } });
|
||||
const p2 = await PostRepo.create({ values: { title: 't2' } });
|
||||
|
||||
// get the 2nd execution
|
||||
const [execution] = await workflow.getExecutions({ order: [['id', 'DESC']] });
|
||||
@ -164,7 +213,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -186,7 +235,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
@ -208,7 +257,7 @@ describe('workflow > instructions > query', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
|
@ -7,7 +7,7 @@ import { getApp } from '..';
|
||||
describe('workflow > instructions > update', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
let workflow;
|
||||
|
||||
@ -16,7 +16,7 @@ describe('workflow > instructions > update', () => {
|
||||
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
|
||||
workflow = await WorkflowModel.create({
|
||||
title: 'test workflow',
|
||||
@ -48,14 +48,14 @@ describe('workflow > instructions > update', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const post = await PostModel.create({ title: 't1' });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
expect(post.published).toBe(false);
|
||||
|
||||
const [execution] = await workflow.getExecutions();
|
||||
const [job] = await execution.getJobs();
|
||||
expect(job.result.published).toBe(true);
|
||||
|
||||
const updatedPost = await PostModel.findByPk(post.id);
|
||||
const updatedPost = await PostRepo.findById(post.id);
|
||||
expect(updatedPost.published).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -92,9 +92,9 @@ describe('workflow > instructions > update', () => {
|
||||
await n1.setDownstream(n2);
|
||||
|
||||
// NOTE: the result of post immediately created will not be changed by workflow
|
||||
const { id } = await PostModel.create({ title: 'test' });
|
||||
const { id } = await PostRepo.create({ values: { title: 'test' } });
|
||||
// should get from db
|
||||
const post = await PostModel.findByPk(id);
|
||||
const post = await PostRepo.findById(id);
|
||||
expect(post.title).toBe('changed');
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Application } from '@nocobase/server';
|
||||
import Database from '@nocobase/database';
|
||||
import { getApp } from '.';
|
||||
import { EXECUTION_STATUS } from '../constants';
|
||||
|
||||
|
||||
|
||||
@ -8,6 +9,7 @@ describe('workflow > workflow', () => {
|
||||
let app: Application;
|
||||
let db: Database;
|
||||
let PostModel;
|
||||
let PostRepo;
|
||||
let WorkflowModel;
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -16,6 +18,7 @@ describe('workflow > workflow', () => {
|
||||
db = app.db;
|
||||
WorkflowModel = db.getCollection('workflows').model;
|
||||
PostModel = db.getCollection('posts').model;
|
||||
PostRepo = db.getCollection('posts').repository;
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
@ -31,41 +34,11 @@ describe('workflow > workflow', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const { hooks } = PostModel.options;
|
||||
expect(hooks.afterCreate.length).toBe(1);
|
||||
await workflow.update({ enabled: false });
|
||||
const post = await PostRepo.create({ values: { title: 't1' } });
|
||||
|
||||
await workflow.update({
|
||||
config: {
|
||||
mode: 2,
|
||||
collection: 'posts'
|
||||
}
|
||||
});
|
||||
expect(hooks.afterCreate.length).toBe(0);
|
||||
expect(hooks.afterUpdate.length).toBe(1);
|
||||
|
||||
await workflow.update({
|
||||
config: {
|
||||
mode: 7,
|
||||
collection: 'posts'
|
||||
}
|
||||
});
|
||||
expect(hooks.afterCreate.length).toBe(1);
|
||||
expect(hooks.afterUpdate.length).toBe(1);
|
||||
expect(hooks.afterDestroy.length).toBe(1);
|
||||
|
||||
await workflow.update({
|
||||
enabled: false
|
||||
});
|
||||
expect(hooks.afterCreate.length).toBe(0);
|
||||
expect(hooks.afterUpdate.length).toBe(0);
|
||||
expect(hooks.afterDestroy.length).toBe(0);
|
||||
|
||||
await workflow.update({
|
||||
enabled: true
|
||||
});
|
||||
expect(hooks.afterCreate.length).toBe(1);
|
||||
expect(hooks.afterUpdate.length).toBe(1);
|
||||
expect(hooks.afterDestroy.length).toBe(1);
|
||||
const executions = await workflow.getExecutions();
|
||||
expect(executions.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -8,7 +8,7 @@ function make(name, mod) {
|
||||
}), {})
|
||||
}
|
||||
|
||||
export default function(app) {
|
||||
export default function({ app }) {
|
||||
app.actions({
|
||||
...make('workflows', workflows),
|
||||
...make('workflows.nodes', {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import parse from 'json-templates';
|
||||
|
||||
import { Context, utils } from '@nocobase/actions';
|
||||
import { Op } from '@nocobase/database';
|
||||
|
||||
|
@ -13,15 +13,14 @@ export default {
|
||||
const options = execution.getParsedValue(params);
|
||||
const result = await (multiple ? repo.find : repo.findOne).call(repo, {
|
||||
...options,
|
||||
// NOTE: `raw` to avoid getting undefined value from Proxied model instance (#380)
|
||||
// e.g. Object.prototype.hasOwnProperty.call(result, 'id') // false
|
||||
// so the properties can not be get by json-templates(object-path)
|
||||
raw: true,
|
||||
transaction: execution.tx
|
||||
});
|
||||
|
||||
// NOTE: `toJSON()` to avoid getting undefined value from Proxied model instance (#380)
|
||||
// e.g. Object.prototype.hasOwnProperty.call(result, 'id') // false
|
||||
// so the properties can not be get by json-templates(object-path)
|
||||
return {
|
||||
result,
|
||||
result: multiple ? result.map(item => item.toJSON()) : result?.toJSON(),
|
||||
status: JOB_STATUS.RESOLVED
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Database, Model } from '@nocobase/database';
|
||||
import { HasManyCountAssociationsMixin, HasManyCreateAssociationMixin, HasManyGetAssociationsMixin } from 'sequelize';
|
||||
|
||||
import triggers from '../triggers';
|
||||
import { EXECUTION_STATUS } from '../constants';
|
||||
import ExecutionModel from './Execution';
|
||||
import FlowNodeModel from './FlowNode';
|
||||
@ -30,25 +29,6 @@ export default class WorkflowModel extends Model {
|
||||
declare getExecutions: HasManyGetAssociationsMixin<ExecutionModel>;
|
||||
declare createExecution: HasManyCreateAssociationMixin<ExecutionModel>;
|
||||
|
||||
static async mount() {
|
||||
const collection = this.database.getCollection('workflows');
|
||||
const workflows = await collection.repository.find({
|
||||
filter: { enabled: true },
|
||||
});
|
||||
|
||||
workflows.forEach((workflow: WorkflowModel) => {
|
||||
workflow.toggle();
|
||||
});
|
||||
|
||||
this.addHook('afterCreate', (model: WorkflowModel) => model.toggle());
|
||||
this.addHook('afterUpdate', (model: WorkflowModel) => model.toggle());
|
||||
this.addHook('afterDestroy', (model: WorkflowModel) => model.toggle(false));
|
||||
}
|
||||
|
||||
getHookId() {
|
||||
return `workflow-${this.get('id')}`;
|
||||
}
|
||||
|
||||
getTransaction(options) {
|
||||
if (!this.useTransaction) {
|
||||
return undefined;
|
||||
@ -59,17 +39,7 @@ export default class WorkflowModel extends Model {
|
||||
: (<typeof WorkflowModel>this.constructor).database.sequelize.transaction();
|
||||
}
|
||||
|
||||
async toggle(enable?: boolean) {
|
||||
const type = this.get('type');
|
||||
const { on, off } = triggers.get(type);
|
||||
if (typeof enable !== 'undefined' ? enable : this.get('enabled')) {
|
||||
on.call(this, this.trigger.bind(this));
|
||||
} else {
|
||||
off.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
async trigger(context: Object, options) {
|
||||
trigger = async (context: Object, options) => {
|
||||
// `null` means not to trigger
|
||||
if (context === null) {
|
||||
return;
|
||||
|
@ -4,9 +4,19 @@ import { Plugin } from '@nocobase/server';
|
||||
|
||||
import WorkflowModel from './models/Workflow';
|
||||
import ExecutionModel from './models/Execution';
|
||||
import actions from './actions';
|
||||
import initActions from './actions';
|
||||
import initTriggers, { Trigger } from './triggers';
|
||||
import { Registry } from '@nocobase/utils';
|
||||
|
||||
|
||||
|
||||
export default class extends Plugin {
|
||||
triggers: Registry<Trigger> = new Registry<Trigger>();
|
||||
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
}
|
||||
|
||||
export default class WorkflowPlugin extends Plugin {
|
||||
async load(options = {}) {
|
||||
const { db } = this.app;
|
||||
|
||||
@ -19,47 +29,40 @@ export default class WorkflowPlugin extends Plugin {
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
actions(this.app);
|
||||
initActions(this);
|
||||
|
||||
initTriggers(this);
|
||||
|
||||
// [Life Cycle]:
|
||||
// * load all workflows in db
|
||||
// * add all hooks for enabled workflows
|
||||
// * add hooks for create/update[enabled]/delete workflow to add/remove specific hooks
|
||||
this.app.on('beforeStart', async () => {
|
||||
const { model } = db.getCollection('workflows');
|
||||
await (model as typeof WorkflowModel).mount();
|
||||
const collection = db.getCollection('workflows');
|
||||
const workflows = await collection.repository.find({
|
||||
filter: { enabled: true },
|
||||
});
|
||||
|
||||
workflows.forEach((workflow: WorkflowModel) => {
|
||||
this.toggle(workflow);
|
||||
});
|
||||
|
||||
db.on('workflows.afterCreate', (model: WorkflowModel) => this.toggle(model));
|
||||
db.on('workflows.afterUpdate', (model: WorkflowModel) => this.toggle(model));
|
||||
db.on('workflows.afterDestroy', (model: WorkflowModel) => this.toggle(model, false));
|
||||
});
|
||||
|
||||
// [Life Cycle]: initialize all necessary seed data
|
||||
this.app.on('db.init', async () => {});
|
||||
|
||||
// const [Automation, AutomationJob] = database.getModels(['automations', 'automations_jobs']);
|
||||
|
||||
// Automation.addHook('afterCreate', async (model: AutomationModel) => {
|
||||
// model.get('enabled') && await model.loadJobs();
|
||||
// });
|
||||
|
||||
// Automation.addHook('afterUpdate', async (model: AutomationModel) => {
|
||||
// if (!model.changed('enabled' as any)) {
|
||||
// return;
|
||||
// }
|
||||
// model.get('enabled') ? await model.loadJobs() : await model.cancelJobs();
|
||||
// });
|
||||
|
||||
// Automation.addHook('beforeDestroy', async (model: AutomationModel) => {
|
||||
// await model.cancelJobs();
|
||||
// });
|
||||
|
||||
// AutomationJob.addHook('afterCreate', async (model: AutomationJobModel) => {
|
||||
// await model.bootstrap();
|
||||
// });
|
||||
|
||||
// AutomationJob.addHook('beforeDestroy', async (model: AutomationJobModel) => {
|
||||
// await model.cancel();
|
||||
// });
|
||||
// this.app.on('db.init', async () => {});
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.getPackageName(__dirname);
|
||||
async toggle(workflow: WorkflowModel, enable?: boolean) {
|
||||
const type = workflow.get('type');
|
||||
const trigger = this.triggers.get(type);
|
||||
if (typeof enable !== 'undefined' ? enable : workflow.get('enabled')) {
|
||||
await trigger.on(workflow);
|
||||
} else {
|
||||
await trigger.off(workflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Model } from "@nocobase/database";
|
||||
import { Trigger } from ".";
|
||||
import WorkflowModel from "../models/Workflow";
|
||||
|
||||
export interface ModelChangeTriggerConfig {
|
||||
export interface CollectionChangeTriggerConfig {
|
||||
collection: string;
|
||||
mode: number;
|
||||
// TODO: ICondition
|
||||
@ -14,58 +16,78 @@ const MODE_BITMAP = {
|
||||
};
|
||||
|
||||
const MODE_BITMAP_EVENTS = new Map();
|
||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.CREATE, 'afterCreate');
|
||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.UPDATE, 'afterUpdate');
|
||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.CREATE, 'afterCreateWithAssociations');
|
||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.UPDATE, 'afterUpdateWithAssociations');
|
||||
MODE_BITMAP_EVENTS.set(MODE_BITMAP.DESTROY, 'afterDestroy');
|
||||
|
||||
// async function, should return promise
|
||||
function bindHandler(this: WorkflowModel, callback: Function) {
|
||||
const { condition, changed } = this.config;
|
||||
return (data: any, options) => {
|
||||
// NOTE: if no configured fields changed, do not trigger
|
||||
if (changed && changed.length && changed.every(name => !data.changed(name))) {
|
||||
return;
|
||||
}
|
||||
// NOTE: if no configured condition match, do not trigger
|
||||
if (condition && condition.$and.length) {
|
||||
// TODO: check all conditions in condition against data
|
||||
// const calculation = toCalculation(condition);
|
||||
}
|
||||
|
||||
return callback({ data: data.get() }, options);
|
||||
};
|
||||
|
||||
function getHookId(workflow, type) {
|
||||
return `${type}#${workflow.get('id')}`;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'collection',
|
||||
on(this: WorkflowModel, callback: Function) {
|
||||
const { database } = <typeof WorkflowModel>this.constructor;
|
||||
const { collection, mode } = this.config;
|
||||
// async function, should return promise
|
||||
function handler(this: WorkflowModel, data: Model, options) {
|
||||
const { condition, changed } = this.config;
|
||||
// NOTE: if no configured fields changed, do not trigger
|
||||
if (changed && changed.length && changed.every(name => !data.changed(name))) {
|
||||
return;
|
||||
}
|
||||
// NOTE: if no configured condition match, do not trigger
|
||||
if (condition && condition.$and.length) {
|
||||
// TODO: check all conditions in condition against data
|
||||
// const calculation = toCalculation(condition);
|
||||
}
|
||||
|
||||
return this.trigger({ data: data.get() }, options);
|
||||
}
|
||||
|
||||
export default class CollectionTrigger implements Trigger {
|
||||
events = new Map();
|
||||
|
||||
on(workflow: WorkflowModel) {
|
||||
const { database } = <typeof WorkflowModel>workflow.constructor;
|
||||
const { collection, mode } = workflow.config;
|
||||
const Collection = database.getCollection(collection);
|
||||
if (!Collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
||||
for (let [key, type] of MODE_BITMAP_EVENTS.entries()) {
|
||||
const event = `${collection}.${type}`;
|
||||
const name = getHookId(workflow, event);
|
||||
if (mode & key) {
|
||||
if (!Collection.model.options.hooks[event]?.find(item => item.name && item.name === this.getHookId())) {
|
||||
Collection.model.addHook(event, this.getHookId(), bindHandler.call(this, callback));
|
||||
if (!this.events.has(name)) {
|
||||
const listener = handler.bind(workflow);
|
||||
this.events.set(name, listener);
|
||||
database.on(event, listener);
|
||||
}
|
||||
} else {
|
||||
Collection.model.removeHook(event, this.getHookId());
|
||||
const listener = this.events.get(name);
|
||||
if (listener) {
|
||||
database.off(event, listener);
|
||||
this.events.delete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
off(this: WorkflowModel) {
|
||||
const { database } = <typeof WorkflowModel>this.constructor;
|
||||
const { collection, mode } = this.config;
|
||||
}
|
||||
|
||||
off(workflow: WorkflowModel) {
|
||||
const { database } = <typeof WorkflowModel>workflow.constructor;
|
||||
const { collection, mode } = workflow.config;
|
||||
const Collection = database.getCollection(collection);
|
||||
if (!Collection) {
|
||||
return;
|
||||
}
|
||||
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
||||
for (let [key, type] of MODE_BITMAP_EVENTS.entries()) {
|
||||
const event = `${collection}.${type}`;
|
||||
const name = getHookId(workflow, event);
|
||||
if (mode & key) {
|
||||
Collection.model.removeHook(event, this.getHookId());
|
||||
const listener = this.events.get(name);
|
||||
if (listener) {
|
||||
database.off(event, listener);
|
||||
this.events.delete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { Registry } from '@nocobase/utils';
|
||||
import WorkflowModel from '../models/Workflow';
|
||||
import collectionlTrigger from './collection';
|
||||
import Collection from './collection';
|
||||
// import Schedule from './schedule';
|
||||
|
||||
export interface Trigger {
|
||||
name: string;
|
||||
on(this: WorkflowModel, callback: Function): void;
|
||||
off(this: WorkflowModel): void;
|
||||
on(workflow: WorkflowModel): void;
|
||||
off(workflow: WorkflowModel): void;
|
||||
}
|
||||
|
||||
export const triggers = new Registry<Trigger>();
|
||||
|
||||
export default triggers;
|
||||
|
||||
triggers.register(collectionlTrigger.name, collectionlTrigger);
|
||||
export default function({ triggers }) {
|
||||
triggers.register('collection', new Collection());
|
||||
// triggers.register('schedule', new Schedule());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user