Add handling for failure conditions

This commit is contained in:
Peter Clement 2022-04-11 10:26:59 +01:00
parent d4ff168ae4
commit 999199dcf6
5 changed files with 205 additions and 56 deletions

View File

@ -92,7 +92,7 @@
) )
loopBlock.blockToLoop = block.id loopBlock.blockToLoop = block.id
block.loopBlock = loopBlock.id block.loopBlock = loopBlock.id
automationStore.actions.addBlockToAutomation(loopBlock, blockIdx - 1) automationStore.actions.addBlockToAutomation(loopBlock, blockIdx)
await automationStore.actions.save( await automationStore.actions.save(
$automationStore.selectedAutomation?.automation $automationStore.selectedAutomation?.automation
) )
@ -131,16 +131,6 @@
</div> </div>
<div class="blockTitle"> <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 <div
style="margin-left: 10px;" style="margin-left: 10px;"
on:click={() => { on:click={() => {

View File

@ -1,5 +1,5 @@
<script> <script>
import { ModalContent, Icon, Detail, TextArea } from "@budibase/bbui" import { ModalContent, Icon, Detail, TextArea, Label } from "@budibase/bbui"
export let testResult export let testResult
export let isTrigger export let isTrigger
@ -10,7 +10,7 @@
<ModalContent <ModalContent
showCloseIcon={false} showCloseIcon={false}
showConfirmButton={false} showConfirmButton={false}
title="Test Automation" title="Test Results"
cancelText="Close" cancelText="Close"
> >
<div slot="header"> <div slot="header">
@ -26,7 +26,18 @@
{/if} {/if}
</div> </div>
</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 <div
on:click={() => { on:click={() => {
inputToggled = !inputToggled inputToggled = !inputToggled

View File

@ -30,6 +30,7 @@
import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte" import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
import { LuceneUtils } from "@budibase/frontend-core" import { LuceneUtils } from "@budibase/frontend-core"
import { getSchemaForTable } from "builderStore/dataBinding" import { getSchemaForTable } from "builderStore/dataBinding"
import { cloneDeep } from "lodash/fp"
export let block export let block
export let testData export let testData
@ -88,34 +89,61 @@
if (!block || !automation) { if (!block || !automation) {
return [] return []
} }
// Find previous steps to the selected one // Find previous steps to the selected one
let allSteps = [...automation.steps] let allSteps = [...automation.steps]
if (automation.trigger) { if (automation.trigger) {
allSteps = [automation.trigger, ...allSteps] 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 = [] let bindings = []
for (let idx = 0; idx < blockIdx; idx++) { for (let idx = 0; idx < blockIdx; idx++) {
const outputs = Object.entries( let isLoopBlock = allSteps[idx + 1]?.blockToLoop === block.id
allSteps[idx].schema?.outputs?.properties ?? {}
) 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( bindings = bindings.concat(
outputs.map(([name, value]) => { outputs.map(([name, value]) => {
let runtimeName = let runtimeName = isLoopBlock
$automationStore.selectedAutomation.automation.definition.steps.find( ? `loop.${name}`
x => block.id === x.blockToLoop : `steps.${idx}.${name}`
)
? `loop.${name}`
: `steps.${idx}.${name}`
const runtime = idx === 0 ? `trigger.${name}` : runtimeName const runtime = idx === 0 ? `trigger.${name}` : runtimeName
return { return {
label: runtime, label: runtime,
type: value.type, type: value.type,
description: value.description, description: value.description,
category: idx === 0 ? "Trigger outputs" : `Step ${idx} outputs`, category:
idx === 0
? "Trigger outputs"
: isLoopBlock
? "Loop Outputs"
: `Step ${idx} outputs`,
path: runtime, path: runtime,
} }
}) })

View File

@ -48,8 +48,3 @@ exports.definition = {
}, },
type: "LOGIC", type: "LOGIC",
} }
exports.run = async function filter({ inputs }) {
let currentItem = inputs.binding
return { currentItem }
}

View File

@ -86,26 +86,149 @@ class Orchestrator {
let stepCount = 0 let stepCount = 0
let loopStepNumber let loopStepNumber
let loopSteps = [] let loopSteps = []
let lastLoopStep
for (let step of automation.definition.steps) { for (let step of automation.definition.steps) {
stepCount++ stepCount++
let input
if (step.stepId === LOOP_STEP_ID) { if (step.stepId === LOOP_STEP_ID) {
loopStep = step loopStep = step
loopStepNumber = stepCount loopStepNumber = stepCount
continue 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++) { for (let index = 0; index < iterations; index++) {
let originalStepInput = cloneDeep(step.inputs) let originalStepInput = cloneDeep(step.inputs)
/* // Handle if the user has set a max iteration count or if it reaches the max limit set by us
if (step.stepId === LOOP_STEP_ID && index >= loopStep.inputs.iterations) { if (loopStep) {
this.executionOutput.steps[loopStepNumber].outputs.status = "Loop Broken" // lets first of all handle the input
break // 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 // execution stopped, record state for that
if (stopped) { if (stopped) {
this.updateExecutionOutput(step.id, step.stepId, {}, STOPPED_STATUS) 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 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 stepFn = await this.getStepFunctionality(step.stepId)
let inputs = await processObject(originalStepInput, this._context) let inputs = await processObject(originalStepInput, this._context)
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs) inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
@ -143,7 +261,6 @@ class Orchestrator {
}) })
continue continue
} }
this._context.steps.splice(loopStepNumber, 1)
if (loopStep && loopSteps) { if (loopStep && loopSteps) {
loopSteps.push(outputs) loopSteps.push(outputs)
} else { } else {
@ -158,26 +275,34 @@ class Orchestrator {
console.error(`Automation error - ${step.stepId} - ${err}`) console.error(`Automation error - ${step.stepId} - ${err}`)
return err return err
} }
if (loopStep) {
if (index === iterations - 1) { iterationCount++
lastLoopStep = loopStep if (index === iterations - 1) {
loopStep = null loopStep = null
break this._context.steps.splice(loopStepNumber, 1)
break
}
} }
} }
if (loopSteps && loopSteps.length) { if (loopSteps && loopSteps.length) {
let tempOutput = { success: true, outputs: loopSteps } let tempOutput = {
this.executionOutput.steps.splice(loopStep, 0, { success: true,
id: lastLoopStep.id, items: loopSteps,
stepId: lastLoopStep.stepId, iterations: iterationCount,
}
this.executionOutput.steps.splice(loopStepNumber + 1, 0, {
id: step.id,
stepId: step.stepId,
outputs: tempOutput, outputs: tempOutput,
inputs: step.inputs, inputs: step.inputs,
}) })
this._context.steps.splice(loopStep, 0, tempOutput)
this._context.steps.splice(loopStepNumber, 0, tempOutput)
loopSteps = null loopSteps = null
} }
} }
return this.executionOutput return this.executionOutput
} }
} }