Automation overhaul to Typescript, plus type updates.

This commit is contained in:
mike12345567 2022-11-25 19:57:07 +00:00
parent eeebd0fe70
commit a3bb2e0d77
40 changed files with 442 additions and 302 deletions

View File

@ -25,7 +25,7 @@ const DefaultBucketName = {
PLUGINS: "plugins", PLUGINS: "plugins",
} }
const env = { const environment = {
isTest, isTest,
isDev, isDev,
JS_BCRYPT: process.env.JS_BCRYPT, JS_BCRYPT: process.env.JS_BCRYPT,
@ -80,7 +80,7 @@ const env = {
} }
// clean up any environment variable edge cases // clean up any environment variable edge cases
for (let [key, value] of Object.entries(env)) { for (let [key, value] of Object.entries(environment)) {
// handle the edge case of "0" to disable an environment variable // handle the edge case of "0" to disable an environment variable
if (value === "0") { if (value === "0") {
// @ts-ignore // @ts-ignore
@ -88,4 +88,4 @@ for (let [key, value] of Object.entries(env)) {
} }
} }
export = env export = environment

View File

@ -39,7 +39,7 @@ export function createQueue<T>(
return queue return queue
} }
exports.shutdown = async () => { export async function shutdown() {
if (QUEUES.length) { if (QUEUES.length) {
clearInterval(cleanupInterval) clearInterval(cleanupInterval)
for (let queue of QUEUES) { for (let queue of QUEUES) {

View File

@ -1,5 +1,5 @@
import actions from "../../automations/actions" import * as actions from "../../automations/actions"
import triggers from "../../automations/triggers" import * as triggers from "../../automations/triggers"
import { import {
getAutomationParams, getAutomationParams,
generateAutomationID, generateAutomationID,

View File

@ -1,22 +1,26 @@
const sendSmtpEmail = require("./steps/sendSmtpEmail") import * as sendSmtpEmail from "./steps/sendSmtpEmail"
const createRow = require("./steps/createRow") import * as createRow from "./steps/createRow"
const updateRow = require("./steps/updateRow") import * as updateRow from "./steps/updateRow"
const deleteRow = require("./steps/deleteRow") import * as deleteRow from "./steps/deleteRow"
const executeScript = require("./steps/executeScript") import * as executeScript from "./steps/executeScript"
const executeQuery = require("./steps/executeQuery") import * as executeQuery from "./steps/executeQuery"
const outgoingWebhook = require("./steps/outgoingWebhook") import * as outgoingWebhook from "./steps/outgoingWebhook"
const serverLog = require("./steps/serverLog") import * as serverLog from "./steps/serverLog"
const discord = require("./steps/discord") import * as discord from "./steps/discord"
const slack = require("./steps/slack") import * as slack from "./steps/slack"
const zapier = require("./steps/zapier") import * as zapier from "./steps/zapier"
const integromat = require("./steps/integromat") import * as integromat from "./steps/integromat"
let filter = require("./steps/filter") import * as filter from "./steps/filter"
let delay = require("./steps/delay") import * as delay from "./steps/delay"
let queryRow = require("./steps/queryRows") import * as queryRow from "./steps/queryRows"
let loop = require("./steps/loop") import * as loop from "./steps/loop"
const env = require("../environment") import env from "../environment"
import { AutomationStep, AutomationStepInput } from "@budibase/types"
const ACTION_IMPLS = { const ACTION_IMPLS: Record<
string,
(opts: AutomationStepInput) => Promise<any>
> = {
SEND_EMAIL_SMTP: sendSmtpEmail.run, SEND_EMAIL_SMTP: sendSmtpEmail.run,
CREATE_ROW: createRow.run, CREATE_ROW: createRow.run,
UPDATE_ROW: updateRow.run, UPDATE_ROW: updateRow.run,
@ -28,14 +32,13 @@ const ACTION_IMPLS = {
DELAY: delay.run, DELAY: delay.run,
FILTER: filter.run, FILTER: filter.run,
QUERY_ROWS: queryRow.run, QUERY_ROWS: queryRow.run,
LOOP: loop.run,
// these used to be lowercase step IDs, maintain for backwards compat // these used to be lowercase step IDs, maintain for backwards compat
discord: discord.run, discord: discord.run,
slack: slack.run, slack: slack.run,
zapier: zapier.run, zapier: zapier.run,
integromat: integromat.run, integromat: integromat.run,
} }
const ACTION_DEFINITIONS = { export const ACTION_DEFINITIONS: Record<string, AutomationStep> = {
SEND_EMAIL_SMTP: sendSmtpEmail.definition, SEND_EMAIL_SMTP: sendSmtpEmail.definition,
CREATE_ROW: createRow.definition, CREATE_ROW: createRow.definition,
UPDATE_ROW: updateRow.definition, UPDATE_ROW: updateRow.definition,
@ -60,15 +63,15 @@ const ACTION_DEFINITIONS = {
// ran at all // ran at all
if (env.SELF_HOSTED) { if (env.SELF_HOSTED) {
const bash = require("./steps/bash") const bash = require("./steps/bash")
// @ts-ignore
ACTION_IMPLS["EXECUTE_BASH"] = bash.run ACTION_IMPLS["EXECUTE_BASH"] = bash.run
// @ts-ignore
ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition ACTION_DEFINITIONS["EXECUTE_BASH"] = bash.definition
} }
/* istanbul ignore next */ /* istanbul ignore next */
exports.getAction = async function (actionName) { export async function getAction(actionName: string) {
if (ACTION_IMPLS[actionName] != null) { if (ACTION_IMPLS[actionName] != null) {
return ACTION_IMPLS[actionName] return ACTION_IMPLS[actionName]
} }
} }
exports.ACTION_DEFINITIONS = ACTION_DEFINITIONS

View File

@ -1,9 +1,10 @@
const { import {
decodeJSBinding, decodeJSBinding,
isJSBinding, isJSBinding,
encodeJSBinding, encodeJSBinding,
} = require("@budibase/string-templates") } from "@budibase/string-templates"
const sdk = require("../sdk") import sdk from "../sdk"
import { Row } from "@budibase/types"
/** /**
* When values are input to the system generally they will be of type string as this is required for template strings. * When values are input to the system generally they will be of type string as this is required for template strings.
@ -21,7 +22,7 @@ const sdk = require("../sdk")
* @returns {object} The inputs object which has had all the various types supported by this function converted to their * @returns {object} The inputs object which has had all the various types supported by this function converted to their
* primitive types. * primitive types.
*/ */
exports.cleanInputValues = (inputs, schema) => { export function cleanInputValues(inputs: Record<string, any>, schema: any) {
if (schema == null) { if (schema == null) {
return inputs return inputs
} }
@ -62,12 +63,12 @@ exports.cleanInputValues = (inputs, schema) => {
* @param {object} row The input row structure which requires clean-up after having been through template statements. * @param {object} row The input row structure which requires clean-up after having been through template statements.
* @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types. * @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types.
*/ */
exports.cleanUpRow = async (tableId, row) => { export async function cleanUpRow(tableId: string, row: Row) {
let table = await sdk.tables.getTable(tableId) let table = await sdk.tables.getTable(tableId)
return exports.cleanInputValues(row, { properties: table.schema }) return cleanInputValues(row, { properties: table.schema })
} }
exports.getError = err => { export function getError(err: any) {
if (err == null) { if (err == null) {
return "No error provided." return "No error provided."
} }
@ -80,13 +81,13 @@ exports.getError = err => {
return typeof err !== "string" ? err.toString() : err return typeof err !== "string" ? err.toString() : err
} }
exports.substituteLoopStep = (hbsString, substitute) => { export function substituteLoopStep(hbsString: string, substitute: string) {
let checkForJS = isJSBinding(hbsString) let checkForJS = isJSBinding(hbsString)
let substitutedHbsString = "" let substitutedHbsString = ""
let open = checkForJS ? `$("` : "{{" let open = checkForJS ? `$("` : "{{"
let closed = checkForJS ? `")` : "}}" let closed = checkForJS ? `")` : "}}"
if (checkForJS) { if (checkForJS) {
hbsString = decodeJSBinding(hbsString) hbsString = decodeJSBinding(hbsString) as string
} }
let pointer = 0, let pointer = 0,
openPointer = 0, openPointer = 0,
@ -111,9 +112,9 @@ exports.substituteLoopStep = (hbsString, substitute) => {
return substitutedHbsString return substitutedHbsString
} }
exports.stringSplit = value => { export function stringSplit(value: string | string[]) {
if (value == null) { if (value == null || Array.isArray(value)) {
return [] return value || []
} }
if (value.split("\n").length > 1) { if (value.split("\n").length > 1) {
value = value.split("\n") value = value.split("\n")

View File

@ -1,39 +0,0 @@
const { createBullBoard } = require("@bull-board/api")
const { BullAdapter } = require("@bull-board/api/bullAdapter")
const { KoaAdapter } = require("@bull-board/koa")
const { queue } = require("@budibase/backend-core")
const automation = require("../threads/automation")
const { backups } = require("@budibase/pro")
let automationQueue = queue.createQueue(
queue.JobQueue.AUTOMATION,
automation.removeStalled
)
const PATH_PREFIX = "/bulladmin"
exports.init = async () => {
// Set up queues for bull board admin
const backupQueue = await backups.getBackupQueue()
const queues = [automationQueue]
if (backupQueue) {
queues.push(backupQueue)
}
const adapters = []
const serverAdapter = new KoaAdapter()
for (let queue of queues) {
adapters.push(new BullAdapter(queue))
}
createBullBoard({
queues: adapters,
serverAdapter,
})
serverAdapter.setBasePath(PATH_PREFIX)
return serverAdapter.registerPlugin()
}
exports.shutdown = async () => {
await queue.shutdown()
}
exports.automationQueue = automationQueue

View File

@ -0,0 +1,38 @@
import { BullAdapter } from "@bull-board/api/bullAdapter"
import { KoaAdapter } from "@bull-board/koa"
import { queue } from "@budibase/backend-core"
import * as automation from "../threads/automation"
import { backups } from "@budibase/pro"
import { createBullBoard } from "@bull-board/api"
import BullQueue from "bull"
export const automationQueue: BullQueue.Queue = queue.createQueue(
queue.JobQueue.AUTOMATION,
{ removeStalledCb: automation.removeStalled }
)
const PATH_PREFIX = "/bulladmin"
export async function init() {
// Set up queues for bull board admin
const backupQueue = await backups.getBackupQueue()
const queues = [automationQueue]
if (backupQueue) {
queues.push(backupQueue)
}
const adapters = []
const serverAdapter: any = new KoaAdapter()
for (let queue of queues) {
adapters.push(new BullAdapter(queue))
}
createBullBoard({
queues: adapters,
serverAdapter,
})
serverAdapter.setBasePath(PATH_PREFIX)
return serverAdapter.registerPlugin()
}
export async function shutdown() {
await queue.shutdown()
}

View File

@ -1,29 +0,0 @@
const { processEvent } = require("./utils")
const { automationQueue, shutdown } = require("./bullboard")
const { TRIGGER_DEFINITIONS, rebootTrigger } = require("./triggers")
const { ACTION_DEFINITIONS } = require("./actions")
/**
* This module is built purely to kick off the worker farm and manage the inputs/outputs
*/
exports.init = async function () {
// this promise will not complete
const promise = automationQueue.process(async job => {
await processEvent(job)
})
// on init we need to trigger any reboot automations
await rebootTrigger()
return promise
}
exports.getQueues = () => {
return [automationQueue]
}
exports.shutdown = () => {
return shutdown()
}
exports.automationQueue = automationQueue
exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS
exports.ACTION_DEFINITIONS = ACTION_DEFINITIONS

View File

@ -0,0 +1,26 @@
import { processEvent } from "./utils"
import { automationQueue } from "./bullboard"
import { rebootTrigger } from "./triggers"
import BullQueue from "bull"
export { automationQueue } from "./bullboard"
export { shutdown } from "./bullboard"
export { TRIGGER_DEFINITIONS } from "./triggers"
export { ACTION_DEFINITIONS } from "./actions"
/**
* This module is built purely to kick off the worker farm and manage the inputs/outputs
*/
export async function init() {
// this promise will not complete
const promise = automationQueue.process(async job => {
await processEvent(job)
})
// on init we need to trigger any reboot automations
await rebootTrigger()
return promise
}
export function getQueues(): BullQueue.Queue[] {
return [automationQueue]
}

View File

@ -1,16 +1,21 @@
const { execSync } = require("child_process") import { execSync } from "child_process"
const { processStringSync } = require("@budibase/string-templates") import { processStringSync } from "@budibase/string-templates"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
const environment = require("../../environment") import environment from "../../environment"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Bash Scripting", name: "Bash Scripting",
tagline: "Execute a bash command", tagline: "Execute a bash command",
icon: "JourneyEvent", icon: "JourneyEvent",
description: "Run a bash script", description: "Run a bash script",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "EXECUTE_BASH", stepId: AutomationActionStepId.EXECUTE_BASH,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -39,7 +44,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, context }) { export async function run({ inputs, context }: AutomationStepInput) {
if (inputs.code == null) { if (inputs.code == null) {
return { return {
stdout: "Budibase bash automation failed: Invalid inputs", stdout: "Budibase bash automation failed: Invalid inputs",
@ -55,7 +60,7 @@ exports.run = async function ({ inputs, context }) {
stdout = execSync(command, { stdout = execSync(command, {
timeout: environment.QUERY_THREAD_TIMEOUT || 500, timeout: environment.QUERY_THREAD_TIMEOUT || 500,
}).toString() }).toString()
} catch (err) { } catch (err: any) {
stdout = err.message stdout = err.message
success = false success = false
} }

View File

@ -1,15 +1,20 @@
import { save } from "../../api/controllers/row" import { save } from "../../api/controllers/row"
import { cleanUpRow, getError } from "../automationUtils" import { cleanUpRow, getError } from "../automationUtils"
import { buildCtx } from "./utils" import { buildCtx } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
export const definition = { export const definition: AutomationStep = {
name: "Create Row", name: "Create Row",
tagline: "Create a {{inputs.enriched.table.name}} row", tagline: "Create a {{inputs.enriched.table.name}} row",
icon: "TableRowAddBottom", icon: "TableRowAddBottom",
description: "Add a row to your database", description: "Add a row to your database",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "CREATE_ROW", stepId: AutomationActionStepId.CREATE_ROW,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -58,7 +63,7 @@ export const definition = {
}, },
} }
export async function run({ inputs, appId, emitter }: any) { export async function run({ inputs, appId, emitter }: AutomationStepInput) {
if (inputs.row == null || inputs.row.tableId == null) { if (inputs.row == null || inputs.row.tableId == null) {
return { return {
success: false, success: false,

View File

@ -1,11 +1,16 @@
let { wait } = require("../../utilities") import { wait } from "../../utilities"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Delay", name: "Delay",
icon: "Clock", icon: "Clock",
tagline: "Delay for {{inputs.time}} milliseconds", tagline: "Delay for {{inputs.time}} milliseconds",
description: "Delay the automation until an amount of time has passed", description: "Delay the automation until an amount of time has passed",
stepId: "DELAY", stepId: AutomationActionStepId.DELAY,
internal: true, internal: true,
inputs: {}, inputs: {},
schema: { schema: {
@ -31,7 +36,7 @@ exports.definition = {
type: "LOGIC", type: "LOGIC",
} }
exports.run = async function delay({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
await wait(inputs.time) await wait(inputs.time)
return { return {
success: true, success: true,

View File

@ -1,14 +1,19 @@
import { destroy } from "../../api/controllers/row" import { destroy } from "../../api/controllers/row"
import { buildCtx } from "./utils" import { buildCtx } from "./utils"
import { getError } from "../automationUtils" import { getError } from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
export const definition = { export const definition: AutomationStep = {
description: "Delete a row from your database", description: "Delete a row from your database",
icon: "TableRowRemoveCenter", icon: "TableRowRemoveCenter",
name: "Delete Row", name: "Delete Row",
tagline: "Delete a {{inputs.enriched.table.name}} row", tagline: "Delete a {{inputs.enriched.table.name}} row",
type: "ACTION", type: "ACTION",
stepId: "DELETE_ROW", stepId: AutomationActionStepId.DELETE_ROW,
internal: true, internal: true,
inputs: {}, inputs: {},
schema: { schema: {
@ -47,7 +52,7 @@ export const definition = {
}, },
} }
export async function run({ inputs, appId, emitter }: any) { export async function run({ inputs, appId, emitter }: AutomationStepInput) {
if (inputs.id == null) { if (inputs.id == null) {
return { return {
success: false, success: false,

View File

@ -1,15 +1,20 @@
const fetch = require("node-fetch") import fetch from "node-fetch"
const { getFetchResponse } = require("./utils") import { getFetchResponse } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
const DEFAULT_USERNAME = "Budibase Automate" const DEFAULT_USERNAME = "Budibase Automate"
const DEFAULT_AVATAR_URL = "https://i.imgur.com/a1cmTKM.png" const DEFAULT_AVATAR_URL = "https://i.imgur.com/a1cmTKM.png"
exports.definition = { export const definition: AutomationStep = {
name: "Discord Message", name: "Discord Message",
tagline: "Send a message to a Discord server", tagline: "Send a message to a Discord server",
description: "Send a message to a Discord server", description: "Send a message to a Discord server",
icon: "ri-discord-line", icon: "ri-discord-line",
stepId: "discord", stepId: AutomationActionStepId.discord,
type: "ACTION", type: "ACTION",
internal: false, internal: false,
inputs: {}, inputs: {},
@ -54,7 +59,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
let { url, username, avatar_url, content } = inputs let { url, username, avatar_url, content } = inputs
if (!username) { if (!username) {
username = DEFAULT_USERNAME username = DEFAULT_USERNAME

View File

@ -1,14 +1,19 @@
const queryController = require("../../api/controllers/query") import * as queryController from "../../api/controllers/query"
const { buildCtx } = require("./utils") import { buildCtx } from "./utils"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "External Data Connector", name: "External Data Connector",
tagline: "Execute Data Connector", tagline: "Execute Data Connector",
icon: "Data", icon: "Data",
description: "Execute a query in an external data connector", description: "Execute a query in an external data connector",
type: "ACTION", type: "ACTION",
stepId: "EXECUTE_QUERY", stepId: AutomationActionStepId.EXECUTE_QUERY,
internal: true, internal: true,
inputs: {}, inputs: {},
schema: { schema: {
@ -50,7 +55,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, appId, emitter }) { export async function run({ inputs, appId, emitter }: AutomationStepInput) {
if (inputs.query == null) { if (inputs.query == null) {
return { return {
success: false, success: false,
@ -62,7 +67,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
const { queryId, ...rest } = inputs.query const { queryId, ...rest } = inputs.query
const ctx = buildCtx(appId, emitter, { const ctx: any = buildCtx(appId, emitter, {
body: { body: {
parameters: rest, parameters: rest,
}, },

View File

@ -1,15 +1,20 @@
const scriptController = require("../../api/controllers/script") import * as scriptController from "../../api/controllers/script"
const { buildCtx } = require("./utils") import { buildCtx } from "./utils"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "JS Scripting", name: "JS Scripting",
tagline: "Execute JavaScript Code", tagline: "Execute JavaScript Code",
icon: "Code", icon: "Code",
description: "Run a piece of JavaScript code in your automation", description: "Run a piece of JavaScript code in your automation",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "EXECUTE_SCRIPT", stepId: AutomationActionStepId.EXECUTE_SCRIPT,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -38,7 +43,12 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, appId, context, emitter }) { export async function run({
inputs,
appId,
context,
emitter,
}: AutomationStepInput) {
if (inputs.code == null) { if (inputs.code == null) {
return { return {
success: false, success: false,
@ -48,7 +58,7 @@ exports.run = async function ({ inputs, appId, context, emitter }) {
} }
} }
const ctx = buildCtx(appId, emitter, { const ctx: any = buildCtx(appId, emitter, {
body: { body: {
script: inputs.code, script: inputs.code,
context, context,

View File

@ -1,21 +1,24 @@
const FilterConditions = { import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
export const FilterConditions = {
EQUAL: "EQUAL", EQUAL: "EQUAL",
NOT_EQUAL: "NOT_EQUAL", NOT_EQUAL: "NOT_EQUAL",
GREATER_THAN: "GREATER_THAN", GREATER_THAN: "GREATER_THAN",
LESS_THAN: "LESS_THAN", LESS_THAN: "LESS_THAN",
} }
const PrettyFilterConditions = { export const PrettyFilterConditions = {
[FilterConditions.EQUAL]: "Equals", [FilterConditions.EQUAL]: "Equals",
[FilterConditions.NOT_EQUAL]: "Not equals", [FilterConditions.NOT_EQUAL]: "Not equals",
[FilterConditions.GREATER_THAN]: "Greater than", [FilterConditions.GREATER_THAN]: "Greater than",
[FilterConditions.LESS_THAN]: "Less than", [FilterConditions.LESS_THAN]: "Less than",
} }
exports.FilterConditions = FilterConditions export const definition: AutomationStep = {
exports.PrettyFilterConditions = PrettyFilterConditions
exports.definition = {
name: "Condition", name: "Condition",
tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}", tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}",
icon: "Branch2", icon: "Branch2",
@ -23,9 +26,9 @@ exports.definition = {
"Conditionally halt automations which do not meet certain conditions", "Conditionally halt automations which do not meet certain conditions",
type: "LOGIC", type: "LOGIC",
internal: true, internal: true,
stepId: "FILTER", stepId: AutomationActionStepId.FILTER,
inputs: { inputs: {
condition: FilterConditions.EQUALS, condition: FilterConditions.EQUAL,
}, },
schema: { schema: {
inputs: { inputs: {
@ -63,7 +66,7 @@ exports.definition = {
}, },
} }
exports.run = async function filter({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
try { try {
let { field, condition, value } = inputs let { field, condition, value } = inputs
// coerce types so that we can use them // coerce types so that we can use them

View File

@ -1,13 +1,18 @@
const fetch = require("node-fetch") import fetch from "node-fetch"
const { getFetchResponse } = require("./utils") import { getFetchResponse } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Integromat Integration", name: "Integromat Integration",
tagline: "Trigger an Integromat scenario", tagline: "Trigger an Integromat scenario",
description: description:
"Performs a webhook call to Integromat and gets the response (if configured)", "Performs a webhook call to Integromat and gets the response (if configured)",
icon: "ri-shut-down-line", icon: "ri-shut-down-line",
stepId: "integromat", stepId: AutomationActionStepId.integromat,
type: "ACTION", type: "ACTION",
internal: false, internal: false,
inputs: {}, inputs: {},
@ -61,7 +66,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
const { url, value1, value2, value3, value4, value5 } = inputs const { url, value1, value2, value3, value4, value5 } = inputs
const response = await fetch(url, { const response = await fetch(url, {

View File

@ -1,9 +1,11 @@
exports.definition = { import { AutomationActionStepId, AutomationStep } from "@budibase/types"
export const definition: AutomationStep = {
name: "Looping", name: "Looping",
icon: "Reuse", icon: "Reuse",
tagline: "Loop the block", tagline: "Loop the block",
description: "Loop", description: "Loop",
stepId: "LOOP", stepId: AutomationActionStepId.LOOP,
internal: true, internal: true,
inputs: {}, inputs: {},
schema: { schema: {

View File

@ -1,13 +1,18 @@
const fetch = require("node-fetch") import fetch from "node-fetch"
const { getFetchResponse } = require("./utils") import { getFetchResponse } from "./utils"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
const RequestType = { enum RequestType {
POST: "POST", POST = "POST",
GET: "GET", GET = "GET",
PUT: "PUT", PUT = "PUT",
DELETE: "DELETE", DELETE = "DELETE",
PATCH: "PATCH", PATCH = "PATCH",
} }
const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH] const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH]
@ -16,7 +21,7 @@ const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH]
* NOTE: this functionality is deprecated - it no longer should be used. * NOTE: this functionality is deprecated - it no longer should be used.
*/ */
exports.definition = { export const definition: AutomationStep = {
deprecated: true, deprecated: true,
name: "Outgoing webhook", name: "Outgoing webhook",
tagline: "Send a {{inputs.requestMethod}} request", tagline: "Send a {{inputs.requestMethod}} request",
@ -24,7 +29,7 @@ exports.definition = {
description: "Send a request of specified method to a URL", description: "Send a request of specified method to a URL",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "OUTGOING_WEBHOOK", stepId: AutomationActionStepId.OUTGOING_WEBHOOK,
inputs: { inputs: {
requestMethod: "POST", requestMethod: "POST",
url: "http://", url: "http://",
@ -76,12 +81,12 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
let { requestMethod, url, requestBody, headers } = inputs let { requestMethod, url, requestBody, headers } = inputs
if (!url.startsWith("http")) { if (!url.startsWith("http")) {
url = `http://${url}` url = `http://${url}`
} }
const request = { const request: any = {
method: requestMethod, method: requestMethod,
} }
if (headers) { if (headers) {

View File

@ -1,36 +1,43 @@
const rowController = require("../../api/controllers/row") import * as rowController from "../../api/controllers/row"
const tableController = require("../../api/controllers/table") import * as tableController from "../../api/controllers/table"
const { FieldTypes } = require("../../constants") import { FieldTypes } from "../../constants"
const { buildCtx } = require("./utils") import { buildCtx } from "./utils"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
SearchFilters,
Table,
} from "@budibase/types"
const SortOrders = { enum SortOrder {
ASCENDING: "ascending", ASCENDING = "ascending",
DESCENDING: "descending", DESCENDING = "descending",
} }
const SortOrdersPretty = { const SortOrderPretty = {
[SortOrders.ASCENDING]: "Ascending", [SortOrder.ASCENDING]: "Ascending",
[SortOrders.DESCENDING]: "Descending", [SortOrder.DESCENDING]: "Descending",
} }
const EmptyFilterOptions = { enum EmptyFilterOption {
RETURN_ALL: "all", RETURN_ALL = "all",
RETURN_NONE: "none", RETURN_NONE = "none",
} }
const EmptyFilterOptionsPretty = { const EmptyFilterOptionPretty = {
[EmptyFilterOptions.RETURN_ALL]: "Return all table rows", [EmptyFilterOption.RETURN_ALL]: "Return all table rows",
[EmptyFilterOptions.RETURN_NONE]: "Return no rows", [EmptyFilterOption.RETURN_NONE]: "Return no rows",
} }
exports.definition = { export const definition: AutomationStep = {
description: "Query rows from the database", description: "Query rows from the database",
icon: "Search", icon: "Search",
name: "Query rows", name: "Query rows",
tagline: "Query rows from {{inputs.enriched.table.name}} table", tagline: "Query rows from {{inputs.enriched.table.name}} table",
type: "ACTION", type: "ACTION",
stepId: "QUERY_ROWS", stepId: AutomationActionStepId.QUERY_ROWS,
internal: true, internal: true,
inputs: {}, inputs: {},
schema: { schema: {
@ -54,8 +61,8 @@ exports.definition = {
sortOrder: { sortOrder: {
type: "string", type: "string",
title: "Sort Order", title: "Sort Order",
enum: Object.values(SortOrders), enum: Object.values(SortOrder),
pretty: Object.values(SortOrdersPretty), pretty: Object.values(SortOrderPretty),
}, },
limit: { limit: {
type: "number", type: "number",
@ -63,8 +70,8 @@ exports.definition = {
customType: "queryLimit", customType: "queryLimit",
}, },
onEmptyFilter: { onEmptyFilter: {
pretty: Object.values(EmptyFilterOptionsPretty), pretty: Object.values(EmptyFilterOptionPretty),
enum: Object.values(EmptyFilterOptions), enum: Object.values(EmptyFilterOption),
type: "string", type: "string",
title: "When Filter Empty", title: "When Filter Empty",
}, },
@ -88,8 +95,8 @@ exports.definition = {
}, },
} }
async function getTable(appId, tableId) { async function getTable(appId: string, tableId: string) {
const ctx = buildCtx(appId, null, { const ctx: any = buildCtx(appId, null, {
params: { params: {
tableId, tableId,
}, },
@ -98,20 +105,22 @@ async function getTable(appId, tableId) {
return ctx.body return ctx.body
} }
function typeCoercion(filters, table) { function typeCoercion(filters: SearchFilters, table: Table) {
if (!filters || !table) { if (!filters || !table) {
return filters return filters
} }
for (let key of Object.keys(filters)) { for (let key of Object.keys(filters)) {
if (typeof filters[key] === "object") { // @ts-ignore
for (let [property, value] of Object.entries(filters[key])) { const searchParam = filters[key]
if (typeof searchParam === "object") {
for (let [property, value] of Object.entries(searchParam)) {
const column = table.schema[property] const column = table.schema[property]
// convert string inputs // convert string inputs
if (!column || typeof value !== "string") { if (!column || typeof value !== "string") {
continue continue
} }
if (column.type === FieldTypes.NUMBER) { if (column.type === FieldTypes.NUMBER) {
filters[key][property] = parseFloat(value) searchParam[property] = parseFloat(value)
} }
} }
} }
@ -119,11 +128,14 @@ function typeCoercion(filters, table) {
return filters return filters
} }
const hasNullFilters = filters => function hasNullFilters(filters: any[]) {
return (
filters.length === 0 || filters.length === 0 ||
filters.some(filter => filter.value === null || filter.value === "") filters.some(filter => filter.value === null || filter.value === "")
)
}
exports.run = async function ({ inputs, appId }) { export async function run({ inputs, appId }: AutomationStepInput) {
const { tableId, filters, sortColumn, sortOrder, limit } = inputs const { tableId, filters, sortColumn, sortOrder, limit } = inputs
if (!tableId) { if (!tableId) {
return { return {
@ -140,7 +152,7 @@ exports.run = async function ({ inputs, appId }) {
sortType = sortType =
fieldType === FieldTypes.NUMBER ? FieldTypes.NUMBER : FieldTypes.STRING fieldType === FieldTypes.NUMBER ? FieldTypes.NUMBER : FieldTypes.STRING
} }
const ctx = buildCtx(appId, null, { const ctx: any = buildCtx(appId, null, {
params: { params: {
tableId, tableId,
}, },
@ -150,7 +162,7 @@ exports.run = async function ({ inputs, appId }) {
sort: sortColumn, sort: sortColumn,
query: typeCoercion(filters || {}, table), query: typeCoercion(filters || {}, table),
// default to ascending, like data tab // default to ascending, like data tab
sortOrder: sortOrder || SortOrders.ASCENDING, sortOrder: sortOrder || SortOrder.ASCENDING,
}, },
version: "1", version: "1",
}) })
@ -158,7 +170,7 @@ exports.run = async function ({ inputs, appId }) {
let rows let rows
if ( if (
inputs.onEmptyFilter === EmptyFilterOptions.RETURN_NONE && inputs.onEmptyFilter === EmptyFilterOption.RETURN_NONE &&
inputs["filters-def"] && inputs["filters-def"] &&
hasNullFilters(inputs["filters-def"]) hasNullFilters(inputs["filters-def"])
) { ) {

View File

@ -1,14 +1,19 @@
const { sendSmtpEmail } = require("../../utilities/workerRequests") import { sendSmtpEmail } from "../../utilities/workerRequests"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
description: "Send an email using SMTP", description: "Send an email using SMTP",
tagline: "Send SMTP email to {{inputs.to}}", tagline: "Send SMTP email to {{inputs.to}}",
icon: "Email", icon: "Email",
name: "Send Email (SMTP)", name: "Send Email (SMTP)",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "SEND_EMAIL_SMTP", stepId: AutomationActionStepId.SEND_EMAIL_SMTP,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -56,7 +61,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
let { to, from, subject, contents, cc, bcc } = inputs let { to, from, subject, contents, cc, bcc } = inputs
if (!contents) { if (!contents) {
contents = "<h1>No content</h1>" contents = "<h1>No content</h1>"

View File

@ -1,17 +1,23 @@
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
/** /**
* Note, there is some functionality in this that is not currently exposed as it * Note, there is some functionality in this that is not currently exposed as it
* is complex and maybe better to be opinionated here. * is complex and maybe better to be opinionated here.
* GET/DELETE requests cannot handle body elements so they will not be sent if configured. * GET/DELETE requests cannot handle body elements so they will not be sent if configured.
*/ */
exports.definition = { export const definition: AutomationStep = {
name: "Backend log", name: "Backend log",
tagline: "Console log a value in the backend", tagline: "Console log a value in the backend",
icon: "Monitoring", icon: "Monitoring",
description: "Logs the given text to the server (using console.log)", description: "Logs the given text to the server (using console.log)",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "SERVER_LOG", stepId: AutomationActionStepId.SERVER_LOG,
inputs: { inputs: {
text: "", text: "",
}, },
@ -41,7 +47,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, appId }) { export async function run({ inputs, appId }: AutomationStepInput) {
const message = `App ${appId} - ${inputs.text}` const message = `App ${appId} - ${inputs.text}`
console.log(message) console.log(message)
return { return {

View File

@ -1,12 +1,17 @@
const fetch = require("node-fetch") import fetch from "node-fetch"
const { getFetchResponse } = require("./utils") import { getFetchResponse } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Slack Message", name: "Slack Message",
tagline: "Send a message to Slack", tagline: "Send a message to Slack",
description: "Send a message to Slack", description: "Send a message to Slack",
icon: "ri-slack-line", icon: "ri-slack-line",
stepId: "slack", stepId: AutomationActionStepId.slack,
type: "ACTION", type: "ACTION",
internal: false, internal: false,
inputs: {}, inputs: {},
@ -43,7 +48,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
let { url, text } = inputs let { url, text } = inputs
const response = await fetch(url, { const response = await fetch(url, {
method: "post", method: "post",

View File

@ -1,15 +1,20 @@
const rowController = require("../../api/controllers/row") import * as rowController from "../../api/controllers/row"
const automationUtils = require("../automationUtils") import automationUtils from "../automationUtils"
const { buildCtx } = require("./utils") import { buildCtx } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Update Row", name: "Update Row",
tagline: "Update a {{inputs.enriched.table.name}} row", tagline: "Update a {{inputs.enriched.table.name}} row",
icon: "Refresh", icon: "Refresh",
description: "Update a row in your database", description: "Update a row in your database",
type: "ACTION", type: "ACTION",
internal: true, internal: true,
stepId: "UPDATE_ROW", stepId: AutomationActionStepId.UPDATE_ROW,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {
@ -55,7 +60,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs, appId, emitter }) { export async function run({ inputs, appId, emitter }: AutomationStepInput) {
if (inputs.rowId == null || inputs.row == null) { if (inputs.rowId == null || inputs.row == null) {
return { return {
success: false, success: false,
@ -74,7 +79,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
} }
// have to clean up the row, remove the table from it // have to clean up the row, remove the table from it
const ctx = buildCtx(appId, emitter, { const ctx: any = buildCtx(appId, emitter, {
body: { body: {
...inputs.row, ...inputs.row,
_id: inputs.rowId, _id: inputs.rowId,

View File

@ -1,4 +1,6 @@
exports.getFetchResponse = async fetched => { import { EventEmitter } from "events"
export async function getFetchResponse(fetched: any) {
let status = fetched.status, let status = fetched.status,
message message
const contentType = fetched.headers.get("content-type") const contentType = fetched.headers.get("content-type")
@ -18,12 +20,16 @@ exports.getFetchResponse = async fetched => {
// throw added to them, so that controllers don't // throw added to them, so that controllers don't
// throw a ctx.throw undefined when error occurs // throw a ctx.throw undefined when error occurs
// opts can contain, body, params and version // opts can contain, body, params and version
exports.buildCtx = (appId, emitter, opts = {}) => { export async function buildCtx(
const ctx = { appId: string,
emitter?: EventEmitter | null,
opts: any = {}
) {
const ctx: any = {
appId, appId,
user: { appId }, user: { appId },
eventEmitter: emitter, eventEmitter: emitter,
throw: (code, error) => { throw: (code: string, error: any) => {
throw error throw error
}, },
} }

View File

@ -1,14 +1,20 @@
const fetch = require("node-fetch") import fetch from "node-fetch"
const { getFetchResponse } = require("./utils") import { getFetchResponse } from "./utils"
import {
AutomationActionStepId,
AutomationStep,
AutomationStepInput,
} from "@budibase/types"
exports.definition = { export const definition: AutomationStep = {
name: "Zapier Webhook", name: "Zapier Webhook",
stepId: "zapier", stepId: AutomationActionStepId.zapier,
type: "ACTION", type: "ACTION",
internal: false, internal: false,
description: "Trigger a Zapier Zap via webhooks", description: "Trigger a Zapier Zap via webhooks",
tagline: "Trigger a Zapier Zap", tagline: "Trigger a Zapier Zap",
icon: "ri-flashlight-line", icon: "ri-flashlight-line",
inputs: {},
schema: { schema: {
inputs: { inputs: {
properties: { properties: {
@ -54,7 +60,7 @@ exports.definition = {
}, },
} }
exports.run = async function ({ inputs }) { export async function run({ inputs }: AutomationStepInput) {
const { url, value1, value2, value3, value4, value5 } = inputs const { url, value1, value2, value3, value4, value5 } = inputs
// send the platform to make sure zaps always work, even // send the platform to make sure zaps always work, even

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "App Action", name: "App Action",
event: "app:trigger", event: "app:trigger",
icon: "Apps", icon: "Apps",
tagline: "Automation fired from the frontend", tagline: "Automation fired from the frontend",
description: "Trigger an automation from an action inside your app", description: "Trigger an automation from an action inside your app",
stepId: "APP", stepId: AutomationTriggerStepId.APP,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "Cron Trigger", name: "Cron Trigger",
event: "cron:trigger", event: "cron:trigger",
icon: "Clock", icon: "Clock",
tagline: "Cron Trigger (<b>{{inputs.cron}}</b>)", tagline: "Cron Trigger (<b>{{inputs.cron}}</b>)",
description: "Triggers automation on a cron schedule.", description: "Triggers automation on a cron schedule.",
stepId: "CRON", stepId: AutomationTriggerStepId.CRON,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,15 +0,0 @@
const app = require("./app")
const cron = require("./cron")
const rowDeleted = require("./rowDeleted")
const rowSaved = require("./rowSaved")
const rowUpdated = require("./rowUpdated")
const webhook = require("./webhook")
exports.definitions = {
ROW_SAVED: rowSaved.definition,
ROW_UPDATED: rowUpdated.definition,
ROW_DELETED: rowDeleted.definition,
WEBHOOK: webhook.definition,
APP: app.definition,
CRON: cron.definition,
}

View File

@ -0,0 +1,15 @@
import app from "./app"
import cron from "./cron"
import rowDeleted from "./rowDeleted"
import rowSaved from "./rowSaved"
import rowUpdated from "./rowUpdated"
import webhook from "./webhook"
export const definitions = {
ROW_SAVED: rowSaved.definition,
ROW_UPDATED: rowUpdated.definition,
ROW_DELETED: rowDeleted.definition,
WEBHOOK: webhook.definition,
APP: app.definition,
CRON: cron.definition,
}

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "Row Deleted", name: "Row Deleted",
event: "row:delete", event: "row:delete",
icon: "TableRowRemoveCenter", icon: "TableRowRemoveCenter",
tagline: "Row is deleted from {{inputs.enriched.table.name}}", tagline: "Row is deleted from {{inputs.enriched.table.name}}",
description: "Fired when a row is deleted from your database", description: "Fired when a row is deleted from your database",
stepId: "ROW_DELETED", stepId: AutomationTriggerStepId.ROW_DELETED,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "Row Created", name: "Row Created",
event: "row:save", event: "row:save",
icon: "TableRowAddBottom", icon: "TableRowAddBottom",
tagline: "Row is added to {{inputs.enriched.table.name}}", tagline: "Row is added to {{inputs.enriched.table.name}}",
description: "Fired when a row is added to your database", description: "Fired when a row is added to your database",
stepId: "ROW_SAVED", stepId: AutomationTriggerStepId.ROW_SAVED,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "Row Updated", name: "Row Updated",
event: "row:update", event: "row:update",
icon: "Refresh", icon: "Refresh",
tagline: "Row is updated in {{inputs.enriched.table.name}}", tagline: "Row is updated in {{inputs.enriched.table.name}}",
description: "Fired when a row is updated in your database", description: "Fired when a row is updated in your database",
stepId: "ROW_UPDATED", stepId: AutomationTriggerStepId.ROW_UPDATED,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,10 +1,12 @@
exports.definition = { import { AutomationTrigger, AutomationTriggerStepId } from "@budibase/types"
export const definition: AutomationTrigger = {
name: "Webhook", name: "Webhook",
event: "web:trigger", event: "web:trigger",
icon: "Send", icon: "Send",
tagline: "Webhook endpoint is hit", tagline: "Webhook endpoint is hit",
description: "Trigger an automation when a HTTP POST webhook is hit", description: "Trigger an automation when a HTTP POST webhook is hit",
stepId: "WEBHOOK", stepId: AutomationTriggerStepId.WEBHOOK,
inputs: {}, inputs: {},
schema: { schema: {
inputs: { inputs: {

View File

@ -1,16 +1,17 @@
const emitter = require("../events/index") import emitter from "../events/index"
const { getAutomationParams } = require("../db/utils") import { getAutomationParams } from "../db/utils"
const { coerce } = require("../utilities/rowProcessor") import { coerce } from "../utilities/rowProcessor"
const { definitions } = require("./triggerInfo") import { definitions } from "./triggerInfo"
const { isDevAppID } = require("../db/utils") import { isDevAppID } from "../db/utils"
// need this to call directly, so we can get a response // need this to call directly, so we can get a response
const { automationQueue } = require("./bullboard") import { automationQueue } from "./bullboard"
const { checkTestFlag } = require("../utilities/redis") import { checkTestFlag } from "../utilities/redis"
const utils = require("./utils") import * as utils from "./utils"
const env = require("../environment") import env from "../environment"
const { context, db: dbCore } = require("@budibase/backend-core") import { context, db as dbCore } from "@budibase/backend-core"
import { Automation, Row } from "@budibase/types"
const TRIGGER_DEFINITIONS = definitions export const TRIGGER_DEFINITIONS = definitions
const JOB_OPTS = { const JOB_OPTS = {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,
@ -24,12 +25,15 @@ async function getAllAutomations() {
return automations.rows.map(row => row.doc) return automations.rows.map(row => row.doc)
} }
async function queueRelevantRowAutomations(event, eventType) { async function queueRelevantRowAutomations(
event: { appId: string; row: Row },
eventType: string
) {
if (event.appId == null) { if (event.appId == null) {
throw `No appId specified for ${eventType} - check event emitters.` throw `No appId specified for ${eventType} - check event emitters.`
} }
context.doInAppContext(event.appId, async () => { await context.doInAppContext(event.appId, async () => {
let automations = await getAllAutomations() let automations = await getAllAutomations()
// filter down to the correct event type // filter down to the correct event type
@ -85,20 +89,20 @@ emitter.on("row:delete", async function (event) {
await queueRelevantRowAutomations(event, "row:delete") await queueRelevantRowAutomations(event, "row:delete")
}) })
exports.externalTrigger = async function ( export async function externalTrigger(
automation, automation: Automation,
params, params: { fields: Record<string, any> },
{ getResponses } = {} { getResponses }: { getResponses?: boolean } = {}
) { ) {
if ( if (
automation.definition != null && automation.definition != null &&
automation.definition.trigger != null && automation.definition.trigger != null &&
automation.definition.trigger.stepId === definitions.APP.stepId && automation.definition.trigger.stepId === definitions.APP.stepId &&
automation.definition.trigger.stepId === "APP" && automation.definition.trigger.stepId === "APP" &&
!(await checkTestFlag(automation._id)) !(await checkTestFlag(automation._id!))
) { ) {
// values are likely to be submitted as strings, so we shall convert to correct type // values are likely to be submitted as strings, so we shall convert to correct type
const coercedFields = {} const coercedFields: any = {}
const fields = automation.definition.trigger.inputs.fields const fields = automation.definition.trigger.inputs.fields
for (let key of Object.keys(fields || {})) { for (let key of Object.keys(fields || {})) {
coercedFields[key] = coerce(params.fields[key], fields[key]) coercedFields[key] = coerce(params.fields[key], fields[key])
@ -113,7 +117,7 @@ exports.externalTrigger = async function (
} }
} }
exports.rebootTrigger = async () => { export async function rebootTrigger() {
// reboot cron option is only available on the main thread at // reboot cron option is only available on the main thread at
// startup and only usable in self host and single tenant environments // startup and only usable in self host and single tenant environments
if (env.isInThread() || !env.SELF_HOSTED || env.MULTI_TENANCY) { if (env.isInThread() || !env.SELF_HOSTED || env.MULTI_TENANCY) {
@ -121,7 +125,10 @@ exports.rebootTrigger = async () => {
} }
// iterate through all production apps, find the reboot crons // iterate through all production apps, find the reboot crons
// and trigger events for them // and trigger events for them
const appIds = await dbCore.getAllApps({ dev: false, idsOnly: true }) const appIds = (await dbCore.getAllApps({
dev: false,
idsOnly: true,
})) as string[]
for (let prodAppId of appIds) { for (let prodAppId of appIds) {
await context.doInAppContext(prodAppId, async () => { await context.doInAppContext(prodAppId, async () => {
let automations = await getAllAutomations() let automations = await getAllAutomations()
@ -142,5 +149,3 @@ exports.rebootTrigger = async () => {
}) })
} }
} }
exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS

View File

@ -1,4 +1,4 @@
const { join } = require("path") import { join } from "path"
function isTest() { function isTest() {
return isCypress() || isJest() return isCypress() || isJest()
@ -28,7 +28,7 @@ if (!LOADED && isDev() && !isTest()) {
LOADED = true LOADED = true
} }
function parseIntSafe(number) { function parseIntSafe(number?: string) {
if (number) { if (number) {
return parseInt(number) return parseInt(number)
} }
@ -36,7 +36,7 @@ function parseIntSafe(number) {
let inThread = false let inThread = false
module.exports = { const environment = {
// important - prefer app port to generic port // important - prefer app port to generic port
PORT: process.env.APP_PORT || process.env.PORT, PORT: process.env.APP_PORT || process.env.PORT,
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
@ -86,7 +86,7 @@ module.exports = {
SELF_HOSTED: process.env.SELF_HOSTED, SELF_HOSTED: process.env.SELF_HOSTED,
// old // old
CLIENT_ID: process.env.CLIENT_ID, CLIENT_ID: process.env.CLIENT_ID,
_set(key, value) { _set(key: string, value: any) {
process.env[key] = value process.env[key] = value
module.exports[key] = value module.exports[key] = value
}, },
@ -108,13 +108,16 @@ module.exports = {
// threading can cause memory issues with node-ts in development // threading can cause memory issues with node-ts in development
if (isDev() && module.exports.DISABLE_THREADING == null) { if (isDev() && module.exports.DISABLE_THREADING == null) {
module.exports._set("DISABLE_THREADING", "1") environment._set("DISABLE_THREADING", "1")
} }
// clean up any environment variable edge cases // clean up any environment variable edge cases
for (let [key, value] of Object.entries(module.exports)) { for (let [key, value] of Object.entries(module.exports)) {
// handle the edge case of "0" to disable an environment variable // handle the edge case of "0" to disable an environment variable
if (value === "0") { if (value === "0") {
module.exports[key] = 0 // @ts-ignore
environment[key] = 0
} }
} }
export = environment

View File

@ -62,7 +62,7 @@ export async function sendSmtpEmail(
contents: string, contents: string,
cc: string, cc: string,
bcc: string, bcc: string,
automation: Automation automation: boolean
) { ) {
// tenant ID will be set in header // tenant ID will be set in header
const response = await fetch( const response = await fetch(

View File

@ -1,4 +1,5 @@
import { Document } from "../document" import { Document } from "../document"
import { EventEmitter } from "events"
export enum AutomationTriggerStepId { export enum AutomationTriggerStepId {
ROW_SAVED = "ROW_SAVED", ROW_SAVED = "ROW_SAVED",
@ -14,6 +15,7 @@ export enum AutomationActionStepId {
CREATE_ROW = "CREATE_ROW", CREATE_ROW = "CREATE_ROW",
UPDATE_ROW = "UPDATE_ROW", UPDATE_ROW = "UPDATE_ROW",
DELETE_ROW = "DELETE_ROW", DELETE_ROW = "DELETE_ROW",
EXECUTE_BASH = "EXECUTE_BASH",
OUTGOING_WEBHOOK = "OUTGOING_WEBHOOK", OUTGOING_WEBHOOK = "OUTGOING_WEBHOOK",
EXECUTE_SCRIPT = "EXECUTE_SCRIPT", EXECUTE_SCRIPT = "EXECUTE_SCRIPT",
EXECUTE_QUERY = "EXECUTE_QUERY", EXECUTE_QUERY = "EXECUTE_QUERY",
@ -40,7 +42,14 @@ export interface Automation extends Document {
} }
export interface AutomationStep { export interface AutomationStep {
id: string id?: string
name: string
tagline: string
icon: string
description: string
type: string
internal?: boolean
deprecated?: boolean
stepId: AutomationTriggerStepId | AutomationActionStepId stepId: AutomationTriggerStepId | AutomationActionStepId
inputs: { inputs: {
[key: string]: any [key: string]: any
@ -52,10 +61,12 @@ export interface AutomationStep {
outputs: { outputs: {
[key: string]: any [key: string]: any
} }
required?: string[]
} }
} }
export interface AutomationTrigger extends AutomationStep { export interface AutomationTrigger extends AutomationStep {
event?: string
cronJobId?: string cronJobId?: string
} }
@ -91,3 +102,10 @@ export interface AutomationLogPage {
hasNextPage: boolean hasNextPage: boolean
nextPage?: string nextPage?: string
} }
export type AutomationStepInput = {
inputs: Record<string, any>
context: Record<string, any>
emitter: EventEmitter
appId: string
}

View File

@ -26,7 +26,7 @@ function parseIntSafe(number: any) {
} }
} }
const env = { const environment = {
// auth // auth
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
@ -77,8 +77,10 @@ const env = {
} }
// if some var haven't been set, define them // if some var haven't been set, define them
if (!env.APPS_URL) { if (!environment.APPS_URL) {
env.APPS_URL = isDev() ? "http://localhost:4001" : "http://app-service:4002" environment.APPS_URL = isDev()
? "http://localhost:4001"
: "http://app-service:4002"
} }
// clean up any environment variable edge cases // clean up any environment variable edge cases
@ -90,4 +92,4 @@ for (let [key, value] of Object.entries(module.exports)) {
} }
} }
export = env export = environment