Merge branch 'master' into view-calculation-sql-2

This commit is contained in:
Sam Rose 2024-10-02 09:46:44 +01:00 committed by GitHub
commit d00513db33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 94 additions and 49 deletions

View File

@ -17,44 +17,65 @@ describe("Branching automations", () => {
afterAll(setup.afterAll) afterAll(setup.afterAll)
it("should run a multiple nested branching automation", async () => { 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({ const builder = createAutomationBuilder({
name: "Test Trigger with Loop and Create Row", name: "Test Trigger with Loop and Create Row",
}) })
const results = await builder const results = await builder
.appAction({ fields: {} }) .appAction({ fields: {} })
.serverLog({ text: "Starting automation" }) .serverLog(
{ text: "Starting automation" },
{ stepName: "FirstLog", stepId: firstLogId }
)
.branch({ .branch({
topLevelBranch1: { topLevelBranch1: {
steps: stepBuilder => steps: stepBuilder =>
stepBuilder.serverLog({ text: "Branch 1" }).branch({ stepBuilder
branch1: { .serverLog(
steps: stepBuilder => { text: "Branch 1" },
stepBuilder.serverLog({ text: "Branch 1.1" }), { stepId: "66666666-6666-6666-6666-666666666666" }
condition: { )
equal: { "{{steps.1.success}}": true }, .branch({
branch1: {
steps: stepBuilder =>
stepBuilder.serverLog(
{ text: "Branch 1.1" },
{ stepId: branch1LogId }
),
condition: {
equal: { [`{{ steps.${firstLogId}.success }}`]: true },
},
}, },
}, branch2: {
branch2: { steps: stepBuilder =>
steps: stepBuilder => stepBuilder.serverLog(
stepBuilder.serverLog({ text: "Branch 1.2" }), { text: "Branch 1.2" },
condition: { { stepId: branch2LogId }
equal: { "{{steps.1.success}}": false }, ),
condition: {
equal: { [`{{ steps.${firstLogId}.success }}`]: false },
},
}, },
}, }),
}),
condition: { condition: {
equal: { "{{steps.1.success}}": true }, equal: { [`{{ steps.${firstLogId}.success }}`]: true },
}, },
}, },
topLevelBranch2: { topLevelBranch2: {
steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }), steps: stepBuilder =>
stepBuilder.serverLog({ text: "Branch 2" }, { stepId: branch2Id }),
condition: { condition: {
equal: { "{{steps.1.success}}": false }, equal: { [`{{ steps.${firstLogId}.success }}`]: false },
}, },
}, },
}) })
.run() .run()
expect(results.steps[3].outputs.status).toContain("branch1 branch taken") expect(results.steps[3].outputs.status).toContain("branch1 branch taken")
expect(results.steps[4].outputs.message).toContain("Branch 1.1") expect(results.steps[4].outputs.message).toContain("Branch 1.1")
}) })

View File

@ -64,18 +64,18 @@ class BaseStepBuilder {
stepId: TStep, stepId: TStep,
stepSchema: Omit<AutomationStep, "id" | "stepId" | "inputs">, stepSchema: Omit<AutomationStep, "id" | "stepId" | "inputs">,
inputs: AutomationStepInputs<TStep>, inputs: AutomationStepInputs<TStep>,
stepName?: string opts?: { stepName?: string; stepId?: string }
): this { ): this {
const id = uuidv4() const id = opts?.stepId || uuidv4()
this.steps.push({ this.steps.push({
...stepSchema, ...stepSchema,
inputs: inputs as any, inputs: inputs as any,
id, id,
stepId, stepId,
name: stepName || stepSchema.name, name: opts?.stepName || stepSchema.name,
}) })
if (stepName) { if (opts?.stepName) {
this.stepNames[id] = stepName this.stepNames[id] = opts.stepName
} }
return this return this
} }
@ -95,7 +95,6 @@ class BaseStepBuilder {
}) })
branchStepInputs.children![key] = stepBuilder.build() branchStepInputs.children![key] = stepBuilder.build()
}) })
const branchStep: AutomationStep = { const branchStep: AutomationStep = {
...definition, ...definition,
id: uuidv4(), id: uuidv4(),
@ -106,80 +105,98 @@ class BaseStepBuilder {
} }
// STEPS // STEPS
createRow(inputs: CreateRowStepInputs, opts?: { stepName?: string }): this { createRow(
inputs: CreateRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.CREATE_ROW, AutomationActionStepId.CREATE_ROW,
BUILTIN_ACTION_DEFINITIONS.CREATE_ROW, BUILTIN_ACTION_DEFINITIONS.CREATE_ROW,
inputs, inputs,
opts?.stepName opts
) )
} }
updateRow(inputs: UpdateRowStepInputs, opts?: { stepName?: string }): this { updateRow(
inputs: UpdateRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.UPDATE_ROW, AutomationActionStepId.UPDATE_ROW,
BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW, BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW,
inputs, inputs,
opts?.stepName opts
) )
} }
deleteRow(inputs: DeleteRowStepInputs, opts?: { stepName?: string }): this { deleteRow(
inputs: DeleteRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.DELETE_ROW, AutomationActionStepId.DELETE_ROW,
BUILTIN_ACTION_DEFINITIONS.DELETE_ROW, BUILTIN_ACTION_DEFINITIONS.DELETE_ROW,
inputs, inputs,
opts?.stepName opts
) )
} }
sendSmtpEmail( sendSmtpEmail(
inputs: SmtpEmailStepInputs, inputs: SmtpEmailStepInputs,
opts?: { stepName?: string } opts?: { stepName?: string; stepId?: string }
): this { ): this {
return this.step( return this.step(
AutomationActionStepId.SEND_EMAIL_SMTP, AutomationActionStepId.SEND_EMAIL_SMTP,
BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP, BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP,
inputs, inputs,
opts?.stepName opts
) )
} }
executeQuery( executeQuery(
inputs: ExecuteQueryStepInputs, inputs: ExecuteQueryStepInputs,
opts?: { stepName?: string } opts?: { stepName?: string; stepId?: string }
): this { ): this {
return this.step( return this.step(
AutomationActionStepId.EXECUTE_QUERY, AutomationActionStepId.EXECUTE_QUERY,
BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY, BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY,
inputs, inputs,
opts?.stepName opts
) )
} }
queryRows(inputs: QueryRowsStepInputs, opts?: { stepName?: string }): this { queryRows(
inputs: QueryRowsStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.QUERY_ROWS, AutomationActionStepId.QUERY_ROWS,
BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS, BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS,
inputs, inputs,
opts?.stepName opts
) )
} }
loop(inputs: LoopStepInputs, opts?: { stepName?: string }): this { loop(
inputs: LoopStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.LOOP, AutomationActionStepId.LOOP,
BUILTIN_ACTION_DEFINITIONS.LOOP, BUILTIN_ACTION_DEFINITIONS.LOOP,
inputs, inputs,
opts?.stepName opts
) )
} }
serverLog(input: ServerLogStepInputs, opts?: { stepName?: string }): this { serverLog(
input: ServerLogStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step( return this.step(
AutomationActionStepId.SERVER_LOG, AutomationActionStepId.SERVER_LOG,
BUILTIN_ACTION_DEFINITIONS.SERVER_LOG, BUILTIN_ACTION_DEFINITIONS.SERVER_LOG,
input, input,
opts?.stepName opts
) )
} }

