diff --git a/packages/builder/package.json b/packages/builder/package.json index 1cb53bc99b..5cd18c3955 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -43,6 +43,7 @@ "@nx-js/compiler-util": "^2.0.0", "codemirror": "^5.51.0", "date-fns": "^1.29.0", + "deepmerge": "^4.2.2", "feather-icons": "^4.21.0", "flatpickr": "^4.5.7", "lodash": "^4.17.13", diff --git a/packages/builder/src/builderStore/store/workflow/Workflow.js b/packages/builder/src/builderStore/store/workflow/Workflow.js index 623eb07f48..3e5a0e8b4e 100644 --- a/packages/builder/src/builderStore/store/workflow/Workflow.js +++ b/packages/builder/src/builderStore/store/workflow/Workflow.js @@ -1,6 +1,6 @@ import mustache from "mustache" // TODO: tidy up import -import blockDefinitions from "../../../components/workflow/WorkflowPanel/blockDefinitions" +import blockDefinitions from "components/workflow/WorkflowPanel/blockDefinitions" import { generate } from "shortid" /** @@ -12,6 +12,10 @@ export default class Workflow { this.workflow = workflow } + isEmpty() { + return !this.workflow.definition.next + } + addBlock(block) { let node = this.workflow.definition while (node.next) node = node.next @@ -74,6 +78,7 @@ export default class Workflow { const tagline = definition.tagline || "" const args = block.args || {} + // all the fields the workflow block needs to render in the UI tree.push({ id: block.id, type: block.type, @@ -81,6 +86,7 @@ export default class Workflow { args, heading: block.actionId, body: mustache.render(tagline, args), + name: definition.name }) return this.buildUiTree(block.next, tree) diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte new file mode 100644 index 0000000000..fa7a57a57e --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentSelector.svelte @@ -0,0 +1,45 @@ + + +
+ +
+ +
+ {#if components.length > 0} + +
+ +
+ {/if} +
diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelParam.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelParam.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte new file mode 100644 index 0000000000..3fd6344f99 --- /dev/null +++ b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ModelSelector.svelte @@ -0,0 +1,17 @@ + + + +
+ +
+ +
+
diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/NumberParam.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/NumberParam.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentParam.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/RecordSelector.svelte similarity index 100% rename from packages/builder/src/components/workflow/SetupPanel/ParamInputs/ComponentParam.svelte rename to packages/builder/src/components/workflow/SetupPanel/ParamInputs/RecordSelector.svelte diff --git a/packages/builder/src/components/workflow/SetupPanel/ParamInputs/StringParam.svelte b/packages/builder/src/components/workflow/SetupPanel/ParamInputs/StringParam.svelte deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte b/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte index d91eb3ffaf..b0e94e4175 100644 --- a/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte +++ b/packages/builder/src/components/workflow/SetupPanel/SetupPanel.svelte @@ -79,10 +79,11 @@ display: flex; align-items: center; justify-content: space-between; + margin-bottom: 20px; } - span:not(.selected) { - color: var(--dark-grey); + header > span { + color: var(--font); } label { diff --git a/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte b/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte index 2b35d0ab44..22a7c56f4a 100644 --- a/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte +++ b/packages/builder/src/components/workflow/SetupPanel/WorkflowBlockSetup.svelte @@ -1,21 +1,19 @@ {#each workflowParams as [parameter, type]}
@@ -29,6 +27,8 @@ {/each} + {:else if type === 'component'} + {:else if type === 'accessLevel'} - {#each $backendUiStore.models as model} - - {/each} - - {:else if type === 'component'} - + {:else if type === 'string'} diff --git a/packages/builder/src/components/workflow/WorkflowPanel/BlockList/BlockList.svelte b/packages/builder/src/components/workflow/WorkflowPanel/BlockList/BlockList.svelte index e288e78e94..a690492672 100644 --- a/packages/builder/src/components/workflow/WorkflowPanel/BlockList/BlockList.svelte +++ b/packages/builder/src/components/workflow/WorkflowPanel/BlockList/BlockList.svelte @@ -1,42 +1,45 @@
- {#each SUB_TABS as tab} + {#if $workflowStore.currentWorkflow.isEmpty()} (selectedTab = tab.key)}> - {tab.name} + class:selected={'TRIGGER' === selectedTab} + on:click={() => (selectedTab = 'TRIGGER')}> + Triggers - {/each} + {/if} + (selectedTab = 'ACTION')}> + Actions + + (selectedTab = 'LOGIC')}> + Logic +
{#each definitions as [actionId, blockDefinition]} diff --git a/packages/builder/src/components/workflow/WorkflowPanel/BlockList/WorkflowBlock.svelte b/packages/builder/src/components/workflow/WorkflowPanel/BlockList/WorkflowBlock.svelte index 31014b86f7..0907efe7ae 100644 --- a/packages/builder/src/components/workflow/WorkflowPanel/BlockList/WorkflowBlock.svelte +++ b/packages/builder/src/components/workflow/WorkflowPanel/BlockList/WorkflowBlock.svelte @@ -6,7 +6,6 @@ export let actionId function addBlockToWorkflow() { - // TODO: store the block type in the DB as well workflowStore.actions.addBlockToWorkflow({ ...blockDefinition, args: {}, diff --git a/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte b/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte index c831ed0304..b057177cae 100644 --- a/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte +++ b/packages/builder/src/components/workflow/WorkflowPanel/WorkflowPanel.svelte @@ -11,7 +11,7 @@
(selectedTab = 'WORKFLOWS')}> Workflows @@ -37,10 +37,13 @@ font-weight: bold; display: flex; align-items: center; - justify-content: space-between; margin-bottom: 20px; } + .workflow-header { + margin-right: 20px; + } + span:not(.selected) { color: var(--dark-grey); } diff --git a/packages/builder/src/components/workflow/WorkflowPanel/blockDefinitions.js b/packages/builder/src/components/workflow/WorkflowPanel/blockDefinitions.js index 3f61e4f5d3..17203646a8 100644 --- a/packages/builder/src/components/workflow/WorkflowPanel/blockDefinitions.js +++ b/packages/builder/src/components/workflow/WorkflowPanel/blockDefinitions.js @@ -7,7 +7,7 @@ const ACTION = { environment: "CLIENT", params: { path: "string", - value: "string", + value: "longText", }, }, NAVIGATE: { @@ -77,8 +77,9 @@ const ACTION = { const TRIGGER = { RECORD_SAVED: { name: "Record Saved", + event: "record:save", icon: "ri-save-line", - tagline: "Record is added to {{model}}", + tagline: "Record is added to {{model.name}}", description: "Save a record to your database.", environment: "SERVER", params: { @@ -87,44 +88,45 @@ const TRIGGER = { }, RECORD_DELETED: { name: "Record Deleted", + event: "record:delete", icon: "ri-delete-bin-line", - tagline: "Record is deleted from {{model}}", + tagline: "Record is deleted from {{model.name}}", description: "Fired when a record is deleted from your database.", environment: "SERVER", params: { model: "model" }, }, - CLICK: { - name: "Click", - icon: "ri-cursor-line", - tagline: "{{component}} is clicked", - description: "Trigger when you click on an element in the UI.", - environment: "CLIENT", - params: { - component: "component" - } - }, - LOAD: { - name: "Load", - icon: "ri-loader-line", - tagline: "{{component}} is loaded", - description: "Trigger an element has finished loading.", - environment: "CLIENT", - params: { - component: "component" - } - }, - INPUT: { - name: "Input", - icon: "ri-text", - tagline: "Text entered into {{component}", - description: "Trigger when you type into an input box.", - environment: "CLIENT", - params: { - component: "component" - } - }, + // CLICK: { + // name: "Click", + // icon: "ri-cursor-line", + // tagline: "{{component}} is clicked", + // description: "Trigger when you click on an element in the UI.", + // environment: "CLIENT", + // params: { + // component: "component" + // } + // }, + // LOAD: { + // name: "Load", + // icon: "ri-loader-line", + // tagline: "{{component}} is loaded", + // description: "Trigger an element has finished loading.", + // environment: "CLIENT", + // params: { + // component: "component" + // } + // }, + // INPUT: { + // name: "Input", + // icon: "ri-text", + // tagline: "Text entered into {{component}", + // description: "Trigger when you type into an input box.", + // environment: "CLIENT", + // params: { + // component: "component" + // } + // }, } const LOGIC = { @@ -135,9 +137,9 @@ const LOGIC = { description: "Filter any workflows which do not meet certain conditions.", environment: "CLIENT", params: { - field: "string", + filter: "string", condition: [ - "equals" + "equals", ], value: "string" }, diff --git a/packages/client/src/api/workflow/index.js b/packages/client/src/api/workflow/index.js index 2e3c025adb..4226027df1 100644 --- a/packages/client/src/api/workflow/index.js +++ b/packages/client/src/api/workflow/index.js @@ -8,6 +8,4 @@ export const triggerWorkflow = api => ({ workflow }) => { workflowOrchestrator.strategy = clientStrategy workflowOrchestrator.execute(workflow) - - // hit the API and get the workflow data back } diff --git a/packages/client/src/api/workflow/orchestrator.js b/packages/client/src/api/workflow/orchestrator.js index 7a26c1648c..bd8f111861 100644 --- a/packages/client/src/api/workflow/orchestrator.js +++ b/packages/client/src/api/workflow/orchestrator.js @@ -23,7 +23,10 @@ export default class Orchestrator { async execute(workflowId) { const EXECUTE_WORKFLOW_URL = `/api/${this.instanceId}/workflows/${workflowId}` const workflow = await this.api.get({ url: EXECUTE_WORKFLOW_URL }) - this._strategy.run(workflow.definition) + + if (workflow.live) { + this._strategy.run(workflow.definition) + } } } @@ -80,17 +83,9 @@ export const clientStrategy = ({ api, instanceId }) => ({ if (block.actionId === "FILTER") { const { field, condition, value } = block.args; switch (condition) { - case "=": + case "equals": if (field !== value) return; break; - case "!=": - if (field === value) return; - break; - case "gt": - if (field < value) return; - break; - case "lt": - if (field > value) return; default: return; } diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js index 573860438c..f5e8239168 100644 --- a/packages/server/src/api/controllers/instance.js +++ b/packages/server/src/api/controllers/instance.js @@ -29,6 +29,16 @@ exports.create = async function(ctx) { emit([doc.type], doc._id) }.toString(), }, + by_workflow_trigger: { + map: function(doc) { + if (doc.type === "workflow") { + const trigger = doc.definition.next + if (trigger) { + emit([trigger.event], trigger) + } + } + }.toString() + } }, }) diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js index c3e1fa940d..f1c04d4a7a 100644 --- a/packages/server/src/api/controllers/record.js +++ b/packages/server/src/api/controllers/record.js @@ -5,7 +5,6 @@ const newid = require("../../db/newid") const ajv = new Ajv() exports.save = async function(ctx) { - console.log("THIS INSTANCE", ctx.params.instanceId) const db = new CouchDB(ctx.params.instanceId) const record = ctx.request.body record.modelId = ctx.params.modelId @@ -45,7 +44,11 @@ exports.save = async function(ctx) { record.type = "record" const response = await db.post(record) record._rev = response.rev - // ctx.eventPublisher.emit("RECORD_CREATED", record) + + ctx.eventEmitter.emit(`record:save`, { + record, + instanceId: ctx.params.instanceId + }) ctx.body = record ctx.status = 200 ctx.message = `${model.name} created successfully` @@ -85,4 +88,5 @@ exports.destroy = async function(ctx) { return } ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) + ctx.eventEmitter.emit(`record:delete`, record) } diff --git a/packages/server/src/api/controllers/workflow/index.js b/packages/server/src/api/controllers/workflow/index.js index 1a4cfa8591..34438b0892 100644 --- a/packages/server/src/api/controllers/workflow/index.js +++ b/packages/server/src/api/controllers/workflow/index.js @@ -10,24 +10,6 @@ exports.create = async function(ctx) { workflow._id = newid() - // TODO: Possibly validate the workflow against a schema - - // // validation with ajv - // const model = await db.get(record.modelId) - // const validate = ajv.compile({ - // properties: model.schema, - // }) - // const valid = validate(record) - - // if (!valid) { - // ctx.status = 400 - // ctx.body = { - // status: 400, - // errors: validate.errors, - // } - // return - // } - workflow.type = "workflow" const response = await db.post(workflow) workflow._rev = response.rev diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 57fb037626..2fb959933e 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -4,7 +4,7 @@ const logger = require("koa-pino-logger") const http = require("http") const api = require("./api") const env = require("./environment") -const eventPublisher = require("./events") +const eventEmitter = require("./events") const app = new Koa() @@ -20,7 +20,7 @@ app.use( }) ) -app.context.publisher = eventPublisher +app.context.eventEmitter = eventEmitter // api routes app.use(api.routes()) diff --git a/packages/server/src/events/index.js b/packages/server/src/events/index.js index 7d54c4cd95..3e6d30869c 100644 --- a/packages/server/src/events/index.js +++ b/packages/server/src/events/index.js @@ -1,3 +1,31 @@ const EventEmitter = require("events").EventEmitter +const CouchDB = require("../db"); -module.exports = new EventEmitter() +const emitter = new EventEmitter() + +function determineWorkflowsToTrigger(instanceId, event) { + const db = new CouchDB(instanceId); + const workflowsToTrigger = await db.query("database/by_workflow_trigger", { + key: [event] + }) + + return workflowsToTrigger.rows; +} + +emitter.on("record:save", async function(event) { + const workflowsToTrigger = await determineWorkflowsToTrigger(instanceId, "record:save") + + for (let workflow of workflowsToTrigger) { + // SERVER SIDE STUFF!! + } +}) + +emitter.on("record:delete", function(event) { + const workflowsToTrigger = await determineWorkflowsToTrigger(instanceId, "record:delete") + + for (let workflow of workflowsToTrigger) { + // SERVER SIDE STUFF!! + } +}) + +module.exports = emitter diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js index 63a6514043..93fa005a80 100644 --- a/packages/server/src/schemas/index.js +++ b/packages/server/src/schemas/index.js @@ -17,18 +17,19 @@ const WORKFLOW_SCHEMA = { type: "object", properties: { triggers: { type: "array" }, - next: { - type: "object", - properties: { - environment: { environment: "string" }, - type: { type: "string" }, - actionId: { type: "string" }, - args: { type: "object" }, - conditions: { type: "array" }, - errorHandling: { type: "object" }, - next: { type: "object" }, - }, - }, + steps: { type: "array" } + // next: { + // type: "object", + // properties: { + // environment: { environment: "string" }, + // type: { type: "string" }, + // actionId: { type: "string" }, + // args: { type: "object" }, + // conditions: { type: "array" }, + // errorHandling: { type: "object" }, + // next: { type: "object" }, + // }, + // }, }, }, },