Merge remote-tracking branch 'origin/v3-ui' into feature/automation-branching-ux
This commit is contained in:
commit
8de5a0d7b6
|
@ -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 @@
|
|||
<div class="icon">
|
||||
<svelte:component
|
||||
this={logos[config.name || config.provider]}
|
||||
height="30"
|
||||
width="30"
|
||||
height="18"
|
||||
width="18"
|
||||
/>
|
||||
</div>
|
||||
<div class="header">
|
||||
|
@ -110,7 +112,7 @@
|
|||
|
||||
.tag {
|
||||
display: flex;
|
||||
color: var(--spectrum-body-m-text-color);
|
||||
color: #ffffff;
|
||||
padding: 4px 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { it, expect, describe, vi } from "vitest"
|
||||
import AISettings from "./index.svelte"
|
||||
import { render } from "@testing-library/svelte"
|
||||
import { render, fireEvent } from "@testing-library/svelte"
|
||||
import { admin, licensing } from "stores/portal"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
|
@ -55,39 +55,43 @@ describe("AISettings", () => {
|
|||
expect(enterpriseTag).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show the premium label on cloud when Budibase AI isn't enabled", async () => {
|
||||
setupEnv(Hosting.Cloud)
|
||||
instance = render(AISettings, {})
|
||||
const premiumTag = instance.queryByText("Premium")
|
||||
expect(premiumTag).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not show the add configuration button if the user doesn't have the correct license on cloud", async () => {
|
||||
it("the add configuration button should not do anything the user doesn't have the correct license on cloud", async () => {
|
||||
let addConfigurationButton
|
||||
let configModal
|
||||
|
||||
setupEnv(Hosting.Cloud)
|
||||
instance = render(AISettings)
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).not.toBeInTheDocument()
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
configModal = instance.queryByText("Custom AI Configuration")
|
||||
expect(configModal).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("the add configuration button should open the config modal if the user has the correct license on cloud", async () => {
|
||||
let addConfigurationButton
|
||||
let configModal
|
||||
|
||||
setupEnv(Hosting.Cloud, { customAIConfigsEnabled: true })
|
||||
instance = render(AISettings)
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
configModal = instance.queryByText("Custom AI Configuration")
|
||||
expect(configModal).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should not show the add configuration button if the user doesn't have the correct license on self host", async () => {
|
||||
it("the add configuration button should open the config modal if the user has the correct license on self host", async () => {
|
||||
let addConfigurationButton
|
||||
|
||||
setupEnv(Hosting.Self)
|
||||
instance = render(AISettings)
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).not.toBeInTheDocument()
|
||||
let configModal
|
||||
|
||||
setupEnv(Hosting.Self, { customAIConfigsEnabled: true })
|
||||
instance = render(AISettings, {})
|
||||
instance = render(AISettings)
|
||||
addConfigurationButton = instance.queryByText("Add configuration")
|
||||
expect(addConfigurationButton).toBeInTheDocument()
|
||||
await fireEvent.click(addConfigurationButton)
|
||||
configModal = instance.queryByText("Custom AI Configuration")
|
||||
expect(configModal).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -84,8 +84,10 @@
|
|||
<Label size="M">API Key</Label>
|
||||
<Input type="password" bind:value={config.apiKey} />
|
||||
</div>
|
||||
<Toggle text="Active" bind:value={config.active} />
|
||||
<Toggle text="Set as default" bind:value={config.isDefault} />
|
||||
<div class="form-row">
|
||||
<Toggle text="Active" bind:value={config.active} />
|
||||
<Toggle text="Set as default" bind:value={config.isDefault} />
|
||||
</div>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -23,7 +23,7 @@ export const Providers = {
|
|||
models: [{ label: "Llama 3 8B", value: "meta-llama/Meta-Llama-3-8B" }],
|
||||
},
|
||||
AzureOpenAI: {
|
||||
name: "Azure Open AI",
|
||||
name: "Azure OpenAI",
|
||||
models: [
|
||||
{ label: "GPT 4o Mini", value: "gpt-4o-mini" },
|
||||
{ label: "GPT 4o", value: "gpt-4o" },
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
let editingUuid
|
||||
|
||||
$: isCloud = $admin.cloud
|
||||
$: budibaseAIEnabled = $licensing.budibaseAIEnabled
|
||||
$: customAIConfigsEnabled = $licensing.customAIConfigsEnabled
|
||||
|
||||
async function fetchAIConfig() {
|
||||
|
@ -127,18 +126,8 @@
|
|||
</Modal>
|
||||
<Layout noPadding>
|
||||
<Layout gap="XS" noPadding>
|
||||
<Heading size="M">AI</Heading>
|
||||
{#if isCloud && !budibaseAIEnabled}
|
||||
<Tags>
|
||||
<Tag icon="LockClosed">Premium</Tag>
|
||||
</Tags>
|
||||
{/if}
|
||||
<Body>Configure your AI settings within this section:</Body>
|
||||
</Layout>
|
||||
<Divider />
|
||||
<Layout noPadding>
|
||||
<div class="config-heading">
|
||||
<Heading size="S">AI Configurations</Heading>
|
||||
<div class="header">
|
||||
<Heading size="M">AI</Heading>
|
||||
{#if !isCloud && !customAIConfigsEnabled}
|
||||
<Tags>
|
||||
<Tag icon="LockClosed">Premium</Tag>
|
||||
|
@ -147,24 +136,43 @@
|
|||
<Tags>
|
||||
<Tag icon="LockClosed">Enterprise</Tag>
|
||||
</Tags>
|
||||
{:else}
|
||||
<Button size="S" cta on:click={newConfig}>Add configuration</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<Body size="S"
|
||||
>Use the following interface to select your preferred AI configuration.</Body
|
||||
>
|
||||
<Body size="S">Select your AI Model:</Body>
|
||||
{#if fullAIConfig?.config}
|
||||
{#each Object.keys(fullAIConfig.config) as key}
|
||||
<AIConfigTile
|
||||
config={fullAIConfig.config[key]}
|
||||
editHandler={() => editConfig(key)}
|
||||
deleteHandler={() => deleteConfig(key)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
<Body>Configure your AI settings within this section:</Body>
|
||||
</Layout>
|
||||
<Divider />
|
||||
<div style={`opacity: ${customAIConfigsEnabled ? 1 : 0.5}`}>
|
||||
<Layout noPadding>
|
||||
<div class="config-heading">
|
||||
<Heading size="S">AI Configurations</Heading>
|
||||
<Button
|
||||
size="S"
|
||||
cta={customAIConfigsEnabled}
|
||||
secondary={!customAIConfigsEnabled}
|
||||
on:click={customAIConfigsEnabled ? newConfig : null}
|
||||
>
|
||||
Add configuration
|
||||
</Button>
|
||||
</div>
|
||||
<Body size="S"
|
||||
>Use the following interface to select your preferred AI configuration.</Body
|
||||
>
|
||||
{#if customAIConfigsEnabled}
|
||||
<Body size="S">Select your AI Model:</Body>
|
||||
{/if}
|
||||
{#if fullAIConfig?.config}
|
||||
{#each Object.keys(fullAIConfig.config) as key}
|
||||
<AIConfigTile
|
||||
config={fullAIConfig.config[key]}
|
||||
editHandler={customAIConfigsEnabled ? () => editConfig(key) : null}
|
||||
deleteHandler={customAIConfigsEnabled
|
||||
? () => deleteConfig(key)
|
||||
: null}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</Layout>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
|
@ -172,5 +180,12 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: -18px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<script>
|
||||
export let width
|
||||
export let height
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" {width} {height} viewBox="0 0 96 96">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="e399c19f-b68f-429d-b176-18c2117ff73c"
|
||||
x1="-1032.172"
|
||||
x2="-1059.213"
|
||||
y1="145.312"
|
||||
y2="65.426"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-color="#114a8b" />
|
||||
<stop offset="1" stop-color="#0669bc" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="ac2a6fc2-ca48-4327-9a3c-d4dcc3256e15"
|
||||
x1="-1023.725"
|
||||
x2="-1029.98"
|
||||
y1="108.083"
|
||||
y2="105.968"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-opacity=".3" />
|
||||
<stop offset=".071" stop-opacity=".2" />
|
||||
<stop offset=".321" stop-opacity=".1" />
|
||||
<stop offset=".623" stop-opacity=".05" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="a7fee970-a784-4bb1-af8d-63d18e5f7db9"
|
||||
x1="-1027.165"
|
||||
x2="-997.482"
|
||||
y1="147.642"
|
||||
y2="68.561"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-color="#3ccbf4" />
|
||||
<stop offset="1" stop-color="#2892df" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill="url(#e399c19f-b68f-429d-b176-18c2117ff73c)"
|
||||
d="M33.338 6.544h26.038l-27.03 80.087a4.152 4.152 0 0 1-3.933 2.824H8.149a4.145 4.145 0 0 1-3.928-5.47L29.404 9.368a4.152 4.152 0 0 1 3.934-2.825z"
|
||||
/>
|
||||
<path
|
||||
fill="#0078d4"
|
||||
d="M71.175 60.261h-41.29a1.911 1.911 0 0 0-1.305 3.309l26.532 24.764a4.171 4.171 0 0 0 2.846 1.121h23.38z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#ac2a6fc2-ca48-4327-9a3c-d4dcc3256e15)"
|
||||
d="M33.338 6.544a4.118 4.118 0 0 0-3.943 2.879L4.252 83.917a4.14 4.14 0 0 0 3.908 5.538h20.787a4.443 4.443 0 0 0 3.41-2.9l5.014-14.777 17.91 16.705a4.237 4.237 0 0 0 2.666.972H81.24L71.024 60.261l-29.781.007L59.47 6.544z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#a7fee970-a784-4bb1-af8d-63d18e5f7db9)"
|
||||
d="M66.595 9.364a4.145 4.145 0 0 0-3.928-2.82H33.648a4.146 4.146 0 0 1 3.928 2.82l25.184 74.62a4.146 4.146 0 0 1-3.928 5.472h29.02a4.146 4.146 0 0 0 3.927-5.472z"
|
||||
/>
|
||||
</svg>
|
|
@ -1,5 +1,10 @@
|
|||
<script>
|
||||
import { redirect } from "@roxi/routify"
|
||||
import { licensing } from "stores/portal"
|
||||
|
||||
$redirect("./auth")
|
||||
if ($licensing.customAIConfigsEnabled) {
|
||||
$redirect("./ai")
|
||||
} else {
|
||||
$redirect("./auth")
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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[] {
|
||||
|
|
Loading…
Reference in New Issue