diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 5a00b3e790..d1659e51d1 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -92,7 +92,7 @@ ) loopBlock.blockToLoop = block.id block.loopBlock = loopBlock.id - automationStore.actions.addBlockToAutomation(loopBlock, blockIdx - 1) + automationStore.actions.addBlockToAutomation(loopBlock, blockIdx) await automationStore.actions.save( $automationStore.selectedAutomation?.automation ) @@ -131,16 +131,6 @@ </div> <div class="blockTitle"> - {#if testResult && testResult[0]} - <div style="float: right;" on:click={() => resultsModal.show()}> - <StatusLight - positive={isTrigger || testResult[0].outputs?.success} - negative={!testResult[0].outputs?.success} - ><Body size="XS">View response</Body></StatusLight - > - </div> - {/if} - <div style="margin-left: 10px;" on:click={() => { diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte index 7dfdff20a7..67c7f493e8 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ResultsModal.svelte @@ -1,5 +1,5 @@ <script> - import { ModalContent, Icon, Detail, TextArea } from "@budibase/bbui" + import { ModalContent, Icon, Detail, TextArea, Label } from "@budibase/bbui" export let testResult export let isTrigger @@ -10,7 +10,7 @@ <ModalContent showCloseIcon={false} showConfirmButton={false} - title="Test Automation" + title="Test Results" cancelText="Close" > <div slot="header"> @@ -26,7 +26,18 @@ {/if} </div> </div> - + <span> + {#if testResult[0].outputs.iterations} + <div style="display: flex;"> + <Icon name="Reuse" /> + <div style="margin-left: 10px;"> + <Label> + This loop ran {testResult[0].outputs.iterations} times.</Label + > + </div> + </div> + {/if} + </span> <div on:click={() => { inputToggled = !inputToggled diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index b87d08a5ab..ae5fc3d0df 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -30,6 +30,7 @@ import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte" import { LuceneUtils } from "@budibase/frontend-core" import { getSchemaForTable } from "builderStore/dataBinding" + import { cloneDeep } from "lodash/fp" export let block export let testData @@ -88,34 +89,61 @@ if (!block || !automation) { return [] } - // Find previous steps to the selected one let allSteps = [...automation.steps] + if (automation.trigger) { allSteps = [automation.trigger, ...allSteps] } - const blockIdx = allSteps.findIndex(step => step.id === block.id) + let blockIdx = allSteps.findIndex(step => step.id === block.id) - // Extract all outputs from all previous steps as available bindings + let loopBlockIdx = cloneDeep(allSteps) + .splice(0, blockIdx) + .findIndex(x => x.stepId === "LOOP") + // if a loop stepId exists in previous steps, we need to decerement the blockIdx + if (loopBlockIdx > -1 && blockIdx > loopBlockIdx) { + blockIdx-- + } + // Extract all outputs from all previous steps as available bindins let bindings = [] for (let idx = 0; idx < blockIdx; idx++) { - const outputs = Object.entries( - allSteps[idx].schema?.outputs?.properties ?? {} - ) + let isLoopBlock = allSteps[idx + 1]?.blockToLoop === block.id + + let schema = allSteps[idx]?.schema?.outputs?.properties ?? {} + if (isLoopBlock) { + schema = { + currentItem: { + type: "string", + description: "the item currently being executed", + }, + } + } + + if (loopBlockIdx && allSteps[blockIdx - 1]?.stepId === "LOOP") { + schema = { + ...schema, + ...$automationStore.blockDefinitions.ACTION.LOOP.schema.outputs + .properties.properties, + } + } + const outputs = Object.entries(schema) + bindings = bindings.concat( outputs.map(([name, value]) => { - let runtimeName = - $automationStore.selectedAutomation.automation.definition.steps.find( - x => block.id === x.blockToLoop - ) - ? `loop.${name}` - : `steps.${idx}.${name}` + let runtimeName = isLoopBlock + ? `loop.${name}` + : `steps.${idx}.${name}` const runtime = idx === 0 ? `trigger.${name}` : runtimeName return { label: runtime, type: value.type, description: value.description, - category: idx === 0 ? "Trigger outputs" : `Step ${idx} outputs`, + category: + idx === 0 + ? "Trigger outputs" + : isLoopBlock + ? "Loop Outputs" + : `Step ${idx} outputs`, path: runtime, } }) diff --git a/packages/server/src/automations/steps/loop.js b/packages/server/src/automations/steps/loop.js index 543317f3be..14c58aee6a 100644 --- a/packages/server/src/automations/steps/loop.js +++ b/packages/server/src/automations/steps/loop.js @@ -48,8 +48,3 @@ exports.definition = { }, type: "LOGIC", } - -exports.run = async function filter({ inputs }) { - let currentItem = inputs.binding - return { currentItem } -} diff --git a/packages/server/src/threads/automation.js b/packages/server/src/threads/automation.js index 6669f529a3..8297b5ab12 100644 --- a/packages/server/src/threads/automation.js +++ b/packages/server/src/threads/automation.js @@ -86,26 +86,149 @@ class Orchestrator { let stepCount = 0 let loopStepNumber let loopSteps = [] - let lastLoopStep for (let step of automation.definition.steps) { stepCount++ + let input if (step.stepId === LOOP_STEP_ID) { loopStep = step loopStepNumber = stepCount continue } - let iterations = loopStep ? loopStep.inputs.binding.split(",").length : 1 + if (loopStep) { + input = await processObject(loopStep.inputs, this._context) + } + let iterations = loopStep ? input.binding.length : 1 + let iterationCount = 0 for (let index = 0; index < iterations; index++) { let originalStepInput = cloneDeep(step.inputs) - /* - if (step.stepId === LOOP_STEP_ID && index >= loopStep.inputs.iterations) { - this.executionOutput.steps[loopStepNumber].outputs.status = "Loop Broken" - break + // Handle if the user has set a max iteration count or if it reaches the max limit set by us + if (loopStep) { + // lets first of all handle the input + // if the input is array then use it, if it is a string then split it on every new line + let newInput = await processObject( + loopStep.inputs, + cloneDeep(this._context) + ) + newInput = automationUtils.cleanInputValues( + newInput, + loopStep.schema.inputs + ) + this._context.steps[loopStepNumber] = { + currentItem: newInput.binding[index], + } + let tempOutput = { items: loopSteps, iterations: iterationCount } + + if ( + loopStep.inputs.option === "Array" && + !Array.isArray(newInput.binding) + ) { + this.executionOutput.steps.splice(loopStepNumber, 0, { + id: step.id, + stepId: step.stepId, + outputs: { + ...tempOutput, + success: true, + status: "INCORRECT_TYPE", + }, + inputs: step.inputs, + }) + this._context.steps.splice(loopStepNumber, 0, { + ...tempOutput, + success: true, + status: "INCORRECT_TYPE", + }) + + loopSteps = null + loopStep = null + break + } else if ( + loopStep.inputs.option === "String" && + typeof newInput.binding !== "string" + ) { + this.executionOutput.steps.splice(loopStepNumber, 0, { + id: step.id, + stepId: step.stepId, + outputs: { + ...tempOutput, + success: false, + status: "INCORRECT_TYPE", + }, + inputs: step.inputs, + }) + this._context.steps.splice(loopStepNumber, 0, { + ...tempOutput, + success: true, + status: "INCORRECT_TYPE", + }) + + loopSteps = null + loopStep = null + break + } + + // The "Loop" binding in the front end is "fake", so replace it here so the context can understand it + for (let key in originalStepInput) { + if (key === "row") { + for (let test in originalStepInput["row"]) { + originalStepInput["row"][test] = originalStepInput["row"][ + test + ].replace(/loop/, `steps.${loopStepNumber}`) + } + } else { + originalStepInput[key] = originalStepInput[key].replace( + /loop/, + `steps.${loopStepNumber}` + ) + } + } + + if (index >= loopStep.inputs.iterations) { + this.executionOutput.steps.splice(loopStepNumber, 0, { + id: step.id, + stepId: step.stepId, + outputs: { ...tempOutput, success: true, status: "LOOP_BROKEN" }, + inputs: step.inputs, + }) + this._context.steps.splice(loopStepNumber, 0, { + ...tempOutput, + success: true, + status: "LOOP_BROKEN", + }) + + loopSteps = null + loopStep = null + break + } + + if ( + this._context.steps[loopStepNumber]?.currentItem === + loopStep.inputs.failure + ) { + console.log("hello?????") + this.executionOutput.steps.splice(loopStepNumber, 0, { + id: step.id, + stepId: step.stepId, + outputs: { + ...tempOutput, + success: false, + status: "FAILURE_CONDITION_MET", + }, + inputs: step.inputs, + }) + this._context.steps.splice(loopStepNumber, 0, { + ...tempOutput, + success: false, + status: "FAILURE_CONDITION_MET", + }) + + loopSteps = null + loopStep = null + break + } } - - */ + // execution stopped, record state for that if (stopped) { this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS) @@ -113,11 +236,6 @@ class Orchestrator { } // If it's a loop step, we need to manually add the bindings to the context - if (loopStep) { - this._context.steps[loopStepNumber] = { - currentItem: loopStep.inputs.binding.split(",")[index], - } - } let stepFn = await this.getStepFunctionality(step.stepId) let inputs = await processObject(originalStepInput, this._context) inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs) @@ -143,7 +261,6 @@ class Orchestrator { }) continue } - this._context.steps.splice(loopStepNumber, 1) if (loopStep && loopSteps) { loopSteps.push(outputs) } else { @@ -158,26 +275,34 @@ class Orchestrator { console.error(`Automation error - ${step.stepId} - ${err}`) return err } - - if (index === iterations - 1) { - lastLoopStep = loopStep - loopStep = null - break + if (loopStep) { + iterationCount++ + if (index === iterations - 1) { + loopStep = null + this._context.steps.splice(loopStepNumber, 1) + break + } } } if (loopSteps && loopSteps.length) { - let tempOutput = { success: true, outputs: loopSteps } - this.executionOutput.steps.splice(loopStep, 0, { - id: lastLoopStep.id, - stepId: lastLoopStep.stepId, + let tempOutput = { + success: true, + items: loopSteps, + iterations: iterationCount, + } + this.executionOutput.steps.splice(loopStepNumber + 1, 0, { + id: step.id, + stepId: step.stepId, outputs: tempOutput, inputs: step.inputs, }) - this._context.steps.splice(loopStep, 0, tempOutput) + + this._context.steps.splice(loopStepNumber, 0, tempOutput) loopSteps = null } } + return this.executionOutput } }