From a3cffa8905b172f550d25c4a92e22787d1b9aead Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 3 Sep 2021 17:36:00 +0100 Subject: [PATCH 01/24] 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 2531d9a6818793fc7e47aecb5ecce683602cb4ee Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 6 Sep 2021 17:53:02 +0100 Subject: [PATCH 02/24] 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 4450883a0a0debc408b062d754fb6f324ff43175 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 13:58:53 +0100 Subject: [PATCH 03/24] 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 f429bc1a08a7034ccc744a48ebd59033485688e3 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 13:59:58 +0100 Subject: [PATCH 04/24] 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 1c24e3f52062e3d32c49d356437ce6376714a061 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 17:31:54 +0100 Subject: [PATCH 05/24] 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 213bee1e3b1b1a6d8271f1f3cee6fb8a3644b834 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 7 Sep 2021 19:06:20 +0100 Subject: [PATCH 06/24] 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 e84d30524bd3565605cf0219e6f0dbf1cf16f435 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 8 Sep 2021 14:08:22 +0100 Subject: [PATCH 07/24] 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 557bd2df9fa85e0ac42483739c46ba851bce54a4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 8 Sep 2021 19:29:28 +0100 Subject: [PATCH 08/24] 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 0e15911284a8005d4255151b032f2ab38a5a437d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 9 Sep 2021 12:23:52 +0100 Subject: [PATCH 09/24] 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 b32bebf8c880f00c3926699488d4df360471fb2a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 9 Sep 2021 17:58:10 +0100 Subject: [PATCH 10/24] 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 84cdec59079dce064c9aa2eb9644d8da4adb0f6b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 10 Sep 2021 13:52:41 +0100 Subject: [PATCH 11/24] 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 3eeb7c27b8b1c466be3371446f5cb066b107dd4f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 10 Sep 2021 14:37:34 +0100 Subject: [PATCH 12/24] 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 a92530f17044cca7229750ec1b4dc15387d587ec Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 16:28:52 +0100 Subject: [PATCH 13/24] 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 b43143f1b7a00133b0b9b5f5a509ef9cbfc73750 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 16:43:05 +0100 Subject: [PATCH 14/24] 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 198ada80b3307d8bbcc6ed1abf1993c194a01156 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 17:08:15 +0100 Subject: [PATCH 15/24] 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 8119a5f7f7c52c6eb396706a5af5ceb1b05caae4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 17:43:53 +0100 Subject: [PATCH 16/24] 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 0ff5fd1ef0aa38b053ad1fcf2adaaa8e6754f387 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 18:03:09 +0100 Subject: [PATCH 17/24] 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 a5dbcd81bd8e9f54b5a7b082c4cfa092f0f02eb2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 13 Sep 2021 18:07:33 +0100 Subject: [PATCH 18/24] 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 500179e312dcc9a4d1b4fa4e34bd092eb0f9bad6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 11:18:02 +0100 Subject: [PATCH 19/24] 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 8f1f1c2d5454065faa44c143f3c520d6c7a24542 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 11:28:39 +0100 Subject: [PATCH 20/24] 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 70bc68fb619aef1310e9142d823355139058dac3 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 12:40:19 +0100 Subject: [PATCH 21/24] 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 7d1dec28da65084557d70b2e6b3a45ce20969e44 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 16:43:06 +0100 Subject: [PATCH 22/24] 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 c57e66e21f2d8b910d3c5690b9cc9594b7c6b798 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 16:54:42 +0100 Subject: [PATCH 23/24] 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 35dba2e1cb8713f08ff0e736c6623642cd599356 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 14 Sep 2021 17:14:44 +0100 Subject: [PATCH 24/24] 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) {