1
0
Fork 0
mirror of synced 2024-06-29 11:31:06 +12:00
budibase/packages/server/src/automations/triggers.js

359 lines
9.4 KiB
JavaScript
Raw Normal View History

const CouchDB = require("../db")
const emitter = require("../events/index")
const env = require("../environment")
2021-05-06 04:49:53 +12:00
const Queue = env.isTest()
? require("../utilities/queue/inMemoryQueue")
: require("bull")
const { getAutomationParams } = require("../db/utils")
const { coerce } = require("../utilities/rowProcessor")
const { utils } = require("@budibase/auth/redis")
2021-05-19 03:37:54 +12:00
const { JobQueues } = require("../constants")
2021-06-19 09:18:18 +12:00
const {
isExternalTable,
breakExternalTableId,
} = require("../integrations/utils")
const { getExternalTable } = require("../api/controllers/table/utils")
const { opts } = utils.getRedisOptions()
2021-05-19 03:37:54 +12:00
let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts })
2021-03-27 03:56:34 +13:00
const FAKE_STRING = "TEST"
const FAKE_BOOL = false
const FAKE_NUMBER = 1
const FAKE_DATETIME = "1970-01-01T00:00:00.000Z"
const BUILTIN_DEFINITIONS = {
ROW_SAVED: {
name: "Row Created",
event: "row:save",
icon: "ri-save-line",
tagline: "Row is added to {{inputs.enriched.table.name}}",
description: "Fired when a row is added to your database",
stepId: "ROW_SAVED",
inputs: {},
schema: {
inputs: {
properties: {
tableId: {
type: "string",
customType: "table",
title: "Table",
},
},
required: ["tableId"],
},
outputs: {
properties: {
row: {
type: "object",
customType: "row",
description: "The new row that was created",
},
id: {
type: "string",
description: "Row ID - can be used for updating",
},
revision: {
type: "string",
description: "Revision of row",
},
},
required: ["row", "id"],
},
},
type: "TRIGGER",
},
ROW_UPDATED: {
name: "Row Updated",
event: "row:update",
2021-02-24 03:16:44 +13:00
icon: "ri-refresh-line",
tagline: "Row is updated in {{inputs.enriched.table.name}}",
description: "Fired when a row is updated in your database",
stepId: "ROW_UPDATED",
inputs: {},
schema: {
inputs: {
properties: {
tableId: {
type: "string",
customType: "table",
title: "Table",
},
},
required: ["tableId"],
},
outputs: {
properties: {
row: {
type: "object",
customType: "row",
description: "The row that was updated",
},
id: {
type: "string",
description: "Row ID - can be used for updating",
},
revision: {
type: "string",
description: "Revision of row",
},
},
required: ["row", "id"],
},
},
type: "TRIGGER",
},
ROW_DELETED: {
name: "Row Deleted",
event: "row:delete",
icon: "ri-delete-bin-line",
tagline: "Row is deleted from {{inputs.enriched.table.name}}",
description: "Fired when a row is deleted from your database",
stepId: "ROW_DELETED",
inputs: {},
schema: {
inputs: {
properties: {
tableId: {
type: "string",
customType: "table",
title: "Table",
},
},
required: ["tableId"],
},
outputs: {
properties: {
row: {
type: "object",
customType: "row",
description: "The row that was deleted",
},
},
required: ["row"],
},
},
type: "TRIGGER",
},
WEBHOOK: {
name: "Webhook",
event: "web:trigger",
icon: "ri-global-line",
tagline: "Webhook endpoint is hit",
description: "Trigger an automation when a HTTP POST webhook is hit",
stepId: "WEBHOOK",
inputs: {},
schema: {
inputs: {
properties: {
schemaUrl: {
type: "string",
customType: "webhookUrl",
title: "Schema URL",
},
triggerUrl: {
type: "string",
customType: "webhookUrl",
title: "Trigger URL",
},
},
required: ["schemaUrl", "triggerUrl"],
},
outputs: {
properties: {
body: {
type: "object",
description: "Body of the request which hit the webhook",
},
},
required: ["body"],
},
},
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",
},
2021-05-19 03:37:54 +12:00
CRON: {
name: "Cron Trigger",
event: "cron:trigger",
icon: "ri-timer-line",
2021-05-19 09:20:41 +12:00
tagline: "Cron Trigger (<b>{{inputs.cron}}</b>)",
2021-05-19 03:37:54 +12:00
description: "Triggers automation on a cron schedule.",
stepId: "CRON",
inputs: {},
schema: {
inputs: {
properties: {
cron: {
type: "string",
customType: "cron",
2021-05-19 09:20:41 +12:00
title: "Expression",
2021-05-19 03:37:54 +12:00
},
},
required: ["cron"],
},
2021-05-26 00:39:30 +12:00
outputs: {
properties: {
timestamp: {
type: "number",
description: "Timestamp the cron was executed",
},
},
required: ["timestamp"],
},
2021-05-19 03:37:54 +12:00
},
type: "TRIGGER",
},
}
async function queueRelevantRowAutomations(event, eventType) {
if (event.appId == null) {
throw `No appId specified for ${eventType} - check event emitters.`
}
const db = new CouchDB(event.appId)
let automations = await db.allDocs(
getAutomationParams(null, { include_docs: true })
)
// filter down to the correct event type
automations = automations.rows
2021-05-04 22:32:22 +12:00
.map(automation => automation.doc)
.filter(automation => {
const trigger = automation.definition.trigger
return trigger && trigger.event === eventType
})
for (let automation of automations) {
let automationDef = automation.definition
let automationTrigger = automationDef ? automationDef.trigger : {}
if (
!automation.live ||
!automationTrigger.inputs ||
automationTrigger.inputs.tableId !== event.row.tableId
) {
continue
}
automationQueue.add({ automation, event })
}
}
2021-05-03 19:31:09 +12:00
emitter.on("row:save", async function (event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, "row:save")
})
2021-05-03 19:31:09 +12:00
emitter.on("row:update", async function (event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, "row:update")
})
2021-05-03 19:31:09 +12:00
emitter.on("row:delete", async function (event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
await queueRelevantRowAutomations(event, "row:delete")
})
async function fillRowOutput(automation, params) {
let triggerSchema = automation.definition.trigger
let tableId = triggerSchema.inputs.tableId
try {
let table
if (!isExternalTable(tableId)) {
const db = new CouchDB(params.appId)
table = await db.get(tableId)
} else {
const { datasourceId, tableName } = breakExternalTableId(tableId)
table = await getExternalTable(params.appId, datasourceId, tableName)
}
let row = {}
for (let schemaKey of Object.keys(table.schema)) {
const paramValue = params[schemaKey]
let propSchema = table.schema[schemaKey]
switch (propSchema.constraints.type) {
case "string":
row[schemaKey] = paramValue || FAKE_STRING
break
case "boolean":
row[schemaKey] = paramValue || FAKE_BOOL
break
case "number":
row[schemaKey] = paramValue || FAKE_NUMBER
break
case "datetime":
row[schemaKey] = paramValue || FAKE_DATETIME
break
}
}
params.row = row
} catch (err) {
/* istanbul ignore next */
throw "Failed to find table for trigger"
}
return params
}
2021-05-03 19:31:09 +12:00
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) {
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 of Object.keys(fields)) {
coercedFields[key] = coerce(params.fields[key], fields[key])
}
params.fields = coercedFields
}
}
automationQueue.add({ automation, event: params })
}
module.exports.getQueues = () => {
2021-05-08 00:55:57 +12:00
return [automationQueue]
}
module.exports.fillRowOutput = fillRowOutput
module.exports.automationQueue = automationQueue
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS