diff --git a/packages/plugins/workflow/src/__tests__/collections/approvals.ts b/packages/plugins/workflow/src/__tests__/collections/comments.ts similarity index 92% rename from packages/plugins/workflow/src/__tests__/collections/approvals.ts rename to packages/plugins/workflow/src/__tests__/collections/comments.ts index 4c4788db4..dfb104209 100644 --- a/packages/plugins/workflow/src/__tests__/collections/approvals.ts +++ b/packages/plugins/workflow/src/__tests__/collections/comments.ts @@ -1,7 +1,7 @@ import { CollectionOptions } from '@nocobase/database'; export default { - name: 'approvals', + name: 'comments', fields: [ { type: 'belongsTo', diff --git a/packages/plugins/workflow/src/__tests__/collections/posts.ts b/packages/plugins/workflow/src/__tests__/collections/posts.ts index 59c564c83..30afa1a7c 100644 --- a/packages/plugins/workflow/src/__tests__/collections/posts.ts +++ b/packages/plugins/workflow/src/__tests__/collections/posts.ts @@ -16,6 +16,14 @@ export default { type: 'integer', name: 'read', defaultValue: 0 + }, + { + type: 'hasMany', + name: 'comments' + }, + { + type: 'belongsToMany', + name: 'tags' } ] } as CollectionOptions; diff --git a/packages/plugins/workflow/src/__tests__/collections/tags.ts b/packages/plugins/workflow/src/__tests__/collections/tags.ts new file mode 100644 index 000000000..d6017b60d --- /dev/null +++ b/packages/plugins/workflow/src/__tests__/collections/tags.ts @@ -0,0 +1,15 @@ +import { CollectionOptions } from '@nocobase/database'; + +export default { + name: 'tags', + fields: [ + { + type: 'belongsToMany', + name: 'posts', + }, + { + type: 'string', + name: 'name' + } + ], +} as CollectionOptions; diff --git a/packages/plugins/workflow/src/__tests__/execution.test.ts b/packages/plugins/workflow/src/__tests__/execution.test.ts index 661114b82..7d19bfe21 100644 --- a/packages/plugins/workflow/src/__tests__/execution.test.ts +++ b/packages/plugins/workflow/src/__tests__/execution.test.ts @@ -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(); diff --git a/packages/plugins/workflow/src/__tests__/instructions/calculation.test.ts b/packages/plugins/workflow/src/__tests__/instructions/calculation.test.ts index 94faafc4f..ea5966021 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/calculation.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/calculation.test.ts @@ -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); diff --git a/packages/plugins/workflow/src/__tests__/instructions/condition.test.ts b/packages/plugins/workflow/src/__tests__/instructions/condition.test.ts index 5de88597d..7499b5e94 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/condition.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/condition.test.ts @@ -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(); diff --git a/packages/plugins/workflow/src/__tests__/instructions/create.test.ts b/packages/plugins/workflow/src/__tests__/instructions/create.test.ts index 3e4316c46..b2ddf77d8 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/create.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/create.test.ts @@ -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(); diff --git a/packages/plugins/workflow/src/__tests__/instructions/destroy.test.ts b/packages/plugins/workflow/src/__tests__/instructions/destroy.test.ts index ec156022f..2a86b650a 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/destroy.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/destroy.test.ts @@ -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); }); }); diff --git a/packages/plugins/workflow/src/__tests__/instructions/query.test.ts b/packages/plugins/workflow/src/__tests__/instructions/query.test.ts index c55bfa579..ef52cb10f 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/query.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/query.test.ts @@ -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(); diff --git a/packages/plugins/workflow/src/__tests__/instructions/update.test.ts b/packages/plugins/workflow/src/__tests__/instructions/update.test.ts index b66a357c4..61880d8d6 100644 --- a/packages/plugins/workflow/src/__tests__/instructions/update.test.ts +++ b/packages/plugins/workflow/src/__tests__/instructions/update.test.ts @@ -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'); }); }); diff --git a/packages/plugins/workflow/src/__tests__/workflow.test.ts b/packages/plugins/workflow/src/__tests__/workflow.test.ts index 63d7ec559..90f3c3360 100644 --- a/packages/plugins/workflow/src/__tests__/workflow.test.ts +++ b/packages/plugins/workflow/src/__tests__/workflow.test.ts @@ -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); }); }); }); diff --git a/packages/plugins/workflow/src/actions/index.ts b/packages/plugins/workflow/src/actions/index.ts index 32140ba30..243b6ab90 100644 --- a/packages/plugins/workflow/src/actions/index.ts +++ b/packages/plugins/workflow/src/actions/index.ts @@ -8,7 +8,7 @@ function make(name, mod) { }), {}) } -export default function(app) { +export default function({ app }) { app.actions({ ...make('workflows', workflows), ...make('workflows.nodes', { diff --git a/packages/plugins/workflow/src/actions/workflows.ts b/packages/plugins/workflow/src/actions/workflows.ts index 4be8eb676..4e1dc32ad 100644 --- a/packages/plugins/workflow/src/actions/workflows.ts +++ b/packages/plugins/workflow/src/actions/workflows.ts @@ -1,5 +1,3 @@ -import parse from 'json-templates'; - import { Context, utils } from '@nocobase/actions'; import { Op } from '@nocobase/database'; diff --git a/packages/plugins/workflow/src/instructions/query.ts b/packages/plugins/workflow/src/instructions/query.ts index d0a633925..e123e2697 100644 --- a/packages/plugins/workflow/src/instructions/query.ts +++ b/packages/plugins/workflow/src/instructions/query.ts @@ -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 }; } diff --git a/packages/plugins/workflow/src/models/Workflow.ts b/packages/plugins/workflow/src/models/Workflow.ts index 0772afc2a..3b84717a4 100644 --- a/packages/plugins/workflow/src/models/Workflow.ts +++ b/packages/plugins/workflow/src/models/Workflow.ts @@ -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; declare createExecution: HasManyCreateAssociationMixin; - 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 { : (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; diff --git a/packages/plugins/workflow/src/server.ts b/packages/plugins/workflow/src/server.ts index ddc95bdec..9e3389574 100644 --- a/packages/plugins/workflow/src/server.ts +++ b/packages/plugins/workflow/src/server.ts @@ -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 = new Registry(); + + 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); + } } } diff --git a/packages/plugins/workflow/src/triggers/collection.ts b/packages/plugins/workflow/src/triggers/collection.ts index a778ec5ef..e6e285679 100644 --- a/packages/plugins/workflow/src/triggers/collection.ts +++ b/packages/plugins/workflow/src/triggers/collection.ts @@ -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 } = 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 } = 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 } = this.constructor; - const { collection, mode } = this.config; + } + + off(workflow: WorkflowModel) { + const { database } = 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); + } } } } diff --git a/packages/plugins/workflow/src/triggers/index.ts b/packages/plugins/workflow/src/triggers/index.ts index 4d574bf9a..358fc46fa 100644 --- a/packages/plugins/workflow/src/triggers/index.ts +++ b/packages/plugins/workflow/src/triggers/index.ts @@ -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(); - -export default triggers; - -triggers.register(collectionlTrigger.name, collectionlTrigger); +export default function({ triggers }) { + triggers.register('collection', new Collection()); + // triggers.register('schedule', new Schedule()); +}