diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 60f3c981d6..3aa5b2fb7b 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -208,5 +208,10 @@ exports.AutomationErrors = { FAILURE_CONDITION: "FAILURE_CONDITION_MET", } +exports.LoopStepTypes = { + ARRAY: "Array", + STRING: "String", +} + // 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 98c45e4af3..4ca490affd 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 { AutomationErrors, LoopStepTypes } = require("../constants") const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId @@ -17,6 +17,41 @@ const STOPPED_STATUS = { success: false, status: "STOPPED" } const { cloneDeep } = require("lodash/fp") const env = require("../environment") +function typecastForLooping(loopStep, input) { + if (!input || !input.binding) { + return null + } + const isArray = Array.isArray(input.binding), + isString = typeof input.binding === "string" + try { + switch (loopStep.inputs.option) { + case LoopStepTypes.ARRAY: + if (isString) { + return JSON.parse(input.binding) + } + break + case LoopStepTypes.STRING: + if (isArray) { + return input.binding.join(",") + } + break + } + } catch (err) { + throw new Error("Unable to cast to correct type") + } + return input.binding +} + +function getLoopIterations(loopStep, input) { + const binding = typecastForLooping(loopStep, input) + if (!loopStep || !binding) { + return 1 + } + return Array.isArray(binding) + ? binding.length + : automationUtils.stringSplit(binding).length +} + /** * The automation orchestrator is a class responsible for executing automations. * It handles the context of the automation and makes sure each step gets the correct @@ -107,7 +142,9 @@ class Orchestrator { let loopSteps = [] for (let step of automation.definition.steps) { stepCount++ - let input + let input, + iterations = 1, + iterationCount = 0 if (step.stepId === LOOP_STEP_ID) { loopStep = step loopStepNumber = stepCount @@ -116,13 +153,9 @@ class Orchestrator { if (loopStep) { input = await processObject(loopStep.inputs, this._context) + iterations = getLoopIterations(loopStep, input) } - let iterations = loopStep - ? Array.isArray(input.binding) - ? input.binding.length - : automationUtils.stringSplit(input.binding).length - : 1 - let iterationCount = 0 + for (let index = 0; index < iterations; index++) { let originalStepInput = cloneDeep(step.inputs) @@ -132,18 +165,11 @@ class Orchestrator { loopStep.inputs, cloneDeep(this._context) ) - newInput = automationUtils.cleanInputValues( - newInput, - loopStep.schema.inputs - ) let tempOutput = { items: loopSteps, iterations: iterationCount } - if ( - (loopStep.inputs.option === "Array" && - !Array.isArray(newInput.binding)) || - (loopStep.inputs.option === "String" && - typeof newInput.binding !== "string") - ) { + try { + newInput.binding = typecastForLooping(loopStep, newInput) + } catch (err) { this.updateContextAndOutput(loopStepNumber, step, tempOutput, { status: AutomationErrors.INCORRECT_TYPE, success: false, @@ -205,21 +231,13 @@ class Orchestrator { } let isFailure = false - if ( - typeof this._context.steps[loopStepNumber]?.currentItem === "object" - ) { - isFailure = Object.keys( - this._context.steps[loopStepNumber].currentItem - ).some(value => { - return ( - this._context.steps[loopStepNumber].currentItem[value] === - loopStep.inputs.failure - ) + const currentItem = this._context.steps[loopStepNumber]?.currentItem + if (currentItem && typeof currentItem === "object") { + isFailure = Object.keys(currentItem).some(value => { + return currentItem[value] === loopStep.inputs.failure }) } else { - isFailure = - this._context.steps[loopStepNumber]?.currentItem === - loopStep.inputs.failure + isFailure = currentItem && currentItem === loopStep.inputs.failure } if (isFailure) {