diff --git a/packages/server/src/automations/tests/scenarios/branching.spec.ts b/packages/server/src/automations/tests/scenarios/branching.spec.ts index ae89fc18b5..76e04afdd3 100644 --- a/packages/server/src/automations/tests/scenarios/branching.spec.ts +++ b/packages/server/src/automations/tests/scenarios/branching.spec.ts @@ -17,44 +17,65 @@ describe("Branching automations", () => { afterAll(setup.afterAll) it("should run a multiple nested branching automation", async () => { + const firstLogId = "11111111-1111-1111-1111-111111111111" + const branch1LogId = "22222222-2222-2222-2222-222222222222" + const branch2LogId = "33333333-3333-3333-3333-333333333333" + const branch2Id = "44444444-4444-4444-4444-444444444444" + const builder = createAutomationBuilder({ name: "Test Trigger with Loop and Create Row", }) const results = await builder .appAction({ fields: {} }) - .serverLog({ text: "Starting automation" }) + .serverLog( + { text: "Starting automation" }, + { stepName: "FirstLog", id: firstLogId } + ) .branch({ topLevelBranch1: { steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1" }).branch({ - branch1: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1.1" }), - condition: { - equal: { "{{steps.1.success}}": true }, + stepBuilder + .serverLog( + { text: "Branch 1" }, + { id: "66666666-6666-6666-6666-666666666666" } + ) + .branch({ + branch1: { + steps: stepBuilder => + stepBuilder.serverLog( + { text: "Branch 1.1" }, + { id: branch1LogId } + ), + condition: { + equal: { [`{{ steps.${firstLogId}.success }}`]: true }, + }, }, - }, - branch2: { - steps: stepBuilder => - stepBuilder.serverLog({ text: "Branch 1.2" }), - condition: { - equal: { "{{steps.1.success}}": false }, + branch2: { + steps: stepBuilder => + stepBuilder.serverLog( + { text: "Branch 1.2" }, + { id: branch2LogId } + ), + condition: { + equal: { [`{{ steps.${firstLogId}.success }}`]: false }, + }, }, - }, - }), + }), condition: { - equal: { "{{steps.1.success}}": true }, + equal: { [`{{ steps.${firstLogId}.success }}`]: true }, }, }, topLevelBranch2: { - steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }), + steps: stepBuilder => + stepBuilder.serverLog({ text: "Branch 2" }, { id: branch2Id }), condition: { - equal: { "{{steps.1.success}}": false }, + equal: { [`{{ steps.${firstLogId}.success }}`]: false }, }, }, }) .run() + expect(results.steps[3].outputs.status).toContain("branch1 branch taken") expect(results.steps[4].outputs.message).toContain("Branch 1.1") }) diff --git a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts index 2269f075b2..6af18cd27e 100644 --- a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts +++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts @@ -64,18 +64,18 @@ class BaseStepBuilder { stepId: TStep, stepSchema: Omit, inputs: AutomationStepInputs, - stepName?: string + opts?: { stepName?: string; id?: string } ): this { - const id = uuidv4() + const id = opts?.id || uuidv4() this.steps.push({ ...stepSchema, inputs: inputs as any, id, stepId, - name: stepName || stepSchema.name, + name: opts?.stepName || stepSchema.name, }) - if (stepName) { - this.stepNames[id] = stepName + if (opts?.stepName) { + this.stepNames[id] = opts.stepName } return this } @@ -95,7 +95,6 @@ class BaseStepBuilder { }) branchStepInputs.children![key] = stepBuilder.build() }) - const branchStep: AutomationStep = { ...definition, id: uuidv4(), @@ -106,80 +105,98 @@ class BaseStepBuilder { } // STEPS - createRow(inputs: CreateRowStepInputs, opts?: { stepName?: string }): this { + createRow( + inputs: CreateRowStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.CREATE_ROW, BUILTIN_ACTION_DEFINITIONS.CREATE_ROW, inputs, - opts?.stepName + opts ) } - updateRow(inputs: UpdateRowStepInputs, opts?: { stepName?: string }): this { + updateRow( + inputs: UpdateRowStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.UPDATE_ROW, BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW, inputs, - opts?.stepName + opts ) } - deleteRow(inputs: DeleteRowStepInputs, opts?: { stepName?: string }): this { + deleteRow( + inputs: DeleteRowStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.DELETE_ROW, BUILTIN_ACTION_DEFINITIONS.DELETE_ROW, inputs, - opts?.stepName + opts ) } sendSmtpEmail( inputs: SmtpEmailStepInputs, - opts?: { stepName?: string } + opts?: { stepName?: string; id?: string } ): this { return this.step( AutomationActionStepId.SEND_EMAIL_SMTP, BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP, inputs, - opts?.stepName + opts ) } executeQuery( inputs: ExecuteQueryStepInputs, - opts?: { stepName?: string } + opts?: { stepName?: string; id?: string } ): this { return this.step( AutomationActionStepId.EXECUTE_QUERY, BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY, inputs, - opts?.stepName + opts ) } - queryRows(inputs: QueryRowsStepInputs, opts?: { stepName?: string }): this { + queryRows( + inputs: QueryRowsStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.QUERY_ROWS, BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS, inputs, - opts?.stepName + opts ) } - loop(inputs: LoopStepInputs, opts?: { stepName?: string }): this { + loop( + inputs: LoopStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.LOOP, BUILTIN_ACTION_DEFINITIONS.LOOP, inputs, - opts?.stepName + opts ) } - serverLog(input: ServerLogStepInputs, opts?: { stepName?: string }): this { + serverLog( + input: ServerLogStepInputs, + opts?: { stepName?: string; id?: string } + ): this { return this.step( AutomationActionStepId.SERVER_LOG, BUILTIN_ACTION_DEFINITIONS.SERVER_LOG, input, - opts?.stepName + opts ) } diff --git a/packages/server/src/definitions/automations.ts b/packages/server/src/definitions/automations.ts index 6488e604e9..44758b727b 100644 --- a/packages/server/src/definitions/automations.ts +++ b/packages/server/src/definitions/automations.ts @@ -15,6 +15,7 @@ export interface TriggerOutput { export interface AutomationContext extends AutomationResults { steps: any[] + stepsById?: Record stepsByName?: Record env?: Record trigger: any diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index e2a5a1c192..7788744ea2 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -91,6 +91,7 @@ class Orchestrator { // step zero is never used as the template string is zero indexed for customer facing this.context = { steps: [{}], + stepsById: {}, stepsByName: {}, trigger: triggerOutput, } @@ -457,6 +458,7 @@ class Orchestrator { inputs: steps[stepToLoopIndex].inputs, }) + this.context.stepsById![steps[stepToLoopIndex].id] = tempOutput const stepName = steps[stepToLoopIndex].name || steps[stepToLoopIndex].id this.context.stepsByName![stepName] = tempOutput this.context.steps[this.context.steps.length] = tempOutput @@ -517,7 +519,10 @@ class Orchestrator { Object.entries(filter).forEach(([_, value]) => { Object.entries(value).forEach(([field, _]) => { const updatedField = field.replace("{{", "{{ literal ") - const fromContext = processStringSync(updatedField, this.context) + const fromContext = processStringSync( + updatedField, + this.processContext(this.context) + ) toFilter[field] = fromContext }) }) @@ -563,9 +568,9 @@ class Orchestrator { } const stepFn = await this.getStepFunctionality(step.stepId) - let inputs = await this.addContextAndProcess( + let inputs = await processObject( originalStepInput, - this.context + this.processContext(this.context) ) inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs) @@ -594,16 +599,16 @@ class Orchestrator { return null } - private async addContextAndProcess(inputs: any, context: any) { + private processContext(context: AutomationContext) { const processContext = { ...context, steps: { ...context.steps, + ...context.stepsById, ...context.stepsByName, }, } - - return processObject(inputs, processContext) + return processContext } private handleStepOutput( @@ -623,6 +628,7 @@ class Orchestrator { } else { this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs) this.context.steps[this.context.steps.length] = outputs + this.context.stepsById![step.id] = outputs const stepName = step.name || step.id this.context.stepsByName![stepName] = outputs }