diff --git a/packages/server/src/api/routes/tests/automation.spec.ts b/packages/server/src/api/routes/tests/automation.spec.ts index 5b451eef4a..7a86548278 100644 --- a/packages/server/src/api/routes/tests/automation.spec.ts +++ b/packages/server/src/api/routes/tests/automation.spec.ts @@ -108,7 +108,7 @@ describe("/automations", () => { it("Should ensure you can't have a branch as not a last step", async () => { const automation = createAutomationBuilder(config) - .appAction({ fields: { status: "active" } }) + .appAction() .branch({ activeBranch: { steps: stepBuilder => @@ -132,7 +132,7 @@ describe("/automations", () => { it("Should check validation on an automation that has a branch step with no children", async () => { const automation = createAutomationBuilder(config) - .appAction({ fields: { status: "active" } }) + .appAction() .branch({}) .serverLog({ text: "Inactive user" }) .build() @@ -148,7 +148,7 @@ describe("/automations", () => { it("Should check validation on a branch step with empty conditions", async () => { const automation = createAutomationBuilder(config) - .appAction({ fields: { status: "active" } }) + .appAction() .branch({ activeBranch: { steps: stepBuilder => @@ -169,7 +169,7 @@ describe("/automations", () => { it("Should check validation on an branch that has a condition that is not valid", async () => { const automation = createAutomationBuilder(config) - .appAction({ fields: { status: "active" } }) + .appAction() .branch({ activeBranch: { steps: stepBuilder => @@ -241,6 +241,7 @@ describe("/automations", () => { it("should be able to access platformUrl, logoUrl and company in the automation", async () => { const result = await createAutomationBuilder(config) + .appAction() .serverLog({ text: "{{ settings.url }}", }) @@ -250,7 +251,7 @@ describe("/automations", () => { .serverLog({ text: "{{ settings.company }}", }) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.message).toEndWith("https://example.com") expect(result.steps[1].outputs.message).toEndWith( diff --git a/packages/server/src/automations/tests/branching.spec.ts b/packages/server/src/automations/tests/branching.spec.ts index d9fad543f6..da3f10ec53 100644 --- a/packages/server/src/automations/tests/branching.spec.ts +++ b/packages/server/src/automations/tests/branching.spec.ts @@ -25,6 +25,7 @@ describe("Branching automations", () => { const branch2Id = "44444444-4444-4444-4444-444444444444" const results = await createAutomationBuilder(config) + .appAction() .serverLog( { text: "Starting automation" }, { stepName: "FirstLog", stepId: firstLogId } @@ -75,7 +76,7 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: {} }) expect(results.steps[3].outputs.status).toContain("branch1 branch taken") expect(results.steps[4].outputs.message).toContain("Branch 1.1") @@ -83,7 +84,7 @@ describe("Branching automations", () => { it("should execute correct branch based on string equality", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { status: "active" } }) + .appAction() .branch({ activeBranch: { steps: stepBuilder => stepBuilder.serverLog({ text: "Active user" }), @@ -99,7 +100,7 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { status: "active" } }) expect(results.steps[0].outputs.status).toContain( "activeBranch branch taken" ) @@ -108,7 +109,7 @@ describe("Branching automations", () => { it("should handle multiple conditions with AND operator", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { status: "active", role: "admin" } }) + .appAction() .branch({ activeAdminBranch: { steps: stepBuilder => @@ -129,14 +130,14 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { status: "active", role: "admin" } }) expect(results.steps[1].outputs.message).toContain("Active admin user") }) it("should handle multiple conditions with OR operator", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { status: "test", role: "user" } }) + .appAction() .branch({ specialBranch: { steps: stepBuilder => stepBuilder.serverLog({ text: "Special user" }), @@ -161,14 +162,14 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { status: "test", role: "user" } }) expect(results.steps[1].outputs.message).toContain("Special user") }) it("should stop the branch automation when no conditions are met", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { status: "test", role: "user" } }) + .appAction() .createRow({ row: { name: "Test", tableId: table._id } }) .branch({ specialBranch: { @@ -194,7 +195,7 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { status: "test", role: "user" } }) expect(results.steps[1].outputs.status).toEqual( AutomationStatus.NO_CONDITION_MET @@ -204,7 +205,7 @@ describe("Branching automations", () => { it("evaluate multiple conditions", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { test_trigger: true } }) + .appAction() .serverLog({ text: "Starting automation" }, { stepId: "aN6znRYHG" }) .branch({ specialBranch: { @@ -238,14 +239,14 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { test_trigger: true } }) expect(results.steps[2].outputs.message).toContain("Special user") }) it("evaluate multiple conditions with interpolated text", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { test_trigger: true } }) + .appAction() .serverLog({ text: "Starting automation" }, { stepId: "aN6znRYHG" }) .branch({ specialBranch: { @@ -275,7 +276,7 @@ describe("Branching automations", () => { }, }, }) - .run() + .run({ fields: { test_trigger: true } }) expect(results.steps[2].outputs.message).toContain("Special user") }) diff --git a/packages/server/src/automations/tests/scenarios.spec.ts b/packages/server/src/automations/tests/scenarios.spec.ts index 2c13b7c019..c831d6f203 100644 --- a/packages/server/src/automations/tests/scenarios.spec.ts +++ b/packages/server/src/automations/tests/scenarios.spec.ts @@ -30,13 +30,7 @@ describe("Automation Scenarios", () => { const table = await config.api.table.save(basicTable()) const results = await createAutomationBuilder(config) - .rowUpdated( - { tableId: table._id! }, - { - row: { name: "Test", description: "TEST" }, - id: "1234", - } - ) + .rowUpdated({ tableId: table._id! }) .createRow({ row: { name: "{{trigger.row.name}}", @@ -44,7 +38,10 @@ describe("Automation Scenarios", () => { tableId: table._id, }, }) - .run() + .run({ + row: { name: "Test", description: "TEST" }, + id: "1234", + }) expect(results.steps).toHaveLength(1) @@ -66,10 +63,11 @@ describe("Automation Scenarios", () => { await config.api.row.save(table._id!, row) await config.api.row.save(table._id!, row) const results = await createAutomationBuilder(config) + .appAction() .queryRows({ tableId: table._id!, }) - .run() + .run({ fields: {} }) expect(results.steps).toHaveLength(1) expect(results.steps[0].outputs.rows).toHaveLength(2) @@ -84,6 +82,7 @@ describe("Automation Scenarios", () => { await config.api.row.save(table._id!, row) await config.api.row.save(table._id!, row) const results = await createAutomationBuilder(config) + .appAction() .queryRows({ tableId: table._id!, }) @@ -94,7 +93,7 @@ describe("Automation Scenarios", () => { .queryRows({ tableId: table._id!, }) - .run() + .run({ fields: {} }) expect(results.steps).toHaveLength(3) expect(results.steps[1].outputs.success).toBeTruthy() @@ -125,6 +124,7 @@ describe("Automation Scenarios", () => { }) const results = await createAutomationBuilder(config) + .appAction() .createRow( { row: { @@ -153,7 +153,7 @@ describe("Automation Scenarios", () => { }, { stepName: "QueryRowsStep" } ) - .run() + .run({ fields: {} }) expect(results.steps).toHaveLength(3) @@ -193,6 +193,7 @@ describe("Automation Scenarios", () => { await config.api.row.save(table._id!, row) await config.api.row.save(table._id!, row) const results = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -206,7 +207,7 @@ describe("Automation Scenarios", () => { .queryRows({ tableId: table._id!, }) - .run() + .run({ fields: {} }) expect(results.steps).toHaveLength(3) expect(results.steps[1].outputs.success).toBeTruthy() @@ -242,6 +243,7 @@ describe("Automation Scenarios", () => { it("should stop an automation if the condition is not met", async () => { const results = await createAutomationBuilder(config) + .appAction() .createRow({ row: { name: "Equal Test", @@ -258,7 +260,7 @@ describe("Automation Scenarios", () => { value: 20, }) .serverLog({ text: "Equal condition met" }) - .run() + .run({ fields: {} }) expect(results.steps[2].outputs.success).toBeTrue() expect(results.steps[2].outputs.result).toBeFalse() @@ -267,6 +269,7 @@ describe("Automation Scenarios", () => { it("should continue the automation if the condition is met", async () => { const results = await createAutomationBuilder(config) + .appAction() .createRow({ row: { name: "Not Equal Test", @@ -283,7 +286,7 @@ describe("Automation Scenarios", () => { value: 20, }) .serverLog({ text: "Not Equal condition met" }) - .run() + .run({ fields: {} }) expect(results.steps[2].outputs.success).toBeTrue() expect(results.steps[2].outputs.result).toBeTrue() @@ -333,6 +336,7 @@ describe("Automation Scenarios", () => { "should pass the filter when condition is $condition", async ({ condition, value, rowValue, expectPass }) => { const results = await createAutomationBuilder(config) + .appAction() .createRow({ row: { name: `${condition} Test`, @@ -351,7 +355,7 @@ describe("Automation Scenarios", () => { .serverLog({ text: `${condition} condition ${expectPass ? "passed" : "failed"}`, }) - .run() + .run({ fields: {} }) expect(results.steps[2].outputs.result).toBe(expectPass) if (expectPass) { @@ -367,23 +371,21 @@ describe("Automation Scenarios", () => { const table = await config.api.table.save(basicTable()) const results = await createAutomationBuilder(config) - .rowUpdated( - { tableId: table._id! }, - { - row: { name: "Test", description: "TEST" }, - id: "1234", - } - ) + .rowUpdated({ tableId: table._id! }) .serverLog({ text: "{{ [user].[email] }}" }) - .run() + .run({ + row: { name: "Test", description: "TEST" }, + id: "1234", + }) expect(results.steps[0].outputs.message).toContain("example.com") }) it("Check user is passed through from app trigger", async () => { const results = await createAutomationBuilder(config) + .appAction() .serverLog({ text: "{{ [user].[email] }}" }) - .run() + .run({ fields: {} }) expect(results.steps[0].outputs.message).toContain("example.com") }) @@ -453,9 +455,7 @@ if (descriptions.length) { }) const results = await createAutomationBuilder(config) - .appAction({ - fields: {}, - }) + .appAction() .executeQuery({ query: { queryId: query._id!, @@ -475,7 +475,7 @@ if (descriptions.length) { .queryRows({ tableId: newTable._id!, }) - .run() + .run({ fields: {} }) expect(results.steps).toHaveLength(3) diff --git a/packages/server/src/automations/tests/steps/executeScript.spec.ts b/packages/server/src/automations/tests/steps/executeScript.spec.ts index e2bea9d0c1..9f9b4e71f4 100644 --- a/packages/server/src/automations/tests/steps/executeScript.spec.ts +++ b/packages/server/src/automations/tests/steps/executeScript.spec.ts @@ -21,30 +21,32 @@ describe("Execute Script Automations", () => { it("should execute a basic script and return the result", async () => { const results = await createAutomationBuilder(config) + .appAction() .executeScript({ code: "return 2 + 2" }) - .run() + .run({ fields: {} }) expect(results.steps[0].outputs.value).toEqual(4) }) it("should access bindings from previous steps", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { data: [1, 2, 3] } }) + .appAction() .executeScript( { code: "return trigger.fields.data.map(x => x * 2)", }, { stepId: "binding-script-step" } ) - .run() + .run({ fields: { data: [1, 2, 3] } }) expect(results.steps[0].outputs.value).toEqual([2, 4, 6]) }) it("should handle script execution errors gracefully", async () => { const results = await createAutomationBuilder(config) + .appAction() .executeScript({ code: "return nonexistentVariable.map(x => x)" }) - .run() + .run({ fields: {} }) expect(results.steps[0].outputs.response).toContain( "ReferenceError: nonexistentVariable is not defined" @@ -54,7 +56,7 @@ describe("Execute Script Automations", () => { it("should handle conditional logic in scripts", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: { value: 10 } }) + .appAction() .executeScript({ code: ` if (trigger.fields.value > 5) { @@ -64,14 +66,14 @@ describe("Execute Script Automations", () => { } `, }) - .run() + .run({ fields: { value: 10 } }) expect(results.steps[0].outputs.value).toEqual("Value is greater than 5") }) it("should use multiple steps and validate script execution", async () => { const results = await createAutomationBuilder(config) - .appAction({ fields: {} }) + .appAction() .serverLog( { text: "Starting multi-step automation" }, { stepId: "start-log-step" } @@ -92,7 +94,7 @@ describe("Execute Script Automations", () => { .serverLog({ text: `Final result is {{ steps.ScriptingStep1.value }}`, }) - .run() + .run({ fields: {} }) expect(results.steps[0].outputs.message).toContain( "Starting multi-step automation" diff --git a/packages/server/src/automations/tests/steps/openai.spec.ts b/packages/server/src/automations/tests/steps/openai.spec.ts index 4fbf0aa6d6..eef4bca25b 100644 --- a/packages/server/src/automations/tests/steps/openai.spec.ts +++ b/packages/server/src/automations/tests/steps/openai.spec.ts @@ -58,8 +58,9 @@ describe("test the openai action", () => { // own API key. We don't count this against your quota. const result = await expectAIUsage(0, () => createAutomationBuilder(config) + .appAction() .openai({ prompt: "Hello, world", model: Model.GPT_4O_MINI }) - .run() + .run({ fields: {} }) ) expect(result.steps[0].outputs.response).toEqual("This is a test") @@ -69,8 +70,9 @@ describe("test the openai action", () => { it("should present the correct error message when a prompt is not provided", async () => { const result = await expectAIUsage(0, () => createAutomationBuilder(config) + .appAction() .openai({ prompt: "", model: Model.GPT_4O_MINI }) - .run() + .run({ fields: {} }) ) expect(result.steps[0].outputs.response).toEqual( @@ -84,8 +86,9 @@ describe("test the openai action", () => { const result = await expectAIUsage(0, () => createAutomationBuilder(config) + .appAction() .openai({ prompt: "Hello, world", model: Model.GPT_4O_MINI }) - .run() + .run({ fields: {} }) ) expect(result.steps[0].outputs.response).toEqual( @@ -106,8 +109,9 @@ describe("test the openai action", () => { // key, so we charge users for it. const result = await expectAIUsage(14, () => createAutomationBuilder(config) + .appAction() .openai({ model: Model.GPT_4O_MINI, prompt: "Hello, world" }) - .run() + .run({ fields: {} }) ) expect(result.steps[0].outputs.response).toEqual("This is a test") diff --git a/packages/server/src/automations/tests/steps/queryRows.spec.ts b/packages/server/src/automations/tests/steps/queryRows.spec.ts index 9cda9c94c0..7d54590987 100644 --- a/packages/server/src/automations/tests/steps/queryRows.spec.ts +++ b/packages/server/src/automations/tests/steps/queryRows.spec.ts @@ -29,6 +29,7 @@ describe("Test a query step automation", () => { it("should be able to run the query step", async () => { const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -43,7 +44,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query All Rows" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() @@ -53,6 +54,7 @@ describe("Test a query step automation", () => { it("Returns all rows when onEmptyFilter has no value and no filters are passed", async () => { const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -63,7 +65,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query With Empty Filter" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() @@ -73,6 +75,7 @@ describe("Test a query step automation", () => { it("Returns no rows when onEmptyFilter is RETURN_NONE and theres no filters", async () => { const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -85,7 +88,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query With Return None" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() @@ -94,6 +97,7 @@ describe("Test a query step automation", () => { it("Returns no rows when onEmptyFilters RETURN_NONE and a filter is passed with a null value", async () => { const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -110,7 +114,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query With Null Filter" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() @@ -119,6 +123,7 @@ describe("Test a query step automation", () => { it("Returns rows when onEmptyFilter is RETURN_ALL and no filter is passed", async () => { const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: table._id!, @@ -130,7 +135,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query With Return All" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() @@ -146,6 +151,7 @@ describe("Test a query step automation", () => { name: NAME, }) const result = await createAutomationBuilder(config) + .appAction() .queryRows( { tableId: tableWithSpaces._id!, @@ -154,7 +160,7 @@ describe("Test a query step automation", () => { }, { stepName: "Query table with spaces" } ) - .run() + .run({ fields: {} }) expect(result.steps[0].outputs.success).toBe(true) expect(result.steps[0].outputs.rows).toBeDefined() expect(result.steps[0].outputs.rows.length).toBe(1) diff --git a/packages/server/src/automations/tests/triggers/webhook.spec.ts b/packages/server/src/automations/tests/triggers/webhook.spec.ts index 61fab1e891..e549a6f496 100644 --- a/packages/server/src/automations/tests/triggers/webhook.spec.ts +++ b/packages/server/src/automations/tests/triggers/webhook.spec.ts @@ -11,7 +11,7 @@ describe("Branching automations", () => { let webhook: Webhook async function createWebhookAutomation() { - const automation = await createAutomationBuilder(config) + const { automation } = await createAutomationBuilder(config) .webhook({ fields: { parameter: "string" } }) .createRow({ row: { tableId: table._id!, name: "{{ trigger.parameter }}" }, diff --git a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts index 2ce2ca3f2e..b29124ad0e 100644 --- a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts +++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts @@ -2,38 +2,26 @@ import { v4 as uuidv4 } from "uuid" import { BUILTIN_ACTION_DEFINITIONS } from "../../actions" import { TRIGGER_DEFINITIONS } from "../../triggers" import { - AppActionTriggerOutputs, Automation, AutomationActionStepId, AutomationStep, AutomationStepInputs, AutomationTrigger, + AutomationTriggerDefinition, AutomationTriggerInputs, AutomationTriggerOutputs, AutomationTriggerStepId, BranchStepInputs, - CronTriggerOutputs, isDidNotTriggerResponse, - RowCreatedTriggerOutputs, - RowDeletedTriggerOutputs, - RowUpdatedTriggerOutputs, SearchFilters, TestAutomationRequest, - WebhookTriggerOutputs, } from "@budibase/types" import TestConfiguration from "../../../tests/utilities/TestConfiguration" import { automations } from "@budibase/shared-core" -type TriggerOutputs = - | RowCreatedTriggerOutputs - | RowUpdatedTriggerOutputs - | RowDeletedTriggerOutputs - | AppActionTriggerOutputs - | WebhookTriggerOutputs - | CronTriggerOutputs - | undefined - -type StepBuilderFunction = (stepBuilder: AutomationBuilder) => void +type StepBuilderFunction = ( + stepBuilder: BranchStepBuilder +) => void type BranchConfig = { [key: string]: { @@ -42,38 +30,42 @@ type BranchConfig = { } } -class AutomationBuilder { - private automationConfig: Automation - private triggerOutputs: TriggerOutputs - private triggerSet = false +class TriggerBuilder { private config: TestConfiguration - private steps: AutomationStep[] = [] - private stepNames: { [key: string]: string } = {} constructor(config: TestConfiguration) { this.config = config - this.triggerOutputs = { fields: {} } - this.automationConfig = { - name: `Test Automation ${uuidv4()}`, - definition: { - steps: [], - trigger: { - ...TRIGGER_DEFINITIONS[AutomationTriggerStepId.APP], - stepId: AutomationTriggerStepId.APP, - inputs: this.triggerOutputs, - id: uuidv4(), - }, - stepNames: {}, - }, - type: "automation", - appId: this.config.getAppId(), + } + + protected trigger< + TStep extends AutomationTriggerStepId, + TInput = AutomationTriggerInputs + >(stepId: TStep) { + return (inputs: TInput) => { + const definition: AutomationTriggerDefinition = + TRIGGER_DEFINITIONS[stepId] + const trigger: AutomationTrigger = { + ...definition, + stepId, + inputs: (inputs || {}) as any, + id: uuidv4(), + } + return new StepBuilder(this.config, trigger) } } - name(n: string): this { - this.automationConfig.name = n - return this - } + appAction = this.trigger(AutomationTriggerStepId.APP) + + rowSaved = this.trigger(AutomationTriggerStepId.ROW_SAVED) + rowUpdated = this.trigger(AutomationTriggerStepId.ROW_UPDATED) + rowDeleted = this.trigger(AutomationTriggerStepId.ROW_DELETED) + webhook = this.trigger(AutomationTriggerStepId.WEBHOOK) + cron = this.trigger(AutomationTriggerStepId.CRON) +} + +class BranchStepBuilder { + protected steps: AutomationStep[] = [] + protected stepNames: { [key: string]: string } = {} protected createStepFn(stepId: TStep) { return ( @@ -120,113 +112,103 @@ class AutomationBuilder { delay = this.createStepFn(AutomationActionStepId.DELAY) protected addBranchStep(branchConfig: BranchConfig): void { - const branchStepInputs: BranchStepInputs = { + const inputs: BranchStepInputs = { branches: [], children: {}, } - Object.entries(branchConfig).forEach(([key, branch]) => { - const stepBuilder = new AutomationBuilder(this.config) - branch.steps(stepBuilder) - let branchId = uuidv4() - branchStepInputs.branches.push({ - name: key, - condition: branch.condition, - id: branchId, - }) - branchStepInputs.children![branchId] = stepBuilder.steps - }) - const branchStep: AutomationStep = { + for (const [name, branch] of Object.entries(branchConfig)) { + const builder = new BranchStepBuilder() + branch.steps(builder) + let id = uuidv4() + inputs.branches.push({ name, condition: branch.condition, id }) + inputs.children![id] = builder.steps + } + + this.steps.push({ ...automations.steps.branch.definition, id: uuidv4(), stepId: AutomationActionStepId.BRANCH, - inputs: branchStepInputs, - } - this.steps.push(branchStep) + inputs, + }) } branch(branchConfig: BranchConfig): this { this.addBranchStep(branchConfig) return this } - protected triggerInputOutput< - TStep extends AutomationTriggerStepId, - TInput = AutomationTriggerInputs, - TOutput = AutomationTriggerOutputs - >(stepId: TStep) { - return (inputs: TInput, outputs?: TOutput) => { - if (this.triggerSet) { - throw new Error("Only one trigger can be set for an automation.") - } - this.triggerOutputs = outputs as TriggerOutputs | undefined - this.automationConfig.definition.trigger = { - ...TRIGGER_DEFINITIONS[stepId], - stepId, - inputs, - id: uuidv4(), - } as AutomationTrigger - this.triggerSet = true - return this - } +} + +class StepBuilder< + TStep extends AutomationTriggerStepId +> extends BranchStepBuilder { + private config: TestConfiguration + private trigger: AutomationTrigger + private _name: string | undefined = undefined + + constructor(config: TestConfiguration, trigger: AutomationTrigger) { + super() + this.config = config + this.trigger = trigger } - protected triggerOutputOnly< - TStep extends AutomationTriggerStepId, - TOutput = AutomationTriggerOutputs - >(stepId: TStep) { - return (outputs: TOutput) => { - this.triggerOutputs = outputs as TriggerOutputs - this.automationConfig.definition.trigger = { - ...TRIGGER_DEFINITIONS[stepId], - stepId, - id: uuidv4(), - } as AutomationTrigger - this.triggerSet = true - return this - } + name(n: string): this { + this._name = n + return this } - // The input and output for appAction is identical, and we only ever seem to - // set the output, so we're ignoring the input for now. - appAction = this.triggerOutputOnly(AutomationTriggerStepId.APP) - - rowSaved = this.triggerInputOutput(AutomationTriggerStepId.ROW_SAVED) - rowUpdated = this.triggerInputOutput(AutomationTriggerStepId.ROW_UPDATED) - rowDeleted = this.triggerInputOutput(AutomationTriggerStepId.ROW_DELETED) - webhook = this.triggerInputOutput(AutomationTriggerStepId.WEBHOOK) - cron = this.triggerInputOutput(AutomationTriggerStepId.CRON) - build(): Automation { - this.automationConfig.definition.steps = this.steps - this.automationConfig.definition.stepNames = this.stepNames - return this.automationConfig + const name = this._name || `Test Automation ${uuidv4()}` + return { + name, + definition: { + steps: this.steps, + trigger: this.trigger, + stepNames: this.stepNames, + }, + type: "automation", + appId: this.config.getAppId(), + } } async save() { - this.automationConfig.definition.steps = this.steps const { automation } = await this.config.api.automation.post(this.build()) - return automation + return new AutomationRunner(this.config, automation) } - async run() { - const automation = await this.save() + async run(outputs: AutomationTriggerOutputs) { + const runner = await this.save() + return await runner.run(outputs) + } +} + +class AutomationRunner { + private config: TestConfiguration + automation: Automation + + constructor(config: TestConfiguration, automation: Automation) { + this.config = config + this.automation = automation + } + + async run(outputs: AutomationTriggerOutputs) { const response = await this.config.api.automation.test( - automation._id!, - this.triggerOutputs as TestAutomationRequest + this.automation._id!, + // TODO: figure out why this cast is needed. + outputs as TestAutomationRequest ) if (isDidNotTriggerResponse(response)) { throw new Error(response.message) } + // Remove the trigger step from the response. response.steps.shift() - return { - trigger: response.trigger, - steps: response.steps, - } + + return response } } export function createAutomationBuilder(config: TestConfiguration) { - return new AutomationBuilder(config) + return new TriggerBuilder(config) } diff --git a/packages/types/src/documents/app/automation/StepInputsOutputs.ts b/packages/types/src/documents/app/automation/StepInputsOutputs.ts index b9c54cec34..18a6f86284 100644 --- a/packages/types/src/documents/app/automation/StepInputsOutputs.ts +++ b/packages/types/src/documents/app/automation/StepInputsOutputs.ts @@ -253,10 +253,6 @@ export type OutgoingWebhookStepInputs = { headers: string | Record } -export type AppActionTriggerInputs = { - fields: object -} - export type AppActionTriggerOutputs = { fields: object } diff --git a/packages/types/src/documents/app/automation/schema.ts b/packages/types/src/documents/app/automation/schema.ts index 952397b511..66a6f508fe 100644 --- a/packages/types/src/documents/app/automation/schema.ts +++ b/packages/types/src/documents/app/automation/schema.ts @@ -45,7 +45,6 @@ import { OpenAIStepInputs, OpenAIStepOutputs, LoopStepInputs, - AppActionTriggerInputs, CronTriggerInputs, RowUpdatedTriggerInputs, RowCreatedTriggerInputs, @@ -332,7 +331,7 @@ export type AutomationTriggerDefinition = Omit< export type AutomationTriggerInputs = T extends AutomationTriggerStepId.APP - ? AppActionTriggerInputs + ? void : T extends AutomationTriggerStepId.CRON ? CronTriggerInputs : T extends AutomationTriggerStepId.ROW_ACTION