diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js index 8fc041c6af..2a8ec79c05 100644 --- a/packages/server/src/api/controllers/record.js +++ b/packages/server/src/api/controllers/record.js @@ -182,6 +182,7 @@ exports.destroy = async function(ctx) { return } ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) + ctx.status = 200 emitEvent(`record:delete`, ctx, record) } diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 9183c76517..a22a2a427f 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -67,6 +67,13 @@ exports.createModel = async (request, appId, instanceId, model) => { return res.body } +exports.getAllFromModel = async (request, appId, instanceId, modelId) => { + const res = await request + .get(`/api/${modelId}/records`) + .set(exports.defaultHeaders(appId, instanceId)) + return res.body +} + exports.createView = async (request, appId, instanceId, modelId, view) => { view = view || { map: "function(doc) { emit(doc[doc.key], doc._id); } ", diff --git a/packages/server/src/api/routes/tests/testUtils.js b/packages/server/src/api/routes/tests/testUtils.js new file mode 100644 index 0000000000..0e66b47c3d --- /dev/null +++ b/packages/server/src/api/routes/tests/testUtils.js @@ -0,0 +1 @@ +module.exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/packages/server/src/api/routes/tests/workflow.spec.js b/packages/server/src/api/routes/tests/workflow.spec.js index a631955ec2..ce82cf8b13 100644 --- a/packages/server/src/api/routes/tests/workflow.spec.js +++ b/packages/server/src/api/routes/tests/workflow.spec.js @@ -3,6 +3,7 @@ const { createApplication, createInstance, createModel, + getAllFromModel, defaultHeaders, supertest, insertDocument, @@ -10,6 +11,8 @@ const { builderEndpointShouldBlockNormalUsers } = require("./couchTestUtils") +const { delay } = require("./testUtils") + const TEST_WORKFLOW = { _id: "Test Workflow", name: "My Workflow", @@ -37,7 +40,7 @@ describe("/workflows", () => { let app let instance let workflow - let model + let workflowId beforeAll(async () => { ({ request, server } = await supertest()) @@ -48,7 +51,6 @@ describe("/workflows", () => { beforeEach(async () => { if (workflow) await destroyDocument(workflow.id) instance = await createInstance(request, app._id) - model = await createModel(request, app._id, instance._id) }) afterAll(async () => { @@ -59,7 +61,7 @@ describe("/workflows", () => { workflow = await insertDocument(instance._id, { type: "workflow", ...TEST_WORKFLOW - }); + }) workflow = { ...workflow, ...TEST_WORKFLOW } } @@ -113,21 +115,14 @@ describe("/workflows", () => { describe("create", () => { it("should setup the workflow fully", () => { let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"] - trigger.inputs.modelId = model._id trigger.id = "wadiawdo34" let saveAction = ACTION_DEFINITIONS["SAVE_RECORD"] saveAction.inputs.record = { - modelId: model._id, - name: "Testing", + name: "{{trigger.name}}", } saveAction.id = "awde444wk" - let deleteAction = ACTION_DEFINITIONS["DELETE_RECORD"] - deleteAction.inputs.id = "{{blocks[1].id}}" - deleteAction.inputs.revision = "{{blocks[1].revision}}" - deleteAction.id = "78MOt8nQO" TEST_WORKFLOW.definition.steps.push(saveAction) - TEST_WORKFLOW.definition.steps.push(deleteAction) TEST_WORKFLOW.definition.trigger = trigger }) @@ -139,8 +134,10 @@ describe("/workflows", () => { .expect('Content-Type', /json/) .expect(200) - expect(res.body.message).toEqual("Workflow created successfully"); - expect(res.body.workflow.name).toEqual("My Workflow"); + expect(res.body.message).toEqual("Workflow created successfully") + expect(res.body.workflow.name).toEqual("My Workflow") + expect(res.body.workflow._id).not.toEqual(null) + workflowId = res.body.workflow._id }) it("should apply authorization to endpoint", async () => { @@ -155,12 +152,34 @@ describe("/workflows", () => { }) }) + describe("trigger", () => { + it("trigger the workflow successfully", async () => { + let model = await createModel(request, app._id, instance._id) + TEST_WORKFLOW.definition.trigger.inputs.modelId = model._id + TEST_WORKFLOW.definition.steps[0].inputs.record.modelId = model._id + await createWorkflow() + const res = await request + .post(`/api/workflows/${workflow._id}/trigger`) + .send({ name: "Test", description: "Test" }) + .set(defaultHeaders(app._id, instance._id)) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.message).toEqual(`Workflow ${workflow._id} has been triggered.`) + expect(res.body.workflow.name).toEqual(TEST_WORKFLOW.name) + // wait for workflow to complete in background + await delay(500) + let elements = await getAllFromModel(request, app._id, instance._id, model._id) + expect(elements.length).toEqual(1) + expect(elements[0].name).toEqual("Test") + }) + }) + describe("update", () => { it("updates a workflows data", async () => { - await createWorkflow(); + await createWorkflow() workflow._id = workflow.id workflow._rev = workflow.rev - workflow.name = "Updated Name"; + workflow.name = "Updated Name" workflow.type = "workflow" const res = await request @@ -170,21 +189,21 @@ describe("/workflows", () => { .expect('Content-Type', /json/) .expect(200) - expect(res.body.message).toEqual("Workflow Test Workflow updated successfully."); - expect(res.body.workflow.name).toEqual("Updated Name"); + expect(res.body.message).toEqual("Workflow Test Workflow updated successfully.") + expect(res.body.workflow.name).toEqual("Updated Name") }) }) describe("fetch", () => { it("return all the workflows for an instance", async () => { - await createWorkflow(); + await createWorkflow() const res = await request .get(`/api/workflows`) .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) - expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW)); + expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW)) }) it("should apply authorization to endpoint", async () => { @@ -200,18 +219,18 @@ describe("/workflows", () => { describe("destroy", () => { it("deletes a workflow by its ID", async () => { - await createWorkflow(); + await createWorkflow() const res = await request .delete(`/api/workflows/${workflow.id}/${workflow.rev}`) .set(defaultHeaders(app._id, instance._id)) .expect('Content-Type', /json/) .expect(200) - expect(res.body.id).toEqual(TEST_WORKFLOW._id); + expect(res.body.id).toEqual(TEST_WORKFLOW._id) }) it("should apply authorization to endpoint", async () => { - await createWorkflow(); + await createWorkflow() await builderEndpointShouldBlockNormalUsers({ request, method: "DELETE", @@ -221,4 +240,4 @@ describe("/workflows", () => { }) }) }) -}); +}) diff --git a/packages/server/src/workflows/thread.js b/packages/server/src/workflows/thread.js index 8d754ded56..0c897761a6 100644 --- a/packages/server/src/workflows/thread.js +++ b/packages/server/src/workflows/thread.js @@ -2,11 +2,34 @@ const mustache = require("mustache") const actions = require("./actions") const logic = require("./logic") +function cleanMustache(string) { + let charToReplace = { + "[": ".", + "]": "", + } + let regex = new RegExp(/{{[^}}]*}}/g) + let match + while ((match = regex.exec(string)) !== null) { + let baseIdx = string.indexOf(match) + for (let key of Object.keys(charToReplace)) { + let idxChar = match[0].indexOf(key) + if (idxChar !== -1) { + string = + string.slice(baseIdx, baseIdx + idxChar) + + charToReplace[key] + + string.slice(baseIdx + idxChar + 1) + } + } + } + return string +} + function recurseMustache(inputs, context) { - for (let key in Object.keys(inputs)) { + for (let key of Object.keys(inputs)) { let val = inputs[key] if (typeof val === "string") { - inputs[key] = mustache.render(val, { context }) + val = cleanMustache(inputs[key]) + inputs[key] = mustache.render(val, context) } // this covers objects and arrays else if (typeof val === "object") { @@ -24,8 +47,8 @@ function recurseMustache(inputs, context) { class Orchestrator { constructor(workflow, triggerOutput) { this._instanceId = triggerOutput.instanceId - // block zero is never used as the mustache is zero indexed for customer facing - this._context = { blocks: [{}], trigger: triggerOutput } + // step zero is never used as the mustache is zero indexed for customer facing + this._context = { steps: [{}], trigger: triggerOutput } this._workflow = workflow } @@ -44,15 +67,15 @@ class Orchestrator { async execute() { let workflow = this._workflow - for (let block of workflow.definition.steps) { - let stepFn = await this.getStepFunctionality(block.type, block.stepId) - block.inputs = recurseMustache(block.inputs, this._context) + for (let step of workflow.definition.steps) { + let stepFn = await this.getStepFunctionality(step.type, step.stepId) + step.inputs = recurseMustache(step.inputs, this._context) // instanceId is always passed const outputs = await stepFn({ - inputs: block.inputs, + inputs: step.inputs, instanceId: this._instanceId, }) - this._context.blocks.push(outputs) + this._context.steps.push(outputs) } } }