diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index b0b5f9f1ba..4577eccbaa 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -8,12 +8,10 @@ const bash = require("./steps/bash") const executeQuery = require("./steps/executeQuery") const outgoingWebhook = require("./steps/outgoingWebhook") const serverLog = require("./steps/serverLog") -const env = require("../environment") -const Sentry = require("@sentry/node") -const { - automationInit, - getExternalAutomationStep, -} = require("../utilities/fileSystem") +const discord = require("./steps/discord") +// TODO: remove zapier/integromat some time in the future/deprecate them +const zapier = require("./steps/zapier") +const integromat = require("./steps/integromat") const BUILTIN_ACTIONS = { SEND_EMAIL: sendgridEmail.run, @@ -26,6 +24,10 @@ const BUILTIN_ACTIONS = { EXECUTE_BASH: bash.run, EXECUTE_QUERY: executeQuery.run, SERVER_LOG: serverLog.run, + // these used to be lowercase step IDs, maintain for backwards compat + discord: discord.run, + zapier: zapier.run, + integromat: integromat.run, } const BUILTIN_DEFINITIONS = { SEND_EMAIL: sendgridEmail.definition, @@ -38,13 +40,10 @@ const BUILTIN_DEFINITIONS = { EXECUTE_QUERY: executeQuery.definition, EXECUTE_BASH: bash.definition, SERVER_LOG: serverLog.definition, -} - -let MANIFEST = null - -/* istanbul ignore next */ -function buildBundleName(pkgName, version) { - return `${pkgName}@${version}.min.js` + // these used to be lowercase step IDs, maintain for backwards compat + discord: discord.definition, + zapier: zapier.definition, + integromat: integromat.definition, } /* istanbul ignore next */ @@ -52,31 +51,6 @@ module.exports.getAction = async function (actionName) { if (BUILTIN_ACTIONS[actionName] != null) { return BUILTIN_ACTIONS[actionName] } - // worker pools means that a worker may not have manifest - if (env.isProd() && MANIFEST == null) { - MANIFEST = await module.exports.init() - } - // env setup to get async packages - if (!MANIFEST || !MANIFEST.packages || !MANIFEST.packages[actionName]) { - return null - } - const pkg = MANIFEST.packages[actionName] - const bundleName = buildBundleName(pkg.stepId, pkg.version) - return getExternalAutomationStep(pkg.stepId, pkg.version, bundleName) -} - -module.exports.init = async function () { - try { - MANIFEST = await automationInit() - module.exports.DEFINITIONS = - MANIFEST && MANIFEST.packages - ? Object.assign(MANIFEST.packages, BUILTIN_DEFINITIONS) - : BUILTIN_DEFINITIONS - } catch (err) { - console.error(err) - Sentry.captureException(err) - } - return MANIFEST } // definitions will have downloaded ones added to it, while builtin won't diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 8f30b6b32f..0d4b07aff5 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -1,5 +1,4 @@ const triggers = require("./triggers") -const actions = require("./actions") const env = require("../environment") const workerFarm = require("worker-farm") const singleThread = require("./thread") @@ -31,7 +30,6 @@ async function updateQuota(automation) { * This module is built purely to kick off the worker farm and manage the inputs/outputs */ module.exports.init = async function () { - await actions.init() triggers.automationQueue.process(async job => { try { if (env.USE_QUOTAS) { diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js new file mode 100644 index 0000000000..36406da5ce --- /dev/null +++ b/packages/server/src/automations/steps/discord.js @@ -0,0 +1,65 @@ +const fetch = require("node-fetch") + +module.exports.definition = { + name: "Discord Message", + tagline: "Send a message to a Discord server", + description: "Send a message to a Discord server", + icon: "ri-discord-line", + stepId: "discord", + type: "ACTION", + inputs: { + username: "Budibase Automate", + avatar_url: "https://i.imgur.com/a1cmTKM.png", + }, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Discord Webhook URL", + }, + username: { + type: "string", + title: "Bot Name", + }, + avatar_url: { + type: "string", + title: "Bot Avatar URL", + }, + content: { + type: "string", + title: "Message", + }, + }, + required: ["url", "content"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + }, + }, + }, +} + +module.exports.run = async function ({ inputs }) { + const { url, username, avatar_url, content } = inputs + + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + username, + avatar_url, + content, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + return { + httpStatus: response.status, + } +} diff --git a/packages/server/src/automations/steps/integromat.js b/packages/server/src/automations/steps/integromat.js new file mode 100644 index 0000000000..2f610d6852 --- /dev/null +++ b/packages/server/src/automations/steps/integromat.js @@ -0,0 +1,90 @@ +const fetch = require("node-fetch") + +module.exports.definition = { + name: "Integromat Integration", + tagline: "Trigger an Integromat scenario", + description: + "Performs a webhook call to Integromat and gets the response (if configured)", + icon: "ri-shut-down-line", + stepId: "integromat", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Webhook URL", + }, + value1: { + type: "string", + title: "Input Value 1", + }, + value2: { + type: "string", + title: "Input Value 2", + }, + value3: { + type: "string", + title: "Input Value 3", + }, + value4: { + type: "string", + title: "Input Value 4", + }, + value5: { + type: "string", + title: "Input Value 5", + }, + }, + required: ["url", "value1", "value2", "value3", "value4", "value5"], + }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether call was successful", + }, + response: { + type: "object", + description: "The webhook response - this can have properties", + }, + }, + required: ["success", "response"], + }, + }, +} + +module.exports.run = async function ({ inputs }) { + const { url, value1, value2, value3, value4, value5 } = inputs + + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + value1, + value2, + value3, + value4, + value5, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + let data + if (response.status === 200) { + try { + data = await response.json() + } catch (err) { + data = {} + } + } else { + data = await response.text() + } + + return { + success: response.status === 200, + response: data, + } +} diff --git a/packages/server/src/automations/steps/zapier.js b/packages/server/src/automations/steps/zapier.js new file mode 100644 index 0000000000..676286e236 --- /dev/null +++ b/packages/server/src/automations/steps/zapier.js @@ -0,0 +1,88 @@ +const fetch = require("node-fetch") + +module.exports.definition = { + name: "Zapier Webhook", + stepId: "zapier", + type: "ACTION", + description: "Trigger a Zapier Zap via webhooks", + tagline: "Trigger a Zapier Zap", + icon: "ri-flashlight-line", + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Webhook URL", + }, + value1: { + type: "string", + title: "Payload Value 1", + }, + value2: { + type: "string", + title: "Payload Value 2", + }, + value3: { + type: "string", + title: "Payload Value 3", + }, + value4: { + type: "string", + title: "Payload Value 4", + }, + value5: { + type: "string", + title: "Payload Value 5", + }, + }, + required: ["url"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + zapierStatus: { + type: "string", + description: "The result status from Zapier", + }, + }, + }, + }, +} + +module.exports.run = async function ({ inputs }) { + const { url, value1, value2, value3, value4, value5 } = inputs + + // send the platform to make sure zaps always work, even + // if no values supplied + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + platform: "budibase", + value1, + value2, + value3, + value4, + value5, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + let data = null + if (response.status === 200) { + try { + data = await response.json() + } catch (err) { + data = null + } + } + + return { + httpStatus: response.status, + zapierStatus: data && data.status ? data.status : data, + } +} diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index b83ff03854..4a97718b1b 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -14,18 +14,12 @@ const { downloadTarball, } = require("./utilities") const { updateClientLibrary } = require("./clientLibrary") -const download = require("download") const env = require("../../environment") -const { homedir } = require("os") -const fetch = require("node-fetch") const { USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX, } = require("../../db/utils") -const DEFAULT_AUTOMATION_BUCKET = - "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" -const DEFAULT_AUTOMATION_DIRECTORY = ".budibase-automations" const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") @@ -209,30 +203,6 @@ exports.getComponentLibraryManifest = async (appId, library) => { return JSON.parse(resp) } -exports.automationInit = async () => { - const directory = - env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) - const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }) - } - // env setup to get async packages - let response = await fetch(`${bucket}/manifest.json`) - return response.json() -} - -exports.getExternalAutomationStep = async (name, version, bundleName) => { - const directory = - env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) - const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET - try { - return require(join(directory, bundleName)) - } catch (err) { - await download(`${bucket}/${name}/${version}/${bundleName}`, directory) - return require(join(directory, bundleName)) - } -} - /** * All file reads come through here just to make sure all of them make sense * allows a centralised location to check logic is all good.