diff --git a/packages/server/src/automations/steps/branch.ts b/packages/server/src/automations/steps/branch.ts new file mode 100644 index 0000000000..56396488cb --- /dev/null +++ b/packages/server/src/automations/steps/branch.ts @@ -0,0 +1,51 @@ +import { + AutomationActionStepId, + AutomationCustomIOType, + AutomationIOType, + AutomationStepDefinition, + AutomationStepType, +} from "@budibase/types" + +export const definition: AutomationStepDefinition = { + name: "Branch", + icon: "Branch3", + tagline: "Branch from this step", + description: "Branching", + stepId: AutomationActionStepId.BRANCH, + internal: true, + features: {}, + inputs: {}, + schema: { + inputs: { + properties: { + branches: { + properties: { + name: { + type: AutomationIOType.STRING, + }, + condition: { + customType: AutomationCustomIOType.FILTERS, + }, + }, + }, + children: { + type: AutomationIOType.ARRAY, + }, + }, + required: ["conditions"], + }, + outputs: { + properties: { + branchName: { + type: AutomationIOType.STRING, + }, + result: { + type: AutomationIOType.BOOLEAN, + description: "Whether the condition was met", + }, + }, + required: ["output"], + }, + }, + type: AutomationStepType.LOGIC, +} diff --git a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts index 7df902d61c..c0c30e46e4 100644 --- a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts +++ b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts @@ -7,7 +7,7 @@ import { ServerLogStepOutputs, FieldType, } from "@budibase/types" -import { createAutomationBuilder } from "../utilities/AutomationBuilder" +import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" import { DatabaseName } from "../../../integrations/tests/utils" describe("Automation Scenarios", () => { @@ -23,6 +23,43 @@ describe("Automation Scenarios", () => { afterAll(setup.afterAll) + // eslint-disable-next-line jest/no-commented-out-tests + // describe("Branching automations", () => { + // eslint-disable-next-line jest/no-commented-out-tests + // it("should run an automation with a trigger, loop, and create row step", async () => { + // const builder = createAutomationBuilder({ + // name: "Test Trigger with Loop and Create Row", + // }) + + // builder + // .serverLog({ text: "Starting automation" }) + // .branch({ + // topLevelBranch1: { + // steps: stepBuilder => + // stepBuilder.serverLog({ text: "Branch 1" }).branch({ + // branch1: { + // steps: stepBuilder => + // stepBuilder.serverLog({ text: "Branch 1.1" }), + // condition: { notEmpty: { column: 10 } }, + // }, + // branch2: { + // steps: stepBuilder => + // stepBuilder.serverLog({ text: "Branch 1.2" }), + // condition: { fuzzy: { column: "sadsd" } }, + // }, + // }), + // condition: { equal: { column: 10 } }, + // }, + // topLevelBranch2: { + // steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }), + // condition: { equal: { column: 20 } }, + // }, + // }) + // .run() + // }) + + // }) + describe("Loop automations", () => { it("should run an automation with a trigger, loop, and create row step", async () => { const builder = createAutomationBuilder({ diff --git a/packages/server/src/automations/tests/utilities/AutomationBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts similarity index 69% rename from packages/server/src/automations/tests/utilities/AutomationBuilder.ts rename to packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts index b3a9d57d06..16cab73b75 100644 --- a/packages/server/src/automations/tests/utilities/AutomationBuilder.ts +++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts @@ -30,10 +30,13 @@ import { AutomationStepInputs, AutomationTriggerInputs, ServerLogStepInputs, + BranchStepInputs, + SearchFilters, + Branch, } from "@budibase/types" -import {} from "../../steps/loop" import TestConfiguration from "../../../tests/utilities/TestConfiguration" import * as setup from "../utilities" +import { definition } from "../../../automations/steps/branch" type TriggerOutputs = | RowCreatedTriggerOutputs @@ -43,69 +46,56 @@ type TriggerOutputs = | CronTriggerOutputs | undefined -class AutomationBuilder { - private automationConfig: Automation = { - name: "", - definition: { - steps: [], - trigger: {} as AutomationTrigger, - }, - type: "automation", - appId: setup.getConfig().getAppId(), - } - private config: TestConfiguration = setup.getConfig() - private triggerOutputs: TriggerOutputs - private triggerSet: boolean = false +type StepBuilderFunction = (stepBuilder: StepBuilder) => void - constructor(options: { name?: string } = {}) { - this.automationConfig.name = options.name || `Test Automation ${uuidv4()}` +type BranchConfig = { + [key: string]: { + steps: StepBuilderFunction + condition: SearchFilters + } +} + +class BaseStepBuilder { + protected steps: AutomationStep[] = [] + + protected step( + stepId: TStep, + stepSchema: Omit, + inputs: AutomationStepInputs + ): this { + this.steps.push({ + ...stepSchema, + inputs: inputs as any, + id: uuidv4(), + stepId, + }) + return this } - // TRIGGERS - rowSaved(inputs: RowCreatedTriggerInputs, outputs: RowCreatedTriggerOutputs) { - this.triggerOutputs = outputs - return this.trigger( - TRIGGER_DEFINITIONS.ROW_SAVED, - AutomationTriggerStepId.ROW_SAVED, - inputs, - outputs - ) - } + protected addBranchStep(branchConfig: BranchConfig): void { + const branchStepInputs: BranchStepInputs = { + branches: [] as Branch[], + children: {}, + } - rowUpdated( - inputs: RowUpdatedTriggerInputs, - outputs: RowUpdatedTriggerOutputs - ) { - this.triggerOutputs = outputs - return this.trigger( - TRIGGER_DEFINITIONS.ROW_UPDATED, - AutomationTriggerStepId.ROW_UPDATED, - inputs, - outputs - ) - } + Object.entries(branchConfig).forEach(([key, branch]) => { + const stepBuilder = new StepBuilder() + branch.steps(stepBuilder) - rowDeleted( - inputs: RowDeletedTriggerInputs, - outputs: RowDeletedTriggerOutputs - ) { - this.triggerOutputs = outputs - return this.trigger( - TRIGGER_DEFINITIONS.ROW_DELETED, - AutomationTriggerStepId.ROW_DELETED, - inputs, - outputs - ) - } + branchStepInputs.branches.push({ + name: key, + condition: branch.condition, + }) + branchStepInputs.children![key] = stepBuilder.build() + }) - appAction(outputs: AppActionTriggerOutputs, inputs?: AppActionTriggerInputs) { - this.triggerOutputs = outputs - return this.trigger( - TRIGGER_DEFINITIONS.APP, - AutomationTriggerStepId.APP, - inputs, - outputs - ) + const branchStep: AutomationStep = { + ...definition, + id: uuidv4(), + stepId: AutomationActionStepId.BRANCH, + inputs: branchStepInputs, + } + this.steps.push(branchStep) } // STEPS @@ -171,6 +161,84 @@ class AutomationBuilder { input ) } +} +class StepBuilder extends BaseStepBuilder { + build(): AutomationStep[] { + return this.steps + } + + branch(branchConfig: BranchConfig): this { + this.addBranchStep(branchConfig) + return this + } +} + +class AutomationBuilder extends BaseStepBuilder { + private automationConfig: Automation + private config: TestConfiguration + private triggerOutputs: any + private triggerSet: boolean = false + + constructor(options: { name?: string } = {}) { + super() + this.automationConfig = { + name: options.name || `Test Automation ${uuidv4()}`, + definition: { + steps: [], + trigger: {} as AutomationTrigger, + }, + type: "automation", + appId: setup.getConfig().getAppId(), + } + this.config = setup.getConfig() + } + + // TRIGGERS + rowSaved(inputs: RowCreatedTriggerInputs, outputs: RowCreatedTriggerOutputs) { + this.triggerOutputs = outputs + return this.trigger( + TRIGGER_DEFINITIONS.ROW_SAVED, + AutomationTriggerStepId.ROW_SAVED, + inputs, + outputs + ) + } + + rowUpdated( + inputs: RowUpdatedTriggerInputs, + outputs: RowUpdatedTriggerOutputs + ) { + this.triggerOutputs = outputs + return this.trigger( + TRIGGER_DEFINITIONS.ROW_UPDATED, + AutomationTriggerStepId.ROW_UPDATED, + inputs, + outputs + ) + } + + rowDeleted( + inputs: RowDeletedTriggerInputs, + outputs: RowDeletedTriggerOutputs + ) { + this.triggerOutputs = outputs + return this.trigger( + TRIGGER_DEFINITIONS.ROW_DELETED, + AutomationTriggerStepId.ROW_DELETED, + inputs, + outputs + ) + } + + appAction(outputs: AppActionTriggerOutputs, inputs?: AppActionTriggerInputs) { + this.triggerOutputs = outputs + return this.trigger( + TRIGGER_DEFINITIONS.APP, + AutomationTriggerStepId.APP, + inputs, + outputs + ) + } private trigger( triggerSchema: AutomationTriggerDefinition, @@ -181,7 +249,6 @@ class AutomationBuilder { if (this.triggerSet) { throw new Error("Only one trigger can be set for an automation.") } - this.automationConfig.definition.trigger = { ...triggerSchema, stepId, @@ -194,21 +261,20 @@ class AutomationBuilder { return this } - private step( - stepId: TStep, - stepSchema: Omit, - inputs: AutomationStepInputs - ): this { - this.automationConfig.definition.steps.push({ - ...stepSchema, - inputs: inputs as any, - id: uuidv4(), - stepId, - }) - return this + branch(branchConfig: BranchConfig): { + run: () => Promise + } { + this.addBranchStep(branchConfig) + return { + run: () => this.run(), + } } async run() { + if (!Object.keys(this.automationConfig.definition.trigger).length) { + throw new Error("Please add a trigger to this automation test") + } + this.automationConfig.definition.steps = this.steps const automation = await this.config.createAutomation(this.automationConfig) const results = await testAutomation( this.config, @@ -218,7 +284,9 @@ class AutomationBuilder { return this.processResults(results) } - private processResults(results: { body: AutomationResults }) { + private processResults(results: { + body: AutomationResults + }): AutomationResults { results.body.steps.shift() return { trigger: results.body.trigger, diff --git a/packages/types/src/documents/app/automation/StepInputsOutputs.ts b/packages/types/src/documents/app/automation/StepInputsOutputs.ts index e62a6e3f08..983a67daf5 100644 --- a/packages/types/src/documents/app/automation/StepInputsOutputs.ts +++ b/packages/types/src/documents/app/automation/StepInputsOutputs.ts @@ -111,10 +111,15 @@ export type LoopStepOutputs = { } export type BranchStepInputs = { - conditions: SearchFilters + branches: Branch[] children?: Record } +export type Branch = { + name: string + condition: SearchFilters +} + export type MakeIntegrationInputs = { url: string body: any