Merge pull request #15054 from Budibase/fix/automation-adjacent-bugs
Some automation bugs
This commit is contained in:
commit
ef4da634a0
|
@ -114,7 +114,7 @@
|
||||||
$: schemaFields = search.getFields(
|
$: schemaFields = search.getFields(
|
||||||
$tables.list,
|
$tables.list,
|
||||||
Object.values(schema || {}),
|
Object.values(schema || {}),
|
||||||
{ allowLinks: true }
|
{ allowLinks: false }
|
||||||
)
|
)
|
||||||
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
|
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
|
||||||
$: isTrigger = $memoBlock?.type === AutomationStepType.TRIGGER
|
$: isTrigger = $memoBlock?.type === AutomationStepType.TRIGGER
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
AutomationTriggerStepId,
|
AutomationTriggerStepId,
|
||||||
AutomationEventType,
|
AutomationEventType,
|
||||||
AutomationStepType,
|
AutomationStepType,
|
||||||
|
AutomationActionStepId,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { ActionStepID } from "constants/backend/automations"
|
import { ActionStepID } from "constants/backend/automations"
|
||||||
import { FIELDS } from "constants/backend"
|
import { FIELDS } from "constants/backend"
|
||||||
|
@ -466,9 +467,13 @@ const automationActions = store => ({
|
||||||
.getPathSteps(block.pathTo, automation)
|
.getPathSteps(block.pathTo, automation)
|
||||||
.slice(0, -1)
|
.slice(0, -1)
|
||||||
|
|
||||||
|
// Current step will always be the last step of the path
|
||||||
|
const currentBlock = store.actions
|
||||||
|
.getPathSteps(block.pathTo, automation)
|
||||||
|
.at(-1)
|
||||||
|
|
||||||
// Extract all outputs from all previous steps as available bindingsx§x
|
// Extract all outputs from all previous steps as available bindingsx§x
|
||||||
let bindings = []
|
let bindings = []
|
||||||
|
|
||||||
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
|
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
|
||||||
if (!name) return
|
if (!name) return
|
||||||
const runtimeBinding = determineRuntimeBinding(
|
const runtimeBinding = determineRuntimeBinding(
|
||||||
|
@ -519,9 +524,24 @@ const automationActions = store => ({
|
||||||
runtimeName = `loop.${name}`
|
runtimeName = `loop.${name}`
|
||||||
} else if (idx === 0) {
|
} else if (idx === 0) {
|
||||||
runtimeName = `trigger.${name}`
|
runtimeName = `trigger.${name}`
|
||||||
|
} else if (
|
||||||
|
currentBlock?.stepId === AutomationActionStepId.EXECUTE_SCRIPT
|
||||||
|
) {
|
||||||
|
const stepId = pathSteps[idx].id
|
||||||
|
if (!stepId) {
|
||||||
|
notifications.error("Error generating binding: Step ID not found.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
runtimeName = `steps["${stepId}"].${name}`
|
||||||
} else {
|
} else {
|
||||||
runtimeName = `steps.${pathSteps[idx]?.id}.${name}`
|
const stepId = pathSteps[idx].id
|
||||||
|
if (!stepId) {
|
||||||
|
notifications.error("Error generating binding: Step ID not found.")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
runtimeName = `steps.${stepId}.${name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return runtimeName
|
return runtimeName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,7 +657,6 @@ const automationActions = store => ({
|
||||||
console.error("Loop block missing.")
|
console.error("Loop block missing.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.entries(schema).forEach(([name, value]) => {
|
Object.entries(schema).forEach(([name, value]) => {
|
||||||
addBinding(name, value, icon, blockIdx, isLoopBlock, bindingName)
|
addBinding(name, value, icon, blockIdx, isLoopBlock, bindingName)
|
||||||
})
|
})
|
||||||
|
|
|
@ -61,6 +61,9 @@ export async function run({
|
||||||
inputs: ServerLogStepInputs
|
inputs: ServerLogStepInputs
|
||||||
appId: string
|
appId: string
|
||||||
}): Promise<ServerLogStepOutputs> {
|
}): Promise<ServerLogStepOutputs> {
|
||||||
|
if (typeof inputs.text !== "string") {
|
||||||
|
inputs.text = JSON.stringify(inputs.text)
|
||||||
|
}
|
||||||
const message = `App ${appId} - ${inputs.text}`
|
const message = `App ${appId} - ${inputs.text}`
|
||||||
console.log(message)
|
console.log(message)
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,50 +1,123 @@
|
||||||
import { getConfig, afterAll as _afterAll, runStep, actions } from "./utilities"
|
import { createAutomationBuilder } from "./utilities/AutomationTestBuilder"
|
||||||
|
import * as automation from "../index"
|
||||||
|
import * as setup from "./utilities"
|
||||||
|
import { Table } from "@budibase/types"
|
||||||
|
|
||||||
describe("test the execute script action", () => {
|
describe("Execute Script Automations", () => {
|
||||||
let config = getConfig()
|
let config = setup.getConfig(),
|
||||||
|
table: Table
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
|
await automation.init()
|
||||||
await config.init()
|
await config.init()
|
||||||
|
table = await config.createTable()
|
||||||
|
await config.createRow()
|
||||||
})
|
})
|
||||||
afterAll(_afterAll)
|
|
||||||
|
|
||||||
it("should be able to execute a script", async () => {
|
afterAll(setup.afterAll)
|
||||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
|
||||||
code: "return 1 + 1",
|
it("should execute a basic script and return the result", async () => {
|
||||||
|
const builder = createAutomationBuilder({
|
||||||
|
name: "Basic Script Execution",
|
||||||
})
|
})
|
||||||
expect(res.value).toEqual(2)
|
|
||||||
expect(res.success).toEqual(true)
|
const results = await builder
|
||||||
|
.appAction({ fields: {} })
|
||||||
|
.executeScript({ code: "return 2 + 2" })
|
||||||
|
.run()
|
||||||
|
|
||||||
|
expect(results.steps[0].outputs.value).toEqual(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle a null value", async () => {
|
it("should access bindings from previous steps", async () => {
|
||||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
const builder = createAutomationBuilder({
|
||||||
code: null,
|
name: "Access Bindings",
|
||||||
})
|
})
|
||||||
expect(res.response.message).toEqual("Invalid inputs")
|
|
||||||
expect(res.success).toEqual(false)
|
const results = await builder
|
||||||
|
.appAction({ fields: { data: [1, 2, 3] } })
|
||||||
|
.executeScript(
|
||||||
|
{
|
||||||
|
code: "return trigger.fields.data.map(x => x * 2)",
|
||||||
|
},
|
||||||
|
{ stepId: "binding-script-step" }
|
||||||
|
)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
expect(results.steps[0].outputs.value).toEqual([2, 4, 6])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to get a value from context", async () => {
|
it("should handle script execution errors gracefully", async () => {
|
||||||
const res = await runStep(
|
const builder = createAutomationBuilder({
|
||||||
config,
|
name: "Handle Script Errors",
|
||||||
actions.EXECUTE_SCRIPT.stepId,
|
})
|
||||||
{
|
|
||||||
code: "return steps.map(d => d.value)",
|
const results = await builder
|
||||||
},
|
.appAction({ fields: {} })
|
||||||
{
|
.executeScript({ code: "return nonexistentVariable.map(x => x)" })
|
||||||
steps: [{ value: 0 }, { value: 1 }],
|
.run()
|
||||||
}
|
|
||||||
|
expect(results.steps[0].outputs.response).toContain(
|
||||||
|
"ReferenceError: nonexistentVariable is not defined"
|
||||||
)
|
)
|
||||||
expect(res.value).toEqual([0, 1])
|
expect(results.steps[0].outputs.success).toEqual(false)
|
||||||
expect(res.response).toBeUndefined()
|
|
||||||
expect(res.success).toEqual(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to handle an error gracefully", async () => {
|
it("should handle conditional logic in scripts", async () => {
|
||||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
const builder = createAutomationBuilder({
|
||||||
code: "return something.map(x => x.name)",
|
name: "Conditional Script Logic",
|
||||||
})
|
})
|
||||||
expect(res.response).toEqual("ReferenceError: something is not defined")
|
|
||||||
expect(res.success).toEqual(false)
|
const results = await builder
|
||||||
|
.appAction({ fields: { value: 10 } })
|
||||||
|
.executeScript({
|
||||||
|
code: `
|
||||||
|
if (trigger.fields.value > 5) {
|
||||||
|
return "Value is greater than 5";
|
||||||
|
} else {
|
||||||
|
return "Value is 5 or less";
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
|
||||||
|
expect(results.steps[0].outputs.value).toEqual("Value is greater than 5")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should use multiple steps and validate script execution", async () => {
|
||||||
|
const builder = createAutomationBuilder({
|
||||||
|
name: "Multi-Step Script Execution",
|
||||||
|
})
|
||||||
|
|
||||||
|
const results = await builder
|
||||||
|
.appAction({ fields: {} })
|
||||||
|
.serverLog(
|
||||||
|
{ text: "Starting multi-step automation" },
|
||||||
|
{ stepId: "start-log-step" }
|
||||||
|
)
|
||||||
|
.createRow(
|
||||||
|
{ row: { name: "Test Row", value: 42, tableId: table._id } },
|
||||||
|
{ stepId: "abc123" }
|
||||||
|
)
|
||||||
|
.executeScript(
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
const createdRow = steps['abc123'];
|
||||||
|
return createdRow.row.value * 2;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{ stepId: "ScriptingStep1" }
|
||||||
|
)
|
||||||
|
.serverLog({
|
||||||
|
text: `Final result is {{ steps.ScriptingStep1.value }}`,
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
|
||||||
|
expect(results.steps[0].outputs.message).toContain(
|
||||||
|
"Starting multi-step automation"
|
||||||
|
)
|
||||||
|
expect(results.steps[1].outputs.row.value).toEqual(42)
|
||||||
|
expect(results.steps[2].outputs.value).toEqual(84)
|
||||||
|
expect(results.steps[3].outputs.message).toContain("Final result is 84")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
Branch,
|
Branch,
|
||||||
FilterStepInputs,
|
FilterStepInputs,
|
||||||
|
ExecuteScriptStepInputs,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
||||||
import * as setup from "../utilities"
|
import * as setup from "../utilities"
|
||||||
|
@ -201,6 +202,18 @@ class BaseStepBuilder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executeScript(
|
||||||
|
input: ExecuteScriptStepInputs,
|
||||||
|
opts?: { stepName?: string; stepId?: string }
|
||||||
|
): this {
|
||||||
|
return this.step(
|
||||||
|
AutomationActionStepId.EXECUTE_SCRIPT,
|
||||||
|
BUILTIN_ACTION_DEFINITIONS.EXECUTE_SCRIPT,
|
||||||
|
input,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
filter(input: FilterStepInputs): this {
|
filter(input: FilterStepInputs): this {
|
||||||
return this.step(
|
return this.step(
|
||||||
AutomationActionStepId.FILTER,
|
AutomationActionStepId.FILTER,
|
||||||
|
|
|
@ -385,7 +385,7 @@ class Orchestrator {
|
||||||
stepIdx: number,
|
stepIdx: number,
|
||||||
pathIdx?: number
|
pathIdx?: number
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
await processObject(loopStep.inputs, this.processContext(this.context))
|
await processObject(loopStep.inputs, this.mergeContexts(this.context))
|
||||||
const iterations = getLoopIterations(loopStep)
|
const iterations = getLoopIterations(loopStep)
|
||||||
let stepToLoopIndex = stepIdx + 1
|
let stepToLoopIndex = stepIdx + 1
|
||||||
let pathStepIdx = (pathIdx || stepIdx) + 1
|
let pathStepIdx = (pathIdx || stepIdx) + 1
|
||||||
|
@ -573,14 +573,14 @@ class Orchestrator {
|
||||||
for (const [field, value] of Object.entries(filters[filterKey])) {
|
for (const [field, value] of Object.entries(filters[filterKey])) {
|
||||||
const fromContext = processStringSync(
|
const fromContext = processStringSync(
|
||||||
field,
|
field,
|
||||||
this.processContext(this.context)
|
this.mergeContexts(this.context)
|
||||||
)
|
)
|
||||||
toFilter[field] = fromContext
|
toFilter[field] = fromContext
|
||||||
|
|
||||||
if (typeof value === "string" && findHBSBlocks(value).length > 0) {
|
if (typeof value === "string" && findHBSBlocks(value).length > 0) {
|
||||||
const processedVal = processStringSync(
|
const processedVal = processStringSync(
|
||||||
value,
|
value,
|
||||||
this.processContext(this.context)
|
this.mergeContexts(this.context)
|
||||||
)
|
)
|
||||||
|
|
||||||
filters[filterKey][field] = processedVal
|
filters[filterKey][field] = processedVal
|
||||||
|
@ -637,7 +637,7 @@ class Orchestrator {
|
||||||
const stepFn = await this.getStepFunctionality(step.stepId)
|
const stepFn = await this.getStepFunctionality(step.stepId)
|
||||||
let inputs = await processObject(
|
let inputs = await processObject(
|
||||||
originalStepInput,
|
originalStepInput,
|
||||||
this.processContext(this.context)
|
this.mergeContexts(this.context)
|
||||||
)
|
)
|
||||||
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
||||||
|
|
||||||
|
@ -645,7 +645,7 @@ class Orchestrator {
|
||||||
inputs: inputs,
|
inputs: inputs,
|
||||||
appId: this.appId,
|
appId: this.appId,
|
||||||
emitter: this.emitter,
|
emitter: this.emitter,
|
||||||
context: this.context,
|
context: this.mergeContexts(this.context),
|
||||||
})
|
})
|
||||||
this.handleStepOutput(step, outputs, loopIteration)
|
this.handleStepOutput(step, outputs, loopIteration)
|
||||||
}
|
}
|
||||||
|
@ -665,8 +665,8 @@ class Orchestrator {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private processContext(context: AutomationContext) {
|
private mergeContexts(context: AutomationContext) {
|
||||||
const processContext = {
|
const mergeContexts = {
|
||||||
...context,
|
...context,
|
||||||
steps: {
|
steps: {
|
||||||
...context.steps,
|
...context.steps,
|
||||||
|
@ -674,7 +674,7 @@ class Orchestrator {
|
||||||
...context.stepsByName,
|
...context.stepsByName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return processContext
|
return mergeContexts
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleStepOutput(
|
private handleStepOutput(
|
||||||
|
|
Loading…
Reference in New Issue