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 @@
+
+
+
+
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
}