diff --git a/packages/builder/src/pages/builder/portal/settings/ai/AIConfigTile.svelte b/packages/builder/src/pages/builder/portal/settings/ai/AIConfigTile.svelte
index 2907919ce7..fb040204c2 100644
--- a/packages/builder/src/pages/builder/portal/settings/ai/AIConfigTile.svelte
+++ b/packages/builder/src/pages/builder/portal/settings/ai/AIConfigTile.svelte
@@ -4,6 +4,7 @@
import OpenAILogo from "./logos/OpenAI.svelte"
import AnthropicLogo from "./logos/Anthropic.svelte"
import TogetherAILogo from "./logos/TogetherAI.svelte"
+ import AzureOpenAILogo from "./logos/AzureOpenAI.svelte"
import { Providers } from "./constants"
const logos = {
@@ -11,6 +12,7 @@
[Providers.OpenAI.name]: OpenAILogo,
[Providers.Anthropic.name]: AnthropicLogo,
[Providers.TogetherAI.name]: TogetherAILogo,
+ [Providers.AzureOpenAI.name]: AzureOpenAILogo,
}
export let config
@@ -26,8 +28,8 @@
-
-
+
+
+
+
diff --git a/packages/builder/src/pages/builder/portal/settings/ai/logos/AzureOpenAI.svelte b/packages/builder/src/pages/builder/portal/settings/ai/logos/AzureOpenAI.svelte
new file mode 100644
index 0000000000..647a32c9eb
--- /dev/null
+++ b/packages/builder/src/pages/builder/portal/settings/ai/logos/AzureOpenAI.svelte
@@ -0,0 +1,64 @@
+
+
+
diff --git a/packages/builder/src/pages/builder/portal/settings/index.svelte b/packages/builder/src/pages/builder/portal/settings/index.svelte
index 09ead3e410..1448b43ec4 100644
--- a/packages/builder/src/pages/builder/portal/settings/index.svelte
+++ b/packages/builder/src/pages/builder/portal/settings/index.svelte
@@ -1,5 +1,10 @@
diff --git a/packages/server/src/automations/tests/scenarios/looping.spec.ts b/packages/server/src/automations/tests/scenarios/looping.spec.ts
index 6dde05ddb8..379927342a 100644
--- a/packages/server/src/automations/tests/scenarios/looping.spec.ts
+++ b/packages/server/src/automations/tests/scenarios/looping.spec.ts
@@ -5,6 +5,7 @@ import {
LoopStepType,
CreateRowStepOutputs,
ServerLogStepOutputs,
+ FieldType,
} from "@budibase/types"
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
@@ -269,4 +270,145 @@ describe("Loop automations", () => {
expect(results.steps[1].outputs.message).toContain("- 3")
})
+
+ it("should run an automation with a loop and update row step", async () => {
+ const table = await config.createTable({
+ name: "TestTable",
+ type: "table",
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: {
+ presence: true,
+ },
+ },
+ value: {
+ name: "value",
+ type: FieldType.NUMBER,
+ constraints: {
+ presence: true,
+ },
+ },
+ },
+ })
+
+ const rows = [
+ { name: "Row 1", value: 1, tableId: table._id },
+ { name: "Row 2", value: 2, tableId: table._id },
+ { name: "Row 3", value: 3, tableId: table._id },
+ ]
+
+ await config.api.row.bulkImport(table._id!, { rows })
+
+ const builder = createAutomationBuilder({
+ name: "Test Loop and Update Row",
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .loop({
+ option: LoopStepType.ARRAY,
+ binding: "{{ steps.1.rows }}",
+ })
+ .updateRow({
+ rowId: "{{ loop.currentItem._id }}",
+ row: {
+ name: "Updated {{ loop.currentItem.name }}",
+ value: "{{ loop.currentItem.value }}",
+ tableId: table._id,
+ },
+ meta: {},
+ })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .run()
+
+ const expectedRows = [
+ { name: "Updated Row 1", value: 1 },
+ { name: "Updated Row 2", value: 2 },
+ { name: "Updated Row 3", value: 3 },
+ ]
+
+ expect(results.steps[1].outputs.items).toEqual(
+ expect.arrayContaining(
+ expectedRows.map(row =>
+ expect.objectContaining({
+ success: true,
+ row: expect.objectContaining(row),
+ })
+ )
+ )
+ )
+
+ expect(results.steps[2].outputs.rows).toEqual(
+ expect.arrayContaining(
+ expectedRows.map(row => expect.objectContaining(row))
+ )
+ )
+
+ expect(results.steps[1].outputs.items).toHaveLength(expectedRows.length)
+ expect(results.steps[2].outputs.rows).toHaveLength(expectedRows.length)
+ })
+
+ it("should run an automation with a loop and delete row step", async () => {
+ const table = await config.createTable({
+ name: "TestTable",
+ type: "table",
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: {
+ presence: true,
+ },
+ },
+ value: {
+ name: "value",
+ type: FieldType.NUMBER,
+ constraints: {
+ presence: true,
+ },
+ },
+ },
+ })
+
+ const rows = [
+ { name: "Row 1", value: 1, tableId: table._id },
+ { name: "Row 2", value: 2, tableId: table._id },
+ { name: "Row 3", value: 3, tableId: table._id },
+ ]
+
+ await config.api.row.bulkImport(table._id!, { rows })
+
+ const builder = createAutomationBuilder({
+ name: "Test Loop and Delete Row",
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .loop({
+ option: LoopStepType.ARRAY,
+ binding: "{{ steps.1.rows }}",
+ })
+ .deleteRow({
+ tableId: table._id!,
+ id: "{{ loop.currentItem._id }}",
+ })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .run()
+
+ expect(results.steps).toHaveLength(3)
+
+ expect(results.steps[2].outputs.rows).toHaveLength(0)
+ })
})
diff --git a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts
index 49c05984fe..850a04a95a 100644
--- a/packages/server/src/automations/tests/scenarios/scenarios.spec.ts
+++ b/packages/server/src/automations/tests/scenarios/scenarios.spec.ts
@@ -1,8 +1,9 @@
import * as automation from "../../index"
import * as setup from "../utilities"
-import { LoopStepType, FieldType } from "@budibase/types"
+import { LoopStepType, FieldType, Table } from "@budibase/types"
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
import { DatabaseName } from "../../../integrations/tests/utils"
+import { FilterConditions } from "../../../automations/steps/filter"
describe("Automation Scenarios", () => {
let config = setup.getConfig()
@@ -195,6 +196,91 @@ describe("Automation Scenarios", () => {
)
})
})
+
+ it("should trigger an automation which creates and then updates a row", async () => {
+ const table = await config.createTable({
+ name: "TestTable",
+ type: "table",
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: {
+ presence: true,
+ },
+ },
+ value: {
+ name: "value",
+ type: FieldType.NUMBER,
+ constraints: {
+ presence: true,
+ },
+ },
+ },
+ })
+
+ const builder = createAutomationBuilder({
+ name: "Test Create and Update Row",
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .createRow(
+ {
+ row: {
+ name: "Initial Row",
+ value: 1,
+ tableId: table._id,
+ },
+ },
+ { stepName: "CreateRowStep" }
+ )
+ .updateRow(
+ {
+ rowId: "{{ steps.CreateRowStep.row._id }}",
+ row: {
+ name: "Updated Row",
+ value: 2,
+ tableId: table._id,
+ },
+ meta: {},
+ },
+ { stepName: "UpdateRowStep" }
+ )
+ .queryRows(
+ {
+ tableId: table._id!,
+ },
+ { stepName: "QueryRowsStep" }
+ )
+ .run()
+
+ expect(results.steps).toHaveLength(3)
+
+ expect(results.steps[0].outputs).toMatchObject({
+ success: true,
+ row: {
+ name: "Initial Row",
+ value: 1,
+ },
+ })
+
+ expect(results.steps[1].outputs).toMatchObject({
+ success: true,
+ row: {
+ name: "Updated Row",
+ value: 2,
+ },
+ })
+
+ const expectedRows = [{ name: "Updated Row", value: 2 }]
+
+ expect(results.steps[2].outputs.rows).toEqual(
+ expect.arrayContaining(
+ expectedRows.map(row => expect.objectContaining(row))
+ )
+ )
+ })
})
describe("Name Based Automations", () => {
@@ -233,4 +319,167 @@ describe("Automation Scenarios", () => {
expect(results.steps[2].outputs.rows).toHaveLength(1)
})
})
+ describe("Automations with filter", () => {
+ let table: Table
+
+ beforeEach(async () => {
+ table = await config.createTable({
+ name: "TestTable",
+ type: "table",
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: {
+ presence: true,
+ },
+ },
+ value: {
+ name: "value",
+ type: FieldType.NUMBER,
+ constraints: {
+ presence: true,
+ },
+ },
+ },
+ })
+ })
+
+ it("should stop an automation if the condition is not met", async () => {
+ const builder = createAutomationBuilder({
+ name: "Test Equal",
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .createRow({
+ row: {
+ name: "Equal Test",
+ value: 10,
+ tableId: table._id,
+ },
+ })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .filter({
+ field: "{{ steps.2.rows.0.value }}",
+ condition: FilterConditions.EQUAL,
+ value: 20,
+ })
+ .serverLog({ text: "Equal condition met" })
+ .run()
+
+ expect(results.steps[2].outputs.success).toBeTrue()
+ expect(results.steps[2].outputs.result).toBeFalse()
+ expect(results.steps[3]).toBeUndefined()
+ })
+
+ it("should continue the automation if the condition is met", async () => {
+ const builder = createAutomationBuilder({
+ name: "Test Not Equal",
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .createRow({
+ row: {
+ name: "Not Equal Test",
+ value: 10,
+ tableId: table._id,
+ },
+ })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .filter({
+ field: "{{ steps.2.rows.0.value }}",
+ condition: FilterConditions.NOT_EQUAL,
+ value: 20,
+ })
+ .serverLog({ text: "Not Equal condition met" })
+ .run()
+
+ expect(results.steps[2].outputs.success).toBeTrue()
+ expect(results.steps[2].outputs.result).toBeTrue()
+ expect(results.steps[3].outputs.success).toBeTrue()
+ })
+
+ const testCases = [
+ {
+ condition: FilterConditions.EQUAL,
+ value: 10,
+ rowValue: 10,
+ expectPass: true,
+ },
+ {
+ condition: FilterConditions.NOT_EQUAL,
+ value: 10,
+ rowValue: 20,
+ expectPass: true,
+ },
+ {
+ condition: FilterConditions.GREATER_THAN,
+ value: 10,
+ rowValue: 15,
+ expectPass: true,
+ },
+ {
+ condition: FilterConditions.LESS_THAN,
+ value: 10,
+ rowValue: 5,
+ expectPass: true,
+ },
+ {
+ condition: FilterConditions.GREATER_THAN,
+ value: 10,
+ rowValue: 5,
+ expectPass: false,
+ },
+ {
+ condition: FilterConditions.LESS_THAN,
+ value: 10,
+ rowValue: 15,
+ expectPass: false,
+ },
+ ]
+
+ it.each(testCases)(
+ "should pass the filter when condition is $condition",
+ async ({ condition, value, rowValue, expectPass }) => {
+ const builder = createAutomationBuilder({
+ name: `Test ${condition}`,
+ })
+
+ const results = await builder
+ .appAction({ fields: {} })
+ .createRow({
+ row: {
+ name: `${condition} Test`,
+ value: rowValue,
+ tableId: table._id,
+ },
+ })
+ .queryRows({
+ tableId: table._id!,
+ })
+ .filter({
+ field: "{{ steps.2.rows.0.value }}",
+ condition,
+ value,
+ })
+ .serverLog({
+ text: `${condition} condition ${expectPass ? "passed" : "failed"}`,
+ })
+ .run()
+
+ expect(results.steps[2].outputs.result).toBe(expectPass)
+ if (expectPass) {
+ expect(results.steps[3].outputs.success).toBeTrue()
+ } else {
+ expect(results.steps[3]).toBeUndefined()
+ }
+ }
+ )
+ })
})
diff --git a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts
index 7528ea0f2e..2269f075b2 100644
--- a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts
+++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts
@@ -33,6 +33,7 @@ import {
BranchStepInputs,
SearchFilters,
Branch,
+ FilterStepInputs,
} from "@budibase/types"
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import * as setup from "../utilities"
@@ -181,6 +182,14 @@ class BaseStepBuilder {
opts?.stepName
)
}
+
+ filter(input: FilterStepInputs): this {
+ return this.step(
+ AutomationActionStepId.FILTER,
+ BUILTIN_ACTION_DEFINITIONS.FILTER,
+ input
+ )
+ }
}
class StepBuilder extends BaseStepBuilder {
build(): AutomationStep[] {