Refactor automation builder.

This commit is contained in:
Sam Rose 2025-02-05 17:19:11 +00:00
parent 6ad9ebe63c
commit 74f2ece72b
No known key found for this signature in database
5 changed files with 167 additions and 372 deletions

View File

@ -0,0 +1,67 @@
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import { getQueue } from "../.."
import { Job } from "bull"
import { basicTable } from "../../../tests/utilities/structures"
import { Table } from "@budibase/types"
describe("cron trigger", () => {
const config = new TestConfiguration()
let table: Table
beforeAll(async () => {
await config.init()
table = await config.api.table.save(basicTable())
})
afterAll(() => {
config.end()
})
it("should successfully fire", async () => {
const queue = getQueue()
expect(await queue.getCompletedCount()).toEqual(0)
const jobPromise = new Promise<Job>(resolve => {
queue.on("completed", async job => {
resolve(job)
})
})
await createAutomationBuilder({ config })
.rowSaved({ tableId: table._id! })
.cron({ cron: "* * * * *" })
.serverLog({
text: "Hello, world!",
})
.save()
await config.api.application.publish(config.getAppId())
expect(await queue.getCompletedCount()).toEqual(1)
const job = await jobPromise
const repeat = job.opts?.repeat
if (!repeat || !("cron" in repeat)) {
throw new Error("Expected cron repeat")
}
expect(repeat.cron).toEqual("* * * * *")
})
it("should fail if the cron expression is invalid", async () => {
await createAutomationBuilder({ config })
.cron({ cron: "* * * * * *" })
.serverLog({
text: "Hello, world!",
})
.save()
await config.api.application.publish(config.getAppId(), {
status: 500,
body: {
message:
'Deployment Failed: Invalid automation CRON "* * * * * *" - Expected 5 values, but got 6.',
},
})
})
})

View File