View File

@ -15,7 +15,8 @@ export interface TriggerOutput {
export interface AutomationContext extends AutomationResults { export interface AutomationContext extends AutomationResults {
steps: any[] steps: any[]
stepsByName?: Record<string, any> stepsById: Record<string, any>
stepsByName: Record<string, any>
env?: Record<string, string> env?: Record<string, string>
trigger: any trigger: any
} }

View File

@ -74,7 +74,7 @@ class Orchestrator {
private job: Job private job: Job
private loopStepOutputs: LoopStep[] private loopStepOutputs: LoopStep[]
private stopped: boolean private stopped: boolean
private executionOutput: AutomationContext private executionOutput: Omit<AutomationContext, "stepsByName" | "stepsById">
constructor(job: AutomationJob) { constructor(job: AutomationJob) {
let automation = job.data.automation let automation = job.data.automation
@ -91,6 +91,7 @@ class Orchestrator {
// step zero is never used as the template string is zero indexed for customer facing // step zero is never used as the template string is zero indexed for customer facing
this.context = { this.context = {
steps: [{}], steps: [{}],
stepsById: {},
stepsByName: {}, stepsByName: {},
trigger: triggerOutput, trigger: triggerOutput,
} }
@ -457,8 +458,9 @@ class Orchestrator {
inputs: steps[stepToLoopIndex].inputs, inputs: steps[stepToLoopIndex].inputs,
}) })
this.context.stepsById[steps[stepToLoopIndex].id] = tempOutput
const stepName = steps[stepToLoopIndex].name || steps[stepToLoopIndex].id const stepName = steps[stepToLoopIndex].name || steps[stepToLoopIndex].id
this.context.stepsByName![stepName] = tempOutput this.context.stepsByName[stepName] = tempOutput
this.context.steps[this.context.steps.length] = tempOutput this.context.steps[this.context.steps.length] = tempOutput
this.context.steps = this.context.steps.filter( this.context.steps = this.context.steps.filter(
item => !item.hasOwnProperty.call(item, "currentItem") item => !item.hasOwnProperty.call(item, "currentItem")
@ -517,7 +519,10 @@ class Orchestrator {
Object.entries(filter).forEach(([_, value]) => { Object.entries(filter).forEach(([_, value]) => {
Object.entries(value).forEach(([field, _]) => { Object.entries(value).forEach(([field, _]) => {
const updatedField = field.replace("{{", "{{ literal ") const updatedField = field.replace("{{", "{{ literal ")
const fromContext = processStringSync(updatedField, this.context) const fromContext = processStringSync(
updatedField,
this.processContext(this.context)
)
toFilter[field] = fromContext toFilter[field] = fromContext
}) })
}) })
@ -563,9 +568,9 @@ class Orchestrator {
} }
const stepFn = await this.getStepFunctionality(step.stepId) const stepFn = await this.getStepFunctionality(step.stepId)
let inputs = await this.addContextAndProcess( let inputs = await processObject(
originalStepInput, originalStepInput,
this.context this.processContext(this.context)
) )
inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs) inputs = automationUtils.cleanInputValues(inputs, step.schema.inputs)
@ -594,16 +599,16 @@ class Orchestrator {
return null return null
} }
private async addContextAndProcess(inputs: any, context: any) { private processContext(context: AutomationContext) {
const processContext = { const processContext = {
...context, ...context,
steps: { steps: {
...context.steps, ...context.steps,
...context.stepsById,
...context.stepsByName, ...context.stepsByName,
}, },
} }
return processContext
return processObject(inputs, processContext)
} }
private handleStepOutput( private handleStepOutput(
@ -623,6 +628,7 @@ class Orchestrator {
} else { } else {
this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs) this.updateExecutionOutput(step.id, step.stepId, step.inputs, outputs)
this.context.steps[this.context.steps.length] = outputs this.context.steps[this.context.steps.length] = outputs
this.context.stepsById![step.id] = outputs
const stepName = step.name || step.id const stepName = step.name || step.id
this.context.stepsByName![stepName] = outputs this.context.stepsByName![stepName] = outputs
} }