From 26d7cb3b9b1fad86d362accdfb4c06c4672132d4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 3 Sep 2021 17:36:00 +0100 Subject: [PATCH 01/31] Getting rid of automation step download system. --- packages/server/src/automations/actions.js | 50 +++-------- packages/server/src/automations/index.js | 2 - .../server/src/automations/steps/discord.js | 65 ++++++++++++++ .../src/automations/steps/integromat.js | 90 +++++++++++++++++++ .../server/src/automations/steps/zapier.js | 88 ++++++++++++++++++ .../server/src/utilities/fileSystem/index.js | 30 ------- 6 files changed, 255 insertions(+), 70 deletions(-) create mode 100644 packages/server/src/automations/steps/discord.js create mode 100644 packages/server/src/automations/steps/integromat.js create mode 100644 packages/server/src/automations/steps/zapier.js 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. From 879f1a2368132fe115d42dbb18d887c06bdd3cce Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 6 Sep 2021 17:53:02 +0100 Subject: [PATCH 02/31] Cleanup, prepping for automation history, some refactoring to get rid of concept of builtin. --- .../server/src/api/controllers/automation.js | 16 +- .../src/api/routes/tests/automation.spec.js | 2 +- packages/server/src/automations/actions.js | 14 +- packages/server/src/automations/logic.js | 12 +- packages/server/src/automations/steps/bash.js | 4 +- .../server/src/automations/steps/createRow.js | 4 +- .../server/src/automations/steps/delay.js | 4 +- .../server/src/automations/steps/deleteRow.js | 4 +- .../server/src/automations/steps/discord.js | 4 +- .../src/automations/steps/executeQuery.js | 4 +- .../src/automations/steps/executeScript.js | 4 +- .../server/src/automations/steps/filter.js | 8 +- .../src/automations/steps/integromat.js | 4 +- .../src/automations/steps/outgoingWebhook.js | 4 +- .../src/automations/steps/sendSmtpEmail.js | 4 +- .../src/automations/steps/sendgridEmail.js | 4 +- .../server/src/automations/steps/serverLog.js | 4 +- .../server/src/automations/steps/updateRow.js | 4 +- .../server/src/automations/steps/zapier.js | 4 +- .../src/automations/tests/utilities/index.js | 4 +- packages/server/src/automations/thread.js | 2 +- .../server/src/automations/triggerInfo/app.js | 31 ++ .../src/automations/triggerInfo/cron.js | 31 ++ .../src/automations/triggerInfo/index.js | 15 + .../src/automations/triggerInfo/rowDeleted.js | 32 ++ .../src/automations/triggerInfo/rowSaved.js | 40 +++ .../src/automations/triggerInfo/rowUpdated.js | 40 +++ .../src/automations/triggerInfo/webhook.js | 36 +++ packages/server/src/automations/triggers.js | 280 +----------------- packages/server/src/middleware/usageQuota.js | 2 +- 30 files changed, 290 insertions(+), 331 deletions(-) create mode 100644 packages/server/src/automations/triggerInfo/app.js create mode 100644 packages/server/src/automations/triggerInfo/cron.js create mode 100644 packages/server/src/automations/triggerInfo/index.js create mode 100644 packages/server/src/automations/triggerInfo/rowDeleted.js create mode 100644 packages/server/src/automations/triggerInfo/rowSaved.js create mode 100644 packages/server/src/automations/triggerInfo/rowUpdated.js create mode 100644 packages/server/src/automations/triggerInfo/webhook.js diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 2d164b415d..8a3bbd93ec 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -5,8 +5,8 @@ const triggers = require("../../automations/triggers") const webhooks = require("./webhook") const { getAutomationParams, generateAutomationID } = require("../../db/utils") -const WH_STEP_ID = triggers.BUILTIN_DEFINITIONS.WEBHOOK.stepId -const CRON_STEP_ID = triggers.BUILTIN_DEFINITIONS.CRON.stepId +const WH_STEP_ID = triggers.TRIGGER_DEFINITIONS.WEBHOOK.stepId +const CRON_STEP_ID = triggers.TRIGGER_DEFINITIONS.CRON.stepId /************************* * * @@ -242,22 +242,22 @@ exports.destroy = async function (ctx) { } exports.getActionList = async function (ctx) { - ctx.body = actions.DEFINITIONS + ctx.body = actions.ACTION_DEFINITIONS } exports.getTriggerList = async function (ctx) { - ctx.body = triggers.BUILTIN_DEFINITIONS + ctx.body = triggers.TRIGGER_DEFINITIONS } exports.getLogicList = async function (ctx) { - ctx.body = logic.BUILTIN_DEFINITIONS + ctx.body = logic.LOGIC_DEFINITIONS } module.exports.getDefinitionList = async function (ctx) { ctx.body = { - logic: logic.BUILTIN_DEFINITIONS, - trigger: triggers.BUILTIN_DEFINITIONS, - action: actions.DEFINITIONS, + logic: logic.LOGIC_DEFINITIONS, + trigger: triggers.TRIGGER_DEFINITIONS, + action: actions.ACTION_DEFINITIONS, } } diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 5654c14c17..09e8747813 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -44,7 +44,7 @@ describe("/automations", () => { ACTION_DEFINITIONS = res.body }) - it("returns a list of definitions for triggers", async () => { + it("returns a list of definitions for triggerInfo", async () => { const res = await request .get(`/api/automations/trigger/list`) .set(config.defaultHeaders()) diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 4577eccbaa..767f1120d4 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -13,7 +13,7 @@ const discord = require("./steps/discord") const zapier = require("./steps/zapier") const integromat = require("./steps/integromat") -const BUILTIN_ACTIONS = { +const ACTION_IMPLS = { SEND_EMAIL: sendgridEmail.run, SEND_EMAIL_SMTP: sendSmtpEmail.run, CREATE_ROW: createRow.run, @@ -29,7 +29,7 @@ const BUILTIN_ACTIONS = { zapier: zapier.run, integromat: integromat.run, } -const BUILTIN_DEFINITIONS = { +const ACTION_DEFINITIONS = { SEND_EMAIL: sendgridEmail.definition, SEND_EMAIL_SMTP: sendSmtpEmail.definition, CREATE_ROW: createRow.definition, @@ -47,12 +47,10 @@ const BUILTIN_DEFINITIONS = { } /* istanbul ignore next */ -module.exports.getAction = async function (actionName) { - if (BUILTIN_ACTIONS[actionName] != null) { - return BUILTIN_ACTIONS[actionName] +exports.getAction = async function (actionName) { + if (ACTION_IMPLS[actionName] != null) { + return ACTION_IMPLS[actionName] } } -// definitions will have downloaded ones added to it, while builtin won't -module.exports.DEFINITIONS = BUILTIN_DEFINITIONS -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS +exports.ACTION_DEFINITIONS = ACTION_DEFINITIONS diff --git a/packages/server/src/automations/logic.js b/packages/server/src/automations/logic.js index cbd3d42430..52ee51252b 100644 --- a/packages/server/src/automations/logic.js +++ b/packages/server/src/automations/logic.js @@ -1,20 +1,20 @@ let filter = require("./steps/filter") let delay = require("./steps/delay") -let BUILTIN_LOGIC = { +let LOGIC_IMPLS = { DELAY: delay.run, FILTER: filter.run, } -let BUILTIN_DEFINITIONS = { +let LOGIC_DEFINITIONS = { DELAY: delay.definition, FILTER: filter.definition, } -module.exports.getLogic = function (logicName) { - if (BUILTIN_LOGIC[logicName] != null) { - return BUILTIN_LOGIC[logicName] +exports.getLogic = function (logicName) { + if (LOGIC_IMPLS[logicName] != null) { + return LOGIC_IMPLS[logicName] } } -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS +exports.LOGIC_DEFINITIONS = LOGIC_DEFINITIONS diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index 76d6713c5b..f2d98ce944 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -1,7 +1,7 @@ const { execSync } = require("child_process") const { processStringSync } = require("@budibase/string-templates") -module.exports.definition = { +exports.definition = { name: "Bash Scripting", tagline: "Execute a bash command", icon: "ri-terminal-box-line", @@ -32,7 +32,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, context }) { +exports.run = async function ({ inputs, context }) { if (inputs.code == null) { return { stdout: "Budibase bash automation failed: Invalid inputs", diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index 889f7e98b9..a08adfb000 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -3,7 +3,7 @@ const automationUtils = require("../automationUtils") const env = require("../../environment") const usage = require("../../utilities/usageQuota") -module.exports.definition = { +exports.definition = { name: "Create Row", tagline: "Create a {{inputs.enriched.table.name}} row", icon: "ri-save-3-line", @@ -58,7 +58,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { +exports.run = async function ({ inputs, appId, apiKey, emitter }) { if (inputs.row == null || inputs.row.tableId == null) { return { success: false, diff --git a/packages/server/src/automations/steps/delay.js b/packages/server/src/automations/steps/delay.js index f653d0a980..b79d28e92d 100644 --- a/packages/server/src/automations/steps/delay.js +++ b/packages/server/src/automations/steps/delay.js @@ -1,6 +1,6 @@ let { wait } = require("../../utilities") -module.exports.definition = { +exports.definition = { name: "Delay", icon: "ri-time-line", tagline: "Delay for {{inputs.time}} milliseconds", @@ -21,6 +21,6 @@ module.exports.definition = { type: "LOGIC", } -module.exports.run = async function delay({ inputs }) { +exports.run = async function delay({ inputs }) { await wait(inputs.time) } diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 94243fa03a..83e5969381 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -2,7 +2,7 @@ const rowController = require("../../api/controllers/row") const env = require("../../environment") const usage = require("../../utilities/usageQuota") -module.exports.definition = { +exports.definition = { description: "Delete a row from your database", icon: "ri-delete-bin-line", name: "Delete Row", @@ -50,7 +50,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, apiKey, emitter }) { +exports.run = async function ({ inputs, appId, apiKey, emitter }) { if (inputs.id == null || inputs.revision == null) { return { success: false, diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js index 36406da5ce..2fefc92916 100644 --- a/packages/server/src/automations/steps/discord.js +++ b/packages/server/src/automations/steps/discord.js @@ -1,6 +1,6 @@ const fetch = require("node-fetch") -module.exports.definition = { +exports.definition = { name: "Discord Message", tagline: "Send a message to a Discord server", description: "Send a message to a Discord server", @@ -44,7 +44,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { const { url, username, avatar_url, content } = inputs const response = await fetch(url, { diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index d045a75544..474b03766e 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -1,6 +1,6 @@ const queryController = require("../../api/controllers/query") -module.exports.definition = { +exports.definition = { name: "External Data Connector", tagline: "Execute Data Connector", icon: "ri-database-2-line", @@ -42,7 +42,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, emitter }) { +exports.run = async function ({ inputs, appId, emitter }) { if (inputs.query == null) { return { success: false, diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index 33ffd3ee8e..6eca4f4244 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -1,6 +1,6 @@ const scriptController = require("../../api/controllers/script") -module.exports.definition = { +exports.definition = { name: "JS Scripting", tagline: "Execute JavaScript Code", icon: "ri-terminal-box-line", @@ -36,7 +36,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, context, emitter }) { +exports.run = async function ({ inputs, appId, context, emitter }) { if (inputs.code == null) { return { success: false, diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js index 586e424cc4..e15722e8d0 100644 --- a/packages/server/src/automations/steps/filter.js +++ b/packages/server/src/automations/steps/filter.js @@ -12,10 +12,10 @@ const PrettyLogicConditions = { [LogicConditions.LESS_THAN]: "Less than", } -module.exports.LogicConditions = LogicConditions -module.exports.PrettyLogicConditions = PrettyLogicConditions +exports.LogicConditions = LogicConditions +exports.PrettyLogicConditions = PrettyLogicConditions -module.exports.definition = { +exports.definition = { name: "Filter", tagline: "{{inputs.field}} {{inputs.condition}} {{inputs.value}}", icon: "ri-git-branch-line", @@ -57,7 +57,7 @@ module.exports.definition = { }, } -module.exports.run = async function filter({ inputs }) { +exports.run = async function filter({ inputs }) { let { field, condition, value } = inputs // coerce types so that we can use them if (!isNaN(value) && !isNaN(field)) { diff --git a/packages/server/src/automations/steps/integromat.js b/packages/server/src/automations/steps/integromat.js index 2f610d6852..ef9d551286 100644 --- a/packages/server/src/automations/steps/integromat.js +++ b/packages/server/src/automations/steps/integromat.js @@ -1,6 +1,6 @@ const fetch = require("node-fetch") -module.exports.definition = { +exports.definition = { name: "Integromat Integration", tagline: "Trigger an Integromat scenario", description: @@ -55,7 +55,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { const { url, value1, value2, value3, value4, value5 } = inputs const response = await fetch(url, { diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index c1edde7b4e..317f8967f5 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -16,7 +16,7 @@ const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH] * GET/DELETE requests cannot handle body elements so they will not be sent if configured. */ -module.exports.definition = { +exports.definition = { name: "Outgoing webhook", tagline: "Send a {{inputs.requestMethod}} request", icon: "ri-send-plane-line", @@ -70,7 +70,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { let { requestMethod, url, requestBody, headers } = inputs if (!url.startsWith("http")) { url = `http://${url}` diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index 764972b402..8b8d9b6db9 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.js +++ b/packages/server/src/automations/steps/sendSmtpEmail.js @@ -1,6 +1,6 @@ const { sendSmtpEmail } = require("../../utilities/workerRequests") -module.exports.definition = { +exports.definition = { description: "Send an email using SMTP", tagline: "Send SMTP email to {{inputs.to}}", icon: "ri-mail-open-line", @@ -46,7 +46,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { let { to, from, subject, contents } = inputs if (!contents) { contents = "

No content

" diff --git a/packages/server/src/automations/steps/sendgridEmail.js b/packages/server/src/automations/steps/sendgridEmail.js index 5485116e89..bde5f96e4f 100644 --- a/packages/server/src/automations/steps/sendgridEmail.js +++ b/packages/server/src/automations/steps/sendgridEmail.js @@ -1,4 +1,4 @@ -module.exports.definition = { +exports.definition = { description: "Send an email using SendGrid", tagline: "Send email to {{inputs.to}}", icon: "ri-mail-open-line", @@ -48,7 +48,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { const sgMail = require("@sendgrid/mail") sgMail.setApiKey(inputs.apiKey) const msg = { diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js index 7389b65f54..7183ff89fd 100644 --- a/packages/server/src/automations/steps/serverLog.js +++ b/packages/server/src/automations/steps/serverLog.js @@ -4,7 +4,7 @@ * GET/DELETE requests cannot handle body elements so they will not be sent if configured. */ -module.exports.definition = { +exports.definition = { name: "Backend log", tagline: "Console log a value in the backend", icon: "ri-server-line", @@ -36,6 +36,6 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId }) { +exports.run = async function ({ inputs, appId }) { console.log(`App ${appId} - ${inputs.text}`) } diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 206e429efa..898869959e 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -1,7 +1,7 @@ const rowController = require("../../api/controllers/row") const automationUtils = require("../automationUtils") -module.exports.definition = { +exports.definition = { name: "Update Row", tagline: "Update a {{inputs.enriched.table.name}} row", icon: "ri-refresh-line", @@ -53,7 +53,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs, appId, emitter }) { +exports.run = async function ({ inputs, appId, emitter }) { if (inputs.rowId == null || inputs.row == null) { return { success: false, diff --git a/packages/server/src/automations/steps/zapier.js b/packages/server/src/automations/steps/zapier.js index 676286e236..0a7362cede 100644 --- a/packages/server/src/automations/steps/zapier.js +++ b/packages/server/src/automations/steps/zapier.js @@ -1,6 +1,6 @@ const fetch = require("node-fetch") -module.exports.definition = { +exports.definition = { name: "Zapier Webhook", stepId: "zapier", type: "ACTION", @@ -52,7 +52,7 @@ module.exports.definition = { }, } -module.exports.run = async function ({ inputs }) { +exports.run = async function ({ inputs }) { const { url, value1, value2, value3, value4, value5 } = inputs // send the platform to make sure zaps always work, even diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js index ab9de55430..516463091d 100644 --- a/packages/server/src/automations/tests/utilities/index.js +++ b/packages/server/src/automations/tests/utilities/index.js @@ -56,5 +56,5 @@ exports.runStep = async function runStep(stepId, inputs) { exports.apiKey = "test" -exports.actions = actions.BUILTIN_DEFINITIONS -exports.logic = logic.BUILTIN_DEFINITIONS +exports.actions = actions.ACTION_DEFINITIONS +exports.logic = logic.LOGIC_DEFINITIONS diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index aada0ca0ca..87d3f98d16 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -8,7 +8,7 @@ const CouchDB = require("../db") const { DocumentTypes } = require("../db/utils") const { doInTenant } = require("@budibase/auth/tenancy") -const FILTER_STEP_ID = logic.BUILTIN_DEFINITIONS.FILTER.stepId +const FILTER_STEP_ID = logic.LOGIC_DEFINITIONS.FILTER.stepId /** * The automation orchestrator is a class responsible for executing automations. diff --git a/packages/server/src/automations/triggerInfo/app.js b/packages/server/src/automations/triggerInfo/app.js new file mode 100644 index 0000000000..1c64795cf3 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/app.js @@ -0,0 +1,31 @@ +exports.definition = { + 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", +} diff --git a/packages/server/src/automations/triggerInfo/cron.js b/packages/server/src/automations/triggerInfo/cron.js new file mode 100644 index 0000000000..9ef4649b95 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/cron.js @@ -0,0 +1,31 @@ +exports.defintion = { + name: "Cron Trigger", + event: "cron:trigger", + icon: "ri-timer-line", + tagline: "Cron Trigger ({{inputs.cron}})", + description: "Triggers automation on a cron schedule.", + stepId: "CRON", + inputs: {}, + schema: { + inputs: { + properties: { + cron: { + type: "string", + customType: "cron", + title: "Expression", + }, + }, + required: ["cron"], + }, + outputs: { + properties: { + timestamp: { + type: "number", + description: "Timestamp the cron was executed", + }, + }, + required: ["timestamp"], + }, + }, + type: "TRIGGER", +} diff --git a/packages/server/src/automations/triggerInfo/index.js b/packages/server/src/automations/triggerInfo/index.js new file mode 100644 index 0000000000..476d37d54c --- /dev/null +++ b/packages/server/src/automations/triggerInfo/index.js @@ -0,0 +1,15 @@ +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.defintion, +} diff --git a/packages/server/src/automations/triggerInfo/rowDeleted.js b/packages/server/src/automations/triggerInfo/rowDeleted.js new file mode 100644 index 0000000000..c7ead1fec4 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowDeleted.js @@ -0,0 +1,32 @@ +exports.definition = { + 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", +} diff --git a/packages/server/src/automations/triggerInfo/rowSaved.js b/packages/server/src/automations/triggerInfo/rowSaved.js new file mode 100644 index 0000000000..3a21a26878 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowSaved.js @@ -0,0 +1,40 @@ +exports.definition = { + 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", +} diff --git a/packages/server/src/automations/triggerInfo/rowUpdated.js b/packages/server/src/automations/triggerInfo/rowUpdated.js new file mode 100644 index 0000000000..099ce0a6b2 --- /dev/null +++ b/packages/server/src/automations/triggerInfo/rowUpdated.js @@ -0,0 +1,40 @@ +exports.definition = { + name: "Row Updated", + event: "row:update", + 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", +} diff --git a/packages/server/src/automations/triggerInfo/webhook.js b/packages/server/src/automations/triggerInfo/webhook.js new file mode 100644 index 0000000000..dd83031d8f --- /dev/null +++ b/packages/server/src/automations/triggerInfo/webhook.js @@ -0,0 +1,36 @@ +exports.definition = { + 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", +} diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 2a2d68ecb1..3ce09e15fd 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -8,232 +8,12 @@ const { getAutomationParams } = require("../db/utils") const { coerce } = require("../utilities/rowProcessor") const { utils } = require("@budibase/auth/redis") const { JobQueues } = require("../constants") -const { - isExternalTable, - breakExternalTableId, -} = require("../integrations/utils") -const { getExternalTable } = require("../api/controllers/table/utils") +const { definitions } = require("./triggerInfo") const { opts } = utils.getRedisOptions() let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) -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", - 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", - }, - CRON: { - name: "Cron Trigger", - event: "cron:trigger", - icon: "ri-timer-line", - tagline: "Cron Trigger ({{inputs.cron}})", - description: "Triggers automation on a cron schedule.", - stepId: "CRON", - inputs: {}, - schema: { - inputs: { - properties: { - cron: { - type: "string", - customType: "cron", - title: "Expression", - }, - }, - required: ["cron"], - }, - outputs: { - properties: { - timestamp: { - type: "number", - description: "Timestamp the cron was executed", - }, - }, - required: ["timestamp"], - }, - }, - type: "TRIGGER", - }, -} +const TRIGGER_DEFINITIONS = definitions async function queueRelevantRowAutomations(event, eventType) { if (event.appId == null) { @@ -262,7 +42,7 @@ async function queueRelevantRowAutomations(event, eventType) { ) { continue } - automationQueue.add({ automation, event }) + await automationQueue.add({ automation, event }) } } @@ -290,51 +70,8 @@ emitter.on("row:delete", async function (event) { 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 -} - -module.exports.externalTrigger = async function (automation, params) { - // TODO: replace this with allowing user in builder to input values in future +exports.externalTrigger = async function (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 = {} @@ -346,13 +83,12 @@ module.exports.externalTrigger = async function (automation, params) { } } - automationQueue.add({ automation, event: params }) + await automationQueue.add({ automation, event: params }) } -module.exports.getQueues = () => { +exports.getQueues = () => { return [automationQueue] } -module.exports.fillRowOutput = fillRowOutput -module.exports.automationQueue = automationQueue +exports.automationQueue = automationQueue -module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS +exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index ac8336e769..4647878721 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -14,7 +14,7 @@ const DOMAIN_MAP = { views: usageQuota.Properties.VIEW, users: usageQuota.Properties.USER, // this will not be updated by endpoint calls - // instead it will be updated by triggers + // instead it will be updated by triggerInfo automationRuns: usageQuota.Properties.AUTOMATION, } From d05c60d470b2bc843f85cb8312a60d54ac871cb7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 13:58:53 +0100 Subject: [PATCH 03/31] Adding slack integration. --- packages/auth/src/db/utils.js | 16 +++-- .../server/src/api/controllers/automation.js | 2 + packages/server/src/automations/actions.js | 4 +- .../server/src/automations/steps/discord.js | 22 +++++-- .../server/src/automations/steps/slack.js | 59 +++++++++++++++++++ packages/server/src/automations/triggers.js | 6 ++ packages/server/src/db/utils.js | 4 ++ 7 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 packages/server/src/automations/steps/slack.js diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 4cd29c9bc8..1116c0a605 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -35,10 +35,6 @@ exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR exports.SEPARATOR = SEPARATOR -function isDevApp(app) { - return app.appId.startsWith(exports.APP_DEV_PREFIX) -} - /** * If creating DB allDocs/query params with only a single top level ID this can be used, this * is usually the case as most of our docs are top level e.g. tables, automations, users and so on. @@ -62,6 +58,18 @@ function getDocParams(docType, docId = null, otherProps = {}) { } } +exports.isDevAppID = appId => { + return appId.startsWith(exports.APP_DEV_PREFIX) +} + +exports.isProdAppID = appId => { + return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId) +} + +function isDevApp(app) { + return exports.isDevAppID(app.appId) +} + /** * Generates a new workspace ID. * @returns {string} The new workspace ID which the workspace doc can be stored under. diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 8a3bbd93ec..40006b6865 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -280,3 +280,5 @@ exports.trigger = async function (ctx) { automation, } } + +exports.test = async function (ctx) {} diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 767f1120d4..8d5d1693f1 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -9,7 +9,7 @@ const executeQuery = require("./steps/executeQuery") const outgoingWebhook = require("./steps/outgoingWebhook") const serverLog = require("./steps/serverLog") const discord = require("./steps/discord") -// TODO: remove zapier/integromat some time in the future/deprecate them +const slack = require("./steps/slack") const zapier = require("./steps/zapier") const integromat = require("./steps/integromat") @@ -26,6 +26,7 @@ const ACTION_IMPLS = { SERVER_LOG: serverLog.run, // these used to be lowercase step IDs, maintain for backwards compat discord: discord.run, + slack: slack.run, zapier: zapier.run, integromat: integromat.run, } @@ -42,6 +43,7 @@ const ACTION_DEFINITIONS = { SERVER_LOG: serverLog.definition, // these used to be lowercase step IDs, maintain for backwards compat discord: discord.definition, + slack: slack.definition, zapier: zapier.definition, integromat: integromat.definition, } diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js index 2fefc92916..c01619b0ae 100644 --- a/packages/server/src/automations/steps/discord.js +++ b/packages/server/src/automations/steps/discord.js @@ -1,5 +1,8 @@ const fetch = require("node-fetch") +const DEFAULT_USERNAME = "Budibase Automate" +const DEFAULT_AVATAR_URL = "https://i.imgur.com/a1cmTKM.png" + exports.definition = { name: "Discord Message", tagline: "Send a message to a Discord server", @@ -7,10 +10,7 @@ exports.definition = { icon: "ri-discord-line", stepId: "discord", type: "ACTION", - inputs: { - username: "Budibase Automate", - avatar_url: "https://i.imgur.com/a1cmTKM.png", - }, + inputs: {}, schema: { inputs: { properties: { @@ -39,14 +39,23 @@ exports.definition = { type: "number", description: "The HTTP status code of the request", }, + success: { + type: "boolean", + description: "Whether the message sent successfully", + }, }, }, }, } exports.run = async function ({ inputs }) { - const { url, username, avatar_url, content } = inputs - + let { url, username, avatar_url, content } = inputs + if (!username) { + username = DEFAULT_USERNAME + } + if (!avatar_url) { + avatar_url = DEFAULT_AVATAR_URL + } const response = await fetch(url, { method: "post", body: JSON.stringify({ @@ -61,5 +70,6 @@ exports.run = async function ({ inputs }) { return { httpStatus: response.status, + success: response.status === 200, } } diff --git a/packages/server/src/automations/steps/slack.js b/packages/server/src/automations/steps/slack.js new file mode 100644 index 0000000000..08c26fe6d3 --- /dev/null +++ b/packages/server/src/automations/steps/slack.js @@ -0,0 +1,59 @@ +const fetch = require("node-fetch") + +const DEFAULT_USERNAME = "Budibase Automate" +const DEFAULT_ICON_URL = "https://i.imgur.com/a1cmTKM.png" + +exports.definition = { + name: "Slack Message", + tagline: "Send a message to Slack", + description: "Send a message to Slack", + icon: "ri-slack-line", + stepId: "slack", + type: "ACTION", + inputs: {}, + schema: { + inputs: { + properties: { + url: { + type: "string", + title: "Incoming Webhook URL", + }, + text: { + type: "string", + title: "Message", + }, + }, + required: ["url", "text"], + }, + outputs: { + properties: { + httpStatus: { + type: "number", + description: "The HTTP status code of the request", + }, + success: { + type: "boolean", + description: "Whether the message sent successfully", + }, + }, + }, + }, +} + +exports.run = async function ({ inputs }) { + let { url, text } = inputs + const response = await fetch(url, { + method: "post", + body: JSON.stringify({ + text, + }), + headers: { + "Content-Type": "application/json", + }, + }) + + return { + httpStatus: response.status, + success: response.status === 200, + } +} diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 3ce09e15fd..b76cd4ce2a 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -9,6 +9,7 @@ const { coerce } = require("../utilities/rowProcessor") const { utils } = require("@budibase/auth/redis") const { JobQueues } = require("../constants") const { definitions } = require("./triggerInfo") +const { isDevAppID } = require("../db/utils") const { opts } = utils.getRedisOptions() let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) @@ -19,6 +20,11 @@ async function queueRelevantRowAutomations(event, eventType) { if (event.appId == null) { throw `No appId specified for ${eventType} - check event emitters.` } + // don't queue events which are for dev apps, only way to test automations is + // running tests on them + if (isDevAppID(event.appId)) { + return + } const db = new CouchDB(event.appId) let automations = await db.allDocs( getAutomationParams(null, { include_docs: true }) diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 92734c5e7b..987e9f58f8 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -7,6 +7,8 @@ const { APP_PREFIX, SEPARATOR, StaticDatabases, + isDevAppID, + isProdAppID, } = require("@budibase/auth/db") const UNICODE_MAX = "\ufff0" @@ -62,6 +64,8 @@ const BudibaseInternalDB = { exports.APP_PREFIX = APP_PREFIX exports.APP_DEV_PREFIX = APP_DEV_PREFIX +exports.isDevAppID = isDevAppID +exports.isProdAppID = isProdAppID exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.ViewNames = ViewNames From bcc7f1caea7537dbf0d3d8f65d4c2f2caf7a54b5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 13:59:58 +0100 Subject: [PATCH 04/31] Adding slack integration. --- packages/server/src/api/controllers/automation.js | 4 +++- packages/server/src/automations/steps/slack.js | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 40006b6865..953b510f26 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -281,4 +281,6 @@ exports.trigger = async function (ctx) { } } -exports.test = async function (ctx) {} +exports.test = async function (ctx) { + ctx.body = {} +} diff --git a/packages/server/src/automations/steps/slack.js b/packages/server/src/automations/steps/slack.js index 08c26fe6d3..77d1e3cfd2 100644 --- a/packages/server/src/automations/steps/slack.js +++ b/packages/server/src/automations/steps/slack.js @@ -1,8 +1,5 @@ const fetch = require("node-fetch") -const DEFAULT_USERNAME = "Budibase Automate" -const DEFAULT_ICON_URL = "https://i.imgur.com/a1cmTKM.png" - exports.definition = { name: "Slack Message", tagline: "Send a message to Slack", From 777e24344018e5002eaabeb89dae2ac86bde1c30 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 17:31:54 +0100 Subject: [PATCH 05/31] Re-working all of the actions to have a success output, and make sure everything has useful outputs. --- packages/server/src/automations/actions.js | 3 - packages/server/src/automations/steps/bash.js | 12 ++- .../server/src/automations/steps/createRow.js | 3 +- .../server/src/automations/steps/delay.js | 12 +++ .../server/src/automations/steps/deleteRow.js | 3 +- .../server/src/automations/steps/discord.js | 11 ++- .../src/automations/steps/executeQuery.js | 1 - .../src/automations/steps/executeScript.js | 1 - .../src/automations/steps/integromat.js | 22 +++--- .../src/automations/steps/outgoingWebhook.js | 21 +++--- .../src/automations/steps/sendSmtpEmail.js | 1 - .../src/automations/steps/sendgridEmail.js | 74 ------------------- .../server/src/automations/steps/serverLog.js | 3 + .../server/src/automations/steps/slack.js | 11 ++- .../server/src/automations/steps/updateRow.js | 1 - .../server/src/automations/steps/utils.js | 18 +++++ .../server/src/automations/steps/zapier.js | 19 ++--- 17 files changed, 87 insertions(+), 129 deletions(-) delete mode 100644 packages/server/src/automations/steps/sendgridEmail.js create mode 100644 packages/server/src/automations/steps/utils.js diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 8d5d1693f1..0f395db890 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -1,4 +1,3 @@ -const sendgridEmail = require("./steps/sendgridEmail") const sendSmtpEmail = require("./steps/sendSmtpEmail") const createRow = require("./steps/createRow") const updateRow = require("./steps/updateRow") @@ -14,7 +13,6 @@ const zapier = require("./steps/zapier") const integromat = require("./steps/integromat") const ACTION_IMPLS = { - SEND_EMAIL: sendgridEmail.run, SEND_EMAIL_SMTP: sendSmtpEmail.run, CREATE_ROW: createRow.run, UPDATE_ROW: updateRow.run, @@ -31,7 +29,6 @@ const ACTION_IMPLS = { integromat: integromat.run, } const ACTION_DEFINITIONS = { - SEND_EMAIL: sendgridEmail.definition, SEND_EMAIL_SMTP: sendSmtpEmail.definition, CREATE_ROW: createRow.definition, UPDATE_ROW: updateRow.definition, diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index f2d98ce944..d97f055e14 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -24,7 +24,11 @@ exports.definition = { properties: { stdout: { type: "string", - description: "Standard output of your bash command or script.", + description: "Standard output of your bash command or script", + }, + success: { + type: "boolean", + description: "Whether the command was successful", }, }, }, @@ -42,18 +46,20 @@ exports.run = async function ({ inputs, context }) { try { const command = processStringSync(inputs.code, context) - let stdout + let stdout, + success = true try { stdout = execSync(command, { timeout: 500 }) } catch (err) { stdout = err.message + success = false } return { stdout, + success, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index a08adfb000..f98cbfe2cf 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -42,7 +42,7 @@ exports.definition = { }, success: { type: "boolean", - description: "Whether the action was successful", + description: "Whether the row creation was successful", }, id: { type: "string", @@ -97,7 +97,6 @@ exports.run = async function ({ inputs, appId, apiKey, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/delay.js b/packages/server/src/automations/steps/delay.js index b79d28e92d..ee7ef018e3 100644 --- a/packages/server/src/automations/steps/delay.js +++ b/packages/server/src/automations/steps/delay.js @@ -17,10 +17,22 @@ exports.definition = { }, required: ["time"], }, + outputs: { + properties: { + success: { + type: "boolean", + description: "Whether the delay was successful", + }, + }, + required: ["success"], + }, }, type: "LOGIC", } exports.run = async function delay({ inputs }) { await wait(inputs.time) + return { + success: true, + } } diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 83e5969381..0c0315b1b0 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -42,7 +42,7 @@ exports.definition = { }, success: { type: "boolean", - description: "Whether the action was successful", + description: "Whether the deletion was successful", }, }, required: ["row", "success"], @@ -84,7 +84,6 @@ exports.run = async function ({ inputs, appId, apiKey, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js index c01619b0ae..066eb23a4e 100644 --- a/packages/server/src/automations/steps/discord.js +++ b/packages/server/src/automations/steps/discord.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") const DEFAULT_USERNAME = "Budibase Automate" const DEFAULT_AVATAR_URL = "https://i.imgur.com/a1cmTKM.png" @@ -39,6 +40,10 @@ exports.definition = { type: "number", description: "The HTTP status code of the request", }, + response: { + type: "string", + description: "The response from the Discord Webhook", + }, success: { type: "boolean", description: "Whether the message sent successfully", @@ -68,8 +73,10 @@ exports.run = async function ({ inputs }) { }, }) + const { status, message } = await getFetchResponse(response) return { - httpStatus: response.status, - success: response.status === 200, + httpStatus: status, + success: status === 200, + response: message, } } diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index 474b03766e..f34d994183 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -75,7 +75,6 @@ exports.run = async function ({ inputs, appId, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index 6eca4f4244..b320e38700 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -64,7 +64,6 @@ exports.run = async function ({ inputs, appId, context, emitter }) { value: ctx.body, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/integromat.js b/packages/server/src/automations/steps/integromat.js index ef9d551286..f8427b0dab 100644 --- a/packages/server/src/automations/steps/integromat.js +++ b/packages/server/src/automations/steps/integromat.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") exports.definition = { name: "Integromat Integration", @@ -45,6 +46,10 @@ exports.definition = { type: "boolean", description: "Whether call was successful", }, + httpStatus: { + type: "number", + description: "The HTTP status code returned", + }, response: { type: "object", description: "The webhook response - this can have properties", @@ -72,19 +77,10 @@ exports.run = async function ({ inputs }) { }, }) - let data - if (response.status === 200) { - try { - data = await response.json() - } catch (err) { - data = {} - } - } else { - data = await response.text() - } - + const { status, message } = await getFetchResponse(response) return { - success: response.status === 200, - response: data, + httpStatus: status, + success: status === 200, + response: message, } } diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index 317f8967f5..fe93cb38d8 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") const RequestType = { POST: "POST", @@ -60,6 +61,10 @@ exports.definition = { type: "object", description: "The response from the webhook", }, + httpStatus: { + type: "number", + description: "The HTTP status code returned", + }, success: { type: "boolean", description: "Whether the action was successful", @@ -107,19 +112,11 @@ exports.run = async function ({ inputs }) { JSON.parse(request.body) } const response = await fetch(url, request) - const contentType = response.headers.get("content-type") - const success = response.status === 200 - let resp - if (!success) { - resp = response.statusText - } else if (contentType && contentType.indexOf("application/json") !== -1) { - resp = await response.json() - } else { - resp = await response.text() - } + const { status, message } = await getFetchResponse(response) return { - response: resp, - success: success, + httpStatus: status, + response: message, + success: status === 200, } } catch (err) { /* istanbul ignore next */ diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index 8b8d9b6db9..fc2274a582 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.js +++ b/packages/server/src/automations/steps/sendSmtpEmail.js @@ -58,7 +58,6 @@ exports.run = async function ({ inputs }) { response, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/sendgridEmail.js b/packages/server/src/automations/steps/sendgridEmail.js deleted file mode 100644 index bde5f96e4f..0000000000 --- a/packages/server/src/automations/steps/sendgridEmail.js +++ /dev/null @@ -1,74 +0,0 @@ -exports.definition = { - description: "Send an email using SendGrid", - tagline: "Send email to {{inputs.to}}", - icon: "ri-mail-open-line", - name: "Send Email (SendGrid)", - type: "ACTION", - stepId: "SEND_EMAIL", - inputs: {}, - schema: { - inputs: { - properties: { - apiKey: { - type: "string", - title: "SendGrid API key", - }, - to: { - type: "string", - title: "Send To", - }, - from: { - type: "string", - title: "Send From", - }, - subject: { - type: "string", - title: "Email Subject", - }, - contents: { - type: "string", - title: "Email Contents", - }, - }, - required: ["to", "from", "subject", "contents"], - }, - outputs: { - properties: { - success: { - type: "boolean", - description: "Whether the email was sent", - }, - response: { - type: "object", - description: "A response from the email client, this may be an error", - }, - }, - required: ["success"], - }, - }, -} - -exports.run = async function ({ inputs }) { - const sgMail = require("@sendgrid/mail") - sgMail.setApiKey(inputs.apiKey) - const msg = { - to: inputs.to, - from: inputs.from, - subject: inputs.subject, - text: inputs.contents ? inputs.contents : "Empty", - } - - try { - let response = await sgMail.send(msg) - return { - success: true, - response, - } - } catch (err) { - console.error(err) - return { - success: false, - response: err, - } - } -} diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js index 7183ff89fd..0cde7b22ab 100644 --- a/packages/server/src/automations/steps/serverLog.js +++ b/packages/server/src/automations/steps/serverLog.js @@ -38,4 +38,7 @@ exports.definition = { exports.run = async function ({ inputs, appId }) { console.log(`App ${appId} - ${inputs.text}`) + return { + success: true, + } } diff --git a/packages/server/src/automations/steps/slack.js b/packages/server/src/automations/steps/slack.js index 77d1e3cfd2..a87c614151 100644 --- a/packages/server/src/automations/steps/slack.js +++ b/packages/server/src/automations/steps/slack.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") exports.definition = { name: "Slack Message", @@ -32,6 +33,10 @@ exports.definition = { type: "boolean", description: "Whether the message sent successfully", }, + response: { + type: "string", + description: "The response from the Slack Webhook", + }, }, }, }, @@ -49,8 +54,10 @@ exports.run = async function ({ inputs }) { }, }) + const { status, message } = await getFetchResponse(response) return { - httpStatus: response.status, - success: response.status === 200, + httpStatus: status, + response: message, + success: status === 200, } } diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 898869959e..e58f1fe446 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -100,7 +100,6 @@ exports.run = async function ({ inputs, appId, emitter }) { success: ctx.status === 200, } } catch (err) { - console.error(err) return { success: false, response: err, diff --git a/packages/server/src/automations/steps/utils.js b/packages/server/src/automations/steps/utils.js new file mode 100644 index 0000000000..0429c71ca7 --- /dev/null +++ b/packages/server/src/automations/steps/utils.js @@ -0,0 +1,18 @@ +exports.getFetchResponse = async fetched => { + let status = fetched.status, + message + const contentType = fetched.headers.get("content-type") + try { + if (contentType && contentType.indexOf("application/json") !== -1) { + message = await fetched.json() + } else { + message = await fetched.text() + } + } catch (err) { + message = "Failed to retrieve response" + } + if (typeof message !== "string") { + message = JSON.stringify(message) + } + return { status, message } +} diff --git a/packages/server/src/automations/steps/zapier.js b/packages/server/src/automations/steps/zapier.js index 0a7362cede..57703286e6 100644 --- a/packages/server/src/automations/steps/zapier.js +++ b/packages/server/src/automations/steps/zapier.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch") +const { getFetchResponse } = require("./utils") exports.definition = { name: "Zapier Webhook", @@ -43,9 +44,9 @@ exports.definition = { type: "number", description: "The HTTP status code of the request", }, - zapierStatus: { + response: { type: "string", - description: "The result status from Zapier", + description: "The response from Zapier", }, }, }, @@ -72,17 +73,11 @@ exports.run = async function ({ inputs }) { }, }) - let data = null - if (response.status === 200) { - try { - data = await response.json() - } catch (err) { - data = null - } - } + const { status, message } = await getFetchResponse(response) return { - httpStatus: response.status, - zapierStatus: data && data.status ? data.status : data, + success: status === 200, + httpStatus: status, + response: message, } } From d2070f90611a66cdf769842debaf3b8de518b2e5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 19:06:20 +0100 Subject: [PATCH 06/31] Adding the ability to get back the context. --- .../server/src/api/controllers/automation.js | 15 ++++- packages/server/src/api/routes/automation.js | 2 +- packages/server/src/app.ts | 2 +- packages/server/src/automations/index.js | 45 ++------------- packages/server/src/automations/thread.js | 31 +++++----- packages/server/src/automations/triggers.js | 16 +++++- packages/server/src/automations/utils.js | 56 +++++++++++++++++++ 7 files changed, 105 insertions(+), 62 deletions(-) create mode 100644 packages/server/src/automations/utils.js diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 953b510f26..7261c3a116 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -274,7 +274,6 @@ exports.trigger = async function (ctx) { ...ctx.request.body, appId: ctx.appId, }) - ctx.status = 200 ctx.body = { message: `Automation ${automation._id} has been triggered.`, automation, @@ -282,5 +281,17 @@ exports.trigger = async function (ctx) { } exports.test = async function (ctx) { - ctx.body = {} + const db = new CouchDB(ctx.appId) + let automation = await db.get(ctx.params.id) + ctx.body = { + automation, + responses: await triggers.externalTrigger( + automation, + { + ...ctx.request.body, + appId: ctx.appId, + }, + { getResponses: true } + ), + } } diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index c7b674b4e4..416d270ea9 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -88,7 +88,7 @@ router "/api/automations/:id/trigger", paramResource("id"), authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), - controller.trigger + controller.test ) .delete( "/api/automations/:id/:rev", diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index a32f241aa3..23e05e5e95 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -88,8 +88,8 @@ module.exports = server.listen(env.PORT || 0, async () => { env._set("PORT", server.address().port) eventEmitter.emitPort(env.PORT) fileSystem.init() - await automations.init() await redis.init() + await automations.init() }) process.on("uncaughtException", err => { diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 0d4b07aff5..13948e9839 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -1,49 +1,12 @@ const triggers = require("./triggers") -const env = require("../environment") -const workerFarm = require("worker-farm") -const singleThread = require("./thread") -const { getAPIKey, update, Properties } = require("../utilities/usageQuota") - -let workers = workerFarm(require.resolve("./thread")) - -function runWorker(job) { - return new Promise((resolve, reject) => { - workers(job, err => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) -} - -async function updateQuota(automation) { - const appId = automation.appId - const apiObj = await getAPIKey(appId) - // this will fail, causing automation to escape if limits reached - await update(apiObj.apiKey, Properties.AUTOMATION, 1) - return apiObj.apiKey -} +const { processEvent } = require("./utils") /** * This module is built purely to kick off the worker farm and manage the inputs/outputs */ -module.exports.init = async function () { +exports.init = async function () { + // don't wait this promise, it'll never end triggers.automationQueue.process(async job => { - try { - if (env.USE_QUOTAS) { - job.data.automation.apiKey = await updateQuota(job.data.automation) - } - if (env.isProd()) { - await runWorker(job) - } else { - await singleThread(job) - } - } catch (err) { - console.error( - `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` - ) - } + await processEvent(job) }) } diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 87d3f98d16..0b8e2a981b 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -83,25 +83,28 @@ class Orchestrator { this._context.steps.push(outputs) } catch (err) { console.error(`Automation error - ${step.stepId} - ${err}`) + return err } } + return this._context } } // callback is required for worker-farm to state that the worker thread has completed -module.exports = async (job, cb = null) => { - try { - const automationOrchestrator = new Orchestrator( - job.data.automation, - job.data.event - ) - await automationOrchestrator.execute() - if (cb) { - cb() - } - } catch (err) { - if (cb) { - cb(err) - } +module.exports = (job, cb) => { + if (!cb) { + throw "Callback must be defined." } + const automationOrchestrator = new Orchestrator( + job.data.automation, + job.data.event + ) + automationOrchestrator + .execute() + .then(output => { + cb(null, output) + }) + .catch(err => { + cb(err) + }) } diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index b76cd4ce2a..a026ffe709 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -10,6 +10,8 @@ const { utils } = require("@budibase/auth/redis") const { JobQueues } = require("../constants") const { definitions } = require("./triggerInfo") const { isDevAppID } = require("../db/utils") +// need this to call directly, so we can get a response +const { processEvent } = require("./utils") const { opts } = utils.getRedisOptions() let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) @@ -76,7 +78,11 @@ emitter.on("row:delete", async function (event) { await queueRelevantRowAutomations(event, "row:delete") }) -exports.externalTrigger = async function (automation, params) { +exports.externalTrigger = async function ( + automation, + params, + { getResponses } = {} +) { if (automation.definition != null && automation.definition.trigger != null) { if (automation.definition.trigger.stepId === "APP") { // values are likely to be submitted as strings, so we shall convert to correct type @@ -88,8 +94,12 @@ exports.externalTrigger = async function (automation, params) { params.fields = coercedFields } } - - await automationQueue.add({ automation, event: params }) + const data = { automation, event: params } + if (getResponses) { + return processEvent({ data }) + } else { + return automationQueue.add(data) + } } exports.getQueues = () => { diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js new file mode 100644 index 0000000000..7f5b542fa6 --- /dev/null +++ b/packages/server/src/automations/utils.js @@ -0,0 +1,56 @@ +const env = require("../environment") +const workerFarm = require("worker-farm") +const { getAPIKey, update, Properties } = require("../utilities/usageQuota") +const singleThread = require("./thread") + +let workers = workerFarm(require.resolve("./thread")) + +function runWorker(job) { + return new Promise((resolve, reject) => { + workers(job, (err, output) => { + if (err) { + reject(err) + } else { + resolve(output) + } + }) + }) +} + +function runSingleThread(job) { + return new Promise((resolve, reject) => { + singleThread(job, (err, output) => { + if (err) { + reject(err) + } else { + resolve(output) + } + }) + }) +} + +async function updateQuota(automation) { + const appId = automation.appId + const apiObj = await getAPIKey(appId) + // this will fail, causing automation to escape if limits reached + await update(apiObj.apiKey, Properties.AUTOMATION, 1) + return apiObj.apiKey +} + +exports.processEvent = async job => { + try { + if (env.USE_QUOTAS) { + job.data.automation.apiKey = await updateQuota(job.data.automation) + } + if (!env.isProd()) { + return runSingleThread(job) + } else { + return runWorker(job) + } + } catch (err) { + console.error( + `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` + ) + return err + } +} From b00f8764cbd1178d89505db3fbe0c1c3d5070028 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 8 Sep 2021 14:08:22 +0100 Subject: [PATCH 07/31] Updating automations system to return the inputs and outputs of each step. --- .../builderStore/store/automation/index.js | 7 ++- .../automation/SetupPanel/SetupPanel.svelte | 2 +- .../server/src/api/controllers/automation.js | 44 ++++++++++++------- packages/server/src/api/routes/automation.js | 6 +++ packages/server/src/automations/thread.js | 17 ++++++- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index c372f27bb7..f8e7db04a0 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -78,8 +78,11 @@ const automationActions = store => ({ }, trigger: async ({ automation }) => { const { _id } = automation - const TRIGGER_AUTOMATION_URL = `/api/automations/${_id}/trigger` - return await api.post(TRIGGER_AUTOMATION_URL) + return await api.post(`/api/automations/${_id}/trigger`) + }, + test: async ({ automation }) => { + const { _id } = automation + return await api.post(`/api/automations/${_id}/test`) }, select: automation => { store.update(state => { diff --git a/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte b/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte index 3ba59f36a6..bf6c45074c 100644 --- a/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte +++ b/packages/builder/src/components/automation/SetupPanel/SetupPanel.svelte @@ -25,7 +25,7 @@ } async function testAutomation() { - const result = await automationStore.actions.trigger({ + const result = await automationStore.actions.test({ automation: $automationStore.selectedAutomation.automation, }) if (result.status === 200) { diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 7261c3a116..81744a6c3a 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -3,7 +3,12 @@ const actions = require("../../automations/actions") const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") const webhooks = require("./webhook") -const { getAutomationParams, generateAutomationID } = require("../../db/utils") +const { + getAutomationParams, + generateAutomationID, + isDevAppID, + isProdAppID, +} = require("../../db/utils") const WH_STEP_ID = triggers.TRIGGER_DEFINITIONS.WEBHOOK.stepId const CRON_STEP_ID = triggers.TRIGGER_DEFINITIONS.CRON.stepId @@ -268,11 +273,19 @@ module.exports.getDefinitionList = async function (ctx) { *********************/ exports.trigger = async function (ctx) { - const db = new CouchDB(ctx.appId) + const appId = ctx.appId + if (isDevAppID(appId)) { + // in dev apps don't throw an error, just don't trigger + ctx.body = { + message: "Automation not triggered, app in development.", + } + return + } + const db = new CouchDB(appId) let automation = await db.get(ctx.params.id) await triggers.externalTrigger(automation, { ...ctx.request.body, - appId: ctx.appId, + appId, }) ctx.body = { message: `Automation ${automation._id} has been triggered.`, @@ -281,17 +294,18 @@ exports.trigger = async function (ctx) { } exports.test = async function (ctx) { - const db = new CouchDB(ctx.appId) - let automation = await db.get(ctx.params.id) - ctx.body = { - automation, - responses: await triggers.externalTrigger( - automation, - { - ...ctx.request.body, - appId: ctx.appId, - }, - { getResponses: true } - ), + const appId = ctx.appId + if (isProdAppID(appId)) { + ctx.throw(400, "Cannot test automations in production app.") } + const db = new CouchDB(appId) + let automation = await db.get(ctx.params.id) + ctx.body = await triggers.externalTrigger( + automation, + { + ...ctx.request.body, + appId, + }, + { getResponses: true } + ) } diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 416d270ea9..78090f2da0 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -88,6 +88,12 @@ router "/api/automations/:id/trigger", paramResource("id"), authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + controller.trigger + ) + .post( + "/api/automations/:id/test", + paramResource("id"), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), controller.test ) .delete( diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 0b8e2a981b..12e158c20a 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -30,6 +30,11 @@ class Orchestrator { // create an emitter which has the chain count for this automation run in it, so it can block // excessive chaining if required this._emitter = new AutomationEmitter(this._chainCount + 1) + this.executionOutput = { trigger: {}, steps: [] } + // setup the execution output + const triggerStepId = automation.definition.trigger.stepId + const triggerId = automation.definition.trigger.id + this.updateExecutionOutput(triggerId, triggerStepId, null, triggerOutput) } async getStepFunctionality(type, stepId) { @@ -55,6 +60,15 @@ class Orchestrator { return this._app } + updateExecutionOutput(id, stepId, inputs, outputs) { + const stepObj = { id, stepId, inputs, outputs } + // first entry is always the trigger (constructor) + if (this.executionOutput.steps.length === 0) { + this.executionOutput.trigger = stepObj + } + this.executionOutput.steps.push(stepObj) + } + async execute() { let automation = this._automation const app = await this.getApp() @@ -81,12 +95,13 @@ class Orchestrator { break } this._context.steps.push(outputs) + this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs) } catch (err) { console.error(`Automation error - ${step.stepId} - ${err}`) return err } } - return this._context + return this.executionOutput } } From 4e294fbcd9f4450ebb088817937d2a2d795cc0c2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 8 Sep 2021 19:29:28 +0100 Subject: [PATCH 08/31] Adding metadata system and re-writing how Cron works, previously cron only worked in dev because it would never be enabled for the production app ID, this makes it so that it is never enabled for the dev app and when the production app is deployed it runs through all the automations and checks if any need cron jobs setup/disabled. --- .../server/src/api/controllers/automation.js | 164 ++---------------- .../src/api/controllers/deploy/index.js | 24 ++- .../server/src/api/controllers/metadata.js | 46 +++++ packages/server/src/api/routes/automation.js | 30 ++-- packages/server/src/api/routes/metadata.js | 32 ++++ packages/server/src/automations/bullboard.js | 14 +- packages/server/src/automations/index.js | 13 +- packages/server/src/automations/triggers.js | 20 +-- packages/server/src/automations/utils.js | 126 ++++++++++++++ packages/server/src/constants/index.js | 5 + packages/server/src/db/utils.js | 13 ++ packages/server/src/middleware/appInfo.js | 19 ++ packages/server/src/utilities/index.js | 25 +++ 13 files changed, 348 insertions(+), 183 deletions(-) create mode 100644 packages/server/src/api/controllers/metadata.js create mode 100644 packages/server/src/api/routes/metadata.js create mode 100644 packages/server/src/middleware/appInfo.js diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 81744a6c3a..1dcbbd6874 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -2,16 +2,10 @@ const CouchDB = require("../../db") const actions = require("../../automations/actions") const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") -const webhooks = require("./webhook") -const { - getAutomationParams, - generateAutomationID, - isDevAppID, - isProdAppID, -} = require("../../db/utils") - -const WH_STEP_ID = triggers.TRIGGER_DEFINITIONS.WEBHOOK.stepId -const CRON_STEP_ID = triggers.TRIGGER_DEFINITIONS.CRON.stepId +const { getAutomationParams, generateAutomationID } = require("../../db/utils") +const { saveEntityMetadata } = require("../../utilities") +const { MetadataTypes } = require("../../constants") +const { checkForWebhooks } = require("../../automations/utils") /************************* * * @@ -26,6 +20,10 @@ function cleanAutomationInputs(automation) { let steps = automation.definition.steps let trigger = automation.definition.trigger let allSteps = [...steps, trigger] + // live is not a property used anymore + if (automation.live != null) { + delete automation.live + } for (let step of allSteps) { if (step == null) { continue @@ -39,119 +37,6 @@ function cleanAutomationInputs(automation) { return automation } -/** - * This function handles checking of any cron jobs need to be created or deleted for automations. - * @param {string} appId The ID of the app in which we are checking for webhooks - * @param {object|undefined} oldAuto The old automation object if updating/deleting - * @param {object|undefined} newAuto The new automation object if creating/updating - */ -async function checkForCronTriggers({ appId, oldAuto, newAuto }) { - const oldTrigger = oldAuto ? oldAuto.definition.trigger : null - const newTrigger = newAuto ? newAuto.definition.trigger : null - function isCronTrigger(auto) { - return ( - auto && - auto.definition.trigger && - auto.definition.trigger.stepId === CRON_STEP_ID - ) - } - - const isLive = auto => auto && auto.live - - const cronTriggerRemoved = - isCronTrigger(oldAuto) && !isCronTrigger(newAuto) && oldTrigger.cronJobId - const cronTriggerDeactivated = !isLive(newAuto) && isLive(oldAuto) - - const cronTriggerActivated = isLive(newAuto) && !isLive(oldAuto) - - if (cronTriggerRemoved || (cronTriggerDeactivated && oldTrigger.cronJobId)) { - await triggers.automationQueue.removeRepeatableByKey(oldTrigger.cronJobId) - } - // need to create cron job - else if (isCronTrigger(newAuto) && cronTriggerActivated) { - const job = await triggers.automationQueue.add( - { - automation: newAuto, - event: { appId, timestamp: Date.now() }, - }, - { repeat: { cron: newTrigger.inputs.cron } } - ) - // Assign cron job ID from bull so we can remove it later if the cron trigger is removed - newTrigger.cronJobId = job.id - } - return newAuto -} - -/** - * This function handles checking if any webhooks need to be created or deleted for automations. - * @param {string} appId The ID of the app in which we are checking for webhooks - * @param {object|undefined} oldAuto The old automation object if updating/deleting - * @param {object|undefined} newAuto The new automation object if creating/updating - * @returns {Promise} After this is complete the new automation object may have been updated and should be - * written to DB (this does not write to DB as it would be wasteful to repeat). - */ -async function checkForWebhooks({ appId, oldAuto, newAuto }) { - const oldTrigger = oldAuto ? oldAuto.definition.trigger : null - const newTrigger = newAuto ? newAuto.definition.trigger : null - const triggerChanged = - oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id - function isWebhookTrigger(auto) { - return ( - auto && - auto.definition.trigger && - auto.definition.trigger.stepId === WH_STEP_ID - ) - } - // need to delete webhook - if ( - isWebhookTrigger(oldAuto) && - (!isWebhookTrigger(newAuto) || triggerChanged) && - oldTrigger.webhookId - ) { - try { - let db = new CouchDB(appId) - // need to get the webhook to get the rev - const webhook = await db.get(oldTrigger.webhookId) - const ctx = { - appId, - params: { id: webhook._id, rev: webhook._rev }, - } - // might be updating - reset the inputs to remove the URLs - if (newTrigger) { - delete newTrigger.webhookId - newTrigger.inputs = {} - } - await webhooks.destroy(ctx) - } catch (err) { - // don't worry about not being able to delete, if it doesn't exist all good - } - } - // need to create webhook - if ( - (!isWebhookTrigger(oldAuto) || triggerChanged) && - isWebhookTrigger(newAuto) - ) { - const ctx = { - appId, - request: { - body: new webhooks.Webhook( - "Automation webhook", - webhooks.WebhookType.AUTOMATION, - newAuto._id - ), - }, - } - await webhooks.save(ctx) - const id = ctx.body.webhook._id - newTrigger.webhookId = id - newTrigger.inputs = { - schemaUrl: `api/webhooks/schema/${appId}/${id}`, - triggerUrl: `api/webhooks/trigger/${appId}/${id}`, - } - } - return newAuto -} - exports.create = async function (ctx) { const db = new CouchDB(ctx.appId) let automation = ctx.request.body @@ -170,10 +55,6 @@ exports.create = async function (ctx) { appId: ctx.appId, newAuto: automation, }) - automation = await checkForCronTriggers({ - appId: ctx.appId, - newAuto: automation, - }) const response = await db.put(automation) automation._rev = response.rev @@ -198,11 +79,6 @@ exports.update = async function (ctx) { oldAuto: oldAutomation, newAuto: automation, }) - automation = await checkForCronTriggers({ - appId: ctx.appId, - oldAuto: oldAutomation, - newAuto: automation, - }) const response = await db.put(automation) automation._rev = response.rev @@ -239,10 +115,6 @@ exports.destroy = async function (ctx) { appId: ctx.appId, oldAuto: oldAutomation, }) - await checkForCronTriggers({ - appId: ctx.appId, - oldAuto: oldAutomation, - }) ctx.body = await db.remove(ctx.params.id, ctx.params.rev) } @@ -274,13 +146,6 @@ module.exports.getDefinitionList = async function (ctx) { exports.trigger = async function (ctx) { const appId = ctx.appId - if (isDevAppID(appId)) { - // in dev apps don't throw an error, just don't trigger - ctx.body = { - message: "Automation not triggered, app in development.", - } - return - } const db = new CouchDB(appId) let automation = await db.get(ctx.params.id) await triggers.externalTrigger(automation, { @@ -295,12 +160,9 @@ exports.trigger = async function (ctx) { exports.test = async function (ctx) { const appId = ctx.appId - if (isProdAppID(appId)) { - ctx.throw(400, "Cannot test automations in production app.") - } const db = new CouchDB(appId) let automation = await db.get(ctx.params.id) - ctx.body = await triggers.externalTrigger( + const response = await triggers.externalTrigger( automation, { ...ctx.request.body, @@ -308,4 +170,12 @@ exports.test = async function (ctx) { }, { getResponses: true } ) + // save a test history run + await saveEntityMetadata( + ctx.appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automation._id, + ctx.request.body + ) + ctx.body = response } diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index 4608ca6342..08f9072138 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -1,7 +1,11 @@ const CouchDB = require("../../../db") const Deployment = require("./Deployment") const { Replication } = require("@budibase/auth/db") -const { DocumentTypes } = require("../../../db/utils") +const { DocumentTypes, getAutomationParams } = require("../../../db/utils") +const { + disableAllCrons, + enableCronTrigger, +} = require("../../../automations/utils") // the max time we can wait for an invalidation to complete before considering it failed const MAX_PENDING_TIME_MS = 30 * 60000 @@ -58,6 +62,23 @@ async function storeDeploymentHistory(deployment) { return deployment } +async function initDeployedApp(prodAppId) { + const db = new CouchDB(prodAppId) + const automations = ( + await db.allDocs( + getAutomationParams(null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) + const promises = [] + await disableAllCrons(prodAppId) + for (let automation of automations) { + promises.push(enableCronTrigger(prodAppId, automation)) + } + await Promise.all(promises) +} + async function deployApp(deployment) { try { const productionAppId = deployment.appId.replace("_dev", "") @@ -85,6 +106,7 @@ async function deployApp(deployment) { }, }) + await initDeployedApp(productionAppId) deployment.setStatus(DeploymentStatus.SUCCESS) await storeDeploymentHistory(deployment) } catch (err) { diff --git a/packages/server/src/api/controllers/metadata.js b/packages/server/src/api/controllers/metadata.js new file mode 100644 index 0000000000..d5fa2b94cb --- /dev/null +++ b/packages/server/src/api/controllers/metadata.js @@ -0,0 +1,46 @@ +const { MetadataTypes } = require("../../constants") +const CouchDB = require("../../db") +const { generateMetadataID } = require("../../db/utils") +const { saveEntityMetadata } = require("../../utilities") + +exports.getTypes = async ctx => { + ctx.body = { + types: MetadataTypes, + } +} + +exports.saveMetadata = async ctx => { + const { type, entityId } = ctx.params + if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) { + ctx.throw(400, "Cannot save automation history type") + } + await saveEntityMetadata(ctx.appId, type, entityId, ctx.request.body) +} + +exports.deleteMetadata = async ctx => { + const { type, entityId } = ctx.params + const db = new CouchDB(ctx.appId) + const id = generateMetadataID(type, entityId) + let rev + try { + const metadata = await db.get(id) + if (metadata) { + rev = metadata._rev + } + } catch (err) { + // don't need to error if it doesn't exist + } + if (id && rev) { + await db.remove(id, rev) + } + ctx.body = { + message: "Metadata deleted successfully.", + } +} + +exports.getMetadata = async ctx => { + const { type, entityId } = ctx.params + const db = new CouchDB(ctx.appId) + const id = generateMetadataID(type, entityId) + ctx.body = await db.get(id) +} diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 78090f2da0..cc52a2b6b6 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -9,6 +9,10 @@ const { } = require("@budibase/auth/permissions") const Joi = require("joi") const { bodyResource, paramResource } = require("../../middleware/resourceId") +const { + middleware: appInfoMiddleware, + AppType, +} = require("../../middleware/appInfo") const router = Router() @@ -84,23 +88,25 @@ router generateValidator(false), controller.create ) - .post( - "/api/automations/:id/trigger", - paramResource("id"), - authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), - controller.trigger - ) - .post( - "/api/automations/:id/test", - paramResource("id"), - authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), - controller.test - ) .delete( "/api/automations/:id/:rev", paramResource("id"), authorized(BUILDER), controller.destroy ) + .post( + "/api/automations/:id/trigger", + appInfoMiddleware({ appType: AppType.PROD }), + paramResource("id"), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + controller.trigger + ) + .post( + "/api/automations/:id/test", + appInfoMiddleware({ appType: AppType.DEV }), + paramResource("id"), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), + controller.test + ) module.exports = router diff --git a/packages/server/src/api/routes/metadata.js b/packages/server/src/api/routes/metadata.js new file mode 100644 index 0000000000..7c6ee7a163 --- /dev/null +++ b/packages/server/src/api/routes/metadata.js @@ -0,0 +1,32 @@ +const Router = require("@koa/router") +const controller = require("../controllers/metadata") +const { + middleware: appInfoMiddleware, + AppType, +} = require("../../middleware/appInfo") + +const router = Router() + +router + .post( + "/api/metadata/:type/:entityId", + appInfoMiddleware({ appType: AppType.DEV }), + controller.saveMetadata + ) + .delete( + "/api/metadata/:type/:entityId", + appInfoMiddleware({ appType: AppType.DEV }), + controller.deleteMetadata + ) + .get( + "/api/metadata/type", + appInfoMiddleware({ appType: AppType.DEV }), + controller.getTypes + ) + .get( + "/api/metadata/:type/:entityId", + appInfoMiddleware({ appType: AppType.DEV }), + controller.getMetadata + ) + +module.exports = router diff --git a/packages/server/src/automations/bullboard.js b/packages/server/src/automations/bullboard.js index 364c4afaa1..1f96b6c4d2 100644 --- a/packages/server/src/automations/bullboard.js +++ b/packages/server/src/automations/bullboard.js @@ -1,14 +1,22 @@ const { createBullBoard } = require("bull-board") const { BullAdapter } = require("bull-board/bullAdapter") -const { getQueues } = require("./triggers") const express = require("express") +const env = require("../environment") +const Queue = env.isTest() + ? require("../utilities/queue/inMemoryQueue") + : require("bull") +const { JobQueues } = require("../constants") +const { utils } = require("@budibase/auth/redis") +const { opts } = utils.getRedisOptions() + +let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) exports.pathPrefix = "/bulladmin" exports.init = () => { const expressApp = express() // Set up queues for bull board admin - const queues = getQueues() + const queues = [automationQueue] const adapters = [] for (let queue of queues) { adapters.push(new BullAdapter(queue)) @@ -18,3 +26,5 @@ exports.init = () => { expressApp.use(exports.pathPrefix, router) return expressApp } + +exports.queue = automationQueue diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 13948e9839..87f35ce763 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -1,12 +1,17 @@ -const triggers = require("./triggers") const { processEvent } = require("./utils") +const { queue } = require("./bullboard") /** * This module is built purely to kick off the worker farm and manage the inputs/outputs */ -exports.init = async function () { - // don't wait this promise, it'll never end - triggers.automationQueue.process(async job => { +exports.init = function () { + // this promise will not complete + return queue.process(async job => { await processEvent(job) }) } + +exports.getQueues = () => { + return [queue] +} +exports.queue = queue diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index a026ffe709..b32747d319 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -1,20 +1,12 @@ const CouchDB = require("../db") const emitter = require("../events/index") -const env = require("../environment") -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") -const { JobQueues } = require("../constants") const { definitions } = require("./triggerInfo") const { isDevAppID } = require("../db/utils") // need this to call directly, so we can get a response const { processEvent } = require("./utils") - -const { opts } = utils.getRedisOptions() -let automationQueue = new Queue(JobQueues.AUTOMATIONS, { redis: opts }) +const { queue } = require("./bullboard") const TRIGGER_DEFINITIONS = definitions @@ -44,13 +36,12 @@ async function queueRelevantRowAutomations(event, eventType) { let automationDef = automation.definition let automationTrigger = automationDef ? automationDef.trigger : {} if ( - !automation.live || !automationTrigger.inputs || automationTrigger.inputs.tableId !== event.row.tableId ) { continue } - await automationQueue.add({ automation, event }) + await queue.add({ automation, event }) } } @@ -98,13 +89,8 @@ exports.externalTrigger = async function ( if (getResponses) { return processEvent({ data }) } else { - return automationQueue.add(data) + return queue.add(data) } } -exports.getQueues = () => { - return [automationQueue] -} -exports.automationQueue = automationQueue - exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 7f5b542fa6..8a72e7cf8e 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -2,6 +2,14 @@ const env = require("../environment") const workerFarm = require("worker-farm") const { getAPIKey, update, Properties } = require("../utilities/usageQuota") const singleThread = require("./thread") +const { definitions } = require("./triggerInfo") +const webhooks = require("../api/controllers/webhook") +const CouchDB = require("../db") +const { queue } = require("./bullboard") +const newid = require("../db/newid") + +const WH_STEP_ID = definitions.WEBHOOK.stepId +const CRON_STEP_ID = definitions.CRON.stepId let workers = workerFarm(require.resolve("./thread")) @@ -54,3 +62,121 @@ exports.processEvent = async job => { return err } } + +// end the repetition and the job itself +exports.disableAllCrons = async appId => { + const promises = [] + const jobs = await queue.getRepeatableJobs() + for (let job of jobs) { + if (job.key.includes(`${appId}_cron`)) { + promises.push(queue.removeRepeatableByKey(job.key)) + promises.push(queue.removeJobs(job.id)) + } + } + return Promise.all(promises) +} + +/** + * This function handles checking of any cron jobs that need to be enabled/updated. + * @param {string} appId The ID of the app in which we are checking for webhooks + * @param {object|undefined} automation The automation object to be updated. + */ +exports.enableCronTrigger = async (appId, automation) => { + const trigger = automation ? automation.definition.trigger : null + function isCronTrigger(auto) { + return ( + auto && + auto.definition.trigger && + auto.definition.trigger.stepId === CRON_STEP_ID + ) + } + // need to create cron job + if (isCronTrigger(automation)) { + // make a job id rather than letting Bull decide, makes it easier to handle on way out + const jobId = `${appId}_cron_${newid()}` + const job = await queue.add( + { + automation, + event: { appId, timestamp: Date.now() }, + }, + { repeat: { cron: trigger.inputs.cron }, jobId } + ) + // Assign cron job ID from bull so we can remove it later if the cron trigger is removed + trigger.cronJobId = job.id + const db = new CouchDB(appId) + const response = await db.put(automation) + automation._id = response.id + automation._rev = response.rev + } + return automation +} + +/** + * This function handles checking if any webhooks need to be created or deleted for automations. + * @param {string} appId The ID of the app in which we are checking for webhooks + * @param {object|undefined} oldAuto The old automation object if updating/deleting + * @param {object|undefined} newAuto The new automation object if creating/updating + * @returns {Promise} After this is complete the new automation object may have been updated and should be + * written to DB (this does not write to DB as it would be wasteful to repeat). + */ +exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => { + const oldTrigger = oldAuto ? oldAuto.definition.trigger : null + const newTrigger = newAuto ? newAuto.definition.trigger : null + const triggerChanged = + oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id + function isWebhookTrigger(auto) { + return ( + auto && + auto.definition.trigger && + auto.definition.trigger.stepId === WH_STEP_ID + ) + } + // need to delete webhook + if ( + isWebhookTrigger(oldAuto) && + (!isWebhookTrigger(newAuto) || triggerChanged) && + oldTrigger.webhookId + ) { + try { + let db = new CouchDB(appId) + // need to get the webhook to get the rev + const webhook = await db.get(oldTrigger.webhookId) + const ctx = { + appId, + params: { id: webhook._id, rev: webhook._rev }, + } + // might be updating - reset the inputs to remove the URLs + if (newTrigger) { + delete newTrigger.webhookId + newTrigger.inputs = {} + } + await webhooks.destroy(ctx) + } catch (err) { + // don't worry about not being able to delete, if it doesn't exist all good + } + } + // need to create webhook + if ( + (!isWebhookTrigger(oldAuto) || triggerChanged) && + isWebhookTrigger(newAuto) + ) { + const ctx = { + appId, + request: { + body: new webhooks.Webhook( + "Automation webhook", + webhooks.WebhookType.AUTOMATION, + newAuto._id + ), + }, + } + await webhooks.save(ctx) + const id = ctx.body.webhook._id + newTrigger.webhookId = id + newTrigger.inputs = { + schemaUrl: `api/webhooks/schema/${appId}/${id}`, + triggerUrl: `api/webhooks/trigger/${appId}/${id}`, + } + } + return newAuto +} diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index bc7b5b368f..bea58fd260 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -123,5 +123,10 @@ exports.BaseQueryVerbs = { DELETE: "delete", } +exports.MetadataTypes = { + AUTOMATION_TEST_INPUT: "automationTestInput", + AUTOMATION_TEST_HISTORY: "automationTestHistory", +} + // pass through the list from the auth/core lib exports.ObjectStoreBuckets = ObjectStoreBuckets diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 987e9f58f8..6cdf14204f 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -37,6 +37,7 @@ const DocumentTypes = { DATASOURCE_PLUS: "datasource_plus", QUERY: "query", DEPLOYMENTS: "deployments", + METADATA: "metadata", } const ViewNames = { @@ -334,6 +335,18 @@ exports.getQueryParams = (datasourceId = null, otherProps = {}) => { ) } +exports.generateMetadataID = (type, entityId) => { + return `${DocumentTypes.METADATA}${SEPARATOR}${type}${SEPARATOR}${entityId}` +} + +exports.getMetadataParams = (type, entityId = null, otherProps = {}) => { + let docId = `${type}${SEPARATOR}` + if (entityId != null) { + docId += entityId + } + return getDocParams(DocumentTypes.METADATA, docId, otherProps) +} + /** * This can be used with the db.allDocs to get a list of IDs */ diff --git a/packages/server/src/middleware/appInfo.js b/packages/server/src/middleware/appInfo.js new file mode 100644 index 0000000000..ee3655c6cc --- /dev/null +++ b/packages/server/src/middleware/appInfo.js @@ -0,0 +1,19 @@ +const { isDevAppID, isProdAppID } = require("../db/utils") + +exports.AppType = { + DEV: "dev", + PROD: "prod", +} + +exports.middleware = + ({ appType } = {}) => + (ctx, next) => { + const appId = ctx.appId + if (appType === exports.AppType.DEV && appId && !isDevAppID(appId)) { + ctx.throw(400, "Only apps in development support this endpoint") + } + if (appType === exports.AppType.PROD && appId && !isProdAppID(appId)) { + ctx.throw(400, "Only apps in production support this endpoint") + } + return next() + } diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index dec17ab284..3f44f05a73 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,8 @@ const env = require("../environment") const { OBJ_STORE_DIRECTORY } = require("../constants") const { sanitizeKey } = require("@budibase/auth/src/objectStore") +const CouchDB = require("../db") +const { generateMetadataID } = require("../db/utils") const BB_CDN = "https://cdn.budi.live" @@ -55,3 +57,26 @@ exports.attachmentsRelativeURL = attachmentKey => { `${exports.objectStoreUrl()}/${attachmentKey}` ) } + +exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { + const db = new CouchDB(appId) + const id = generateMetadataID(type, entityId) + // read it to see if it exists, we'll overwrite it no matter what + let rev + try { + const oldMetadata = await db.get(id) + rev = oldMetadata._rev + } catch (err) { + rev = null + } + metadata._id = id + if (rev) { + metadata._rev = rev + } + const response = await db.put(metadata) + return { + ...metadata, + _id: id, + _rev: response.rev, + } +} From c337a855cc9b6d23823bdfbdac0c285d179aefda Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 9 Sep 2021 12:23:52 +0100 Subject: [PATCH 09/31] Saving progress towards re-working CTX usage in automation steps. --- packages/server/src/api/controllers/query.js | 2 -- .../src/automations/steps/executeQuery.js | 20 +++++++----------- .../src/automations/steps/executeScript.js | 20 +++++++----------- .../server/src/automations/steps/utils.js | 21 +++++++++++++++++++ 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index d584b73e20..737c8c9b01 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -160,8 +160,6 @@ exports.execute = async function (ctx) { ) const integration = new Integration(datasource.config) - console.log(query) - // ctx.body = {} // call the relevant CRUD method on the integration class ctx.body = formatResponse(await integration[query.queryVerb](enrichedQuery)) // cleanup diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index f34d994183..1dcb75d0a3 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -1,4 +1,5 @@ const queryController = require("../../api/controllers/query") +const { buildCtx } = require("./utils") exports.definition = { name: "External Data Connector", @@ -54,25 +55,20 @@ exports.run = async function ({ inputs, appId, emitter }) { const { queryId, ...rest } = inputs.query - const ctx = { + const ctx = buildCtx(appId, emitter, { + body: { + parameters: rest, + }, params: { queryId, }, - request: { - body: { - parameters: rest, - }, - }, - appId, - eventEmitter: emitter, - } - - await queryController.execute(ctx) + }) try { + await queryController.execute(ctx) return { response: ctx.body, - success: ctx.status === 200, + success: true, } } catch (err) { return { diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index b320e38700..68a1071fd1 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -1,4 +1,5 @@ const scriptController = require("../../api/controllers/script") +const { buildCtx } = require("./utils") exports.definition = { name: "JS Scripting", @@ -23,8 +24,7 @@ exports.definition = { properties: { value: { type: "string", - description: - "The result of the last statement of the executed script.", + description: "The result of the return statement", }, success: { type: "boolean", @@ -46,21 +46,17 @@ exports.run = async function ({ inputs, appId, context, emitter }) { } } - const ctx = { - request: { - body: { - script: inputs.code, - context, - }, + const ctx = buildCtx(appId, emitter, { + body: { + script: inputs.code, + context, }, - user: { appId }, - eventEmitter: emitter, - } + }) try { await scriptController.execute(ctx) return { - success: ctx.status === 200, + success: true, value: ctx.body, } } catch (err) { diff --git a/packages/server/src/automations/steps/utils.js b/packages/server/src/automations/steps/utils.js index 0429c71ca7..83673d0878 100644 --- a/packages/server/src/automations/steps/utils.js +++ b/packages/server/src/automations/steps/utils.js @@ -16,3 +16,24 @@ exports.getFetchResponse = async fetched => { } return { status, message } } + +// need to make sure all ctx structures have the +// throw added to them, so that controllers don't +// throw a ctx.throw undefined when error occurs +exports.buildCtx = (appId, emitter, { body, params } = {}) => { + const ctx = { + appId, + user: { appId }, + eventEmitter: emitter, + throw: (code, error) => { + throw error + }, + } + if (body) { + ctx.request = { body } + } + if (params) { + ctx.params = params + } + return ctx +} From a7576d86ba1ef73b5c9d35241c921ade7d746783 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 9 Sep 2021 12:32:16 +0100 Subject: [PATCH 10/31] Add new redis key for email verification --- packages/auth/src/redis/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index 415dcbf463..17ecc51bc5 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -8,6 +8,7 @@ const REDIS_PASSWORD = !env.REDIS_PASSWORD ? "budibase" : env.REDIS_PASSWORD exports.Databases = { PW_RESETS: "pwReset", + VERIFICATIONS: "verification", INVITATIONS: "invitation", DEV_LOCKS: "devLocks", DEBOUNCE: "debounce", From 93dcc641310f69b2a4025b577c8a3c1c15dfd881 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 9 Sep 2021 17:08:27 +0100 Subject: [PATCH 11/31] Add pre-hased password option to admin creation --- .../worker/src/api/controllers/global/users.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 24b00fe3a6..7fd367964c 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -33,7 +33,7 @@ async function allUsers() { return response.rows.map(row => row.doc) } -async function saveUser(user, tenantId) { +async function saveUser(user, tenantId, hashPassword = true) { if (!tenantId) { throw "No tenancy specified." } @@ -56,7 +56,7 @@ async function saveUser(user, tenantId) { // get the password, make sure one is defined let hashedPassword if (password) { - hashedPassword = await hash(password) + hashedPassword = hashPassword ? await hash(password) : password } else if (dbUser) { hashedPassword = dbUser.password } else { @@ -110,6 +110,15 @@ exports.save = async ctx => { exports.adminUser = async ctx => { const { email, password, tenantId } = ctx.request.body + + // account portal sends a pre-hashed password - honour param to prevent double hashing + let hashPassword = ctx.request.query.hashPassword + if (hashPassword && hashPassword == "false") { + hashPassword = false + } else { + hashPassword = true + } + if (await doesTenantExist(tenantId)) { ctx.throw(403, "Organisation already exists.") } @@ -141,7 +150,7 @@ exports.adminUser = async ctx => { tenantId, } try { - ctx.body = await saveUser(user, tenantId) + ctx.body = await saveUser(user, tenantId, hashPassword) } catch (err) { ctx.throw(err.status || 400, err) } From c77f9e91e80ee66236350724980c357e411dd499 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 9 Sep 2021 17:58:10 +0100 Subject: [PATCH 12/31] Updating client yarn lock. --- packages/client/yarn.lock | 1190 +------------------------------------ 1 file changed, 9 insertions(+), 1181 deletions(-) diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index cec5dc9a5d..27c542cf09 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@adobe/spectrum-css-workflow-icons@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4" - integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w== - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" @@ -28,94 +23,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@budibase/bbui@^0.9.117-alpha.2": - version "0.9.117" - resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.117.tgz#89a5e2d89e8f225d0b8357a582bb1c57ba95addb" - integrity sha512-OXNPizbHxM5Jh6q20NptCUvNXKjaMcJognUb4LWAXhpcfmNZ8Yt/+eijAhZhyp+lFMYMS3D1NvVWAGraH1Xh6A== - dependencies: - "@adobe/spectrum-css-workflow-icons" "^1.2.1" - "@spectrum-css/actionbutton" "^1.0.1" - "@spectrum-css/actiongroup" "^1.0.1" - "@spectrum-css/avatar" "^3.0.2" - "@spectrum-css/button" "^3.0.1" - "@spectrum-css/buttongroup" "^3.0.2" - "@spectrum-css/checkbox" "^3.0.2" - "@spectrum-css/dialog" "^3.0.1" - "@spectrum-css/divider" "^1.0.3" - "@spectrum-css/dropzone" "^3.0.2" - "@spectrum-css/fieldgroup" "^3.0.2" - "@spectrum-css/fieldlabel" "^3.0.1" - "@spectrum-css/icon" "^3.0.1" - "@spectrum-css/illustratedmessage" "^3.0.2" - "@spectrum-css/inputgroup" "^3.0.2" - "@spectrum-css/label" "^2.0.10" - "@spectrum-css/link" "^3.1.1" - "@spectrum-css/menu" "^3.0.1" - "@spectrum-css/modal" "^3.0.1" - "@spectrum-css/pagination" "^3.0.3" - "@spectrum-css/picker" "^1.0.1" - "@spectrum-css/popover" "^3.0.1" - "@spectrum-css/progressbar" "^1.0.2" - "@spectrum-css/progresscircle" "^1.0.2" - "@spectrum-css/radio" "^3.0.2" - "@spectrum-css/search" "^3.0.2" - "@spectrum-css/sidenav" "^3.0.2" - "@spectrum-css/statuslight" "^3.0.2" - "@spectrum-css/stepper" "^3.0.3" - "@spectrum-css/switch" "^1.0.2" - "@spectrum-css/table" "^3.0.1" - "@spectrum-css/tabs" "^3.0.1" - "@spectrum-css/tags" "^3.0.2" - "@spectrum-css/textfield" "^3.0.1" - "@spectrum-css/toast" "^3.0.1" - "@spectrum-css/tooltip" "^3.0.3" - "@spectrum-css/treeview" "^3.0.2" - "@spectrum-css/typography" "^3.0.1" - "@spectrum-css/underlay" "^2.0.9" - "@spectrum-css/vars" "^3.0.1" - dayjs "^1.10.4" - svelte-flatpickr "^3.1.0" - svelte-portal "^1.0.0" - -"@budibase/handlebars-helpers@^0.11.4": - version "0.11.5" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.5.tgz#e9cc90a44e94ad536992cf10906829b633e94bc5" - integrity sha512-ZxpyNtTHxS8Y+yTicbgWvYDAydooUSjOf3Y+wmTE2d4NpDgO0g0IjepLfZV+KASv9XBr//ylJdjE4hClX9NTFw== - dependencies: - array-sort "^1.0.0" - define-property "^2.0.2" - extend-shallow "^3.0.2" - "falsey" "^1.0.0" - for-in "^1.0.2" - get-object "^0.2.0" - get-value "^3.0.1" - handlebars "^4.7.7" - handlebars-utils "^1.0.6" - has-value "^2.0.2" - helper-date "^1.0.1" - helper-markdown "^1.0.0" - helper-md "^0.2.2" - html-tag "^2.0.0" - is-even "^1.0.0" - is-glob "^4.0.1" - kind-of "^6.0.3" - micromatch "^3.1.5" - relative "^3.0.2" - striptags "^3.1.1" - to-gfm-code-block "^0.1.1" - year "^0.2.1" - -"@budibase/string-templates@^0.9.117-alpha.2": - version "0.9.117" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.117.tgz#7106cc09497083128916ecee2e0208173e9fd27b" - integrity sha512-vaVS1EfiMFmRVErYM5ch/yNMN/OOcJhjvFeEmNDcjCfH9cw+yeA2d/rbZ4LedXgT4itMDvvybJkohqgD2JodYg== - dependencies: - "@budibase/handlebars-helpers" "^0.11.4" - dayjs "^1.10.4" - handlebars "^4.7.6" - handlebars-utils "^1.0.6" - lodash "^4.17.20" - "@rollup/plugin-alias@^3.1.5": version "3.1.5" resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-3.1.5.tgz#73356a3a1eab2e1e2fd952f9f53cd89fc740d952" @@ -157,46 +64,16 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@spectrum-css/actionbutton@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.3.tgz#8f7342a69b303c5acdcfa0a59f5e9267b9f3cb30" - integrity sha512-P9qoCPSiZ1SB6ZYqK5hub0vGty00YYqonQE0KTjtb1i+T1nYR/87vWqVPERx9j63uhgZncjwFYaThTvRqye7eQ== - -"@spectrum-css/actiongroup@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/actiongroup/-/actiongroup-1.0.3.tgz#4713ce65e6f5c72c404a7b638fbc3b4fd7e3874f" - integrity sha512-NlB9Q4ZlWixXxymoPIYG6g2hkwAGKFodHWPFfxHD8ddkjXWRB9G2akUP7cfsJ4DcYn4VisUORCOYQwqDiSmboQ== - -"@spectrum-css/avatar@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/avatar/-/avatar-3.0.2.tgz#4f1826223eae330e64b6d3cc899e9bc2e98dac95" - integrity sha512-wEczvSqxttTWSiL3cOvXV/RmGRwSkw2w6+slcHhnf0kb7ovymMM+9oz8vvEpEsSeo5u598bc+7ktrKFpAd6soQ== - -"@spectrum-css/button@^3.0.1", "@spectrum-css/button@^3.0.3": +"@spectrum-css/button@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.3.tgz#2df1efaab6c7e0b3b06cb4b59e1eae59c7f1fc84" integrity sha512-6CnLPqqtaU/PcSSIGeGRi0iFIIxIUByYLKFO6zn5NEUc12KQ28dJ4PLwB6WBa0L8vRoAGlnWWH2ZZweTijbXgg== -"@spectrum-css/buttongroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-3.0.3.tgz#719d868845ac9d2c4f939c1b9f6044507902d5aa" - integrity sha512-eXl8U4QWMWXqyTu654FdQdEGnmczgOYlpIFSHyCMVjhtPqZp2xwnLFiGh6LKw+bLio6eeOZ0L+vpk1GcoYqgkw== - "@spectrum-css/card@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-3.0.3.tgz#56b2e2da6b80c1583228baa279de7407383bfb6b" integrity sha512-+oKLUI2a0QmQP9EzySeq/G4FpUkkdaDNbuEbqCj2IkPMc/2v/nwzsPhh1fj2UIghGAiiUwXfPpzax1e8fyhQUg== -"@spectrum-css/checkbox@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.3.tgz#8577067fc8b97e4609f92bd242364937a533a7bb" - integrity sha512-QVG9uMHq+lh70Dh6mDNnY+vEvNz2p7VC6xgLfDYfijp2qeiqYPq72fQK6p/SiyqPk96ZACzNRwgeROU6Xf6Wgg== - -"@spectrum-css/dialog@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/dialog/-/dialog-3.0.3.tgz#7715a4ea435e753afb623d99ca5917ed1bcd6f34" - integrity sha512-AhmKgfRIVyTe3ABiJ8lLUQL34VB/H6fN16na2LlbDRJvyRMzkdN1Xf2i6U3f4OMd3qQ8Gm5xat4BvMxIQPBAUQ== - "@spectrum-css/divider@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@spectrum-css/divider/-/divider-1.0.3.tgz#639e2ebaa0834efa40f42397668bbd5c153ea385" @@ -204,56 +81,11 @@ dependencies: "@spectrum-css/vars" "^3.0.2" -"@spectrum-css/dropzone@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/dropzone/-/dropzone-3.0.3.tgz#aee71697a2c195947599d7541b858c3c198741dc" - integrity sha512-ujrswdtB6bHigklyHsm6zomFd6rUnKJ3xVVRjroVF4+ouK4DxK5tX0iVd0EW6AOfOjx4Cc28uyFot3fpxp+MQw== - -"@spectrum-css/fieldgroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.3.tgz#85d85da048d08200f25ceab378026dd2b11e0bb2" - integrity sha512-wXUXTXN1CPnR7M4Ltd+3uh7BfcNGUV1+Xe0/h0tCpq/j+S2Sd4xo7/pUMdU19sIDrAAtpEFp1tt+nouHcU5HGQ== - -"@spectrum-css/fieldlabel@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.3.tgz#f73c04d20734d4718ffb620dc46458904685b449" - integrity sha512-nEvIkEXCD5n4fW67Unq6Iu7VXoauEd/JGpfTY02VsC5p4FJLnwKfPDbJUuUsqClAxqw7nAsmXVKtn4zQFf5yPQ== - -"@spectrum-css/icon@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.3.tgz#5c612822380927087aebd526855d82ed2c3e2cba" - integrity sha512-hyloKOejPCXhP3MBNsm3SjR9j8xT1R1S19p32KW/0Qhj+VMUtfyEPmevyFptpn7wcovQccdl/vZVIVDuML/imw== - -"@spectrum-css/illustratedmessage@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/illustratedmessage/-/illustratedmessage-3.0.2.tgz#6a480be98b027e050b086e7899e40d87adb0a8c0" - integrity sha512-dqnE8X27bGcO0HN8+dYx8O4o0dNNIAqeivOzDHhe2El+V4dTzMrNIerF6G0NLm3GjVf6XliwmitsZK+K6FmbtA== - -"@spectrum-css/inputgroup@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.3.tgz#00c9a370ddc2c55cf0f37dd6069faa9501fd7eb5" - integrity sha512-FqRJTiLL7jiGfzDVXWUGVLqHryJjCcqQIrqAi+Tq0oenapzsBe2qc/zIrKgh2wtMI+NTIBLXTECvij3L1HlqAg== - -"@spectrum-css/label@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@spectrum-css/label/-/label-2.0.10.tgz#2368651d7636a19385b5d300cdf6272db1916001" - integrity sha512-xCbtEiQkZIlLdWFikuw7ifDCC21DOC/KMgVrrVJHXFc4KRQe9LTZSqmGF3tovm+CSq1adE59mYoTbojVQ9YuEQ== - -"@spectrum-css/link@^3.1.1", "@spectrum-css/link@^3.1.3": +"@spectrum-css/link@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@spectrum-css/link/-/link-3.1.3.tgz#b0e560a7e0acdb4a2b329b6669696aa3438f5993" integrity sha512-8Pmy5d73MwKcATTPaj+fSrZYnIw7GmfX40AvpC1xx5LauOxsbUb4AVNp1kn2H8rrOBmxF7czyhoBBhEiv66QUg== -"@spectrum-css/menu@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.3.tgz#46a9b221bb5f470a2f8a934bdfd512d84d2fdc4d" - integrity sha512-qKA9J/MrikNKIpCEHsAazG2vY3im5tjGCmo6p9Pdnu8/aQMsiuZDHZayukeCttJUZkrb9guDVL9OIHlK5RZvcQ== - -"@spectrum-css/modal@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/modal/-/modal-3.0.2.tgz#58b6621cab65f90788d310374f40df1f7090473f" - integrity sha512-YnIivJhoaao7Otu+HV7sgebPyFbO6sd/oMvTN/Rb2wwgnaMnIIuIRdGandSrcgotN2uNgs+P0knG6mv/xA1/dg== - "@spectrum-css/page@^3.0.1": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.2.tgz#8f0c03d25f5565fb13115541a8fcaf0e1d3a8ee0" @@ -261,106 +93,11 @@ dependencies: "@spectrum-css/vars" "^3.0.2" -"@spectrum-css/pagination@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/pagination/-/pagination-3.0.3.tgz#b204c3ada384c4af751a354bc428346d82eeea65" - integrity sha512-OJ/v9GeNXJOZ9Yr9LDBYPrR2NCiLOWP9wANT/a5sqFuugRnQbn/HYMnRp9TBxwpDY6ihaPo0T/wi7kLiAJFdDw== - -"@spectrum-css/picker@^1.0.1": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.3.tgz#21379bcf8ae94277deeb6ad65dcd9e2bbfacb487" - integrity sha512-oHLGxBx5BwVCSGo7/T1C9PTHX1+/5AmVjyLiTJ4UoIdSJmOERw9YcRZbcGZgBJNWbxcjr4TyGtwj1EcSjEy97w== - -"@spectrum-css/popover@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.3.tgz#6fb69873474fb968afb738eacb9e121f93e83a09" - integrity sha512-KvmXv4TV19FBx39KfmgVlDYtvtBqv/8RRK7RRLDDHGViTxZtShjVsVpwIgfkfgn4iJztCnXpWzFqRXWUu2XCpQ== - -"@spectrum-css/progressbar@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/progressbar/-/progressbar-1.0.3.tgz#f70bcc38a2a21cff2f422ec825724ebbb9455e67" - integrity sha512-vJHplefUuy8+NjCw1X7fLbqHVGNVBpvGFXNAeaIj4SFf4ygxiUq/5c9iRhhsCQixEsJlfD/b7BnGXU7BUDkr6Q== - -"@spectrum-css/progresscircle@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/progresscircle/-/progresscircle-1.0.2.tgz#258ea9170fb70f795edda03e38a61d93bef4487c" - integrity sha512-JLULpyzjIY95lzlWR1yE1gv4l1K6p+scQ+edmuZZUHBzwM3pUtkvHJmUlA9TYdResUYW6Uka60VRdY6lZ8gnFQ== - -"@spectrum-css/radio@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/radio/-/radio-3.0.3.tgz#25c3bc5e9c30a8a8ae728717b7c7fb736cdae640" - integrity sha512-LaLGfz/eGNR2iyqouXYILIA+pKRqF769iPdwM0REm5RpWvMQDD7rPZ/kWlg18owjaFsyllEp25gEjmhRJIIVOw== - -"@spectrum-css/search@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/search/-/search-3.0.3.tgz#3415dc106aca0d5dd996e87084a1b47c2b95a882" - integrity sha512-kdLpKTt0obljuhS1q1tukujRlvSs8sBwij76D4Qp8KpMzwePfZyvv1kYzuWPNZfTeISxWsmyZ6Wxd1uvzjn+UA== - -"@spectrum-css/sidenav@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/sidenav/-/sidenav-3.0.3.tgz#132141fbd2500a927c312fa4e1d712c438b3d597" - integrity sha512-cQ+CgwjxGftrcc79i1XnGd66QTl7H7zopSU0UTV4Qq7hvqfrjjWxfZ6b+3tezrrhNlDope1ff9o8sm67PsPXtg== - -"@spectrum-css/statuslight@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/statuslight/-/statuslight-3.0.2.tgz#dc54b6cd113413dcdb909c486b5d7bae60db65c5" - integrity sha512-xodB8g8vGJH20XmUj9ZsPlM1jHrGeRbvmVXkz0q7YvQrYAhim8pP3W+XKKZAletPFAuu8cmUOc6SWn6i4X4z6w== - -"@spectrum-css/stepper@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.3.tgz#ae89846886431e3edeee060207b8f81540f73a34" - integrity sha512-prAD61ImlOTs9b6PfB3cB08x4lAfxtvnW+RZiTYky0E8GgZdrc/MfCkL5/oqQaIQUtyQv/3Lb7ELAf/0K8QTXw== - -"@spectrum-css/switch@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/switch/-/switch-1.0.2.tgz#f0b4c69271964573e02b08e90998096e49e1de44" - integrity sha512-zqmHpgWPNg1gEwdUNFYV3CBX5JaeALfIqcJIxE0FLZqr9d1C4+oLE0ItIFzt1bwr4bFAOmkEpvtiY+amluzGxQ== - -"@spectrum-css/table@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/table/-/table-3.0.3.tgz#7f7f19905ef3275cbf907ce3a5818e63c30b2caf" - integrity sha512-nxwzVjLPsXoY/v4sdxOVYLcC+cEbGgJyLcLclT5LT9MGSbngFeUMJzzVR4EvehzuN4dH7hrATG7Mbuq29Mf0Hg== - -"@spectrum-css/tabs@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.0.3.tgz#51dd6f168c897b0fdc3a7e9f901df7bd2288b4fc" - integrity sha512-iLP1I72bJWz9APdQB1jiw+pOv5a7N+hYOCJvRoc56Us/hJKVzowkyGRe3uH+8v36nCG9bHxiAQNLoU8eXisVrg== - -"@spectrum-css/tags@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.3.tgz#fc76d2735cdc442de91b7eb3bee49a928c0767ac" - integrity sha512-SL8vPxVDfWcY5VdIuyl0TImEXcOU1I7yCyXkk7MudMwfnYs81FaIyY32hFV9OHj0Tz/36UzRzc7AVMSuRQ53pw== - -"@spectrum-css/textfield@^3.0.1": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.2.tgz#907f62d2dc82852dd6236a820be99e252b531631" - integrity sha512-nkFgAb0cP4jUodkUBErMNfyF78jJLtgL1Mrr9/rvGpGobo10IAbb8zZY4CkZ64o8XmMy/85+wZTKcx+KHatqpg== - -"@spectrum-css/toast@^3.0.1": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.3.tgz#97c1527384707600832ecda35643ed304615250f" - integrity sha512-CjLeaMs+cjUXojCCRtbj0YkD2BoZW16kjj2o5omkEpUTjA34IJ8xJ1a+CCtDILWekhXvN0MBN4sbumcnwcnx8w== - -"@spectrum-css/tooltip@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/tooltip/-/tooltip-3.0.3.tgz#26b8ca3b3d30e29630244d85eb4fc11d0c841281" - integrity sha512-ztRF7WW1FzyNavXBRc+80z67UoOrY9wl3cMYsVD3MpDnyxdzP8cjza1pCcolKBaFqRTcQKkxKw3GWtGICRKR5A== - -"@spectrum-css/treeview@^3.0.2": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.3.tgz#aeda5175158b9f8d7529cb2b394428eb2a428046" - integrity sha512-D5gGzZC/KtRArdx86Mesc9+99W9nTbUOeyYGqoJoAfJSOttoT6Tk5CrDvlCmAqjKf5rajemAkGri1ChqvUIwkw== - -"@spectrum-css/typography@^3.0.1", "@spectrum-css/typography@^3.0.2": +"@spectrum-css/typography@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38" integrity sha512-5ZOLmQe0edzsDMyhghUd4hBb5uxGsFrxzf+WasfcUw9klSfTsRZ09n1BsaaWbgrLjlMQ+EEHS46v5VNo0Ms2CA== -"@spectrum-css/underlay@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@spectrum-css/underlay/-/underlay-2.0.10.tgz#8b75b646605a311850f6620caa18d4996cd64ed7" - integrity sha512-PmsmkzeGD/rY4pp3ILXHt9w8BW7uaEqXe08hQRS7rGki7wqCpG4mE0/8N3yEcA3QxWQclmG9gdkg5uz6wMmYzA== - "@spectrum-css/vars@^3.0.1", "@spectrum-css/vars@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.2.tgz#ea9062c3c98dfc6ba59e5df14a03025ad8969999" @@ -474,42 +211,13 @@ apexcharts@^3.19.2, apexcharts@^3.22.1: svg.resize.js "^1.4.3" svg.select.js "^3.0.1" -argparse@^1.0.10, argparse@^1.0.7: +argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-sort@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" - integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== - dependencies: - default-compare "^1.0.0" - get-value "^2.0.6" - kind-of "^5.0.2" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -532,28 +240,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autolinker@~0.28.0: - version "0.28.1" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" - integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= - dependencies: - gulp-header "^1.7.1" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -569,19 +260,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -624,22 +302,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -744,21 +406,6 @@ builtin-modules@^3.1.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -836,16 +483,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - clone@~0.1.9: version "0.1.19" resolved "https://registry.yarnpkg.com/clone/-/clone-0.1.19.tgz#613fb68639b26a494ac53253e15b1a6bd88ada85" @@ -860,14 +497,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -930,11 +559,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -950,18 +574,13 @@ concat-stream@^1.4.4: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-with-sourcemaps@*, concat-with-sourcemaps@^1.1.0: +concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== dependencies: source-map "^0.6.1" -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1198,42 +817,16 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date.js@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" - integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw== - dependencies: - debug "~3.1.0" - -dayjs@^1.10.4, dayjs@^1.10.5: +dayjs@^1.10.5: version "1.10.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - decimal.js@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1244,13 +837,6 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -default-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" - integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== - dependencies: - kind-of "^5.0.2" - deferred-leveldown@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-0.2.0.tgz#2cef1f111e1c57870d8bbb8af2650e587cd2f5b4" @@ -1265,28 +851,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1380,11 +944,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -ent@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -1510,53 +1069,11 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1567,11 +1084,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -"falsey@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/falsey/-/falsey-1.0.0.tgz#71bdd775c24edad9f2f5c015ce8be24400bb5d7d" - integrity sha512-zMDNZ/Ipd8MY0+346CPvhzP1AsiVyNfTOayJza4reAIWf72xbkuFUDcJNxSAsQE1b9Bu0wijKb8Ngnh/a7fI5w== - fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -1587,26 +1099,11 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - flatpickr@^4.5.2: version "4.6.9" resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - foreach@~2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -1626,18 +1123,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -1685,26 +1170,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-object@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" - integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw= - dependencies: - is-number "^2.0.2" - isobject "^0.2.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -get-value@^3.0.0, get-value@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" - integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== - dependencies: - isobject "^3.0.1" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1729,35 +1194,6 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -gulp-header@^1.7.1: - version "1.8.12" - resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" - integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== - dependencies: - concat-with-sourcemaps "*" - lodash.template "^4.4.0" - through2 "^2.0.0" - -handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" - integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== - dependencies: - kind-of "^6.0.0" - typeof-article "^0.1.1" - -handlebars@^4.7.6, handlebars@^4.7.7: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.0" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -1791,52 +1227,6 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-value@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" - integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== - dependencies: - get-value "^3.0.0" - has-values "^2.0.1" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-values@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" - integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== - dependencies: - kind-of "^6.0.2" - has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1861,44 +1251,11 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -helper-date@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/helper-date/-/helper-date-1.0.1.tgz#12fedea3ad8e44a7ca4c4efb0ff4104a5120cffb" - integrity sha512-wU3VOwwTJvGr/w5rZr3cprPHO+hIhlblTJHD6aFBrKLuNbf4lAmkawd2iK3c6NbJEvY7HAmDpqjOFSI5/+Ey2w== - dependencies: - date.js "^0.3.1" - handlebars-utils "^1.0.4" - moment "^2.18.1" - -helper-markdown@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/helper-markdown/-/helper-markdown-1.0.0.tgz#ee7e9fc554675007d37eb90f7853b13ce74f3e10" - integrity sha512-AnDqMS4ejkQK0MXze7pA9TM3pu01ZY+XXsES6gEE0RmCGk5/NIfvTn0NmItfyDOjRAzyo9z6X7YHbHX4PzIvOA== - dependencies: - handlebars-utils "^1.0.2" - highlight.js "^9.12.0" - remarkable "^1.7.1" - -helper-md@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" - integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8= - dependencies: - ent "^2.2.0" - extend-shallow "^2.0.1" - fs-exists-sync "^0.1.0" - remarkable "^1.6.2" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.12.0: - version "9.18.5" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" - integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -1930,14 +1287,6 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -html-tag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" - integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== - dependencies: - is-self-closing "^1.0.1" - kind-of "^6.0.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2032,20 +1381,6 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2068,11 +1403,6 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" @@ -2097,79 +1427,16 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-even@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" - integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY= - dependencies: - is-odd "^0.1.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -2185,20 +1452,6 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== -is-number@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" @@ -2209,20 +1462,6 @@ is-object@~0.1.2: resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" integrity sha1-AO+8CIFsM8/ErIJR0TLhDcZQmNc= -is-odd@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" - integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc= - dependencies: - is-number "^3.0.0" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-potential-custom-element-name@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" @@ -2248,13 +1487,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-self-closing@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" - integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== - dependencies: - self-closing-tags "^1.0.1" - is-string@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" @@ -2279,11 +1511,6 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - is@~0.2.6: version "0.2.7" resolved "https://registry.yarnpkg.com/is/-/is-0.2.7.tgz#3b34a2c48f359972f35042849193ae7264b63562" @@ -2294,7 +1521,7 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2304,23 +1531,6 @@ isbuffer@~0.0.0: resolved "https://registry.yarnpkg.com/isbuffer/-/isbuffer-0.0.0.tgz#38c146d9df528b8bf9b0701c3d43cf12df3fc39b" integrity sha1-OMFG2d9Si4v5sHAcPUPPEt8/w5s= -isobject@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" - integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2434,30 +1644,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0, kind-of@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - level-blobs@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/level-blobs/-/level-blobs-0.1.7.tgz#9ab9b97bb99f1edbf9f78a3433e21ed56386bdaf" @@ -2565,11 +1751,6 @@ loader-utils@^1.1.0: emojis-list "^3.0.0" json5 "^1.0.1" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -2585,27 +1766,12 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.template@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.19, lodash@^4.17.20: +lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2629,18 +1795,6 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -2665,25 +1819,6 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -micromatch@^3.1.5: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -2726,14 +1861,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -2741,16 +1868,6 @@ mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -moment@^2.18.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - nanoid@^2.1.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" @@ -2761,28 +1878,6 @@ nanoid@^3.1.22: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -neo-async@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -2810,15 +1905,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -2843,13 +1929,6 @@ object-keys@~0.4.0: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -2869,13 +1948,6 @@ object.getownpropertydescriptors@^2.1.0: define-properties "^1.1.3" es-abstract "^1.18.0-next.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - object.values@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" @@ -2971,11 +2043,6 @@ parse5@5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -3017,11 +2084,6 @@ pify@^5.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - postcss-calc@^7.0.1: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" @@ -3453,7 +2515,7 @@ readable-stream@^1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@^2.2.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -3485,44 +2547,11 @@ readable-stream@~1.0.26, readable-stream@~1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexparam@1.3.0, regexparam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexparam/-/regexparam-1.3.0.tgz#2fe42c93e32a40eff6235d635e0ffa344b92965f" integrity sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g== -relative@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" - integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8= - dependencies: - isobject "^2.0.0" - -remarkable@^1.6.2, remarkable@^1.7.1: - version "1.7.4" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" - integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== - dependencies: - argparse "^1.0.10" - autolinker "~0.28.0" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" @@ -3585,11 +2614,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - resolve@^1.17.0, resolve@^1.19.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -3598,11 +2622,6 @@ resolve@^1.17.0, resolve@^1.19.0: is-core-module "^2.2.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -3731,13 +2750,6 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3755,11 +2767,6 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" -self-closing-tags@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" - integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== - semver@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52" @@ -3772,16 +2779,6 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -3809,47 +2806,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -3858,16 +2814,6 @@ source-map-support@~0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -3883,13 +2829,6 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3915,14 +2854,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -3973,11 +2904,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -striptags@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" - integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== - style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" @@ -4027,11 +2953,6 @@ svelte-flatpickr@^3.1.0: dependencies: flatpickr "^4.5.2" -svelte-portal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3" - integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q== - svelte-spa-router@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.0.5.tgz#097506578ac2371c9556b9789bd397d408968d92" @@ -4132,49 +3053,11 @@ terser@^5.0.0: source-map "~0.7.2" source-map-support "~0.5.19" -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -to-gfm-code-block@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" - integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -4228,18 +3111,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeof-article@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" - integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8= - dependencies: - kind-of "^3.1.0" - -uglify-js@^3.1.4: - version "3.14.1" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06" - integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g== - unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -4250,16 +3121,6 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -4280,14 +3141,6 @@ unquote@~1.1.1: resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - uri-js@^4.2.2: version "4.4.0" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" @@ -4295,16 +3148,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4405,11 +3248,6 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -4455,17 +3293,7 @@ xtend@~3.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo= -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -year@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" - integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= From fce3a6bbe7e39548fbae172654cc4157e6c1ec38 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 10 Sep 2021 13:52:41 +0100 Subject: [PATCH 13/31] Adding some basic test cases for the metadata API, testing that automation tests do store history. --- .../server/src/api/controllers/automation.js | 17 +++-- .../server/src/api/controllers/metadata.js | 19 +++++- packages/server/src/api/routes/index.js | 2 + packages/server/src/api/routes/metadata.js | 6 ++ .../src/api/routes/tests/automation.spec.js | 12 +--- .../src/api/routes/tests/metadata.spec.js | 64 +++++++++++++++++++ .../routes/tests/utilities/TestFunctions.js | 18 ++++++ packages/server/src/automations/utils.js | 20 ++++++ packages/server/src/utilities/index.js | 13 +++- 9 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 packages/server/src/api/routes/tests/metadata.spec.js diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 1dcbbd6874..36a7b4970e 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -3,9 +3,10 @@ const actions = require("../../automations/actions") const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") const { getAutomationParams, generateAutomationID } = require("../../db/utils") -const { saveEntityMetadata } = require("../../utilities") -const { MetadataTypes } = require("../../constants") -const { checkForWebhooks } = require("../../automations/utils") +const { + checkForWebhooks, + updateTestHistory, +} = require("../../automations/utils") /************************* * * @@ -171,11 +172,9 @@ exports.test = async function (ctx) { { getResponses: true } ) // save a test history run - await saveEntityMetadata( - ctx.appId, - MetadataTypes.AUTOMATION_TEST_HISTORY, - automation._id, - ctx.request.body - ) + await updateTestHistory(ctx.appId, automation, { + ...ctx.request.body, + occurredAt: new Date().toISOString(), + }) ctx.body = response } diff --git a/packages/server/src/api/controllers/metadata.js b/packages/server/src/api/controllers/metadata.js index d5fa2b94cb..7e098b6586 100644 --- a/packages/server/src/api/controllers/metadata.js +++ b/packages/server/src/api/controllers/metadata.js @@ -14,7 +14,12 @@ exports.saveMetadata = async ctx => { if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) { ctx.throw(400, "Cannot save automation history type") } - await saveEntityMetadata(ctx.appId, type, entityId, ctx.request.body) + ctx.body = await saveEntityMetadata( + ctx.appId, + type, + entityId, + ctx.request.body + ) } exports.deleteMetadata = async ctx => { @@ -34,7 +39,7 @@ exports.deleteMetadata = async ctx => { await db.remove(id, rev) } ctx.body = { - message: "Metadata deleted successfully.", + message: "Metadata deleted successfully", } } @@ -42,5 +47,13 @@ exports.getMetadata = async ctx => { const { type, entityId } = ctx.params const db = new CouchDB(ctx.appId) const id = generateMetadataID(type, entityId) - ctx.body = await db.get(id) + try { + ctx.body = await db.get(id) + } catch (err) { + if (err.status === 404) { + ctx.body = {} + } else { + ctx.throw(err.status, err) + } + } } diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 0b09a78bb8..2e1353df98 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -22,6 +22,7 @@ const datasourceRoutes = require("./datasource") const queryRoutes = require("./query") const hostingRoutes = require("./hosting") const backupRoutes = require("./backup") +const metadataRoutes = require("./metadata") const devRoutes = require("./dev") exports.mainRoutes = [ @@ -46,6 +47,7 @@ exports.mainRoutes = [ queryRoutes, hostingRoutes, backupRoutes, + metadataRoutes, devRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this diff --git a/packages/server/src/api/routes/metadata.js b/packages/server/src/api/routes/metadata.js index 7c6ee7a163..f4eba64358 100644 --- a/packages/server/src/api/routes/metadata.js +++ b/packages/server/src/api/routes/metadata.js @@ -4,27 +4,33 @@ const { middleware: appInfoMiddleware, AppType, } = require("../../middleware/appInfo") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("@budibase/auth/permissions") const router = Router() router .post( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.saveMetadata ) .delete( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.deleteMetadata ) .get( "/api/metadata/type", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.getTypes ) .get( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.getMetadata ) diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 09e8747813..28a3718f57 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -2,6 +2,7 @@ const { checkBuilderEndpoint, getAllTableRows, clearAllAutomations, + triggerAutomation, } = require("./utilities/TestFunctions") const setup = require("./utilities") const { basicAutomation } = setup.structures @@ -23,15 +24,6 @@ describe("/automations", () => { await config.init() }) - const triggerWorkflow = async automation => { - return await request - .post(`/api/automations/${automation._id}/trigger`) - .send({ name: "Test", description: "TEST" }) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) - } - describe("get definitions", () => { it("returns a list of definitions for actions", async () => { const res = await request @@ -168,7 +160,7 @@ describe("/automations", () => { automation.definition.steps[0].inputs.row.tableId = table._id automation = await config.createAutomation(automation) await setup.delay(500) - const res = await triggerWorkflow(automation) + const res = await triggerAutomation(config, automation) // this looks a bit mad but we don't actually have a way to wait for a response from the automation to // know that it has finished all of its actions - this is currently the best way // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works diff --git a/packages/server/src/api/routes/tests/metadata.spec.js b/packages/server/src/api/routes/tests/metadata.spec.js new file mode 100644 index 0000000000..68a7b5dbd3 --- /dev/null +++ b/packages/server/src/api/routes/tests/metadata.spec.js @@ -0,0 +1,64 @@ +const { testAutomation } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const { MetadataTypes } = require("../../../constants") + +describe("/metadata", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let automation + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + automation = await config.createAutomation() + }) + + async function createMetadata(data, type = MetadataTypes.AUTOMATION_TEST_INPUT) { + const res = await request + .post(`/api/metadata/${type}/${automation._id}`) + .send(data) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._rev).toBeDefined() + } + + async function getMetadata(type) { + const res = await request + .get(`/api/metadata/${type}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + return res.body + } + + describe("save", () => { + it("should be able to save some metadata", async () => { + await createMetadata({ test: "a" }) + const testInput = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(testInput.test).toBe("a") + }) + + it("should save history metadata on automation run", async () => { + // this should have created some history + await testAutomation(config, automation) + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY) + expect(metadata).toBeDefined() + expect(metadata.history.length).toBe(1) + }) + }) + + describe("destroy", () => { + it("should be able to delete some test inputs", async () => { + const res = await request + .delete(`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(metadata.test).toBeUndefined() + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 944d2ac527..ed5094679c 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -101,3 +101,21 @@ exports.checkPermissionsEndpoint = async ({ exports.getDB = config => { return new CouchDB(config.getAppId()) } + +exports.triggerAutomation = async (config, automation) => { + return await config.request + .post(`/api/automations/${automation._id}/trigger`) + .send({ name: "Test", description: "TEST" }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) +} + +exports.testAutomation = async (config, automation) => { + return await config.request + .post(`/api/automations/${automation._id}/test`) + .send({ name: "Test", description: "TEST" }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) +} diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 8a72e7cf8e..6815de3e62 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -7,6 +7,8 @@ const webhooks = require("../api/controllers/webhook") const CouchDB = require("../db") const { queue } = require("./bullboard") const newid = require("../db/newid") +const { updateEntityMetadata } = require("../utilities") +const { MetadataTypes } = require("../constants") const WH_STEP_ID = definitions.WEBHOOK.stepId const CRON_STEP_ID = definitions.CRON.stepId @@ -63,6 +65,24 @@ exports.processEvent = async job => { } } +exports.updateTestHistory = async (appId, automation, history) => { + return updateEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automation._id, + metadata => { + if (metadata && Array.isArray(metadata.history)) { + metadata.history.push(history) + } else { + metadata = { + history: [history], + } + } + return metadata + } + ) +} + // end the repetition and the job itself exports.disableAllCrons = async appId => { const promises = [] diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 3f44f05a73..114e1bd5e6 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -58,16 +58,19 @@ exports.attachmentsRelativeURL = attachmentKey => { ) } -exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { +exports.updateEntityMetadata = async (appId, type, entityId, updateFn) => { const db = new CouchDB(appId) const id = generateMetadataID(type, entityId) // read it to see if it exists, we'll overwrite it no matter what - let rev + let rev, + metadata = {} try { const oldMetadata = await db.get(id) rev = oldMetadata._rev + metadata = updateFn(oldMetadata) } catch (err) { rev = null + metadata = updateFn({}) } metadata._id = id if (rev) { @@ -80,3 +83,9 @@ exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { _rev: response.rev, } } + +exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { + return exports.updateEntityMetadata(appId, type, entityId, () => { + return metadata + }) +} From 3679579d53d1e62e9d4467ba2bd47fd4b342c73f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 10 Sep 2021 14:37:34 +0100 Subject: [PATCH 14/31] Introducing the concept of flagging an automation as 'in test' which means it can run with triggers and everything as it normally would in development. --- packages/auth/src/redis/utils.js | 1 + .../server/src/api/controllers/automation.js | 3 +++ .../src/api/routes/tests/automation.spec.js | 7 +++---- .../routes/tests/utilities/TestFunctions.js | 16 ++++++---------- packages/server/src/automations/triggers.js | 13 ++++++++----- packages/server/src/utilities/redis.js | 19 ++++++++++++++++++- 6 files changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index 09b4905298..c6702166ff 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -13,6 +13,7 @@ exports.Databases = { DEBOUNCE: "debounce", SESSIONS: "session", USER_CACHE: "users", + FLAGS: "flags", } exports.SEPARATOR = SEPARATOR diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 36a7b4970e..b8f6cc7351 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -7,6 +7,7 @@ const { checkForWebhooks, updateTestHistory, } = require("../../automations/utils") +const { setTestFlag, clearTestFlag } = require("../../utilities/redis") /************************* * * @@ -163,6 +164,7 @@ exports.test = async function (ctx) { const appId = ctx.appId const db = new CouchDB(appId) let automation = await db.get(ctx.params.id) + await setTestFlag(automation._id) const response = await triggers.externalTrigger( automation, { @@ -176,5 +178,6 @@ exports.test = async function (ctx) { ...ctx.request.body, occurredAt: new Date().toISOString(), }) + await clearTestFlag(automation._id) ctx.body = response } diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 28a3718f57..1c920848a7 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -2,7 +2,7 @@ const { checkBuilderEndpoint, getAllTableRows, clearAllAutomations, - triggerAutomation, + testAutomation, } = require("./utilities/TestFunctions") const setup = require("./utilities") const { basicAutomation } = setup.structures @@ -160,14 +160,13 @@ describe("/automations", () => { automation.definition.steps[0].inputs.row.tableId = table._id automation = await config.createAutomation(automation) await setup.delay(500) - const res = await triggerAutomation(config, automation) + const res = await testAutomation(config, automation) // this looks a bit mad but we don't actually have a way to wait for a response from the automation to // know that it has finished all of its actions - this is currently the best way // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works // TODO: update when workflow logs are a thing for (let tries = 0; tries < MAX_RETRIES; tries++) { - expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`) - expect(res.body.automation.name).toEqual(automation.name) + expect(res.body).toBeDefined() await setup.delay(500) let elements = await getAllTableRows(config) // don't test it unless there are values to test diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index ed5094679c..87190c3f76 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -102,19 +102,15 @@ exports.getDB = config => { return new CouchDB(config.getAppId()) } -exports.triggerAutomation = async (config, automation) => { - return await config.request - .post(`/api/automations/${automation._id}/trigger`) - .send({ name: "Test", description: "TEST" }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) -} - exports.testAutomation = async (config, automation) => { return await config.request .post(`/api/automations/${automation._id}/test`) - .send({ name: "Test", description: "TEST" }) + .send({ + row: { + name: "Test", + description: "TEST", + }, + }) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index b32747d319..db4b8304dd 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -7,6 +7,7 @@ const { isDevAppID } = require("../db/utils") // need this to call directly, so we can get a response const { processEvent } = require("./utils") const { queue } = require("./bullboard") +const { checkTestFlag } = require("../utilities/redis") const TRIGGER_DEFINITIONS = definitions @@ -14,11 +15,7 @@ async function queueRelevantRowAutomations(event, eventType) { if (event.appId == null) { throw `No appId specified for ${eventType} - check event emitters.` } - // don't queue events which are for dev apps, only way to test automations is - // running tests on them - if (isDevAppID(event.appId)) { - return - } + const db = new CouchDB(event.appId) let automations = await db.allDocs( getAutomationParams(null, { include_docs: true }) @@ -33,6 +30,12 @@ async function queueRelevantRowAutomations(event, eventType) { }) for (let automation of automations) { + // don't queue events which are for dev apps, only way to test automations is + // running tests on them + // in production the test flag will never be checked due to lazy evaluation (first always false) + if (isDevAppID(event.appId) && !(await checkTestFlag(automation._id))) { + return + } let automationDef = automation.definition let automationTrigger = automationDef ? automationDef.trigger : {} if ( diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index 6f1cf46606..151feabdbb 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -2,20 +2,24 @@ const { Client, utils } = require("@budibase/auth/redis") const { getGlobalIDFromUserMetadataID } = require("../db/utils") const APP_DEV_LOCK_SECONDS = 600 -let devAppClient, debounceClient +const AUTOMATION_TEST_FLAG_SECONDS = 60 +let devAppClient, debounceClient, flagClient // we init this as we want to keep the connection open all the time // reduces the performance hit exports.init = async () => { devAppClient = new Client(utils.Databases.DEV_LOCKS) debounceClient = new Client(utils.Databases.DEBOUNCE) + flagClient = new Client(utils.Databases.FLAGS) await devAppClient.init() await debounceClient.init() + await flagClient.init() } exports.shutdown = async () => { if (devAppClient) await devAppClient.finish() if (debounceClient) await debounceClient.finish() + if (flagClient) await flagClient.finish() } exports.doesUserHaveLock = async (devAppId, user) => { @@ -67,3 +71,16 @@ exports.checkDebounce = async id => { exports.setDebounce = async (id, seconds) => { await debounceClient.store(id, "debouncing", seconds) } + +exports.setTestFlag = async id => { + await flagClient.store(id, { testing: true }, AUTOMATION_TEST_FLAG_SECONDS) +} + +exports.checkTestFlag = async id => { + const flag = await flagClient.get(id) + return !!(flag && flag.testing) +} + +exports.clearTestFlag = async id => { + await devAppClient.delete(id) +} From 6fd23ee51890dd0664f697aba29cc2132c3f8e91 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Mon, 13 Sep 2021 14:13:00 +0000 Subject: [PATCH 15/31] v0.9.125-alpha.3 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index 8970275c9c..590523679d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 23d8f899d4..bfdf834a83 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 91a16ae9c5..c2487752a6 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 0322ebeb6b..df2a3a4a2e 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.2", - "@budibase/client": "^0.9.125-alpha.2", + "@budibase/bbui": "^0.9.125-alpha.3", + "@budibase/client": "^0.9.125-alpha.3", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.125-alpha.2", + "@budibase/string-templates": "^0.9.125-alpha.3", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index fba3d90e6f..66a4294b2e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 18a43bad22..188a12c110 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.2", + "@budibase/bbui": "^0.9.125-alpha.3", "@budibase/standard-components": "^0.9.124", - "@budibase/string-templates": "^0.9.125-alpha.2", + "@budibase/string-templates": "^0.9.125-alpha.3", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index 4bcae1c619..e9f9422e56 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -61,9 +61,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.2", - "@budibase/client": "^0.9.125-alpha.2", - "@budibase/string-templates": "^0.9.125-alpha.2", + "@budibase/auth": "^0.9.125-alpha.3", + "@budibase/client": "^0.9.125-alpha.3", + "@budibase/string-templates": "^0.9.125-alpha.3", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index a1fd861be2..0ee5d0f030 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 23a65e36bd..36089d6300 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.125-alpha.2", + "version": "0.9.125-alpha.3", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -23,8 +23,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.2", - "@budibase/string-templates": "^0.9.125-alpha.2", + "@budibase/auth": "^0.9.125-alpha.3", + "@budibase/string-templates": "^0.9.125-alpha.3", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0", From a5ce11d3ca3a4e31c09ad2bbe41d2ff40a1b48c0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 16:28:52 +0100 Subject: [PATCH 16/31] Fixing issue #2412 - making sure full enriched records are passed along to automations. --- .../src/api/controllers/row/internal.js | 57 +++++++++++-------- packages/server/src/db/linkedRows/index.js | 42 +++++--------- .../server/src/db/tests/linkTests.spec.js | 12 ---- .../src/utilities/rowProcessor/index.js | 20 +++++-- 4 files changed, 65 insertions(+), 66 deletions(-) diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 84e06f5855..2299a20580 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -23,6 +23,19 @@ const CALCULATION_TYPES = { STATS: "stats", } +async function storeResponse(ctx, db, row, oldTable, table) { + row.type = "row" + const response = await db.put(row) + // don't worry about rev, tables handle rev/lastID updates + if (!isEqual(oldTable, table)) { + await db.put(table) + } + row._rev = response.rev + // process the row before return, to include relationships + row = await outputProcessing(ctx, table, row, { squash: false }) + return { row, table } +} + exports.patch = async ctx => { const appId = ctx.appId const db = new CouchDB(appId) @@ -77,14 +90,7 @@ exports.patch = async ctx => { return { row: ctx.body, table } } - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - row.type = "row" - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.save = async function (ctx) { @@ -118,14 +124,7 @@ exports.save = async function (ctx) { table, }) - row.type = "row" - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.fetchView = async ctx => { @@ -221,34 +220,47 @@ exports.destroy = async function (ctx) { const appId = ctx.appId const db = new CouchDB(appId) const { _id, _rev } = ctx.request.body - const row = await db.get(_id) + let row = await db.get(_id) if (row.tableId !== ctx.params.tableId) { throw "Supplied tableId doesn't match the row's tableId" } + const table = await db.get(row.tableId) + // update the row to include full relationships before deleting them + row = await outputProcessing(ctx, table, row, { squash: false }) + // now remove the relationships await linkRows.updateLinks({ appId, eventType: linkRows.EventType.ROW_DELETE, row, tableId: row.tableId, }) + + let response if (ctx.params.tableId === InternalTables.USER_METADATA) { ctx.params = { id: _id, } await userController.destroyMetadata(ctx) - return { response: ctx.body, row } + response = ctx.body } else { - const response = await db.remove(_id, _rev) - return { response, row } + response = await db.remove(_id, _rev) } + return { response, row } } exports.bulkDestroy = async ctx => { const appId = ctx.appId - const { rows } = ctx.request.body const db = new CouchDB(appId) + const tableId = ctx.params.tableId + const table = await db.get(tableId) + let { rows } = ctx.request.body + // before carrying out any updates, make sure the rows are ready to be returned + // they need to be the full rows (including previous relationships) for automations + rows = await outputProcessing(ctx, table, rows, { squash: false }) + + // remove the relationships first let updates = rows.map(row => linkRows.updateLinks({ appId, @@ -257,8 +269,7 @@ exports.bulkDestroy = async ctx => { tableId: row.tableId, }) ) - // TODO remove special user case in future - if (ctx.params.tableId === InternalTables.USER_METADATA) { + if (tableId === InternalTables.USER_METADATA) { updates = updates.concat( rows.map(row => { ctx.params = { diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 34be44336c..67412e7e89 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -36,6 +36,18 @@ exports.IncludeDocs = IncludeDocs exports.getLinkDocuments = getLinkDocuments exports.createLinkView = createLinkView +function clearRelationshipFields(table, rows) { + for (let [key, field] of Object.entries(table.schema)) { + if (field.type === FieldTypes.LINK) { + rows = rows.map(row => { + delete row[key] + return row + }) + } + } + return rows +} + async function getLinksForRows(appId, rows) { const tableIds = [...new Set(rows.map(el => el.tableId))] // start by getting all the link values for performance reasons @@ -126,33 +138,6 @@ exports.updateLinks = async function (args) { } } -/** - * Update a row with information about the links that pertain to it. - * @param {string} appId The instance in which this row has been created. - * @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be - * a single row object or an array of rows - both will be handled. - * @returns {Promise} The updated row (this may be the same if no links were found). If an array was input - * then an array will be output, object input -> object output. - */ -exports.attachLinkIDs = async (appId, rows) => { - const links = await getLinksForRows(appId, rows) - // now iterate through the rows and all field information - for (let row of rows) { - // find anything that matches the row's ID we are searching for and join it - links - .filter(el => el.thisId === row._id) - .forEach(link => { - if (row[link.fieldName] == null) { - row[link.fieldName] = [] - } - row[link.fieldName].push(link.id) - }) - } - // if it was an array when it came in then handle it as an array in response - // otherwise return the first element as there was only one input - return rows -} - /** * Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row. * This is required for formula fields, this may only be utilised internally (for now). @@ -173,6 +158,9 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) + // clear any existing links that could be dupe'd + rows = clearRelationshipFields(table, rows) + // now get the docs and combine into the rows let linked = await getFullLinkedDocs(ctx, appId, links) const linkedTables = [] for (let row of rows) { diff --git a/packages/server/src/db/tests/linkTests.spec.js b/packages/server/src/db/tests/linkTests.spec.js index 3fed6938b7..8dad7be049 100644 --- a/packages/server/src/db/tests/linkTests.spec.js +++ b/packages/server/src/db/tests/linkTests.spec.js @@ -59,16 +59,4 @@ describe("test link functionality", () => { expect(Array.isArray(output)).toBe(true) }) }) - - describe("attachLinkIDs", () => { - it("should be able to attach linkIDs", async () => { - await config.init() - await config.createTable() - const table = await config.createLinkedTable() - const row = await config.createRow() - const linkRow = await config.createRow(basicLinkedRow(table._id, row._id)) - const attached = await links.attachLinkIDs(config.getAppId(), [linkRow]) - expect(attached[0].link[0]).toBe(row._id) - }) - }) }) \ No newline at end of file diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js index 7cb3ac7e02..bb4ac98bb7 100644 --- a/packages/server/src/utilities/rowProcessor/index.js +++ b/packages/server/src/utilities/rowProcessor/index.js @@ -185,10 +185,16 @@ exports.inputProcessing = (user = {}, table, row) => { * @param {object} ctx the request which is looking for enriched rows. * @param {object} table the table from which these rows came from originally, this is used to determine * the schema of the rows and then enrich. - * @param {object[]} rows the rows which are to be enriched. - * @returns {object[]} the enriched rows will be returned. + * @param {object[]|object} rows the rows which are to be enriched. + * @param {object} opts used to set some options for the output, such as disabling relationship squashing. + * @returns {object[]|object} the enriched rows will be returned. */ -exports.outputProcessing = async (ctx, table, rows) => { +exports.outputProcessing = async ( + ctx, + table, + rows, + opts = { squash: true } +) => { const appId = ctx.appId let wasArray = true if (!(rows instanceof Array)) { @@ -214,6 +220,12 @@ exports.outputProcessing = async (ctx, table, rows) => { } } } - enriched = await linkRows.squashLinksToPrimaryDisplay(appId, table, enriched) + if (opts.squash) { + enriched = await linkRows.squashLinksToPrimaryDisplay( + appId, + table, + enriched + ) + } return wasArray ? enriched : enriched[0] } From e30587d1b5597d218520bde81a0adb05aec36fa1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 16:43:05 +0100 Subject: [PATCH 17/31] Removing live prop from JOI def. --- packages/server/src/api/routes/automation.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index cc52a2b6b6..279ad28278 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -26,7 +26,6 @@ function generateStepSchema(allowStepTypes) { tagline: Joi.string().required(), icon: Joi.string().required(), params: Joi.object(), - // TODO: validate args a bit more deeply args: Joi.object(), type: Joi.string().required().valid(...allowStepTypes), }).unknown(true) @@ -35,7 +34,6 @@ function generateStepSchema(allowStepTypes) { function generateValidator(existing = false) { // prettier-ignore return joiValidator.body(Joi.object({ - live: Joi.bool(), _id: existing ? Joi.string().required() : Joi.string(), _rev: existing ? Joi.string().required() : Joi.string(), name: Joi.string().required(), From 36739579e30a203d27e1ed9ace22349903ee55ab Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 17:08:15 +0100 Subject: [PATCH 18/31] Removing sendgrid test as sendgrid automation no longer supported. --- .../src/automations/tests/sendEmail.spec.js | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 packages/server/src/automations/tests/sendEmail.spec.js diff --git a/packages/server/src/automations/tests/sendEmail.spec.js b/packages/server/src/automations/tests/sendEmail.spec.js deleted file mode 100644 index 5f3aabfff8..0000000000 --- a/packages/server/src/automations/tests/sendEmail.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -const setup = require("./utilities") - -jest.mock("@sendgrid/mail") - -describe("test the send email action", () => { - let inputs - let config = setup.getConfig() - - beforeEach(async () => { - await config.init() - inputs = { - to: "me@test.com", - from: "budibase@test.com", - subject: "Testing", - text: "Email contents", - } - }) - - afterAll(setup.afterAll) - - it("should be able to run the action", async () => { - const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, inputs) - expect(res.success).toEqual(true) - // the mocked module throws back the input - expect(res.response.to).toEqual("me@test.com") - }) - - it("should return an error if input an invalid email address", async () => { - const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, { - ...inputs, - to: "invalid@test.com", - }) - expect(res.success).toEqual(false) - }) - -}) From ff39d247c8f7acfebac22c6ffbc3ba4e2449d40c Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 13 Sep 2021 17:38:12 +0100 Subject: [PATCH 19/31] Configurable user cache population in auth middleware --- packages/auth/src/cache/user.js | 24 +++++++++++++++++-- packages/auth/src/middleware/authenticated.js | 15 ++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index e0936182a7..7d0cf698eb 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -3,7 +3,27 @@ const { getTenantId, lookupTenantId, getGlobalDB } = require("../tenancy") const EXPIRY_SECONDS = 3600 -exports.getUser = async (userId, tenantId = null) => { +/** + * The default populate user function + */ +const populateFromDB = (userId, tenantId) => { + return getGlobalDB(tenantId).get(userId) +} + +/** + * Get the requested user by id. + * Use redis cache to first read the user. + * If not present fallback to loading the user directly and re-caching. + * @param {*} userId the id of the user to get + * @param {*} tenantId the tenant of the user to get + * @param {*} loadUser function to provide the user for re-caching. default to couch db + * @returns + */ +exports.getUser = async ( + userId, + tenantId = null, + populateUser = populateFromDB +) => { if (!tenantId) { try { tenantId = getTenantId() @@ -15,7 +35,7 @@ exports.getUser = async (userId, tenantId = null) => { // try cache let user = await client.get(userId) if (!user) { - user = await getGlobalDB(tenantId).get(userId) + user = await populateUser(userId, tenantId) client.store(userId, user, EXPIRY_SECONDS) } if (user && !user.tenantId && tenantId) { diff --git a/packages/auth/src/middleware/authenticated.js b/packages/auth/src/middleware/authenticated.js index e3705a9a24..944f3ee9d9 100644 --- a/packages/auth/src/middleware/authenticated.js +++ b/packages/auth/src/middleware/authenticated.js @@ -21,7 +21,10 @@ function finalise( * The tenancy modules should not be used here and it should be assumed that the tenancy context * has not yet been populated. */ -module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => { +module.exports = ( + noAuthPatterns = [], + opts = { publicAllowed: false, populateUser: null } +) => { const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : [] return async (ctx, next) => { let publicEndpoint = false @@ -46,7 +49,15 @@ module.exports = (noAuthPatterns = [], opts = { publicAllowed: false }) => { error = "No session found" } else { try { - user = await getUser(userId, session.tenantId) + if (opts && opts.populateUser) { + user = await getUser( + userId, + session.tenantId, + opts.populateUser(ctx) + ) + } else { + user = await getUser(userId, session.tenantId) + } delete user.password authenticated = true } catch (err) { From cba326fe6e5deef5e50102012d7a358f9da96bb9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 17:43:53 +0100 Subject: [PATCH 20/31] Fixing automation test cases. --- .../src/automations/tests/automation.spec.js | 39 ++----------------- packages/server/src/automations/triggers.js | 4 +- packages/server/src/automations/utils.js | 9 +++-- 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index 05ec44d70e..689ee4ab76 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -21,7 +21,7 @@ const automation = require("../index") const usageQuota = require("../../utilities/usageQuota") const thread = require("../thread") const triggers = require("../triggers") -const { basicAutomation, basicTable } = require("../../tests/utilities/structures") +const { basicAutomation } = require("../../tests/utilities/structures") const { wait } = require("../../utilities") const { makePartial } = require("../../tests/utilities") const { cleanInputValues } = require("../automationUtils") @@ -61,44 +61,13 @@ describe("Run through some parts of the automations system", () => { it("try error scenario", async () => { await setup.runInProd(async () => { // the second call will throw an error - await triggers.externalTrigger(basicAutomation(), { a: 1 }) + const response = await triggers.externalTrigger(basicAutomation(), {a: 1}, {getResponses: true}) await wait(100) expect(console.error).toHaveBeenCalled() + expect(response.err).toBeDefined() }) }) - it("should be able to check triggering row filling", async () => { - const automation = basicAutomation() - let table = basicTable() - table.schema.boolean = { - type: "boolean", - constraints: { - type: "boolean", - }, - } - table.schema.number = { - type: "number", - constraints: { - type: "number", - }, - } - table.schema.datetime = { - type: "datetime", - constraints: { - type: "datetime", - }, - } - table = await config.createTable(table) - automation.definition.trigger.inputs.tableId = table._id - const params = await triggers.fillRowOutput(automation, { appId: config.getAppId() }) - expect(params.row).toBeDefined() - const date = new Date(params.row.datetime) - expect(typeof params.row.name).toBe("string") - expect(typeof params.row.boolean).toBe("boolean") - expect(typeof params.row.number).toBe("number") - expect(date.getFullYear()).toBe(1970) - }) - it("should check coercion", async () => { const table = await config.createTable() const automation = basicAutomation() @@ -120,7 +89,7 @@ describe("Run through some parts of the automations system", () => { } } } - })) + }), expect.any(Function)) }) it("should be able to clean inputs with the utilities", () => { diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index db4b8304dd..d90511c7ba 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -5,9 +5,9 @@ const { coerce } = require("../utilities/rowProcessor") const { definitions } = require("./triggerInfo") const { isDevAppID } = require("../db/utils") // need this to call directly, so we can get a response -const { processEvent } = require("./utils") const { queue } = require("./bullboard") const { checkTestFlag } = require("../utilities/redis") +const utils = require("./utils") const TRIGGER_DEFINITIONS = definitions @@ -90,7 +90,7 @@ exports.externalTrigger = async function ( } const data = { automation, event: params } if (getResponses) { - return processEvent({ data }) + return utils.processEvent({ data }) } else { return queue.add(data) } diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 6815de3e62..29a8e38b18 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -52,16 +52,19 @@ exports.processEvent = async job => { if (env.USE_QUOTAS) { job.data.automation.apiKey = await updateQuota(job.data.automation) } + // need to actually await these so that an error can be captured properly + let response if (!env.isProd()) { - return runSingleThread(job) + response = await runSingleThread(job) } else { - return runWorker(job) + response = await runWorker(job) } + return response } catch (err) { console.error( `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` ) - return err + return { err } } } From 8d4311d232ecaf667a9e095814f7d3189c403c02 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 13 Sep 2021 17:45:37 +0100 Subject: [PATCH 21/31] Fix typo --- packages/auth/src/cache/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth/src/cache/user.js b/packages/auth/src/cache/user.js index 7d0cf698eb..669192a905 100644 --- a/packages/auth/src/cache/user.js +++ b/packages/auth/src/cache/user.js @@ -16,7 +16,7 @@ const populateFromDB = (userId, tenantId) => { * If not present fallback to loading the user directly and re-caching. * @param {*} userId the id of the user to get * @param {*} tenantId the tenant of the user to get - * @param {*} loadUser function to provide the user for re-caching. default to couch db + * @param {*} populateUser function to provide the user for re-caching. default to couch db * @returns */ exports.getUser = async ( From 421d98d5b7a4fad57bcb1f125f74b60cdc2c233a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 18:03:09 +0100 Subject: [PATCH 22/31] Adding functionality so that when an automation trigger is deleted its test inputs will also be deleted. --- .../server/src/api/controllers/automation.js | 39 ++++++++++++++++++- .../server/src/api/controllers/metadata.js | 17 +------- packages/server/src/utilities/index.js | 17 ++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index b8f6cc7351..93e3f493f5 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -7,6 +7,8 @@ const { checkForWebhooks, updateTestHistory, } = require("../../automations/utils") +const { deleteEntityMetadata } = require("../../utilities") +const { MetadataTypes } = require("../../constants") const { setTestFlag, clearTestFlag } = require("../../utilities/redis") /************************* @@ -15,6 +17,19 @@ const { setTestFlag, clearTestFlag } = require("../../utilities/redis") * * *************************/ +async function cleanupAutomationMetadata(appId, automationId) { + await deleteEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_INPUT, + automationId + ) + await deleteEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automationId + ) +} + function cleanAutomationInputs(automation) { if (automation == null) { return automation @@ -84,6 +99,23 @@ exports.update = async function (ctx) { const response = await db.put(automation) automation._rev = response.rev + const oldAutoTrigger = + oldAutomation && oldAutomation.definition.trigger + ? oldAutomation.definition.trigger + : {} + const newAutoTrigger = + automation && automation.definition.trigger + ? automation.definition.trigger + : {} + // trigger has been updated, remove the test inputs + if (oldAutoTrigger.id !== newAutoTrigger.id) { + await deleteEntityMetadata( + ctx.appId, + MetadataTypes.AUTOMATION_TEST_INPUT, + automation._id + ) + } + ctx.status = 200 ctx.body = { message: `Automation ${automation._id} updated successfully.`, @@ -112,12 +144,15 @@ exports.find = async function (ctx) { exports.destroy = async function (ctx) { const db = new CouchDB(ctx.appId) - const oldAutomation = await db.get(ctx.params.id) + const automationId = ctx.params.id + const oldAutomation = await db.get(automationId) await checkForWebhooks({ appId: ctx.appId, oldAuto: oldAutomation, }) - ctx.body = await db.remove(ctx.params.id, ctx.params.rev) + // delete metadata first + await cleanupAutomationMetadata(ctx.appId, automationId) + ctx.body = await db.remove(automationId, ctx.params.rev) } exports.getActionList = async function (ctx) { diff --git a/packages/server/src/api/controllers/metadata.js b/packages/server/src/api/controllers/metadata.js index 7e098b6586..75236650fd 100644 --- a/packages/server/src/api/controllers/metadata.js +++ b/packages/server/src/api/controllers/metadata.js @@ -1,7 +1,7 @@ const { MetadataTypes } = require("../../constants") const CouchDB = require("../../db") const { generateMetadataID } = require("../../db/utils") -const { saveEntityMetadata } = require("../../utilities") +const { saveEntityMetadata, deleteEntityMetadata } = require("../../utilities") exports.getTypes = async ctx => { ctx.body = { @@ -24,20 +24,7 @@ exports.saveMetadata = async ctx => { exports.deleteMetadata = async ctx => { const { type, entityId } = ctx.params - const db = new CouchDB(ctx.appId) - const id = generateMetadataID(type, entityId) - let rev - try { - const metadata = await db.get(id) - if (metadata) { - rev = metadata._rev - } - } catch (err) { - // don't need to error if it doesn't exist - } - if (id && rev) { - await db.remove(id, rev) - } + await deleteEntityMetadata(ctx.appId, type, entityId) ctx.body = { message: "Metadata deleted successfully", } diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 114e1bd5e6..a81f9ddcf5 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -89,3 +89,20 @@ exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { return metadata }) } + +exports.deleteEntityMetadata = async (appId, type, entityId) => { + const db = new CouchDB(appId) + const id = generateMetadataID(type, entityId) + let rev + try { + const metadata = await db.get(id) + if (metadata) { + rev = metadata._rev + } + } catch (err) { + // don't need to error if it doesn't exist + } + if (id && rev) { + await db.remove(id, rev) + } +} From ce5feda3ed03c688c082497b9c2c2d478a01493b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 18:07:33 +0100 Subject: [PATCH 23/31] Fixing issue discovered by webhook test case. --- packages/server/src/automations/steps/utils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/server/src/automations/steps/utils.js b/packages/server/src/automations/steps/utils.js index 83673d0878..61f4a8080d 100644 --- a/packages/server/src/automations/steps/utils.js +++ b/packages/server/src/automations/steps/utils.js @@ -11,9 +11,6 @@ exports.getFetchResponse = async fetched => { } catch (err) { message = "Failed to retrieve response" } - if (typeof message !== "string") { - message = JSON.stringify(message) - } return { status, message } } From daef4c2d64765d12ab53a5278c77ffcb366690ac Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 11:18:02 +0100 Subject: [PATCH 24/31] Fixing test cases, making it possible to still run automations via env variable. --- packages/builder/cypress/setup.js | 1 + packages/server/src/automations/triggers.js | 22 ++++++++++++--------- packages/server/src/environment.js | 1 + 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 4ad8e5287d..1a6f1d5b2b 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -23,6 +23,7 @@ process.env.MINIO_SECRET_KEY = "budibase" process.env.COUCH_DB_USER = "budibase" process.env.COUCH_DB_PASSWORD = "budibase" process.env.INTERNAL_API_KEY = "budibase" +process.env.ALLOW_DEV_AUTOMATIONS = 1 // Stop info logs polluting test outputs process.env.LOG_LEVEL = "error" diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index d90511c7ba..ae98f0e73a 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -8,6 +8,7 @@ const { isDevAppID } = require("../db/utils") const { queue } = require("./bullboard") const { checkTestFlag } = require("../utilities/redis") const utils = require("./utils") +const env = require("../environment") const TRIGGER_DEFINITIONS = definitions @@ -30,21 +31,24 @@ async function queueRelevantRowAutomations(event, eventType) { }) for (let automation of automations) { - // don't queue events which are for dev apps, only way to test automations is - // running tests on them - // in production the test flag will never be checked due to lazy evaluation (first always false) - if (isDevAppID(event.appId) && !(await checkTestFlag(automation._id))) { - return - } let automationDef = automation.definition let automationTrigger = automationDef ? automationDef.trigger : {} + // don't queue events which are for dev apps, only way to test automations is + // running tests on them, in production the test flag will never + // be checked due to lazy evaluation (first always false) if ( - !automationTrigger.inputs || - automationTrigger.inputs.tableId !== event.row.tableId + !env.ALLOW_DEV_AUTOMATIONS && + isDevAppID(event.appId) && + !(await checkTestFlag(automation._id)) ) { continue } - await queue.add({ automation, event }) + if ( + automationTrigger.inputs && + automationTrigger.inputs.tableId === event.row.tableId + ) { + await queue.add({ automation, event }) + } } } diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 9f69664ffb..9e029e440a 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -55,6 +55,7 @@ module.exports = { BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY, USERID_API_KEY: process.env.USERID_API_KEY, DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + ALLOW_DEV_AUTOMATIONS: process.env.ALLOW_DEV_AUTOMATIONS, _set(key, value) { process.env[key] = value module.exports[key] = value From 6748b75efd368d833d3cc2133ec4a5f6489c278c Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 14 Sep 2021 10:28:17 +0000 Subject: [PATCH 25/31] v0.9.125-alpha.4 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index 590523679d..9cefbf9dea 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index bfdf834a83..f28713a487 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index c2487752a6..b2106f32ed 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index df2a3a4a2e..8d9bb3d83a 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.3", - "@budibase/client": "^0.9.125-alpha.3", + "@budibase/bbui": "^0.9.125-alpha.4", + "@budibase/client": "^0.9.125-alpha.4", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.125-alpha.3", + "@budibase/string-templates": "^0.9.125-alpha.4", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 66a4294b2e..a24f329ae4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 188a12c110..1dd683cfcd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.3", + "@budibase/bbui": "^0.9.125-alpha.4", "@budibase/standard-components": "^0.9.124", - "@budibase/string-templates": "^0.9.125-alpha.3", + "@budibase/string-templates": "^0.9.125-alpha.4", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index e9f9422e56..6ff3a90ef2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -61,9 +61,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.3", - "@budibase/client": "^0.9.125-alpha.3", - "@budibase/string-templates": "^0.9.125-alpha.3", + "@budibase/auth": "^0.9.125-alpha.4", + "@budibase/client": "^0.9.125-alpha.4", + "@budibase/string-templates": "^0.9.125-alpha.4", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 0ee5d0f030..84e819786a 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 36089d6300..4d37d600c5 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.125-alpha.3", + "version": "0.9.125-alpha.4", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -23,8 +23,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.3", - "@budibase/string-templates": "^0.9.125-alpha.3", + "@budibase/auth": "^0.9.125-alpha.4", + "@budibase/string-templates": "^0.9.125-alpha.4", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0", From 458db567ea9f56af1f06b190ca4ba7320dc2739b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 11:28:39 +0100 Subject: [PATCH 26/31] Removing the concept of a logic block. --- .../server/src/api/controllers/automation.js | 6 ---- packages/server/src/api/routes/automation.js | 5 ---- .../src/api/routes/tests/automation.spec.js | 13 -------- packages/server/src/automations/actions.js | 6 ++++ packages/server/src/automations/logic.js | 20 ------------- .../server/src/automations/steps/filter.js | 30 +++++++++---------- .../src/automations/tests/delay.spec.js | 2 +- .../src/automations/tests/filter.spec.js | 26 ++++++++-------- .../src/automations/tests/utilities/index.js | 13 +------- packages/server/src/automations/thread.js | 14 +++------ 10 files changed, 40 insertions(+), 95 deletions(-) delete mode 100644 packages/server/src/automations/logic.js diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 93e3f493f5..d0c0ac4e69 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -1,6 +1,5 @@ const CouchDB = require("../../db") const actions = require("../../automations/actions") -const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") const { getAutomationParams, generateAutomationID } = require("../../db/utils") const { @@ -163,13 +162,8 @@ exports.getTriggerList = async function (ctx) { ctx.body = triggers.TRIGGER_DEFINITIONS } -exports.getLogicList = async function (ctx) { - ctx.body = logic.LOGIC_DEFINITIONS -} - module.exports.getDefinitionList = async function (ctx) { ctx.body = { - logic: logic.LOGIC_DEFINITIONS, trigger: triggers.TRIGGER_DEFINITIONS, action: actions.ACTION_DEFINITIONS, } diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 279ad28278..599b16b583 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -56,11 +56,6 @@ router authorized(BUILDER), controller.getActionList ) - .get( - "/api/automations/logic/list", - authorized(BUILDER), - controller.getLogicList - ) .get( "/api/automations/definitions/list", authorized(BUILDER), diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 1c920848a7..c412c34fdc 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -11,7 +11,6 @@ const MAX_RETRIES = 4 let ACTION_DEFINITIONS = {} let TRIGGER_DEFINITIONS = {} -let LOGIC_DEFINITIONS = {} describe("/automations", () => { let request = setup.getRequest() @@ -47,17 +46,6 @@ describe("/automations", () => { TRIGGER_DEFINITIONS = res.body }) - it("returns a list of definitions for actions", async () => { - const res = await request - .get(`/api/automations/logic/list`) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) - - expect(Object.keys(res.body).length).not.toEqual(0) - LOGIC_DEFINITIONS = res.body - }) - it("returns all of the definitions in one", async () => { const res = await request .get(`/api/automations/definitions/list`) @@ -67,7 +55,6 @@ describe("/automations", () => { expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(Object.keys(ACTION_DEFINITIONS).length) expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length) - expect(Object.keys(res.body.logic).length).toEqual(Object.keys(LOGIC_DEFINITIONS).length) }) }) diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 0f395db890..ef77387a40 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -11,6 +11,8 @@ const discord = require("./steps/discord") const slack = require("./steps/slack") const zapier = require("./steps/zapier") const integromat = require("./steps/integromat") +let filter = require("./steps/filter") +let delay = require("./steps/delay") const ACTION_IMPLS = { SEND_EMAIL_SMTP: sendSmtpEmail.run, @@ -22,6 +24,8 @@ const ACTION_IMPLS = { EXECUTE_BASH: bash.run, EXECUTE_QUERY: executeQuery.run, SERVER_LOG: serverLog.run, + DELAY: delay.run, + FILTER: filter.run, // these used to be lowercase step IDs, maintain for backwards compat discord: discord.run, slack: slack.run, @@ -38,6 +42,8 @@ const ACTION_DEFINITIONS = { EXECUTE_QUERY: executeQuery.definition, EXECUTE_BASH: bash.definition, SERVER_LOG: serverLog.definition, + DELAY: delay.definition, + FILTER: filter.definition, // these used to be lowercase step IDs, maintain for backwards compat discord: discord.definition, slack: slack.definition, diff --git a/packages/server/src/automations/logic.js b/packages/server/src/automations/logic.js deleted file mode 100644 index 52ee51252b..0000000000 --- a/packages/server/src/automations/logic.js +++ /dev/null @@ -1,20 +0,0 @@ -let filter = require("./steps/filter") -let delay = require("./steps/delay") - -let LOGIC_IMPLS = { - DELAY: delay.run, - FILTER: filter.run, -} - -let LOGIC_DEFINITIONS = { - DELAY: delay.definition, - FILTER: filter.definition, -} - -exports.getLogic = function (logicName) { - if (LOGIC_IMPLS[logicName] != null) { - return LOGIC_IMPLS[logicName] - } -} - -exports.LOGIC_DEFINITIONS = LOGIC_DEFINITIONS diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js index e15722e8d0..05727895bf 100644 --- a/packages/server/src/automations/steps/filter.js +++ b/packages/server/src/automations/steps/filter.js @@ -1,19 +1,19 @@ -const LogicConditions = { +const FilterConditions = { EQUAL: "EQUAL", NOT_EQUAL: "NOT_EQUAL", GREATER_THAN: "GREATER_THAN", LESS_THAN: "LESS_THAN", } -const PrettyLogicConditions = { - [LogicConditions.EQUAL]: "Equals", - [LogicConditions.NOT_EQUAL]: "Not equals", - [LogicConditions.GREATER_THAN]: "Greater than", - [LogicConditions.LESS_THAN]: "Less than", +const PrettyFilterConditions = { + [FilterConditions.EQUAL]: "Equals", + [FilterConditions.NOT_EQUAL]: "Not equals", + [FilterConditions.GREATER_THAN]: "Greater than", + [FilterConditions.LESS_THAN]: "Less than", } -exports.LogicConditions = LogicConditions -exports.PrettyLogicConditions = PrettyLogicConditions +exports.FilterConditions = FilterConditions +exports.PrettyFilterConditions = PrettyFilterConditions exports.definition = { name: "Filter", @@ -23,7 +23,7 @@ exports.definition = { type: "LOGIC", stepId: "FILTER", inputs: { - condition: LogicConditions.EQUALS, + condition: FilterConditions.EQUALS, }, schema: { inputs: { @@ -35,8 +35,8 @@ exports.definition = { condition: { type: "string", title: "Condition", - enum: Object.values(LogicConditions), - pretty: Object.values(PrettyLogicConditions), + enum: Object.values(FilterConditions), + pretty: Object.values(PrettyFilterConditions), }, value: { type: "string", @@ -70,16 +70,16 @@ exports.run = async function filter({ inputs }) { let success = false if (typeof field !== "object" && typeof value !== "object") { switch (condition) { - case LogicConditions.EQUAL: + case FilterConditions.EQUAL: success = field === value break - case LogicConditions.NOT_EQUAL: + case FilterConditions.NOT_EQUAL: success = field !== value break - case LogicConditions.GREATER_THAN: + case FilterConditions.GREATER_THAN: success = field > value break - case LogicConditions.LESS_THAN: + case FilterConditions.LESS_THAN: success = field < value break } diff --git a/packages/server/src/automations/tests/delay.spec.js b/packages/server/src/automations/tests/delay.spec.js index 99046e8171..d06bd6cf00 100644 --- a/packages/server/src/automations/tests/delay.spec.js +++ b/packages/server/src/automations/tests/delay.spec.js @@ -4,7 +4,7 @@ describe("test the delay logic", () => { it("should be able to run the delay", async () => { const time = 100 const before = Date.now() - await setup.runStep(setup.logic.DELAY.stepId, { time: time }) + await setup.runStep(setup.actions.DELAY.stepId, { time: time }) const now = Date.now() // divide by two just so that test will always pass as long as there was some sort of delay expect(now - before).toBeGreaterThanOrEqual(time / 2) diff --git a/packages/server/src/automations/tests/filter.spec.js b/packages/server/src/automations/tests/filter.spec.js index 05361f43ed..7895433fe9 100644 --- a/packages/server/src/automations/tests/filter.spec.js +++ b/packages/server/src/automations/tests/filter.spec.js @@ -1,48 +1,48 @@ const setup = require("./utilities") -const { LogicConditions } = require("../steps/filter") +const { FilterConditions } = require("../steps/filter") describe("test the filter logic", () => { async function checkFilter(field, condition, value, pass = true) { - let res = await setup.runStep(setup.logic.FILTER.stepId, + let res = await setup.runStep(setup.actions.FILTER.stepId, { field, condition, value } ) expect(res.success).toEqual(pass) } it("should be able test equality", async () => { - await checkFilter("hello", LogicConditions.EQUAL, "hello", true) - await checkFilter("hello", LogicConditions.EQUAL, "no", false) + await checkFilter("hello", FilterConditions.EQUAL, "hello", true) + await checkFilter("hello", FilterConditions.EQUAL, "no", false) }) it("should be able to test greater than", async () => { - await checkFilter(10, LogicConditions.GREATER_THAN, 5, true) - await checkFilter(10, LogicConditions.GREATER_THAN, 15, false) + await checkFilter(10, FilterConditions.GREATER_THAN, 5, true) + await checkFilter(10, FilterConditions.GREATER_THAN, 15, false) }) it("should be able to test less than", async () => { - await checkFilter(5, LogicConditions.LESS_THAN, 10, true) - await checkFilter(15, LogicConditions.LESS_THAN, 10, false) + await checkFilter(5, FilterConditions.LESS_THAN, 10, true) + await checkFilter(15, FilterConditions.LESS_THAN, 10, false) }) it("should be able to in-equality", async () => { - await checkFilter("hello", LogicConditions.NOT_EQUAL, "no", true) - await checkFilter(10, LogicConditions.NOT_EQUAL, 10, false) + await checkFilter("hello", FilterConditions.NOT_EQUAL, "no", true) + await checkFilter(10, FilterConditions.NOT_EQUAL, 10, false) }) it("check number coercion", async () => { - await checkFilter("10", LogicConditions.GREATER_THAN, "5", true) + await checkFilter("10", FilterConditions.GREATER_THAN, "5", true) }) it("check date coercion", async () => { await checkFilter( (new Date()).toISOString(), - LogicConditions.GREATER_THAN, + FilterConditions.GREATER_THAN, (new Date(-10000)).toISOString(), true ) }) it("check objects always false", async () => { - await checkFilter({}, LogicConditions.EQUAL, {}, false) + await checkFilter({}, FilterConditions.EQUAL, {}, false) }) }) \ No newline at end of file diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js index 516463091d..ca01ea9233 100644 --- a/packages/server/src/automations/tests/utilities/index.js +++ b/packages/server/src/automations/tests/utilities/index.js @@ -1,6 +1,5 @@ const TestConfig = require("../../../tests/utilities/TestConfiguration") const actions = require("../../actions") -const logic = require("../../logic") const emitter = require("../../../events/index") const env = require("../../../environment") @@ -34,16 +33,7 @@ exports.runInProd = async fn => { } exports.runStep = async function runStep(stepId, inputs) { - let step - if ( - Object.values(exports.actions) - .map(action => action.stepId) - .includes(stepId) - ) { - step = await actions.getAction(stepId) - } else { - step = logic.getLogic(stepId) - } + let step = await actions.getAction(stepId) expect(step).toBeDefined() return step({ inputs, @@ -57,4 +47,3 @@ exports.runStep = async function runStep(stepId, inputs) { exports.apiKey = "test" exports.actions = actions.ACTION_DEFINITIONS -exports.logic = logic.LOGIC_DEFINITIONS diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 12e158c20a..c810a5a71d 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -1,5 +1,4 @@ const actions = require("./actions") -const logic = require("./logic") const automationUtils = require("./automationUtils") const AutomationEmitter = require("../events/AutomationEmitter") const { processObject } = require("@budibase/string-templates") @@ -8,7 +7,7 @@ const CouchDB = require("../db") const { DocumentTypes } = require("../db/utils") const { doInTenant } = require("@budibase/auth/tenancy") -const FILTER_STEP_ID = logic.LOGIC_DEFINITIONS.FILTER.stepId +const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId /** * The automation orchestrator is a class responsible for executing automations. @@ -37,13 +36,8 @@ class Orchestrator { this.updateExecutionOutput(triggerId, triggerStepId, null, triggerOutput) } - async getStepFunctionality(type, stepId) { - let step = null - if (type === "ACTION") { - step = await actions.getAction(stepId) - } else if (type === "LOGIC") { - step = logic.getLogic(stepId) - } + async getStepFunctionality(stepId) { + let step = await actions.getAction(stepId) if (step == null) { throw `Cannot find automation step by name ${stepId}` } @@ -73,7 +67,7 @@ class Orchestrator { let automation = this._automation const app = await this.getApp() for (let step of automation.definition.steps) { - let stepFn = await this.getStepFunctionality(step.type, step.stepId) + let stepFn = await this.getStepFunctionality(step.stepId) step.inputs = await processObject(step.inputs, this._context) step.inputs = automationUtils.cleanInputValues( step.inputs, From e94c629bab3238e72c7a73936634fe2414544109 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 12:40:19 +0100 Subject: [PATCH 27/31] Adding concept of internal and external actions. --- .../cypress/integration/createAutomation.spec.js | 2 +- .../automation/AutomationBuilder/BlockList.svelte | 14 ++++++++++---- packages/server/src/automations/steps/bash.js | 1 + packages/server/src/automations/steps/createRow.js | 1 + packages/server/src/automations/steps/delay.js | 1 + packages/server/src/automations/steps/deleteRow.js | 1 + packages/server/src/automations/steps/discord.js | 1 + .../server/src/automations/steps/executeQuery.js | 1 + .../server/src/automations/steps/executeScript.js | 1 + packages/server/src/automations/steps/filter.js | 1 + .../server/src/automations/steps/integromat.js | 1 + .../src/automations/steps/outgoingWebhook.js | 1 + .../server/src/automations/steps/sendSmtpEmail.js | 1 + packages/server/src/automations/steps/serverLog.js | 1 + packages/server/src/automations/steps/slack.js | 1 + packages/server/src/automations/steps/updateRow.js | 1 + packages/server/src/automations/steps/zapier.js | 1 + 17 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js index e82eeff670..e5040c3c45 100644 --- a/packages/builder/cypress/integration/createAutomation.spec.js +++ b/packages/builder/cypress/integration/createAutomation.spec.js @@ -24,7 +24,7 @@ context("Create a automation", () => { }) // Create action - cy.contains("Action").click() + cy.contains("Internal").click() cy.contains("Create Row").click() cy.get(".setup").within(() => { cy.get(".spectrum-Picker-label").click() diff --git a/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte b/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte index af5c9e449e..a04541cfad 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/BlockList.svelte @@ -14,15 +14,17 @@ disabled: hasTrigger, }, { - label: "Action", + label: "Internal", value: "ACTION", + internal: true, icon: "Actions", disabled: !hasTrigger, }, { - label: "Logic", - value: "LOGIC", - icon: "Filter", + label: "External", + value: "ACTION", + internal: false, + icon: "Extension", disabled: !hasTrigger, }, ] @@ -32,9 +34,13 @@ let popover let webhookModal $: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value + $: selectedInternal = + selectedIndex == null ? null : tabs[selectedIndex].internal $: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] $: blocks = sortBy(entry => entry[1].name)( Object.entries($automationStore.blockDefinitions[selectedTab] ?? {}) + ).filter( + entry => selectedInternal == null || entry[1].internal === selectedInternal ) function onChangeTab(idx) { diff --git a/packages/server/src/automations/steps/bash.js b/packages/server/src/automations/steps/bash.js index d97f055e14..6c44c1bbbb 100644 --- a/packages/server/src/automations/steps/bash.js +++ b/packages/server/src/automations/steps/bash.js @@ -7,6 +7,7 @@ exports.definition = { icon: "ri-terminal-box-line", description: "Run a bash script", type: "ACTION", + internal: true, stepId: "EXECUTE_BASH", inputs: {}, schema: { diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index f98cbfe2cf..e3c90fb15b 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -9,6 +9,7 @@ exports.definition = { icon: "ri-save-3-line", description: "Add a row to your database", type: "ACTION", + internal: true, stepId: "CREATE_ROW", inputs: {}, schema: { diff --git a/packages/server/src/automations/steps/delay.js b/packages/server/src/automations/steps/delay.js index ee7ef018e3..899d8f8401 100644 --- a/packages/server/src/automations/steps/delay.js +++ b/packages/server/src/automations/steps/delay.js @@ -6,6 +6,7 @@ exports.definition = { tagline: "Delay for {{inputs.time}} milliseconds", description: "Delay the automation until an amount of time has passed", stepId: "DELAY", + internal: true, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 0c0315b1b0..10f39d2d0c 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -9,6 +9,7 @@ exports.definition = { tagline: "Delete a {{inputs.enriched.table.name}} row", type: "ACTION", stepId: "DELETE_ROW", + internal: true, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/discord.js b/packages/server/src/automations/steps/discord.js index 066eb23a4e..5d225644b1 100644 --- a/packages/server/src/automations/steps/discord.js +++ b/packages/server/src/automations/steps/discord.js @@ -11,6 +11,7 @@ exports.definition = { icon: "ri-discord-line", stepId: "discord", type: "ACTION", + internal: false, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index 1dcb75d0a3..d5799a8f7d 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -8,6 +8,7 @@ exports.definition = { description: "Execute a query in an external data connector", type: "ACTION", stepId: "EXECUTE_QUERY", + internal: true, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/executeScript.js b/packages/server/src/automations/steps/executeScript.js index 68a1071fd1..70298b9f8f 100644 --- a/packages/server/src/automations/steps/executeScript.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -7,6 +7,7 @@ exports.definition = { icon: "ri-terminal-box-line", description: "Run a piece of JavaScript code in your automation", type: "ACTION", + internal: true, stepId: "EXECUTE_SCRIPT", inputs: {}, schema: { diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js index 05727895bf..84bdc10c1d 100644 --- a/packages/server/src/automations/steps/filter.js +++ b/packages/server/src/automations/steps/filter.js @@ -21,6 +21,7 @@ exports.definition = { icon: "ri-git-branch-line", description: "Filter any automations which do not meet certain conditions", type: "LOGIC", + internal: true, stepId: "FILTER", inputs: { condition: FilterConditions.EQUALS, diff --git a/packages/server/src/automations/steps/integromat.js b/packages/server/src/automations/steps/integromat.js index f8427b0dab..e7ea03efca 100644 --- a/packages/server/src/automations/steps/integromat.js +++ b/packages/server/src/automations/steps/integromat.js @@ -9,6 +9,7 @@ exports.definition = { icon: "ri-shut-down-line", stepId: "integromat", type: "ACTION", + internal: false, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index fe93cb38d8..ea0f0ce6be 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -23,6 +23,7 @@ exports.definition = { icon: "ri-send-plane-line", description: "Send a request of specified method to a URL", type: "ACTION", + internal: true, stepId: "OUTGOING_WEBHOOK", inputs: { requestMethod: "POST", diff --git a/packages/server/src/automations/steps/sendSmtpEmail.js b/packages/server/src/automations/steps/sendSmtpEmail.js index fc2274a582..552c9b4d36 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.js +++ b/packages/server/src/automations/steps/sendSmtpEmail.js @@ -6,6 +6,7 @@ exports.definition = { icon: "ri-mail-open-line", name: "Send Email (SMTP)", type: "ACTION", + internal: true, stepId: "SEND_EMAIL_SMTP", inputs: {}, schema: { diff --git a/packages/server/src/automations/steps/serverLog.js b/packages/server/src/automations/steps/serverLog.js index 0cde7b22ab..82e7d073e3 100644 --- a/packages/server/src/automations/steps/serverLog.js +++ b/packages/server/src/automations/steps/serverLog.js @@ -10,6 +10,7 @@ exports.definition = { icon: "ri-server-line", description: "Logs the given text to the server (using console.log)", type: "ACTION", + internal: true, stepId: "SERVER_LOG", inputs: { text: "", diff --git a/packages/server/src/automations/steps/slack.js b/packages/server/src/automations/steps/slack.js index a87c614151..ec6341a26f 100644 --- a/packages/server/src/automations/steps/slack.js +++ b/packages/server/src/automations/steps/slack.js @@ -8,6 +8,7 @@ exports.definition = { icon: "ri-slack-line", stepId: "slack", type: "ACTION", + internal: false, inputs: {}, schema: { inputs: { diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index e58f1fe446..961f75dee7 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -7,6 +7,7 @@ exports.definition = { icon: "ri-refresh-line", description: "Update a row in your database", type: "ACTION", + internal: true, stepId: "UPDATE_ROW", inputs: {}, schema: { diff --git a/packages/server/src/automations/steps/zapier.js b/packages/server/src/automations/steps/zapier.js index 57703286e6..bec90497cd 100644 --- a/packages/server/src/automations/steps/zapier.js +++ b/packages/server/src/automations/steps/zapier.js @@ -5,6 +5,7 @@ exports.definition = { name: "Zapier Webhook", stepId: "zapier", type: "ACTION", + internal: false, description: "Trigger a Zapier Zap via webhooks", tagline: "Trigger a Zapier Zap", icon: "ri-flashlight-line", From 2f9f643592145ff30311777b740afb2fc5cd3569 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 16:43:06 +0100 Subject: [PATCH 28/31] Removing the concept of worker-farm and fixing issues raised in review. --- packages/server/package.json | 1 - packages/server/src/automations/thread.js | 15 +------- .../src/automations/triggerInfo/cron.js | 2 +- .../src/automations/triggerInfo/index.js | 2 +- packages/server/src/automations/utils.js | 37 +------------------ 5 files changed, 6 insertions(+), 51 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index f242601d94..97fa7238e7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -109,7 +109,6 @@ "to-json-schema": "0.2.5", "uuid": "3.3.2", "validate.js": "0.13.1", - "worker-farm": "1.7.0", "yargs": "13.2.4", "zlib": "1.0.5" }, diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index c810a5a71d..a3e81a2274 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -99,21 +99,10 @@ class Orchestrator { } } -// callback is required for worker-farm to state that the worker thread has completed -module.exports = (job, cb) => { - if (!cb) { - throw "Callback must be defined." - } +module.exports = async job => { const automationOrchestrator = new Orchestrator( job.data.automation, job.data.event ) - automationOrchestrator - .execute() - .then(output => { - cb(null, output) - }) - .catch(err => { - cb(err) - }) + return automationOrchestrator.execute() } diff --git a/packages/server/src/automations/triggerInfo/cron.js b/packages/server/src/automations/triggerInfo/cron.js index 9ef4649b95..31e79a4abf 100644 --- a/packages/server/src/automations/triggerInfo/cron.js +++ b/packages/server/src/automations/triggerInfo/cron.js @@ -1,4 +1,4 @@ -exports.defintion = { +exports.definition = { name: "Cron Trigger", event: "cron:trigger", icon: "ri-timer-line", diff --git a/packages/server/src/automations/triggerInfo/index.js b/packages/server/src/automations/triggerInfo/index.js index 476d37d54c..066993d324 100644 --- a/packages/server/src/automations/triggerInfo/index.js +++ b/packages/server/src/automations/triggerInfo/index.js @@ -11,5 +11,5 @@ exports.definitions = { ROW_DELETED: rowDeleted.definition, WEBHOOK: webhook.definition, APP: app.definition, - CRON: cron.defintion, + CRON: cron.definition, } diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 29a8e38b18..f9de64de59 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -1,7 +1,6 @@ const env = require("../environment") -const workerFarm = require("worker-farm") const { getAPIKey, update, Properties } = require("../utilities/usageQuota") -const singleThread = require("./thread") +const runner = require("./thread") const { definitions } = require("./triggerInfo") const webhooks = require("../api/controllers/webhook") const CouchDB = require("../db") @@ -13,32 +12,6 @@ const { MetadataTypes } = require("../constants") const WH_STEP_ID = definitions.WEBHOOK.stepId const CRON_STEP_ID = definitions.CRON.stepId -let workers = workerFarm(require.resolve("./thread")) - -function runWorker(job) { - return new Promise((resolve, reject) => { - workers(job, (err, output) => { - if (err) { - reject(err) - } else { - resolve(output) - } - }) - }) -} - -function runSingleThread(job) { - return new Promise((resolve, reject) => { - singleThread(job, (err, output) => { - if (err) { - reject(err) - } else { - resolve(output) - } - }) - }) -} - async function updateQuota(automation) { const appId = automation.appId const apiObj = await getAPIKey(appId) @@ -53,13 +26,7 @@ exports.processEvent = async job => { job.data.automation.apiKey = await updateQuota(job.data.automation) } // need to actually await these so that an error can be captured properly - let response - if (!env.isProd()) { - response = await runSingleThread(job) - } else { - response = await runWorker(job) - } - return response + return await runner(job) } catch (err) { console.error( `${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}` From df1555185dbc7bfb8b7b8f95f0c3143b28443c78 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 16:54:42 +0100 Subject: [PATCH 29/31] Changing over to using timestamps. --- packages/server/src/api/controllers/automation.js | 2 +- packages/server/src/api/routes/tests/metadata.spec.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index d0c0ac4e69..60a0928e33 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -205,7 +205,7 @@ exports.test = async function (ctx) { // save a test history run await updateTestHistory(ctx.appId, automation, { ...ctx.request.body, - occurredAt: new Date().toISOString(), + occurredAt: new Date().getTime(), }) await clearTestFlag(automation._id) ctx.body = response diff --git a/packages/server/src/api/routes/tests/metadata.spec.js b/packages/server/src/api/routes/tests/metadata.spec.js index 68a7b5dbd3..5befd492a2 100644 --- a/packages/server/src/api/routes/tests/metadata.spec.js +++ b/packages/server/src/api/routes/tests/metadata.spec.js @@ -46,6 +46,7 @@ describe("/metadata", () => { const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY) expect(metadata).toBeDefined() expect(metadata.history.length).toBe(1) + expect(typeof metadata.history[0].occurredAt).toBe("number") }) }) From d2fce749c629af40549d3816b51774c287cde19f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 17:14:44 +0100 Subject: [PATCH 30/31] Fixing test cases after removing worker-farm. --- .../src/automations/tests/automation.spec.js | 38 ++----------------- packages/server/src/automations/utils.js | 13 ------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index 689ee4ab76..e1b82e4327 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -1,20 +1,6 @@ jest.mock("../../utilities/usageQuota") jest.mock("../thread") jest.spyOn(global.console, "error") -jest.mock("worker-farm", () => { - return () => { - const value = jest - .fn() - .mockReturnValueOnce(undefined) - .mockReturnValueOnce("Error") - return (input, callback) => { - workerJob = input - if (callback) { - callback(value()) - } - } - } -}) require("../../environment") const automation = require("../index") @@ -27,8 +13,6 @@ const { makePartial } = require("../../tests/utilities") const { cleanInputValues } = require("../automationUtils") const setup = require("./utilities") -let workerJob - usageQuota.getAPIKey.mockReturnValue({ apiKey: "test" }) describe("Run through some parts of the automations system", () => { @@ -44,28 +28,12 @@ describe("Run through some parts of the automations system", () => { it("should be able to init in builder", async () => { await triggers.externalTrigger(basicAutomation(), { a: 1 }) await wait(100) - expect(workerJob).toBeUndefined() expect(thread).toHaveBeenCalled() }) it("should be able to init in prod", async () => { - await setup.runInProd(async () => { - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined - expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1) - expect(workerJob).toBeDefined() - }) - }) - - it("try error scenario", async () => { - await setup.runInProd(async () => { - // the second call will throw an error - const response = await triggers.externalTrigger(basicAutomation(), {a: 1}, {getResponses: true}) - await wait(100) - expect(console.error).toHaveBeenCalled() - expect(response.err).toBeDefined() - }) + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) }) it("should check coercion", async () => { @@ -89,7 +57,7 @@ describe("Run through some parts of the automations system", () => { } } } - }), expect.any(Function)) + })) }) it("should be able to clean inputs with the utilities", () => { diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index f9de64de59..4bef91a153 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -1,5 +1,3 @@ -const env = require("../environment") -const { getAPIKey, update, Properties } = require("../utilities/usageQuota") const runner = require("./thread") const { definitions } = require("./triggerInfo") const webhooks = require("../api/controllers/webhook") @@ -12,19 +10,8 @@ const { MetadataTypes } = require("../constants") const WH_STEP_ID = definitions.WEBHOOK.stepId const CRON_STEP_ID = definitions.CRON.stepId -async function updateQuota(automation) { - const appId = automation.appId - const apiObj = await getAPIKey(appId) - // this will fail, causing automation to escape if limits reached - await update(apiObj.apiKey, Properties.AUTOMATION, 1) - return apiObj.apiKey -} - exports.processEvent = async job => { try { - if (env.USE_QUOTAS) { - job.data.automation.apiKey = await updateQuota(job.data.automation) - } // need to actually await these so that an error can be captured properly return await runner(job) } catch (err) { From 3fe70fa7ca64245716519d586722894f20ed563c Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 14 Sep 2021 16:39:38 +0000 Subject: [PATCH 31/31] v0.9.125-alpha.5 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index 9cefbf9dea..1de84eca6d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index f28713a487..afc0e99301 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index b2106f32ed..a060614934 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 8d9bb3d83a..66c1acdf67 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.4", - "@budibase/client": "^0.9.125-alpha.4", + "@budibase/bbui": "^0.9.125-alpha.5", + "@budibase/client": "^0.9.125-alpha.5", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.125-alpha.4", + "@budibase/string-templates": "^0.9.125-alpha.5", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index a24f329ae4..d8ef49de10 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 1dd683cfcd..a15b39b591 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.125-alpha.4", + "@budibase/bbui": "^0.9.125-alpha.5", "@budibase/standard-components": "^0.9.124", - "@budibase/string-templates": "^0.9.125-alpha.4", + "@budibase/string-templates": "^0.9.125-alpha.5", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index 14b299bea6..4c5c99e7e5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -61,9 +61,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.4", - "@budibase/client": "^0.9.125-alpha.4", - "@budibase/string-templates": "^0.9.125-alpha.4", + "@budibase/auth": "^0.9.125-alpha.5", + "@budibase/client": "^0.9.125-alpha.5", + "@budibase/string-templates": "^0.9.125-alpha.5", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 84e819786a..a5c6bfeffd 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 4d37d600c5..552a8dc67a 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.125-alpha.4", + "version": "0.9.125-alpha.5", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -23,8 +23,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.125-alpha.4", - "@budibase/string-templates": "^0.9.125-alpha.4", + "@budibase/auth": "^0.9.125-alpha.5", + "@budibase/string-templates": "^0.9.125-alpha.5", "@koa/router": "^8.0.0", "@techpass/passport-openidconnect": "^0.3.0", "aws-sdk": "^2.811.0",