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(
|
||||
$tables.list,
|
||||
Object.values(schema || {}),
|
||||
{ allowLinks: true }
|
||||
{ allowLinks: false }
|
||||
)
|
||||
$: queryLimit = tableId?.includes("datasource") ? "∞" : "1000"
|
||||
$: isTrigger = $memoBlock?.type === AutomationStepType.TRIGGER
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
AutomationTriggerStepId,
|
||||
AutomationEventType,
|
||||
AutomationStepType,
|
||||
AutomationActionStepId,
|
||||
} from "@budibase/types"
|
||||
import { ActionStepID } from "constants/backend/automations"
|
||||
import { FIELDS } from "constants/backend"
|
||||
|
@ -466,9 +467,13 @@ const automationActions = store => ({
|
|||
.getPathSteps(block.pathTo, automation)
|
||||
.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
|
||||
let bindings = []
|
||||
|
||||
const addBinding = (name, value, icon, idx, isLoopBlock, bindingName) => {
|
||||
if (!name) return
|
||||
const runtimeBinding = determineRuntimeBinding(
|
||||
|
@ -519,9 +524,24 @@ const automationActions = store => ({
|
|||
runtimeName = `loop.${name}`
|
||||
} else if (idx === 0) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -637,7 +657,6 @@ const automationActions = store => ({
|
|||
console.error("Loop block missing.")
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(schema).forEach(([name, value]) => {
|
||||
addBinding(name, value, icon, blockIdx, isLoopBlock, bindingName)
|
||||
})
|
||||
|
|
|
@ -61,6 +61,9 @@ export async function run({
|
|||
inputs: ServerLogStepInputs
|
||||
appId: string
|
||||
}): Promise<ServerLogStepOutputs> {
|
||||
if (typeof inputs.text !== "string") {
|
||||
inputs.text = JSON.stringify(inputs.text)
|
||||
}
|
||||
const message = `App ${appId} - ${inputs.text}`
|
||||
console.log(message)
|
||||
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", () => {
|
||||
let config = getConfig()
|
||||
describe("Execute Script Automations", () => {
|
||||
let config = setup.getConfig(),
|
||||
table: Table
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
await config.createRow()
|
||||
})
|
||||
afterAll(_afterAll)
|
||||
|
||||
it("should be able to execute a script", async () => {
|
||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: "return 1 + 1",
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
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 () => {
|
||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: null,
|
||||
it("should access bindings from previous steps", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
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 () => {
|
||||
const res = await runStep(
|
||||
config,
|
||||
actions.EXECUTE_SCRIPT.stepId,
|
||||
{
|
||||
code: "return steps.map(d => d.value)",
|
||||
},
|
||||
{
|
||||
steps: [{ value: 0 }, { value: 1 }],
|
||||
}
|
||||
it("should handle script execution errors gracefully", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Handle Script Errors",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.executeScript({ code: "return nonexistentVariable.map(x => x)" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[0].outputs.response).toContain(
|
||||
"ReferenceError: nonexistentVariable is not defined"
|
||||
)
|
||||
expect(res.value).toEqual([0, 1])
|
||||
expect(res.response).toBeUndefined()
|
||||
expect(res.success).toEqual(true)
|
||||
expect(results.steps[0].outputs.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("should be able to handle an error gracefully", async () => {
|
||||
const res = await runStep(config, actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: "return something.map(x => x.name)",
|
||||
it("should handle conditional logic in scripts", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
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,
|
||||
Branch,
|
||||
FilterStepInputs,
|
||||
ExecuteScriptStepInputs,
|
||||
} from "@budibase/types"
|
||||
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
||||
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 {
|
||||
return this.step(
|
||||
AutomationActionStepId.FILTER,
|
||||
|
|
|
@ -385,7 +385,7 @@ class Orchestrator {
|
|||
stepIdx: number,
|
||||
pathIdx?: number
|
||||
): Promise<number> {
|
||||
await processObject(loopStep.inputs, this.processContext(this.context))
|
||||
await processObject(loopStep.inputs, this.mergeContexts(this.context))
|
||||
const iterations = getLoopIterations(loopStep)
|
||||
let stepToLoopIndex = stepIdx + 1
|
||||
let pathStepIdx = (pathIdx || stepIdx) + 1
|
||||
|
@ -573,14 +573,14 @@ class Orchestrator {
|
|||
for (const [field, value] of Object.entries(filters[filterKey])) {
|
||||
const fromContext = processStringSync(
|
||||
field,
|
||||
this.processContext(this.context)
|
||||
this.mergeContexts(this.context)
|
||||
)
|
||||
toFilter[field] = fromContext
|
||||
|
||||
if (typeof value === "string" && findHBSBlocks(value).length > 0) {
|
||||
const processedVal = processStringSync(
|
||||
value,
|
||||
this.processContext(this.context)
|
||||
this.mergeContexts(this.context)
|
||||
)
|
||||
|
||||
filters[filterKey][field] = processedVal
|
||||
|
@ -637,7 +637,7 @@ class Orchestrator {
|
|||
const stepFn = await this.getStepFunctionality(step.stepId)
|
||||
let inputs = await processObject(
|
||||
originalStepInput,
|
||||
this.processContext(this.context)
|
||||
this.mergeContexts(this.context)
|
||||
)
|
||||
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
|
||||
|
||||
|
@ -645,7 +645,7 @@ class Orchestrator {
|
|||
inputs: inputs,
|
||||
appId: this.appId,
|
||||
emitter: this.emitter,
|
||||
context: this.context,
|
||||
context: this.mergeContexts(this.context),
|
||||
})
|
||||
this.handleStepOutput(step, outputs, loopIteration)
|
||||
}
|
||||
|
@ -665,8 +665,8 @@ class Orchestrator {
|
|||
return null
|
||||
}
|
||||
|
||||
private processContext(context: AutomationContext) {
|
||||
const processContext = {
|
||||
private mergeContexts(context: AutomationContext) {
|
||||
const mergeContexts = {
|
||||
...context,
|
||||
steps: {
|
||||
...context.steps,
|
||||
|
@ -674,7 +674,7 @@ class Orchestrator {
|
|||
...context.stepsByName,
|
||||
},
|
||||
}
|
||||
return processContext
|
||||
return mergeContexts
|
||||
}
|
||||
|
||||
private handleStepOutput(
|
||||
|
|
Loading…
Reference in New Issue