@ -6,7 +6,7 @@ import { Job } from "bull"
describe("cron trigger", () => { describe("cron trigger", () => {
const config = new TestConfiguration() const config = new TestConfiguration()
beforeEach(async () => { beforeAll(async () => {
await config.init() await config.init()
}) })

View File

@ -1,4 +1,3 @@
import * as automation from "../../index"
import { Table, Webhook, WebhookActionType } from "@budibase/types" import { Table, Webhook, WebhookActionType } from "@budibase/types"
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
import { mocks } from "@budibase/backend-core/tests" import { mocks } from "@budibase/backend-core/tests"
@ -37,7 +36,6 @@ describe("Branching automations", () => {
} }
beforeEach(async () => { beforeEach(async () => {
await automation.init()
await config.init() await config.init()
table = await config.createTable() table = await config.createTable()
}) })

View File

@ -2,51 +2,24 @@ import { v4 as uuidv4 } from "uuid"
import { BUILTIN_ACTION_DEFINITIONS } from "../../actions" import { BUILTIN_ACTION_DEFINITIONS } from "../../actions"
import { TRIGGER_DEFINITIONS } from "../../triggers" import { TRIGGER_DEFINITIONS } from "../../triggers"
import { import {
AppActionTriggerInputs,
AppActionTriggerOutputs, AppActionTriggerOutputs,
Automation, Automation,
AutomationActionStepId, AutomationActionStepId,
AutomationStep, AutomationStep,
AutomationStepInputs, AutomationStepInputs,
AutomationTrigger, AutomationTrigger,
AutomationTriggerDefinition,
AutomationTriggerInputs, AutomationTriggerInputs,
AutomationTriggerOutputs,
AutomationTriggerStepId, AutomationTriggerStepId,
BashStepInputs,
Branch,
BranchStepInputs, BranchStepInputs,
CollectStepInputs,
CreateRowStepInputs,
CronTriggerInputs,
CronTriggerOutputs, CronTriggerOutputs,
DelayStepInputs,
DeleteRowStepInputs,
DiscordStepInputs,
ExecuteQueryStepInputs,
ExecuteScriptStepInputs,
FilterStepInputs,
isDidNotTriggerResponse, isDidNotTriggerResponse,
LoopStepInputs,
MakeIntegrationInputs,
n8nStepInputs,
OpenAIStepInputs,
OutgoingWebhookStepInputs,
QueryRowsStepInputs,
RowCreatedTriggerInputs,
RowCreatedTriggerOutputs, RowCreatedTriggerOutputs,
RowDeletedTriggerInputs,
RowDeletedTriggerOutputs, RowDeletedTriggerOutputs,
RowUpdatedTriggerInputs,
RowUpdatedTriggerOutputs, RowUpdatedTriggerOutputs,
SearchFilters, SearchFilters,
ServerLogStepInputs,
SmtpEmailStepInputs,
TestAutomationRequest, TestAutomationRequest,
TriggerAutomationStepInputs,
UpdateRowStepInputs,
WebhookTriggerInputs,
WebhookTriggerOutputs, WebhookTriggerOutputs,
ZapierStepInputs,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import * as setup from "../utilities" import * as setup from "../utilities"
@ -74,28 +47,53 @@ class BaseStepBuilder {
protected steps: AutomationStep[] = [] protected steps: AutomationStep[] = []
protected stepNames: { [key: string]: string } = {} protected stepNames: { [key: string]: string } = {}
protected step<TStep extends AutomationActionStepId>( protected createStepFn<TStep extends AutomationActionStepId>(stepId: TStep) {
stepId: TStep, return (
stepSchema: Omit<AutomationStep, "id" | "stepId" | "inputs">,
inputs: AutomationStepInputs<TStep>, inputs: AutomationStepInputs<TStep>,
opts?: { stepName?: string; stepId?: string } opts?: { stepName?: string; stepId?: string }
): this { ) => {
const schema = BUILTIN_ACTION_DEFINITIONS[stepId]
const id = opts?.stepId || uuidv4() const id = opts?.stepId || uuidv4()
this.steps.push({ this.steps.push({
...stepSchema, ...schema,
inputs: inputs as any, inputs: inputs as any,
id, id,
stepId, stepId,
name: opts?.stepName || stepSchema.name, name: opts?.stepName || schema.name,
}) })
if (opts?.stepName) { if (opts?.stepName) {
this.stepNames[id] = opts.stepName this.stepNames[id] = opts.stepName
} }
return this return this
} }
}
createRow = this.createStepFn(AutomationActionStepId.CREATE_ROW)
updateRow = this.createStepFn(AutomationActionStepId.UPDATE_ROW)
deleteRow = this.createStepFn(AutomationActionStepId.DELETE_ROW)
sendSmtpEmail = this.createStepFn(AutomationActionStepId.SEND_EMAIL_SMTP)
executeQuery = this.createStepFn(AutomationActionStepId.EXECUTE_QUERY)
queryRows = this.createStepFn(AutomationActionStepId.QUERY_ROWS)
loop = this.createStepFn(AutomationActionStepId.LOOP)
serverLog = this.createStepFn(AutomationActionStepId.SERVER_LOG)
executeScript = this.createStepFn(AutomationActionStepId.EXECUTE_SCRIPT)
filter = this.createStepFn(AutomationActionStepId.FILTER)
bash = this.createStepFn(AutomationActionStepId.EXECUTE_BASH)
openai = this.createStepFn(AutomationActionStepId.OPENAI)
collect = this.createStepFn(AutomationActionStepId.COLLECT)
zapier = this.createStepFn(AutomationActionStepId.zapier)
triggerAutomationRun = this.createStepFn(
AutomationActionStepId.TRIGGER_AUTOMATION_RUN
)
outgoingWebhook = this.createStepFn(AutomationActionStepId.OUTGOING_WEBHOOK)
n8n = this.createStepFn(AutomationActionStepId.n8n)
make = this.createStepFn(AutomationActionStepId.integromat)
discord = this.createStepFn(AutomationActionStepId.discord)
delay = this.createStepFn(AutomationActionStepId.DELAY)
protected addBranchStep(branchConfig: BranchConfig): void { protected addBranchStep(branchConfig: BranchConfig): void {
const branchStepInputs: BranchStepInputs = { const branchStepInputs: BranchStepInputs = {
branches: [] as Branch[], branches: [],
children: {}, children: {},
} }
@ -118,243 +116,6 @@ class BaseStepBuilder {
} }
this.steps.push(branchStep) this.steps.push(branchStep)
} }
// STEPS
createRow(
inputs: CreateRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.CREATE_ROW,
BUILTIN_ACTION_DEFINITIONS.CREATE_ROW,
inputs,
opts
)
}
updateRow(
inputs: UpdateRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.UPDATE_ROW,
BUILTIN_ACTION_DEFINITIONS.UPDATE_ROW,
inputs,
opts
)
}
deleteRow(
inputs: DeleteRowStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.DELETE_ROW,
BUILTIN_ACTION_DEFINITIONS.DELETE_ROW,
inputs,
opts
)
}
sendSmtpEmail(
inputs: SmtpEmailStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.SEND_EMAIL_SMTP,
BUILTIN_ACTION_DEFINITIONS.SEND_EMAIL_SMTP,
inputs,
opts
)
}
executeQuery(
inputs: ExecuteQueryStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.EXECUTE_QUERY,
BUILTIN_ACTION_DEFINITIONS.EXECUTE_QUERY,
inputs,
opts
)
}
queryRows(
inputs: QueryRowsStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.QUERY_ROWS,
BUILTIN_ACTION_DEFINITIONS.QUERY_ROWS,
inputs,
opts
)
}
loop(
inputs: LoopStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.LOOP,
BUILTIN_ACTION_DEFINITIONS.LOOP,
inputs,
opts
)
}
serverLog(
input: ServerLogStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.SERVER_LOG,
BUILTIN_ACTION_DEFINITIONS.SERVER_LOG,
input,
opts
)
}
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,
BUILTIN_ACTION_DEFINITIONS.FILTER,
input
)
}
bash(
input: BashStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.EXECUTE_BASH,
BUILTIN_ACTION_DEFINITIONS.EXECUTE_BASH,
input,
opts
)
}
openai(
input: OpenAIStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.OPENAI,
BUILTIN_ACTION_DEFINITIONS.OPENAI,
input,
opts
)
}
collect(
input: CollectStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.COLLECT,
BUILTIN_ACTION_DEFINITIONS.COLLECT,
input,
opts
)
}
zapier(
input: ZapierStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.zapier,
BUILTIN_ACTION_DEFINITIONS.zapier,
input,
opts
)
}
triggerAutomationRun(
input: TriggerAutomationStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.TRIGGER_AUTOMATION_RUN,
BUILTIN_ACTION_DEFINITIONS.TRIGGER_AUTOMATION_RUN,
input,
opts
)
}
outgoingWebhook(
input: OutgoingWebhookStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.OUTGOING_WEBHOOK,
BUILTIN_ACTION_DEFINITIONS.OUTGOING_WEBHOOK,
input,
opts
)
}
n8n(
input: n8nStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.n8n,
BUILTIN_ACTION_DEFINITIONS.n8n,
input,
opts
)
}
make(
input: MakeIntegrationInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.integromat,
BUILTIN_ACTION_DEFINITIONS.integromat,
input,
opts
)
}
discord(
input: DiscordStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.discord,
BUILTIN_ACTION_DEFINITIONS.discord,
input,
opts
)
}
delay(
input: DelayStepInputs,
opts?: { stepName?: string; stepId?: string }
): this {
return this.step(
AutomationActionStepId.DELAY,
BUILTIN_ACTION_DEFINITIONS.DELAY,
input,
opts
)
}
} }
class StepBuilder extends BaseStepBuilder { class StepBuilder extends BaseStepBuilder {
@ -379,11 +140,17 @@ class AutomationBuilder extends BaseStepBuilder {
) { ) {
super() super()
this.config = options.config || setup.getConfig() this.config = options.config || setup.getConfig()
this.triggerOutputs = { fields: {} }
this.automationConfig = { this.automationConfig = {
name: options.name || `Test Automation ${uuidv4()}`, name: options.name || `Test Automation ${uuidv4()}`,
definition: { definition: {
steps: [], steps: [],
trigger: {} as AutomationTrigger, trigger: {
...TRIGGER_DEFINITIONS[AutomationTriggerStepId.APP],
stepId: AutomationTriggerStepId.APP,
inputs: this.triggerOutputs,
id: uuidv4(),
},
stepNames: {}, stepNames: {},
}, },
type: "automation", type: "automation",
@ -391,93 +158,33 @@ class AutomationBuilder extends BaseStepBuilder {
} }
} }
// TRIGGERS protected createTriggerFn<
rowSaved(inputs: RowCreatedTriggerInputs, outputs: RowCreatedTriggerOutputs) { TStep extends AutomationTriggerStepId,
this.triggerOutputs = outputs TInput = AutomationTriggerInputs<TStep>,
return this.trigger( TOutput = AutomationTriggerOutputs<TStep>
TRIGGER_DEFINITIONS.ROW_SAVED, >(stepId: TStep) {
AutomationTriggerStepId.ROW_SAVED, return (inputs: TInput, outputs?: TOutput) => {
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
)
}
webhook(outputs: WebhookTriggerOutputs, inputs?: WebhookTriggerInputs) {
this.triggerOutputs = outputs
return this.trigger(
TRIGGER_DEFINITIONS.WEBHOOK,
AutomationTriggerStepId.WEBHOOK,
inputs,
outputs
)
}
cron(inputs: CronTriggerInputs, outputs?: CronTriggerOutputs) {
this.triggerOutputs = outputs
return this.trigger(
TRIGGER_DEFINITIONS.CRON,
AutomationTriggerStepId.CRON,
inputs,
outputs
)
}
private trigger<TStep extends AutomationTriggerStepId>(
triggerSchema: AutomationTriggerDefinition,
stepId: TStep,
inputs?: AutomationTriggerInputs<TStep>,
outputs?: TriggerOutputs
): this {
if (this.triggerSet) { if (this.triggerSet) {
throw new Error("Only one trigger can be set for an automation.") throw new Error("Only one trigger can be set for an automation.")
} }
this.triggerOutputs = outputs as TriggerOutputs | undefined
this.automationConfig.definition.trigger = { this.automationConfig.definition.trigger = {
...triggerSchema, ...TRIGGER_DEFINITIONS[stepId],
stepId, stepId,
inputs: inputs || ({} as any), inputs,
id: uuidv4(), id: uuidv4(),
} } as AutomationTrigger
this.triggerOutputs = outputs
this.triggerSet = true this.triggerSet = true
return this return this
} }
}
rowSaved = this.createTriggerFn(AutomationTriggerStepId.ROW_SAVED)
rowUpdated = this.createTriggerFn(AutomationTriggerStepId.ROW_UPDATED)
rowDeleted = this.createTriggerFn(AutomationTriggerStepId.ROW_DELETED)
appAction = this.createTriggerFn(AutomationTriggerStepId.APP)
webhook = this.createTriggerFn(AutomationTriggerStepId.WEBHOOK)
cron = this.createTriggerFn(AutomationTriggerStepId.CRON)
branch(branchConfig: BranchConfig): this { branch(branchConfig: BranchConfig): this {
this.addBranchStep(branchConfig) this.addBranchStep(branchConfig)

View File

@ -52,6 +52,12 @@ import {
RowDeletedTriggerInputs, RowDeletedTriggerInputs,
BranchStepInputs, BranchStepInputs,
BaseAutomationOutputs, BaseAutomationOutputs,
AppActionTriggerOutputs,
CronTriggerOutputs,
RowDeletedTriggerOutputs,
RowCreatedTriggerOutputs,
RowUpdatedTriggerOutputs,
WebhookTriggerOutputs,
} from "./StepInputsOutputs" } from "./StepInputsOutputs"
export type ActionImplementations<T extends Hosting> = { export type ActionImplementations<T extends Hosting> = {
@ -341,6 +347,23 @@ export type AutomationTriggerInputs<T extends AutomationTriggerStepId> =
? Record<string, any> ? Record<string, any>
: never : never
export type AutomationTriggerOutputs<T extends AutomationTriggerStepId> =
T extends AutomationTriggerStepId.APP
? AppActionTriggerOutputs
: T extends AutomationTriggerStepId.CRON
? CronTriggerOutputs
: T extends AutomationTriggerStepId.ROW_ACTION
? Record<string, any>
: T extends AutomationTriggerStepId.ROW_DELETED
? RowDeletedTriggerOutputs
: T extends AutomationTriggerStepId.ROW_SAVED
? RowCreatedTriggerOutputs
: T extends AutomationTriggerStepId.ROW_UPDATED
? RowUpdatedTriggerOutputs
: T extends AutomationTriggerStepId.WEBHOOK
? WebhookTriggerOutputs
: never
export interface AutomationTriggerSchema< export interface AutomationTriggerSchema<
TTrigger extends AutomationTriggerStepId TTrigger extends AutomationTriggerStepId
> extends AutomationStepSchemaBase { > extends AutomationStepSchemaBase {