diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 7e26b2155e..5d3371ee7d 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -1,6 +1,7 @@ + +
+ {#each fieldsArray as field} + removeField(field.name)} /> + + + {/each} +
+ +
+ +
+ + diff --git a/packages/builder/src/components/userInterface/EventsEditor/actions/TriggerAutomation.svelte b/packages/builder/src/components/userInterface/EventsEditor/actions/TriggerAutomation.svelte new file mode 100644 index 0000000000..4c58175401 --- /dev/null +++ b/packages/builder/src/components/userInterface/EventsEditor/actions/TriggerAutomation.svelte @@ -0,0 +1,84 @@ + + +
+ + {#if !automations || automations.length === 0} +
+ You must have an automation that has an "App Action" trigger. +
+ {:else} + + + + {#if selectedAutomation} + + {/if} + {/if} +
+ + diff --git a/packages/builder/src/components/userInterface/EventsEditor/actions/index.js b/packages/builder/src/components/userInterface/EventsEditor/actions/index.js index 1c07356ee9..1bb0ab4d00 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/actions/index.js +++ b/packages/builder/src/components/userInterface/EventsEditor/actions/index.js @@ -1,6 +1,7 @@ import NavigateTo from "./NavigateTo.svelte" import SaveRow from "./SaveRow.svelte" import DeleteRow from "./DeleteRow.svelte" +import TriggerAutomation from "./TriggerAutomation.svelte" // defines what actions are available, when adding a new one // the component is the setup panel for the action @@ -20,4 +21,8 @@ export default [ name: "Navigate To", component: NavigateTo, }, + { + name: "Trigger Automation", + component: TriggerAutomation, + }, ] diff --git a/packages/client/src/api/automations.js b/packages/client/src/api/automations.js new file mode 100644 index 0000000000..c163ffee82 --- /dev/null +++ b/packages/client/src/api/automations.js @@ -0,0 +1,10 @@ +import API from "./api" +/** + * Executes an automation. Must have "App Action" trigger. + */ +export const triggerAutomation = async (automationId, fields) => { + return await API.post({ + url: `/api/automations/${automationId}/trigger`, + body: { fields }, + }) +} diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js index 878964107b..a8de677367 100644 --- a/packages/client/src/api/index.js +++ b/packages/client/src/api/index.js @@ -7,3 +7,4 @@ export * from "./views" export * from "./relationships" export * from "./routes" export * from "./app" +export * from "./automations" diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 2968525188..b2caf83d1c 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -1,6 +1,6 @@ import { enrichDataBinding } from "./enrichDataBinding" import { routeStore } from "../store" -import { saveRow, deleteRow } from "../api" +import { saveRow, deleteRow, triggerAutomation } from "../api" const saveRowHandler = async (action, context) => { let draft = context[`${action.parameters.contextPath}_draft`] @@ -21,6 +21,17 @@ const deleteRowHandler = async (action, context) => { }) } +const triggerAutomationHandler = async (action, context) => { + const params = {} + for (let field in action.parameters.fields) { + params[field] = enrichDataBinding( + action.parameters.fields[field].value, + context + ) + } + await triggerAutomation(action.parameters.automationId, params) +} + const navigationHandler = action => { routeStore.actions.navigate(action.parameters.url) } @@ -29,6 +40,7 @@ const handlerMap = { ["Save Row"]: saveRowHandler, ["Delete Row"]: deleteRowHandler, ["Navigate To"]: navigationHandler, + ["Trigger Automation"]: triggerAutomationHandler, } /** diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 6fbd8e4c25..6634016e3f 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -2,6 +2,7 @@ const CouchDB = require("../db") const emitter = require("../events/index") const InMemoryQueue = require("../utilities/queue/inMemoryQueue") const { getAutomationParams } = require("../db/utils") +const { coerceValue } = require("../utilities") let automationQueue = new InMemoryQueue("automationQueue") @@ -119,6 +120,37 @@ const BUILTIN_DEFINITIONS = { }, type: "TRIGGER", }, + APP: { + name: "App Action", + event: "app:trigger", + icon: "ri-window-fill", + tagline: "Automation fired from the frontend", + description: "Trigger an automation from an action inside your app", + stepId: "APP", + inputs: {}, + schema: { + inputs: { + properties: { + fields: { + type: "object", + customType: "triggerSchema", + title: "Fields", + }, + }, + required: [], + }, + outputs: { + properties: { + fields: { + type: "object", + description: "Fields submitted from the app frontend", + }, + }, + required: ["fields"], + }, + }, + type: "TRIGGER", + }, } async function queueRelevantRowAutomations(event, eventType) { @@ -200,12 +232,19 @@ async function fillRowOutput(automation, params) { module.exports.externalTrigger = async function(automation, params) { // TODO: replace this with allowing user in builder to input values in future - if ( - automation.definition != null && - automation.definition.trigger != null && - automation.definition.trigger.inputs.tableId != null - ) { - params = await fillRowOutput(automation, params) + if (automation.definition != null && automation.definition.trigger != null) { + if (automation.definition.trigger.inputs.tableId != null) { + params = await fillRowOutput(automation, params) + } + if (automation.definition.trigger.stepId === "APP") { + // values are likely to be submitted as strings, so we shall convert to correct type + const coercedFields = {} + const fields = automation.definition.trigger.inputs.fields + for (let key in fields) { + coercedFields[key] = coerceValue(params.fields[key], fields[key]) + } + params.fields = coercedFields + } } automationQueue.add({ automation, event: params }) diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 8a43e9d27a..190a66838e 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -144,6 +144,23 @@ exports.walkDir = (dirPath, callback) => { } } +/** + * This will coerce a value to the correct types based on the type transform map + * @param {object} row The value to coerce + * @param {object} type The type fo coerce to + * @returns {object} The coerced value + */ +exports.coerceValue = (value, type) => { + // eslint-disable-next-line no-prototype-builtins + if (TYPE_TRANSFORM_MAP[type].hasOwnProperty(value)) { + return TYPE_TRANSFORM_MAP[type][value] + } else if (TYPE_TRANSFORM_MAP[type].parse) { + return TYPE_TRANSFORM_MAP[type].parse(value) + } + + return value +} + /** * This will coerce the values in a row to the correct types based on the type transform map and the * table schema. @@ -159,12 +176,7 @@ exports.coerceRowValues = (row, table) => { const field = table.schema[key] if (!field) continue - // eslint-disable-next-line no-prototype-builtins - if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { - clonedRow[key] = TYPE_TRANSFORM_MAP[field.type][value] - } else if (TYPE_TRANSFORM_MAP[field.type].parse) { - clonedRow[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) - } + clonedRow[key] = exports.coerceValue(value, field.type) } return clonedRow }