Loop tests passing again.
This commit is contained in:
parent
3f104cb2ab
commit
57149b77e1
|
@ -274,26 +274,3 @@ export function stringSplit(value: string | string[]) {
|
||||||
}
|
}
|
||||||
return value.split(",")
|
return value.split(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function typecastForLooping(input: LoopStepInputs) {
|
|
||||||
if (!input || !input.binding) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
switch (input.option) {
|
|
||||||
case LoopStepType.ARRAY:
|
|
||||||
if (typeof input.binding === "string") {
|
|
||||||
return JSON.parse(input.binding)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case LoopStepType.STRING:
|
|
||||||
if (Array.isArray(input.binding)) {
|
|
||||||
return input.binding.join(",")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error("Unable to cast to correct type")
|
|
||||||
}
|
|
||||||
return input.binding
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { cleanInputValues, substituteLoopStep } from "../automationUtils"
|
||||||
typecastForLooping,
|
|
||||||
cleanInputValues,
|
|
||||||
substituteLoopStep,
|
|
||||||
} from "../automationUtils"
|
|
||||||
import { LoopStepType } from "@budibase/types"
|
|
||||||
|
|
||||||
describe("automationUtils", () => {
|
describe("automationUtils", () => {
|
||||||
describe("substituteLoopStep", () => {
|
describe("substituteLoopStep", () => {
|
||||||
|
@ -30,29 +25,6 @@ describe("automationUtils", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("typeCastForLooping", () => {
|
|
||||||
it("should parse to correct type", () => {
|
|
||||||
expect(
|
|
||||||
typecastForLooping({ option: LoopStepType.ARRAY, binding: [1, 2, 3] })
|
|
||||||
).toEqual([1, 2, 3])
|
|
||||||
expect(
|
|
||||||
typecastForLooping({ option: LoopStepType.ARRAY, binding: "[1,2,3]" })
|
|
||||||
).toEqual([1, 2, 3])
|
|
||||||
expect(
|
|
||||||
typecastForLooping({ option: LoopStepType.STRING, binding: [1, 2, 3] })
|
|
||||||
).toEqual("1,2,3")
|
|
||||||
})
|
|
||||||
it("should handle null values", () => {
|
|
||||||
// expect it to handle where the binding is null
|
|
||||||
expect(
|
|
||||||
typecastForLooping({ option: LoopStepType.ARRAY, binding: null })
|
|
||||||
).toEqual(null)
|
|
||||||
expect(() =>
|
|
||||||
typecastForLooping({ option: LoopStepType.ARRAY, binding: "test" })
|
|
||||||
).toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("cleanInputValues", () => {
|
describe("cleanInputValues", () => {
|
||||||
it("should handle array relationship fields from read binding", () => {
|
it("should handle array relationship fields from read binding", () => {
|
||||||
const schema = {
|
const schema = {
|
||||||
|
|
|
@ -130,11 +130,6 @@ export enum InvalidColumns {
|
||||||
TABLE_ID = "tableId",
|
TABLE_ID = "tableId",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutomationErrors {
|
|
||||||
INCORRECT_TYPE = "INCORRECT_TYPE",
|
|
||||||
FAILURE_CONDITION = "FAILURE_CONDITION_MET",
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass through the list from the auth/core lib
|
// pass through the list from the auth/core lib
|
||||||
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
export const ObjectStoreBuckets = objectStore.ObjectStoreBuckets
|
||||||
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
export const MAX_AUTOMATION_RECURRING_ERRORS = 5
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { dataFilters, helpers, utils } from "@budibase/shared-core"
|
||||||
import { default as AutomationEmitter } from "../events/AutomationEmitter"
|
import { default as AutomationEmitter } from "../events/AutomationEmitter"
|
||||||
import { generateAutomationMetadataID, isProdAppID } from "../db/utils"
|
import { generateAutomationMetadataID, isProdAppID } from "../db/utils"
|
||||||
import { automations } from "@budibase/shared-core"
|
import { automations } from "@budibase/shared-core"
|
||||||
import { AutomationErrors, MAX_AUTOMATION_RECURRING_ERRORS } from "../constants"
|
import { MAX_AUTOMATION_RECURRING_ERRORS } from "../constants"
|
||||||
import { storeLog } from "../automations/logging"
|
import { storeLog } from "../automations/logging"
|
||||||
import {
|
import {
|
||||||
Automation,
|
Automation,
|
||||||
|
@ -65,32 +65,23 @@ function matchesLoopFailureCondition(loopStep: LoopStep, currentItem: any) {
|
||||||
return currentItem === loopStep.inputs.failure
|
return currentItem === loopStep.inputs.failure
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLoopIterations(loopStep: LoopStep) {
|
function getLoopIterable(loopStep: LoopStep): any[] {
|
||||||
const binding = loopStep.inputs.binding
|
const option = loopStep.inputs.option
|
||||||
if (!binding) {
|
let input: any = loopStep.inputs.binding
|
||||||
return 0
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const json = typeof binding === "string" ? JSON.parse(binding) : binding
|
|
||||||
if (Array.isArray(json)) {
|
|
||||||
return json.length
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// ignore error - wasn't able to parse
|
|
||||||
}
|
|
||||||
if (typeof binding === "string") {
|
|
||||||
return automationUtils.stringSplit(binding).length
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoopMaxIterations(loopStep: LoopStep) {
|
if (option === LoopStepType.ARRAY && typeof input === "string") {
|
||||||
const value = loopStep.inputs.iterations
|
input = JSON.parse(input)
|
||||||
if (typeof value === "number") return value
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return parseInt(value)
|
|
||||||
}
|
}
|
||||||
return undefined
|
|
||||||
|
if (option === LoopStepType.STRING && Array.isArray(input)) {
|
||||||
|
input = input.join(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option === LoopStepType.STRING && typeof input === "string") {
|
||||||
|
input = automationUtils.stringSplit(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(input) ? input : [input]
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareContext(context: AutomationContext) {
|
function prepareContext(context: AutomationContext) {
|
||||||
|
@ -333,24 +324,26 @@ class Orchestrator {
|
||||||
|
|
||||||
const step = steps[stepIndex]
|
const step = steps[stepIndex]
|
||||||
if (step.stepId === AutomationActionStepId.BRANCH) {
|
if (step.stepId === AutomationActionStepId.BRANCH) {
|
||||||
// stepIndex for current step context offset
|
const [result, ...childResults] = await this.executeBranchStep(
|
||||||
// pathIdx relating to the full list of steps in the run
|
ctx,
|
||||||
const [branchResult, ...branchStepResults] =
|
step
|
||||||
await this.executeBranchStep(ctx, step)
|
)
|
||||||
|
|
||||||
stepOutputs.push(branchResult)
|
stepOutputs.push(result)
|
||||||
stepOutputs.push(...branchStepResults)
|
stepOutputs.push(...childResults)
|
||||||
|
|
||||||
stepIndex++
|
stepIndex++
|
||||||
} else if (step.stepId === AutomationActionStepId.LOOP) {
|
} else if (step.stepId === AutomationActionStepId.LOOP) {
|
||||||
const output = await this.executeLoopStep(
|
const stepToLoop = steps[stepIndex + 1]
|
||||||
ctx,
|
const result = await this.executeLoopStep(ctx, step, stepToLoop)
|
||||||
step,
|
|
||||||
steps[stepIndex + 1]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
ctx.steps.push(result.outputs)
|
||||||
|
ctx.stepsById[stepToLoop.id] = result.outputs
|
||||||
|
ctx.stepsByName[stepToLoop.name || stepToLoop.id] =
|
||||||
|
result.outputs
|
||||||
|
|
||||||
|
stepOutputs.push(result)
|
||||||
stepIndex += 2
|
stepIndex += 2
|
||||||
stepOutputs.push(output)
|
|
||||||
} else {
|
} else {
|
||||||
const result = await this.executeStep(ctx, step)
|
const result = await this.executeStep(ctx, step)
|
||||||
|
|
||||||
|
@ -381,56 +374,81 @@ class Orchestrator {
|
||||||
stepToLoop: AutomationStep
|
stepToLoop: AutomationStep
|
||||||
): Promise<AutomationStepResult> {
|
): Promise<AutomationStepResult> {
|
||||||
await processObject(loopStep.inputs, prepareContext(ctx))
|
await processObject(loopStep.inputs, prepareContext(ctx))
|
||||||
const maxIterations = getLoopMaxIterations(loopStep)
|
|
||||||
const items: AutomationStepResult[] = []
|
|
||||||
|
|
||||||
let status: AutomationStepStatus | undefined = undefined
|
const result = {
|
||||||
let success = true
|
id: loopStep.id,
|
||||||
|
stepId: loopStep.stepId,
|
||||||
|
inputs: loopStep.inputs,
|
||||||
|
}
|
||||||
|
|
||||||
let i = 0
|
const loopMaxIterations =
|
||||||
for (; i < getLoopIterations(loopStep); i++) {
|
typeof loopStep.inputs.iterations === "string"
|
||||||
try {
|
? parseInt(loopStep.inputs.iterations)
|
||||||
loopStep.inputs.binding = automationUtils.typecastForLooping(
|
: loopStep.inputs.iterations
|
||||||
loopStep.inputs
|
const maxIterations = Math.min(
|
||||||
)
|
loopMaxIterations || env.AUTOMATION_MAX_ITERATIONS,
|
||||||
} catch (err) {
|
env.AUTOMATION_MAX_ITERATIONS
|
||||||
break
|
)
|
||||||
|
|
||||||
|
const items: Record<string, any>[] = []
|
||||||
|
let iterations = 0
|
||||||
|
let iterable: any[] = []
|
||||||
|
try {
|
||||||
|
iterable = getLoopIterable(loopStep)
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
outputs: {
|
||||||
|
success: false,
|
||||||
|
status: AutomationStepStatus.INCORRECT_TYPE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; iterations < iterable.length; iterations++) {
|
||||||
|
const currentItem = iterable[iterations]
|
||||||
|
|
||||||
|
if (iterations === maxIterations) {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
outputs: {
|
||||||
|
success: false,
|
||||||
|
iterations,
|
||||||
|
items,
|
||||||
|
status: AutomationStepStatus.MAX_ITERATIONS,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
i === env.AUTOMATION_MAX_ITERATIONS ||
|
|
||||||
(loopStep.inputs.iterations && i === maxIterations)
|
|
||||||
) {
|
|
||||||
status = AutomationStepStatus.MAX_ITERATIONS
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentItem = this.getCurrentLoopItem(loopStep, i)
|
|
||||||
if (matchesLoopFailureCondition(loopStep, currentItem)) {
|
if (matchesLoopFailureCondition(loopStep, currentItem)) {
|
||||||
status = AutomationStepStatus.FAILURE_CONDITION
|
return {
|
||||||
success = false
|
...result,
|
||||||
break
|
outputs: {
|
||||||
|
success: false,
|
||||||
|
iterations,
|
||||||
|
items,
|
||||||
|
status: AutomationStepStatus.FAILURE_CONDITION,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.loop = { currentItem }
|
ctx.loop = { currentItem }
|
||||||
items.push(await this.executeStep(ctx, stepToLoop))
|
const loopedStepResult = await this.executeStep(ctx, stepToLoop)
|
||||||
|
items.push(loopedStepResult.outputs)
|
||||||
ctx.loop = undefined
|
ctx.loop = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
status = AutomationStepStatus.NO_ITERATIONS
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: loopStep.id,
|
id: stepToLoop.id,
|
||||||
stepId: loopStep.stepId,
|
stepId: stepToLoop.stepId,
|
||||||
|
inputs: stepToLoop.inputs,
|
||||||
outputs: {
|
outputs: {
|
||||||
success,
|
success: true,
|
||||||
status,
|
status:
|
||||||
iterations: i,
|
iterations === 0 ? AutomationStepStatus.NO_ITERATIONS : undefined,
|
||||||
|
iterations,
|
||||||
items,
|
items,
|
||||||
},
|
},
|
||||||
inputs: loopStep.inputs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,6 +591,7 @@ class Orchestrator {
|
||||||
outputs.result === false
|
outputs.result === false
|
||||||
) {
|
) {
|
||||||
this.stopped = true
|
this.stopped = true
|
||||||
|
;(outputs as any).status = AutomationStatus.STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -584,18 +603,6 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentLoopItem(loopStep: LoopStep, index: number): any {
|
|
||||||
if (
|
|
||||||
typeof loopStep.inputs.binding === "string" &&
|
|
||||||
loopStep.inputs.option === LoopStepType.STRING
|
|
||||||
) {
|
|
||||||
return automationUtils.stringSplit(loopStep.inputs.binding)[index]
|
|
||||||
} else if (Array.isArray(loopStep.inputs.binding)) {
|
|
||||||
return loopStep.inputs.binding[index]
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
export function execute(job: Job<AutomationData>, callback: WorkerCallback) {
|
||||||
|
|
|
@ -176,7 +176,8 @@ export enum AutomationFeature {
|
||||||
export enum AutomationStepStatus {
|
export enum AutomationStepStatus {
|
||||||
NO_ITERATIONS = "no_iterations",
|
NO_ITERATIONS = "no_iterations",
|
||||||
MAX_ITERATIONS = "max_iterations_reached",
|
MAX_ITERATIONS = "max_iterations_reached",
|
||||||
FAILURE_CONDITION = "failure_condition",
|
FAILURE_CONDITION = "FAILURE_CONDITION_MET",
|
||||||
|
INCORRECT_TYPE = "INCORRECT_TYPE",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutomationStatus {
|
export enum AutomationStatus {
|
||||||
|
|
Loading…
Reference in New Issue