From 1ab787afd7aa00e7f24a74962c3503e4faa03513 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 16 Sep 2020 14:00:04 +0100 Subject: [PATCH] Updating file structure so that each built in step has its own file containing the definition and the function of it, with the intention of keeping definitions together as they will be handled in the async actions. --- .../{workflow/index.js => workflow.js} | 21 +- .../controllers/workflow/blockDefinitions.js | 330 ------------------ packages/server/src/workflows/actions.js | 127 +------ packages/server/src/workflows/logic.js | 35 +- .../server/src/workflows/steps/createUser.js | 82 +++++ packages/server/src/workflows/steps/delay.js | 25 ++ .../src/workflows/steps/deleteRecord.js | 77 ++++ packages/server/src/workflows/steps/filter.js | 71 ++++ .../server/src/workflows/steps/saveRecord.js | 85 +++++ .../server/src/workflows/steps/sendEmail.js | 70 ++++ packages/server/src/workflows/thread.js | 56 +-- packages/server/src/workflows/triggers.js | 67 ++++ 12 files changed, 549 insertions(+), 497 deletions(-) rename packages/server/src/api/controllers/{workflow/index.js => workflow.js} (82%) delete mode 100644 packages/server/src/api/controllers/workflow/blockDefinitions.js create mode 100644 packages/server/src/workflows/steps/createUser.js create mode 100644 packages/server/src/workflows/steps/delay.js create mode 100644 packages/server/src/workflows/steps/deleteRecord.js create mode 100644 packages/server/src/workflows/steps/filter.js create mode 100644 packages/server/src/workflows/steps/saveRecord.js create mode 100644 packages/server/src/workflows/steps/sendEmail.js diff --git a/packages/server/src/api/controllers/workflow/index.js b/packages/server/src/api/controllers/workflow.js similarity index 82% rename from packages/server/src/api/controllers/workflow/index.js rename to packages/server/src/api/controllers/workflow.js index efb93ad1c..04a0b7f1b 100644 --- a/packages/server/src/api/controllers/workflow/index.js +++ b/packages/server/src/api/controllers/workflow.js @@ -1,7 +1,8 @@ -const CouchDB = require("../../../db") -const newid = require("../../../db/newid") -const blockDefinitions = require("./blockDefinitions") -const triggers = require("../../../workflows/triggers") +const CouchDB = require("../../db") +const newid = require("../../db/newid") +const actions = require("../../workflows/actions") +const logic = require("../../workflows/logic") +const triggers = require("../../workflows/triggers") /************************* * * @@ -67,22 +68,22 @@ exports.destroy = async function(ctx) { } exports.getActionList = async function(ctx) { - ctx.body = blockDefinitions.ACTION + ctx.body = actions.BUILTIN_DEFINITIONS } exports.getTriggerList = async function(ctx) { - ctx.body = blockDefinitions.TRIGGER + ctx.body = triggers.BUILTIN_DEFINITIONS } exports.getLogicList = async function(ctx) { - ctx.body = blockDefinitions.LOGIC + ctx.body = logic.BUILTIN_DEFINITIONS } module.exports.getDefinitionList = async function(ctx) { ctx.body = { - logic: blockDefinitions.LOGIC, - trigger: blockDefinitions.TRIGGER, - action: blockDefinitions.ACTION, + logic: logic.BUILTIN_DEFINITIONS, + trigger: triggers.BUILTIN_DEFINITIONS, + action: actions.BUILTIN_DEFINITIONS, } } diff --git a/packages/server/src/api/controllers/workflow/blockDefinitions.js b/packages/server/src/api/controllers/workflow/blockDefinitions.js deleted file mode 100644 index 5607ac8ee..000000000 --- a/packages/server/src/api/controllers/workflow/blockDefinitions.js +++ /dev/null @@ -1,330 +0,0 @@ -let accessLevels = require("../../../utilities/accessLevels") -let conditions = require("../../../workflows/logic").LogicConditions - -const ACTION = { - SAVE_RECORD: { - name: "Save Record", - tagline: "Save a {{inputs.enriched.model.name}} record", - icon: "ri-save-3-fill", - description: "Save a record to your database", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - record: { - type: "object", - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - customType: "record", - title: "Table", - default: {}, - required: ["modelId"], - }, - }, - required: ["record"], - }, - outputs: { - properties: { - response: { - type: "object", - description: "The response from the table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - id: { - type: "string", - description: "The identifier of the new record", - }, - revision: { - type: "string", - description: "The revision of the new record", - }, - }, - required: ["success", "id", "revision"], - }, - }, - }, - DELETE_RECORD: { - description: "Delete a record from your database", - icon: "ri-delete-bin-line", - name: "Delete Record", - tagline: "Delete a {{inputs.enriched.model.name}} record", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - id: { - type: "string", - title: "Record ID", - }, - revision: { - type: "string", - title: "Record Revision", - }, - }, - required: ["modelId", "id", "revision"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The deleted record", - }, - response: { - type: "object", - description: "The response from the table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - }, - required: ["record", "success"], - }, - }, - }, - CREATE_USER: { - description: "Create a new user", - tagline: "Create user {{inputs.username}}", - icon: "ri-user-add-fill", - name: "Create User", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - username: { - type: "string", - title: "Username", - }, - password: { - type: "string", - customType: "password", - title: "Password", - }, - accessLevelId: { - type: "string", - title: "Access Level", - default: accessLevels.POWERUSER_LEVEL_ID, - enum: accessLevels.ACCESS_LEVELS, - }, - }, - required: ["username", "password", "accessLevelId"], - }, - outputs: { - properties: { - id: { - type: "string", - description: "The identifier of the new user", - }, - revision: { - type: "string", - description: "The revision of the new user", - }, - response: { - type: "object", - description: "The response from the user table", - }, - success: { - type: "boolean", - description: "Whether the action was successful", - }, - }, - required: ["id", "revision", "success"], - }, - }, - }, - SEND_EMAIL: { - description: "Send an email.", - tagline: "Send email to {{inputs.to}}", - icon: "ri-mail-open-fill", - name: "Send Email", - type: "ACTION", - inputs: {}, - schema: { - inputs: { - properties: { - to: { - type: "string", - title: "Send To", - }, - from: { - type: "string", - title: "Send From", - }, - subject: { - type: "string", - title: "Email Subject", - }, - contents: { - type: "string", - title: "Email Contents", - }, - }, - required: ["to", "from", "subject", "contents"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the email was sent", - }, - response: { - type: "object", - description: - "A response from the email client, this may be an error", - }, - }, - required: ["success"], - }, - }, - }, -} - -const LOGIC = { - FILTER: { - name: "Filter", - tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", - icon: "ri-git-branch-line", - description: "Filter any workflows which do not meet certain conditions", - type: "LOGIC", - inputs: {}, - schema: { - inputs: { - properties: { - filter: { - type: "string", - title: "Reference Value", - }, - condition: { - type: "string", - title: "Condition", - enum: conditions, - default: "equals", - }, - value: { - type: "string", - title: "Comparison Value", - }, - }, - required: ["filter", "condition", "value"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the logic block passed", - }, - }, - required: ["success"], - }, - }, - }, - DELAY: { - name: "Delay", - icon: "ri-time-fill", - tagline: "Delay for {{inputs.time}} milliseconds", - description: "Delay the workflow until an amount of time has passed", - inputs: {}, - schema: { - inputs: { - properties: { - time: { - type: "number", - title: "Delay in milliseconds", - }, - }, - required: ["time"], - }, - }, - type: "LOGIC", - }, -} - -const TRIGGER = { - RECORD_SAVED: { - name: "Record Saved", - event: "record:save", - icon: "ri-save-line", - tagline: "Record is added to {{inputs.enriched.model.name}}", - description: "Fired when a record is saved to your database", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - required: ["modelId"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The new record that was saved", - }, - }, - required: ["record"], - }, - }, - type: "TRIGGER", - }, - RECORD_DELETED: { - name: "Record Deleted", - event: "record:delete", - icon: "ri-delete-bin-line", - tagline: "Record is deleted from {{inputs.enriched.model.name}}", - description: "Fired when a record is deleted from your database", - inputs: {}, - schema: { - inputs: { - properties: { - modelId: { - type: "string", - customType: "model", - title: "Table", - }, - }, - required: ["modelId"], - }, - outputs: { - properties: { - record: { - type: "object", - customType: "record", - description: "The record that was deleted", - }, - }, - required: ["record"], - }, - }, - type: "TRIGGER", - }, -} - -// This contains the definitions for the steps and triggers that make up a workflow, a workflow comprises -// of many steps and a single trigger -module.exports = { - ACTION, - LOGIC, - TRIGGER, -} diff --git a/packages/server/src/workflows/actions.js b/packages/server/src/workflows/actions.js index 1f3d8481c..3af75c293 100644 --- a/packages/server/src/workflows/actions.js +++ b/packages/server/src/workflows/actions.js @@ -1,115 +1,20 @@ -const userController = require("../api/controllers/user") -const recordController = require("../api/controllers/record") -const sgMail = require("@sendgrid/mail") +const sendEmail = require("./steps/sendEmail") +const saveRecord = require("./steps/saveRecord") +const deleteRecord = require("./steps/deleteRecord") +const createUser = require("./steps/createUser") -sgMail.setApiKey(process.env.SENDGRID_API_KEY) - -let BUILTIN_ACTIONS = { - CREATE_USER: async function(inputs) { - const { username, password, accessLevelId } = inputs - const ctx = { - user: { - instanceId: inputs.instanceId, - }, - request: { - body: { username, password, accessLevelId }, - }, - } - - try { - await userController.create(ctx) - return { - response: ctx.body, - id: ctx.body._id, - revision: ctx.body._rev, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - SAVE_RECORD: async function(inputs) { - const ctx = { - params: { - instanceId: inputs.instanceId, - modelId: inputs.model._id, - }, - request: { - body: inputs.record, - }, - user: { instanceId: inputs.instanceId }, - } - - try { - await recordController.save(ctx) - return { - response: ctx.body, - id: ctx.body._id, - revision: ctx.body._rev, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - SEND_EMAIL: async function(inputs) { - const msg = { - to: inputs.to, - from: inputs.from, - subject: inputs.subject, - text: inputs.text, - } - - try { - await sgMail.send(msg) - return { - success: true, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, - DELETE_RECORD: async function(inputs) { - const { model, ...record } = inputs.record - // TODO: better logging of when actions are missed due to missing parameters - if (record.recordId == null || record.revId == null) { - return - } - let ctx = { - params: { - modelId: model._id, - recordId: record.recordId, - revId: record.revId, - }, - user: { instanceId: inputs.instanceId }, - } +const BUILTIN_ACTIONS = { + SEND_EMAIL: sendEmail.run, + SAVE_RECORD: saveRecord.run, + DELETE_RECORD: deleteRecord.run, + CREATE_USER: createUser.run, +} - try { - await recordController.destroy(ctx) - return { - response: ctx.body, - success: ctx.status === 200, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } - }, +const BUILTIN_DEFINITIONS = { + SEND_EMAIL: sendEmail.definition, + SAVE_RECORD: saveRecord.definition, + DELETE_RECORD: deleteRecord.definition, + CREATE_USER: createUser.definition, } module.exports.getAction = async function(actionName) { @@ -118,3 +23,5 @@ module.exports.getAction = async function(actionName) { } // TODO: load async actions here } + +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/workflows/logic.js b/packages/server/src/workflows/logic.js index 7a584f995..b74c02bb5 100644 --- a/packages/server/src/workflows/logic.js +++ b/packages/server/src/workflows/logic.js @@ -1,31 +1,20 @@ -const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) +let filter = require("./steps/filter") +let delay = require("./steps/delay") -let LOGIC = { - DELAY: async function delay(inputs) { - await wait(inputs.time) - }, +let BUILTIN_LOGIC = { + DELAY: delay.run, + FILTER: filter.run, +} - FILTER: async function filter(inputs) { - const { field, condition, value } = inputs - switch (condition) { - case "equals": - if (field !== value) return - break - default: - return - } - }, +let BUILTIN_DEFINITIONS = { + DELAY: delay.definition, + FILTER: filter.definition, } module.exports.getLogic = function(logicName) { - if (LOGIC[logicName] != null) { - return LOGIC[logicName] + if (BUILTIN_LOGIC[logicName] != null) { + return BUILTIN_LOGIC[logicName] } } -module.exports.LogicConditions = [ - "Equals", - "Not equals", - "Greater than", - "Less than", -] +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS diff --git a/packages/server/src/workflows/steps/createUser.js b/packages/server/src/workflows/steps/createUser.js new file mode 100644 index 000000000..35aa9bd31 --- /dev/null +++ b/packages/server/src/workflows/steps/createUser.js @@ -0,0 +1,82 @@ +const userController = require("../../api/controllers/user") +let accessLevels = require("../../utilities/accessLevels") + +module.exports.definition = { + description: "Create a new user", + tagline: "Create user {{inputs.username}}", + icon: "ri-user-add-fill", + name: "Create User", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + username: { + type: "string", + title: "Username", + }, + password: { + type: "string", + customType: "password", + title: "Password", + }, + accessLevelId: { + type: "string", + title: "Access Level", + default: accessLevels.POWERUSER_LEVEL_ID, + enum: accessLevels.ACCESS_LEVELS, + }, + }, + required: ["username", "password", "accessLevelId"], + }, + outputs: { + properties: { + id: { + type: "string", + description: "The identifier of the new user", + }, + revision: { + type: "string", + description: "The revision of the new user", + }, + response: { + type: "object", + description: "The response from the user table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + required: ["id", "revision", "success"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + const { username, password, accessLevelId } = inputs + const ctx = { + user: { + instanceId: instanceId, + }, + request: { + body: { username, password, accessLevelId }, + }, + } + + try { + await userController.create(ctx) + return { + response: ctx.body, + id: ctx.body._id, + revision: ctx.body._rev, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} \ No newline at end of file diff --git a/packages/server/src/workflows/steps/delay.js b/packages/server/src/workflows/steps/delay.js new file mode 100644 index 000000000..728d75d25 --- /dev/null +++ b/packages/server/src/workflows/steps/delay.js @@ -0,0 +1,25 @@ +const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) + +module.exports.definition = { + name: "Delay", + icon: "ri-time-fill", + tagline: "Delay for {{inputs.time}} milliseconds", + description: "Delay the workflow until an amount of time has passed", + inputs: {}, + schema: { + inputs: { + properties: { + time: { + type: "number", + title: "Delay in milliseconds", + }, + }, + required: ["time"], + }, + }, + type: "LOGIC", +} + +module.exports.run = async function delay({ inputs }) { + await wait(inputs.time) +} diff --git a/packages/server/src/workflows/steps/deleteRecord.js b/packages/server/src/workflows/steps/deleteRecord.js new file mode 100644 index 000000000..abf44b1c9 --- /dev/null +++ b/packages/server/src/workflows/steps/deleteRecord.js @@ -0,0 +1,77 @@ +const recordController = require("../../api/controllers/record") + +module.exports.definition = { + description: "Delete a record from your database", + icon: "ri-delete-bin-line", + name: "Delete Record", + tagline: "Delete a {{inputs.enriched.model.name}} record", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + id: { + type: "string", + title: "Record ID", + }, + revision: { + type: "string", + title: "Record Revision", + }, + }, + required: ["modelId", "id", "revision"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The deleted record", + }, + response: { + type: "object", + description: "The response from the table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + required: ["record", "success"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + // TODO: better logging of when actions are missed due to missing parameters + if (inputs.id == null || inputs.revision == null) { + return + } + let ctx = { + params: { + modelId: inputs.modelId, + recordId: inputs.id, + revId: inputs.revision, + }, + user: { instanceId }, + } + + try { + await recordController.destroy(ctx) + return { + response: ctx.body, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/steps/filter.js b/packages/server/src/workflows/steps/filter.js new file mode 100644 index 000000000..080af957e --- /dev/null +++ b/packages/server/src/workflows/steps/filter.js @@ -0,0 +1,71 @@ +const LogicConditions = { + EQUALS: "Equals", + NOT_EQUALS: "Not equals", + GREATER_THAN: "Greater than", + LESS_THAN: "Less than", +} + +module.exports.definition = { + name: "Filter", + tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", + icon: "ri-git-branch-line", + description: "Filter any workflows which do not meet certain conditions", + type: "LOGIC", + inputs: {}, + schema: { + inputs: { + properties: { + filter: { + type: "string", + title: "Reference Value", + }, + condition: { + type: "string", + title: "Condition", + enum: Object.values(LogicConditions), + default: "equals", + }, + value: { + type: "string", + title: "Comparison Value", + }, + }, + required: ["filter", "condition", "value"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the logic block passed", + }, + }, + required: ["success"], + }, + }, +} + +module.exports.run = async function filter({ inputs }) { + const { field, condition, value } = inputs + let success + if (typeof field !== "object" && typeof value !== "object") { + switch (condition) { + case LogicConditions.EQUALS: + success = field === value + break + case LogicConditions.NOT_EQUALS: + success = field !== value + break + case LogicConditions.GREATER_THAN: + success = field > value + break + case LogicConditions.LESS_THAN: + success = field < value + break + default: + return + } + } else { + success = false + } + return { success } +} diff --git a/packages/server/src/workflows/steps/saveRecord.js b/packages/server/src/workflows/steps/saveRecord.js new file mode 100644 index 000000000..2d88a74cd --- /dev/null +++ b/packages/server/src/workflows/steps/saveRecord.js @@ -0,0 +1,85 @@ +const recordController = require("../../api/controllers/record") + +module.exports.definition = { + name: "Save Record", + tagline: "Save a {{inputs.enriched.model.name}} record", + icon: "ri-save-3-fill", + description: "Save a record to your database", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + record: { + type: "object", + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + customType: "record", + title: "The record to be written", + default: {}, + required: ["modelId"], + }, + }, + required: ["record"], + }, + outputs: { + properties: { + response: { + type: "object", + description: "The response from the table", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + id: { + type: "string", + description: "The identifier of the new record", + }, + revision: { + type: "string", + description: "The revision of the new record", + }, + }, + required: ["success", "id", "revision"], + }, + }, +} + +module.exports.run = async function({ inputs, instanceId }) { + // TODO: better logging of when actions are missed due to missing parameters + if (inputs.record == null || inputs.record.modelId == null) { + return + } + // have to clean up the record, remove the model from it + const ctx = { + params: { + modelId: inputs.record.modelId, + }, + request: { + body: inputs.record, + }, + user: { instanceId }, + } + + try { + await recordController.save(ctx) + return { + response: ctx.body, + id: ctx.body._id, + revision: ctx.body._rev, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/steps/sendEmail.js b/packages/server/src/workflows/steps/sendEmail.js new file mode 100644 index 000000000..c25e56e76 --- /dev/null +++ b/packages/server/src/workflows/steps/sendEmail.js @@ -0,0 +1,70 @@ +const sgMail = require("@sendgrid/mail") +sgMail.setApiKey(process.env.SENDGRID_API_KEY) + +module.exports.definition = { + description: "Send an email.", + tagline: "Send email to {{inputs.to}}", + icon: "ri-mail-open-fill", + name: "Send Email", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + to: { + type: "string", + title: "Send To", + }, + from: { + type: "string", + title: "Send From", + }, + subject: { + type: "string", + title: "Email Subject", + }, + contents: { + type: "string", + title: "Email Contents", + }, + }, + required: ["to", "from", "subject", "contents"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the email was sent", + }, + response: { + type: "object", + description: "A response from the email client, this may be an error", + }, + }, + required: ["success"], + }, + }, +} + +module.exports.run = async function({ inputs }) { + const msg = { + to: inputs.to, + from: inputs.from, + subject: inputs.subject, + text: inputs.text, + } + + try { + let response = await sgMail.send(msg) + return { + success: true, + response, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/workflows/thread.js b/packages/server/src/workflows/thread.js index 18eb6e243..177e7cbe5 100644 --- a/packages/server/src/workflows/thread.js +++ b/packages/server/src/workflows/thread.js @@ -2,18 +2,34 @@ const mustache = require("mustache") const actions = require("./actions") const logic = require("./logic") +function recurseMustache(inputs, context) { + for (let key in Object.keys(inputs)) { + let val = inputs[key] + if (typeof val === "string") { + inputs[key] = mustache.render(val, { context }) + } + // this covers objects and arrays + else if (typeof val === "object") { + inputs[key] = recurseMustache(inputs[key], context) + } + } + return inputs +} + /** * The workflow orchestrator is a class responsible for executing workflows. * It handles the context of the workflow and makes sure each step gets the correct * inputs and handles any outputs. */ class Orchestrator { - constructor(workflow) { - this._context = {} + constructor(workflow, triggerOutput) { + this._instanceId = triggerOutput.instanceId + // block zero is never used as the mustache is zero indexed for customer facing + this._context = { blocks: [{}, triggerOutput] } this._workflow = workflow } - async getStep(type, stepId) { + async getStepFunctionality(type, stepId) { let step = null if (type === "ACTION") { step = await actions.getAction(stepId) @@ -26,28 +42,17 @@ class Orchestrator { return step } - async execute(context) { + async execute() { let workflow = this._workflow for (let block of workflow.definition.steps) { - let step = await this.getStep(block.type, block.stepId) - let args = { ...block.args } - // bind the workflow action args to the workflow context, if required - for (let arg of Object.keys(args)) { - const argValue = args[arg] - // We don't want to render mustache templates on non-strings - if (typeof argValue !== "string") continue - - args[arg] = mustache.render(argValue, { context: this._context }) - } - const response = await step({ - args, - context, + let stepFn = await this.getStepFunctionality(block.type, block.stepId) + block.inputs = recurseMustache(block.inputs, this._context) + // instanceId is always passed + const outputs = await stepFn({ + inputs: block.inputs, + instanceId: this._instanceId, }) - - this._context = { - ...this._context, - [block.id]: response, - } + this._context.blocks.push(outputs) } } } @@ -55,8 +60,11 @@ class Orchestrator { // callback is required for worker-farm to state that the worker thread has completed module.exports = async (job, cb = null) => { try { - const workflowOrchestrator = new Orchestrator(job.data.workflow) - await workflowOrchestrator.execute(job.data.event) + const workflowOrchestrator = new Orchestrator( + job.data.workflow, + job.data.event + ) + await workflowOrchestrator.execute() if (cb) { cb() } diff --git a/packages/server/src/workflows/triggers.js b/packages/server/src/workflows/triggers.js index f8bbf5b5e..3149c52a6 100644 --- a/packages/server/src/workflows/triggers.js +++ b/packages/server/src/workflows/triggers.js @@ -4,6 +4,71 @@ const InMemoryQueue = require("./queue/inMemoryQueue") let workflowQueue = new InMemoryQueue() +const BUILTIN_DEFINITIONS = { + RECORD_SAVED: { + name: "Record Saved", + event: "record:save", + icon: "ri-save-line", + tagline: "Record is added to {{inputs.enriched.model.name}}", + description: "Fired when a record is saved to your database", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + required: ["modelId"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The new record that was saved", + }, + }, + required: ["record"], + }, + }, + type: "TRIGGER", + }, + RECORD_DELETED: { + name: "Record Deleted", + event: "record:delete", + icon: "ri-delete-bin-line", + tagline: "Record is deleted from {{inputs.enriched.model.name}}", + description: "Fired when a record is deleted from your database", + inputs: {}, + schema: { + inputs: { + properties: { + modelId: { + type: "string", + customType: "model", + title: "Table", + }, + }, + required: ["modelId"], + }, + outputs: { + properties: { + record: { + type: "object", + customType: "record", + description: "The record that was deleted", + }, + }, + required: ["record"], + }, + }, + type: "TRIGGER", + }, +} + async function queueRelevantWorkflows(event, eventType) { if (event.instanceId == null) { throw `No instanceId specified for ${eventType} - check event emitters.` @@ -36,3 +101,5 @@ module.exports.externalTrigger = async function(workflow, params) { } module.exports.workflowQueue = workflowQueue + +module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS