From 1000ef35e19e81e18118031363abf178de7020aa Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Mon, 18 Apr 2022 09:22:23 +0100 Subject: [PATCH] Improve handling of loop handlebars string replacement --- charts/budibase/values.yaml | 2 +- .../server/src/automations/automationUtils.js | 12 +++++++ packages/server/src/automations/steps/loop.js | 4 +-- packages/server/src/constants/index.js | 6 ++++ packages/server/src/threads/automation.js | 36 +++++++++++-------- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 32c241807c..a9dbcd426b 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -103,7 +103,7 @@ globals: google: clientId: "" secret: "" - automationMaxIterations: "0" + automationMaxIterations: "500" createSecrets: true # creates an internal API key, JWT secrets and redis password for you diff --git a/packages/server/src/automations/automationUtils.js b/packages/server/src/automations/automationUtils.js index 9360840efd..0d858741dd 100644 --- a/packages/server/src/automations/automationUtils.js +++ b/packages/server/src/automations/automationUtils.js @@ -1,4 +1,5 @@ const { getTable } = require("../api/controllers/table/utils") +const { findHBSBlocks } = require("@budibase/string-templates") /** * When values are input to the system generally they will be of type string as this is required for template strings. @@ -74,3 +75,14 @@ exports.getError = err => { } return typeof err !== "string" ? err.toString() : err } + +exports.substituteLoopStep = (hbsString, substitute) => { + let blocks = findHBSBlocks(hbsString) + for (let block of blocks) { + let oldBlock = block + block = block.replace(/loop/, substitute) + hbsString = hbsString.replace(new RegExp(oldBlock, "g"), block) + } + + return hbsString +} diff --git a/packages/server/src/automations/steps/loop.js b/packages/server/src/automations/steps/loop.js index 14c58aee6a..238f4b5746 100644 --- a/packages/server/src/automations/steps/loop.js +++ b/packages/server/src/automations/steps/loop.js @@ -32,7 +32,7 @@ exports.definition = { properties: { items: { customType: "item", - description: "the item currently being executed", + description: "The item currently being executed", }, success: { type: "boolean", @@ -43,7 +43,7 @@ exports.definition = { descriptions: "The amount of times the block ran", }, }, - required: ["success"], + required: ["success, items, iterations"], }, }, type: "LOGIC", diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index cf89f1fe99..8dbac5c1b9 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -190,5 +190,11 @@ exports.WebhookType = { AUTOMATION: "automation", } +exports.AutomationErrors = { + INCORRECT_TYPE: "INCORRECT_TYPE", + MAX_ITERATIONS: "MAX_ITERATIONS_REACHED", + FAILURE_CONDITION: "FAILURE_CONDITION_MET", +} + // pass through the list from the auth/core lib exports.ObjectStoreBuckets = ObjectStoreBuckets diff --git a/packages/server/src/threads/automation.js b/packages/server/src/threads/automation.js index aef8263274..db462f5a8d 100644 --- a/packages/server/src/threads/automation.js +++ b/packages/server/src/threads/automation.js @@ -8,7 +8,7 @@ const { DocumentTypes } = require("../db/utils") const { doInTenant } = require("@budibase/backend-core/tenancy") const { definitions: triggerDefs } = require("../automations/triggerInfo") const { doInAppContext, getAppDB } = require("@budibase/backend-core/context") - +const { AutomationErrors } = require("../constants") const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId @@ -146,7 +146,7 @@ class Orchestrator { typeof newInput.binding !== "string") ) { this.updateContextAndOutput(loopStepNumber, step, tempOutput, { - status: "INCORRECT_TYPE", + status: AutomationErrors.INCORRECT_TYPE, success: false, }) loopSteps = null @@ -156,18 +156,26 @@ class Orchestrator { // The "Loop" binding in the front end is "fake", so replace it here so the context can understand it // Pretty hacky because we need to account for the row object - for (let key in originalStepInput) { - if (key === "row") { - for (let val in originalStepInput["row"]) { - originalStepInput["row"][val] = originalStepInput["row"][ - val - ].replace(/loop/, `steps.${loopStepNumber}`) + for (let [key, value] of Object.entries(originalStepInput)) { + if (typeof value === "object") { + for (let [innerKey, innerValue] of Object.entries( + originalStepInput[key] + )) { + if (typeof innerValue === "string") { + originalStepInput[key][innerKey] = + automationUtils.substituteLoopStep( + innerValue, + `steps.${loopStepNumber}` + ) + } } } else { - originalStepInput[key] = originalStepInput[key].replace( - /loop/, - `steps.${loopStepNumber}` - ) + if (typeof value === "string") { + originalStepInput[key] = automationUtils.substituteLoopStep( + value, + `steps.${loopStepNumber}` + ) + } } } @@ -176,7 +184,7 @@ class Orchestrator { index === loopStep.inputs.iterations ) { this.updateContextAndOutput(loopStepNumber, step, tempOutput, { - status: "MAX_ITERATIONS_REACHED", + status: AutomationErrors.MAX_ITERATIONS, success: true, }) loopSteps = null @@ -189,7 +197,7 @@ class Orchestrator { loopStep.inputs.failure ) { this.updateContextAndOutput(loopStepNumber, step, tempOutput, { - status: "FAILURE_CONDITION_MET", + status: AutomationErrors.FAILURE_CONDITION, success: false, }) loopSteps = null