diff --git a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
index c47840ea83..1e951b00cb 100644
--- a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
+++ b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
@@ -19,7 +19,7 @@
$: success = !error && !empty
$: highlightedResult = highlight(expressionResult)
$: highlightedLogs = expressionLogs.map(l => ({
- log: highlight(l.log.join(", ")),
+ log: l.log.map(part => highlight(part)).join(", "),
line: l.line,
type: l.type,
}))
@@ -95,7 +95,9 @@
{#if empty}
Your expression will be evaluated here
{:else if error}
- {formatError(expressionError)}
+
+ {formatError(expressionError)}
+
{:else}
{#each highlightedLogs as logLine}
@@ -118,13 +120,17 @@
{@html logLine.log}
{#if logLine.line}
- :{logLine.line}
+ :{logLine.line}
{/if}
{/each}
-
- {@html highlightedResult}
+
+
+ {@html highlightedResult}
+
{/if}
@@ -169,29 +175,33 @@
.header.error::before {
background: var(--error-bg);
}
+ .error-msg {
+ padding-top: var(--spacing-m);
+ }
.body {
flex: 1 1 auto;
- padding: var(--spacing-m) var(--spacing-l);
font-family: var(--font-mono);
+ margin: 0 var(--spacing-m);
font-size: 12px;
overflow-y: auto;
overflow-x: hidden;
- white-space: pre-line;
- word-wrap: break-word;
+ word-wrap: anywhere;
height: 0;
}
.output-lines {
display: flex;
flex-direction: column;
- gap: var(--spacing-xs);
}
.line {
- border-bottom: var(--border-light);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: end;
- padding: var(--spacing-s);
+ padding: var(--spacing-m) 0;
+ word-wrap: anywhere;
+ }
+ .line:not(:first-of-type) {
+ border-top: var(--border-light);
}
.icon-log {
display: flex;
diff --git a/packages/builder/src/stores/builder/automations.ts b/packages/builder/src/stores/builder/automations.ts
index 9b20b4cd03..ddb8706482 100644
--- a/packages/builder/src/stores/builder/automations.ts
+++ b/packages/builder/src/stores/builder/automations.ts
@@ -2,7 +2,7 @@ import { derived, get } from "svelte/store"
import { API } from "@/api"
import { cloneDeep } from "lodash/fp"
import { generate } from "shortid"
-import { createHistoryStore } from "@/stores/builder/history"
+import { createHistoryStore, HistoryStore } from "@/stores/builder/history"
import { licensing } from "@/stores/portal"
import { tables, appStore } from "@/stores/builder"
import { notifications } from "@budibase/bbui"
@@ -1428,7 +1428,7 @@ const automationActions = (store: AutomationStore) => ({
})
class AutomationStore extends BudiStore {
- history: any
+ history: HistoryStore
actions: ReturnType
constructor() {
@@ -1437,8 +1437,6 @@ class AutomationStore extends BudiStore {
this.history = createHistoryStore({
getDoc: this.actions.getDefinition.bind(this),
selectDoc: this.actions.select.bind(this),
- beforeAction: () => {},
- afterAction: () => {},
})
// Then wrap save and delete with history
diff --git a/packages/builder/src/stores/builder/componentTreeNodes.ts b/packages/builder/src/stores/builder/componentTreeNodes.ts
index cec4e3a4a0..b88563de6a 100644
--- a/packages/builder/src/stores/builder/componentTreeNodes.ts
+++ b/packages/builder/src/stores/builder/componentTreeNodes.ts
@@ -1,7 +1,7 @@
import { get } from "svelte/store"
import { selectedScreen as selectedScreenStore } from "./screens"
import { findComponentPath } from "@/helpers/components"
-import { Screen, Component } from "@budibase/types"
+import { Component, Screen } from "@budibase/types"
import { BudiStore, PersistenceType } from "@/stores/BudiStore"
interface OpenNodesState {
diff --git a/packages/builder/src/stores/builder/components.ts b/packages/builder/src/stores/builder/components.ts
index 46d3e07eae..90e1abfecf 100644
--- a/packages/builder/src/stores/builder/components.ts
+++ b/packages/builder/src/stores/builder/components.ts
@@ -30,9 +30,18 @@ import {
} from "@/constants/backend"
import { BudiStore } from "../BudiStore"
import { Utils } from "@budibase/frontend-core"
-import { Component, FieldType, Screen, Table } from "@budibase/types"
+import {
+ Component as ComponentType,
+ FieldType,
+ Screen,
+ Table,
+} from "@budibase/types"
import { utils } from "@budibase/shared-core"
+interface Component extends ComponentType {
+ _id: string
+}
+
export interface ComponentState {
components: Record
customComponents: string[]
@@ -254,7 +263,10 @@ export class ComponentStore extends BudiStore {
* @param {object} opts
* @returns
*/
- enrichEmptySettings(component: Component, opts: any) {
+ enrichEmptySettings(
+ component: Component,
+ opts: { screen?: Screen; parent?: Component; useDefaultValues?: boolean }
+ ) {
if (!component?._component) {
return
}
@@ -364,7 +376,7 @@ export class ComponentStore extends BudiStore {
getSchemaForDatasource(screen, dataSource, {})
// Finds fields by types from the schema of the configured datasource
- const findFieldTypes = (fieldTypes: any) => {
+ const findFieldTypes = (fieldTypes: FieldType | FieldType[]) => {
if (!Array.isArray(fieldTypes)) {
fieldTypes = [fieldTypes]
}
@@ -439,7 +451,11 @@ export class ComponentStore extends BudiStore {
* @param {object} parent
* @returns
*/
- createInstance(componentName: string, presetProps: any, parent: any) {
+ createInstance(
+ componentName: string,
+ presetProps: any,
+ parent: any
+ ): Component | null {
const screen = get(selectedScreen)
if (!screen) {
throw "A valid screen must be selected"
@@ -451,7 +467,7 @@ export class ComponentStore extends BudiStore {
}
// Generate basic component structure
- let instance = {
+ let instance: Component = {
_id: Helpers.uuid(),
_component: definition.component,
_styles: {
@@ -478,7 +494,7 @@ export class ComponentStore extends BudiStore {
}
// Custom post processing for creation only
- let extras: any = {}
+ let extras: Partial = {}
if (definition.hasChildren) {
extras._children = []
}
@@ -487,7 +503,7 @@ export class ComponentStore extends BudiStore {
if (componentName.endsWith("/formstep")) {
const parentForm = findClosestMatchingComponent(
screen.props,
- get(selectedComponent)._id,
+ get(selectedComponent)?._id,
(component: Component) => component._component.endsWith("/form")
)
const formSteps = findAllMatchingComponents(
@@ -515,7 +531,7 @@ export class ComponentStore extends BudiStore {
async create(
componentName: string,
presetProps: any,
- parent: any,
+ parent: Component,
index: number
) {
const state = get(this.store)
@@ -772,7 +788,7 @@ export class ComponentStore extends BudiStore {
if (!cut) {
componentToPaste = makeComponentUnique(componentToPaste)
}
- newComponentId = componentToPaste._id!
+ newComponentId = componentToPaste._id
// Strip grid position metadata if pasting into a new screen, but keep
// alignment metadata
@@ -915,7 +931,7 @@ export class ComponentStore extends BudiStore {
// If we have children, select first child, and the node is not collapsed
if (
- component._children?.length &&
+ component?._children?.length &&
(state.selectedComponentId === navComponentId ||
componentTreeNodesStore.isNodeExpanded(component._id))
) {
@@ -1339,12 +1355,15 @@ export const componentStore = new ComponentStore()
export const selectedComponent = derived(
[componentStore, selectedScreen],
- ([$store, $selectedScreen]) => {
+ ([$store, $selectedScreen]): Component | null => {
if (
$selectedScreen &&
$store.selectedComponentId?.startsWith(`${$selectedScreen._id}-`)
) {
- return $selectedScreen?.props
+ return {
+ ...$selectedScreen.props,
+ _id: $selectedScreen.props._id!,
+ }
}
if (!$selectedScreen || !$store.selectedComponentId) {
return null
diff --git a/packages/builder/src/stores/builder/history.js b/packages/builder/src/stores/builder/history.ts
similarity index 79%
rename from packages/builder/src/stores/builder/history.js
rename to packages/builder/src/stores/builder/history.ts
index 62a8ed2f97..6568c5abca 100644
--- a/packages/builder/src/stores/builder/history.js
+++ b/packages/builder/src/stores/builder/history.ts
@@ -1,10 +1,25 @@
-import * as jsonpatch from "fast-json-patch/index.mjs"
-import { writable, derived, get } from "svelte/store"
+import { Document } from "@budibase/types"
+import * as jsonpatch from "fast-json-patch"
+import { writable, derived, get, Readable } from "svelte/store"
-export const Operations = {
- Add: "Add",
- Delete: "Delete",
- Change: "Change",
+export const enum Operations {
+ Add = "Add",
+ Delete = "Delete",
+ Change = "Change",
+}
+
+interface Operator {
+ id?: number
+ type: Operations
+ doc: T
+ forwardPatch?: jsonpatch.Operation[]
+ backwardsPatch?: jsonpatch.Operation[]
+}
+
+interface HistoryState {
+ history: Operator[]
+ position: number
+ loading?: boolean
}
export const initialState = {
@@ -13,14 +28,38 @@ export const initialState = {
loading: false,
}
-export const createHistoryStore = ({
+export interface HistoryStore
+ extends Readable<
+ HistoryState & {
+ canUndo: boolean
+ canRedo: boolean
+ }
+ > {
+ wrapSaveDoc: (
+ fn: (doc: T) => Promise
+ ) => (doc: T, operationId?: number) => Promise
+ wrapDeleteDoc: (
+ fn: (doc: T) => Promise
+ ) => (doc: T, operationId?: number) => Promise
+
+ reset: () => void
+ undo: () => Promise
+ redo: () => Promise
+}
+
+export const createHistoryStore = ({
getDoc,
selectDoc,
beforeAction,
afterAction,
-}) => {
+}: {
+ getDoc: (id: string) => T | undefined
+ selectDoc: (id: string) => void
+ beforeAction?: (operation?: Operator) => void
+ afterAction?: (operation?: Operator) => void
+}): HistoryStore => {
// Use a derived store to check if we are able to undo or redo any operations
- const store = writable(initialState)
+ const store = writable>(initialState)
const derivedStore = derived(store, $store => {
return {
...$store,
@@ -31,8 +70,8 @@ export const createHistoryStore = ({
// Wrapped versions of essential functions which we call ourselves when using
// undo and redo
- let saveFn
- let deleteFn
+ let saveFn: (doc: T, operationId?: number) => Promise
+ let deleteFn: (doc: T, operationId?: number) => Promise
/**
* Internal util to set the loading flag
@@ -66,7 +105,7 @@ export const createHistoryStore = ({
* For internal use only.
* @param operation the operation to save
*/
- const saveOperation = operation => {
+ const saveOperation = (operation: Operator) => {
store.update(state => {
// Update history
let history = state.history
@@ -93,15 +132,15 @@ export const createHistoryStore = ({
* @param fn the save function
* @returns {function} a wrapped version of the save function
*/
- const wrapSaveDoc = fn => {
- saveFn = async (doc, operationId) => {
+ const wrapSaveDoc = (fn: (doc: T) => Promise) => {
+ saveFn = async (doc: T, operationId?: number) => {
// Only works on a single doc at a time
if (!doc || Array.isArray(doc)) {
return
}
startLoading()
try {
- const oldDoc = getDoc(doc._id)
+ const oldDoc = getDoc(doc._id!)
const newDoc = jsonpatch.deepClone(await fn(doc))
// Store the change
@@ -141,8 +180,8 @@ export const createHistoryStore = ({
* @param fn the delete function
* @returns {function} a wrapped version of the delete function
*/
- const wrapDeleteDoc = fn => {
- deleteFn = async (doc, operationId) => {
+ const wrapDeleteDoc = (fn: (doc: T) => Promise) => {
+ deleteFn = async (doc: T, operationId?: number) => {
// Only works on a single doc at a time
if (!doc || Array.isArray(doc)) {
return
@@ -201,7 +240,7 @@ export const createHistoryStore = ({
// Undo ADD
if (operation.type === Operations.Add) {
// Try to get the latest doc version to delete
- const latestDoc = getDoc(operation.doc._id)
+ const latestDoc = getDoc(operation.doc._id!)
const doc = latestDoc || operation.doc
await deleteFn(doc, operation.id)
}
@@ -219,7 +258,7 @@ export const createHistoryStore = ({
// Undo CHANGE
else {
// Get the current doc and apply the backwards patch on top of it
- let doc = jsonpatch.deepClone(getDoc(operation.doc._id))
+ let doc = jsonpatch.deepClone(getDoc(operation.doc._id!))
if (doc) {
jsonpatch.applyPatch(
doc,
@@ -283,7 +322,7 @@ export const createHistoryStore = ({
// Redo DELETE
else if (operation.type === Operations.Delete) {
// Try to get the latest doc version to delete
- const latestDoc = getDoc(operation.doc._id)
+ const latestDoc = getDoc(operation.doc._id!)
const doc = latestDoc || operation.doc
await deleteFn(doc, operation.id)
}
@@ -291,7 +330,7 @@ export const createHistoryStore = ({
// Redo CHANGE
else {
// Get the current doc and apply the forwards patch on top of it
- let doc = jsonpatch.deepClone(getDoc(operation.doc._id))
+ let doc = jsonpatch.deepClone(getDoc(operation.doc._id!))
if (doc) {
jsonpatch.applyPatch(doc, jsonpatch.deepClone(operation.forwardPatch))
await saveFn(doc, operation.id)
diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts
index 5163c6a3ea..64fe31752d 100644
--- a/packages/builder/src/stores/builder/screens.ts
+++ b/packages/builder/src/stores/builder/screens.ts
@@ -10,7 +10,7 @@ import {
navigationStore,
selectedComponent,
} from "@/stores/builder"
-import { createHistoryStore } from "@/stores/builder/history"
+import { createHistoryStore, HistoryStore } from "@/stores/builder/history"
import { API } from "@/api"
import { BudiStore } from "../BudiStore"
import {
@@ -33,9 +33,9 @@ export const initialScreenState: ScreenState = {
// Review the nulls
export class ScreenStore extends BudiStore {
- history: any
- delete: any
- save: any
+ history: HistoryStore
+ delete: (screens: Screen) => Promise
+ save: (screen: Screen) => Promise
constructor() {
super(initialScreenState)
@@ -58,13 +58,12 @@ export class ScreenStore extends BudiStore {
getDoc: (id: string) =>
get(this.store).screens?.find(screen => screen._id === id),
selectDoc: this.select,
- beforeAction: () => {},
afterAction: () => {
// Ensure a valid component is selected
if (!get(selectedComponent)) {
- this.update(state => ({
+ componentStore.update(state => ({
...state,
- selectedComponentId: get(selectedScreen)?.props._id,
+ selectedComponentId: get(selectedScreen)?._id,
}))
}
},
@@ -281,7 +280,10 @@ export class ScreenStore extends BudiStore {
* supports deeply mutating the current doc rather than just appending data.
*/
sequentialScreenPatch = Utils.sequential(
- async (patchFn: (screen: Screen) => any, screenId: string) => {
+ async (
+ patchFn: (screen: Screen) => boolean,
+ screenId: string
+ ): Promise => {
const state = get(this.store)
const screen = state.screens.find(screen => screen._id === screenId)
if (!screen) {
@@ -362,10 +364,10 @@ export class ScreenStore extends BudiStore {
* Any deleted screens will then have their routes/links purged
*
* Wrapped by {@link delete}
- * @param {Screen | Screen[]} screens
+ * @param {Screen } screens
*/
- async deleteScreen(screens: Screen | Screen[]) {
- const screensToDelete = Array.isArray(screens) ? screens : [screens]
+ async deleteScreen(screen: Screen) {
+ const screensToDelete = [screen]
// Build array of promises to speed up bulk deletions
let promises: Promise[] = []
let deleteUrls: string[] = []
diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.ts
similarity index 81%
rename from packages/frontend-core/src/utils/utils.js
rename to packages/frontend-core/src/utils/utils.ts
index 3dbaa23d3c..85cc5de54b 100644
--- a/packages/frontend-core/src/utils/utils.js
+++ b/packages/frontend-core/src/utils/utils.ts
@@ -1,8 +1,10 @@
import { makePropSafe as safe } from "@budibase/string-templates"
import { Helpers } from "@budibase/bbui"
import { cloneDeep } from "lodash"
+import { SearchFilterGroup, UISearchFilter } from "@budibase/types"
-export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
+export const sleep = (ms: number) =>
+ new Promise(resolve => setTimeout(resolve, ms))
/**
* Utility to wrap an async function and ensure all invocations happen
@@ -10,12 +12,18 @@ export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
* @param fn the async function to run
* @return {Function} a sequential version of the function
*/
-export const sequential = fn => {
- let queue = []
- return (...params) => {
- return new Promise((resolve, reject) => {
+export const sequential = <
+ TReturn,
+ TFunction extends (...args: any[]) => Promise
+>(
+ fn: TFunction
+): TFunction => {
+ let queue: (() => Promise)[] = []
+ const result = (...params: Parameters) => {
+ return new Promise((resolve, reject) => {
queue.push(async () => {
- let data, error
+ let data: TReturn | undefined
+ let error: unknown
try {
data = await fn(...params)
} catch (err) {
@@ -28,7 +36,7 @@ export const sequential = fn => {
if (error) {
reject(error)
} else {
- resolve(data)
+ resolve(data!)
}
})
if (queue.length === 1) {
@@ -36,6 +44,7 @@ export const sequential = fn => {
}
})
}
+ return result as TFunction
}
/**
@@ -45,9 +54,9 @@ export const sequential = fn => {
* @param minDelay the minimum delay between invocations
* @returns a debounced version of the callback
*/
-export const debounce = (callback, minDelay = 1000) => {
- let timeout
- return async (...params) => {
+export const debounce = (callback: Function, minDelay = 1000) => {
+ let timeout: ReturnType
+ return async (...params: any[]) => {
return new Promise(resolve => {
if (timeout) {
clearTimeout(timeout)
@@ -70,11 +79,11 @@ export const debounce = (callback, minDelay = 1000) => {
* @param minDelay
* @returns {Function} a throttled version function
*/
-export const throttle = (callback, minDelay = 1000) => {
- let lastParams
+export const throttle = (callback: Function, minDelay = 1000) => {
+ let lastParams: any[]
let stalled = false
let pending = false
- const invoke = (...params) => {
+ const invoke = (...params: any[]) => {
lastParams = params
if (stalled) {
pending = true
@@ -98,10 +107,10 @@ export const throttle = (callback, minDelay = 1000) => {
* @param callback the function to run
* @returns {Function}
*/
-export const domDebounce = callback => {
+export const domDebounce = (callback: Function) => {
let active = false
- let lastParams
- return (...params) => {
+ let lastParams: any[]
+ return (...params: any[]) => {
lastParams = params
if (!active) {
active = true
@@ -119,7 +128,17 @@ export const domDebounce = callback => {
*
* @param {any} props
* */
-export const buildFormBlockButtonConfig = props => {
+export const buildFormBlockButtonConfig = (props?: {
+ _id?: string
+ actionType?: string
+ dataSource?: { resourceId: string }
+ notificationOverride?: boolean
+ actionUrl?: string
+ showDeleteButton?: boolean
+ deleteButtonLabel?: string
+ showSaveButton?: boolean
+ saveButtonLabel?: string
+}) => {
const {
_id,
actionType,
@@ -227,7 +246,11 @@ export const buildFormBlockButtonConfig = props => {
const defaultButtons = []
- if (["Update", "Create"].includes(actionType) && showSaveButton !== false) {
+ if (
+ actionType &&
+ ["Update", "Create"].includes(actionType) &&
+ showSaveButton !== false
+ ) {
defaultButtons.push({
text: saveText || "Save",
_id: Helpers.uuid(),
@@ -251,7 +274,13 @@ export const buildFormBlockButtonConfig = props => {
return defaultButtons
}
-export const buildMultiStepFormBlockDefaultProps = props => {
+export const buildMultiStepFormBlockDefaultProps = (props?: {
+ _id: string
+ stepCount: number
+ currentStep: number
+ actionType: string
+ dataSource: { resourceId: string }
+}) => {
const { _id, stepCount, currentStep, actionType, dataSource } = props || {}
// Sanity check
@@ -361,7 +390,7 @@ export const buildMultiStepFormBlockDefaultProps = props => {
* @param {Object} filter UI filter
* @returns {Object} parsed filter
*/
-export function parseFilter(filter) {
+export function parseFilter(filter: UISearchFilter) {
if (!filter?.groups) {
return filter
}
@@ -369,13 +398,13 @@ export function parseFilter(filter) {
const update = cloneDeep(filter)
update.groups = update.groups
- .map(group => {
- group.filters = group.filters.filter(filter => {
+ ?.map(group => {
+ group.filters = group.filters?.filter((filter: any) => {
return filter.field && filter.operator
})
- return group.filters.length ? group : null
+ return group.filters?.length ? group : null
})
- .filter(group => group)
+ .filter((group): group is SearchFilterGroup => !!group)
return update
}
diff --git a/packages/server/src/api/routes/tests/automation.spec.ts b/packages/server/src/api/routes/tests/automation.spec.ts
index 5c0b86d9a0..94517db67a 100644
--- a/packages/server/src/api/routes/tests/automation.spec.ts
+++ b/packages/server/src/api/routes/tests/automation.spec.ts
@@ -1,7 +1,6 @@
import {
checkBuilderEndpoint,
getAllTableRows,
- clearAllAutomations,
testAutomation,
} from "./utilities/TestFunctions"
import * as setup from "./utilities"
@@ -12,9 +11,9 @@ import {
import { configs, context, events } from "@budibase/backend-core"
import sdk from "../../../sdk"
import {
- Automation,
ConfigType,
FieldType,
+ isDidNotTriggerResponse,
SettingsConfig,
Table,
} from "@budibase/types"
@@ -22,11 +21,13 @@ import { mocks } from "@budibase/backend-core/tests"
import { removeDeprecated } from "../../../automations/utils"
import { createAutomationBuilder } from "../../../automations/tests/utilities/AutomationTestBuilder"
import { automations } from "@budibase/shared-core"
+import { basicTable } from "../../../tests/utilities/structures"
+import TestConfiguration from "../../../tests/utilities/TestConfiguration"
const FilterConditions = automations.steps.filter.FilterConditions
const MAX_RETRIES = 4
-let {
+const {
basicAutomation,
newAutomation,
automationTrigger,
@@ -37,10 +38,11 @@ let {
} = setup.structures
describe("/automations", () => {
- let request = setup.getRequest()
- let config = setup.getConfig()
+ const config = new TestConfiguration()
- afterAll(setup.afterAll)
+ afterAll(() => {
+ config.end()
+ })
beforeAll(async () => {
await config.init()
@@ -52,40 +54,26 @@ describe("/automations", () => {
describe("get definitions", () => {
it("returns a list of definitions for actions", async () => {
- const res = await request
- .get(`/api/automations/action/list`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
-
- expect(Object.keys(res.body).length).not.toEqual(0)
+ const res = await config.api.automation.getActions()
+ expect(Object.keys(res).length).not.toEqual(0)
})
it("returns a list of definitions for triggerInfo", async () => {
- const res = await request
- .get(`/api/automations/trigger/list`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
-
- expect(Object.keys(res.body).length).not.toEqual(0)
+ const res = await config.api.automation.getTriggers()
+ expect(Object.keys(res).length).not.toEqual(0)
})
it("returns all of the definitions in one", async () => {
- const res = await request
- .get(`/api/automations/definitions/list`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
+ const { action, trigger } = await config.api.automation.getDefinitions()
let definitionsLength = Object.keys(
removeDeprecated(BUILTIN_ACTION_DEFINITIONS)
).length
- expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(
+ expect(Object.keys(action).length).toBeGreaterThanOrEqual(
definitionsLength
)
- expect(Object.keys(res.body.trigger).length).toEqual(
+ expect(Object.keys(trigger).length).toEqual(
Object.keys(removeDeprecated(TRIGGER_DEFINITIONS)).length
)
})
@@ -93,38 +81,27 @@ describe("/automations", () => {
describe("create", () => {
it("creates an automation with no steps", async () => {
- const automation = newAutomation()
- automation.definition.steps = []
+ const { message, automation } = await config.api.automation.post(
+ newAutomation({ steps: [] })
+ )
- const res = await request
- .post(`/api/automations`)
- .set(config.defaultHeaders())
- .send(automation)
- .expect("Content-Type", /json/)
- .expect(200)
-
- expect(res.body.message).toEqual("Automation created successfully")
- expect(res.body.automation.name).toEqual("My Automation")
- expect(res.body.automation._id).not.toEqual(null)
+ expect(message).toEqual("Automation created successfully")
+ expect(automation.name).toEqual("My Automation")
+ expect(automation._id).not.toEqual(null)
expect(events.automation.created).toHaveBeenCalledTimes(1)
expect(events.automation.stepCreated).not.toHaveBeenCalled()
})
it("creates an automation with steps", async () => {
- const automation = newAutomation()
- automation.definition.steps.push(automationStep())
jest.clearAllMocks()
- const res = await request
- .post(`/api/automations`)
- .set(config.defaultHeaders())
- .send(automation)
- .expect("Content-Type", /json/)
- .expect(200)
+ const { message, automation } = await config.api.automation.post(
+ newAutomation({ steps: [automationStep(), automationStep()] })
+ )
- expect(res.body.message).toEqual("Automation created successfully")
- expect(res.body.automation.name).toEqual("My Automation")
- expect(res.body.automation._id).not.toEqual(null)
+ expect(message).toEqual("Automation created successfully")
+ expect(automation.name).toEqual("My Automation")
+ expect(automation._id).not.toEqual(null)
expect(events.automation.created).toHaveBeenCalledTimes(1)
expect(events.automation.stepCreated).toHaveBeenCalledTimes(2)
})
@@ -241,13 +218,9 @@ describe("/automations", () => {
describe("find", () => {
it("should be able to find the automation", async () => {
const automation = await config.createAutomation()
- const res = await request
- .get(`/api/automations/${automation._id}`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(res.body._id).toEqual(automation._id)
- expect(res.body._rev).toEqual(automation._rev)
+ const { _id, _rev } = await config.api.automation.get(automation._id!)
+ expect(_id).toEqual(automation._id)
+ expect(_rev).toEqual(automation._rev)
})
})
@@ -348,106 +321,104 @@ describe("/automations", () => {
describe("trigger", () => {
it("does not trigger an automation when not synchronous and in dev", async () => {
- let automation = newAutomation()
- automation = await config.createAutomation(automation)
- const res = await request
- .post(`/api/automations/${automation._id}/trigger`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(400)
-
- expect(res.body.message).toEqual(
- "Only apps in production support this endpoint"
+ const { automation } = await config.api.automation.post(newAutomation())
+ await config.api.automation.trigger(
+ automation._id!,
+ {
+ fields: {},
+ timeout: 1000,
+ },
+ {
+ status: 400,
+ body: {
+ message: "Only apps in production support this endpoint",
+ },
+ }
)
})
it("triggers a synchronous automation", async () => {
mocks.licenses.useSyncAutomations()
- let automation = collectAutomation()
- automation = await config.createAutomation(automation)
- const res = await request
- .post(`/api/automations/${automation._id}/trigger`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
-
- expect(res.body.success).toEqual(true)
- expect(res.body.value).toEqual([1, 2, 3])
+ const { automation } = await config.api.automation.post(
+ collectAutomation()
+ )
+ await config.api.automation.trigger(
+ automation._id!,
+ {
+ fields: {},
+ timeout: 1000,
+ },
+ {
+ status: 200,
+ body: {
+ success: true,
+ value: [1, 2, 3],
+ },
+ }
+ )
})
it("should throw an error when attempting to trigger a disabled automation", async () => {
mocks.licenses.useSyncAutomations()
- let automation = collectAutomation()
- automation = await config.createAutomation({
- ...automation,
- disabled: true,
- })
+ const { automation } = await config.api.automation.post(
+ collectAutomation({ disabled: true })
+ )
- const res = await request
- .post(`/api/automations/${automation._id}/trigger`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(400)
-
- expect(res.body.message).toEqual("Automation is disabled")
+ await config.api.automation.trigger(
+ automation._id!,
+ {
+ fields: {},
+ timeout: 1000,
+ },
+ {
+ status: 400,
+ body: {
+ message: "Automation is disabled",
+ },
+ }
+ )
})
it("triggers an asynchronous automation", async () => {
- let automation = newAutomation()
- automation = await config.createAutomation(automation)
+ const { automation } = await config.api.automation.post(newAutomation())
await config.publish()
- const res = await request
- .post(`/api/automations/${automation._id}/trigger`)
- .set(config.defaultHeaders({}, true))
- .expect("Content-Type", /json/)
- .expect(200)
-
- expect(res.body.message).toEqual(
- `Automation ${automation._id} has been triggered.`
+ await config.withProdApp(() =>
+ config.api.automation.trigger(
+ automation._id!,
+ {
+ fields: {},
+ timeout: 1000,
+ },
+ {
+ status: 200,
+ body: {
+ message: `Automation ${automation._id} has been triggered.`,
+ },
+ }
+ )
)
})
})
describe("update", () => {
- const update = async (automation: Automation) => {
- return request
- .put(`/api/automations`)
- .set(config.defaultHeaders())
- .send(automation)
- .expect("Content-Type", /json/)
- .expect(200)
- }
-
- const updateWithPost = async (automation: Automation) => {
- return request
- .post(`/api/automations`)
- .set(config.defaultHeaders())
- .send(automation)
- .expect("Content-Type", /json/)
- .expect(200)
- }
-
it("updates a automations name", async () => {
- const automation = await config.createAutomation(newAutomation())
+ const { automation } = await config.api.automation.post(basicAutomation())
automation.name = "Updated Name"
jest.clearAllMocks()
- const res = await update(automation)
+ const { automation: updatedAutomation, message } =
+ await config.api.automation.update(automation)
- const automationRes = res.body.automation
- const message = res.body.message
+ expect(updatedAutomation._id).toEqual(automation._id)
+ expect(updatedAutomation._rev).toBeDefined()
+ expect(updatedAutomation._rev).not.toEqual(automation._rev)
- // doc attributes
- expect(automationRes._id).toEqual(automation._id)
- expect(automationRes._rev).toBeDefined()
- expect(automationRes._rev).not.toEqual(automation._rev)
- // content updates
- expect(automationRes.name).toEqual("Updated Name")
+ expect(updatedAutomation.name).toEqual("Updated Name")
expect(message).toEqual(
`Automation ${automation._id} updated successfully.`
)
- // events
+
expect(events.automation.created).not.toHaveBeenCalled()
expect(events.automation.stepCreated).not.toHaveBeenCalled()
expect(events.automation.stepDeleted).not.toHaveBeenCalled()
@@ -455,26 +426,23 @@ describe("/automations", () => {
})
it("updates a automations name using POST request", async () => {
- const automation = await config.createAutomation(newAutomation())
+ const { automation } = await config.api.automation.post(basicAutomation())
automation.name = "Updated Name"
jest.clearAllMocks()
- // the POST request will defer to the update
- // when an id has been supplied.
- const res = await updateWithPost(automation)
+ // the POST request will defer to the update when an id has been supplied.
+ const { automation: updatedAutomation, message } =
+ await config.api.automation.post(automation)
- const automationRes = res.body.automation
- const message = res.body.message
- // doc attributes
- expect(automationRes._id).toEqual(automation._id)
- expect(automationRes._rev).toBeDefined()
- expect(automationRes._rev).not.toEqual(automation._rev)
- // content updates
- expect(automationRes.name).toEqual("Updated Name")
+ expect(updatedAutomation._id).toEqual(automation._id)
+ expect(updatedAutomation._rev).toBeDefined()
+ expect(updatedAutomation._rev).not.toEqual(automation._rev)
+
+ expect(updatedAutomation.name).toEqual("Updated Name")
expect(message).toEqual(
`Automation ${automation._id} updated successfully.`
)
- // events
+
expect(events.automation.created).not.toHaveBeenCalled()
expect(events.automation.stepCreated).not.toHaveBeenCalled()
expect(events.automation.stepDeleted).not.toHaveBeenCalled()
@@ -482,16 +450,14 @@ describe("/automations", () => {
})
it("updates an automation trigger", async () => {
- let automation = newAutomation()
- automation = await config.createAutomation(automation)
+ const { automation } = await config.api.automation.post(newAutomation())
automation.definition.trigger = automationTrigger(
TRIGGER_DEFINITIONS.WEBHOOK
)
jest.clearAllMocks()
- await update(automation)
+ await config.api.automation.update(automation)
- // events
expect(events.automation.created).not.toHaveBeenCalled()
expect(events.automation.stepCreated).not.toHaveBeenCalled()
expect(events.automation.stepDeleted).not.toHaveBeenCalled()
@@ -499,16 +465,13 @@ describe("/automations", () => {
})
it("adds automation steps", async () => {
- let automation = newAutomation()
- automation = await config.createAutomation(automation)
+ const { automation } = await config.api.automation.post(newAutomation())
automation.definition.steps.push(automationStep())
automation.definition.steps.push(automationStep())
jest.clearAllMocks()
- // check the post request honours updates with same id
- await update(automation)
+ await config.api.automation.update(automation)
- // events
expect(events.automation.stepCreated).toHaveBeenCalledTimes(2)
expect(events.automation.created).not.toHaveBeenCalled()
expect(events.automation.stepDeleted).not.toHaveBeenCalled()
@@ -516,32 +479,25 @@ describe("/automations", () => {
})
it("removes automation steps", async () => {
- let automation = newAutomation()
- automation.definition.steps.push(automationStep())
- automation = await config.createAutomation(automation)
+ const { automation } = await config.api.automation.post(newAutomation())
automation.definition.steps = []
jest.clearAllMocks()
- // check the post request honours updates with same id
- await update(automation)
+ await config.api.automation.update(automation)
- // events
- expect(events.automation.stepDeleted).toHaveBeenCalledTimes(2)
+ expect(events.automation.stepDeleted).toHaveBeenCalledTimes(1)
expect(events.automation.stepCreated).not.toHaveBeenCalled()
expect(events.automation.created).not.toHaveBeenCalled()
expect(events.automation.triggerUpdated).not.toHaveBeenCalled()
})
it("adds and removes automation steps", async () => {
- let automation = newAutomation()
- automation = await config.createAutomation(automation)
+ const { automation } = await config.api.automation.post(newAutomation())
automation.definition.steps = [automationStep(), automationStep()]
jest.clearAllMocks()
- // check the post request honours updates with same id
- await update(automation)
+ await config.api.automation.update(automation)
- // events
expect(events.automation.stepCreated).toHaveBeenCalledTimes(2)
expect(events.automation.stepDeleted).toHaveBeenCalledTimes(1)
expect(events.automation.created).not.toHaveBeenCalled()
@@ -551,16 +507,24 @@ describe("/automations", () => {
describe("fetch", () => {
it("return all the automations for an instance", async () => {
- await clearAllAutomations(config)
- const autoConfig = await config.createAutomation(basicAutomation())
- const res = await request
- .get(`/api/automations`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
+ const fetchResponse = await config.api.automation.fetch()
+ for (const auto of fetchResponse.automations) {
+ await config.api.automation.delete(auto)
+ }
- expect(res.body.automations[0]).toEqual(
- expect.objectContaining(autoConfig)
+ const { automation: automation1 } = await config.api.automation.post(
+ newAutomation()
+ )
+ const { automation: automation2 } = await config.api.automation.post(
+ newAutomation()
+ )
+ const { automation: automation3 } = await config.api.automation.post(
+ newAutomation()
+ )
+
+ const { automations } = await config.api.automation.fetch()
+ expect(automations).toEqual(
+ expect.arrayContaining([automation1, automation2, automation3])
)
})
@@ -575,29 +539,25 @@ describe("/automations", () => {
describe("destroy", () => {
it("deletes a automation by its ID", async () => {
- const automation = await config.createAutomation()
- const res = await request
- .delete(`/api/automations/${automation._id}/${automation._rev}`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
+ const { automation } = await config.api.automation.post(newAutomation())
+ const { id } = await config.api.automation.delete(automation)
- expect(res.body.id).toEqual(automation._id)
+ expect(id).toEqual(automation._id)
expect(events.automation.deleted).toHaveBeenCalledTimes(1)
})
it("cannot delete a row action automation", async () => {
- const automation = await config.createAutomation(
+ const { automation } = await config.api.automation.post(
setup.structures.rowActionAutomation()
)
- await request
- .delete(`/api/automations/${automation._id}/${automation._rev}`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(422, {
+
+ await config.api.automation.delete(automation, {
+ status: 422,
+ body: {
message: "Row actions automations cannot be deleted",
status: 422,
- })
+ },
+ })
expect(events.automation.deleted).not.toHaveBeenCalled()
})
@@ -614,10 +574,19 @@ describe("/automations", () => {
describe("checkForCollectStep", () => {
it("should return true if a collect step exists in an automation", async () => {
- let automation = collectAutomation()
- await config.createAutomation(automation)
- let res = await sdk.automations.utils.checkForCollectStep(automation)
- expect(res).toEqual(true)
+ const { automation } = await config.api.automation.post(
+ collectAutomation()
+ )
+ expect(sdk.automations.utils.checkForCollectStep(automation)).toEqual(
+ true
+ )
+ })
+
+ it("should return false if a collect step does not exist in an automation", async () => {
+ const { automation } = await config.api.automation.post(newAutomation())
+ expect(sdk.automations.utils.checkForCollectStep(automation)).toEqual(
+ false
+ )
})
})
@@ -628,28 +597,45 @@ describe("/automations", () => {
])(
"triggers an update row automation and compares new to old rows with old city '%s' and new city '%s'",
async ({ oldCity, newCity }) => {
- const expectedResult = oldCity === newCity
+ let table = await config.api.table.save(basicTable())
- let table = await config.createTable()
+ const { automation } = await config.api.automation.post(
+ filterAutomation({
+ definition: {
+ trigger: {
+ inputs: {
+ tableId: table._id,
+ },
+ },
+ steps: [
+ {
+ inputs: {
+ condition: FilterConditions.EQUAL,
+ field: "{{ trigger.row.City }}",
+ value: "{{ trigger.oldRow.City }}",
+ },
+ },
+ ],
+ },
+ })
+ )
- let automation = await filterAutomation(config.getAppId())
- automation.definition.trigger.inputs.tableId = table._id
- automation.definition.steps[0].inputs = {
- condition: FilterConditions.EQUAL,
- field: "{{ trigger.row.City }}",
- value: "{{ trigger.oldRow.City }}",
- }
- automation = await config.createAutomation(automation)
- let triggerInputs = {
+ const res = await config.api.automation.test(automation._id!, {
+ fields: {},
oldRow: {
City: oldCity,
},
row: {
City: newCity,
},
+ })
+
+ if (isDidNotTriggerResponse(res)) {
+ throw new Error("Automation did not trigger")
}
- const res = await testAutomation(config, automation, triggerInputs)
- expect(res.body.steps[1].outputs.result).toEqual(expectedResult)
+
+ const expectedResult = oldCity === newCity
+ expect(res.steps[1].outputs.result).toEqual(expectedResult)
}
)
})
@@ -657,16 +643,18 @@ describe("/automations", () => {
let table: Table
beforeAll(async () => {
- table = await config.createTable({
- name: "table",
- type: "table",
- schema: {
- Approved: {
- name: "Approved",
- type: FieldType.BOOLEAN,
+ table = await config.api.table.save(
+ basicTable(undefined, {
+ name: "table",
+ type: "table",
+ schema: {
+ Approved: {
+ name: "Approved",
+ type: FieldType.BOOLEAN,
+ },
},
- },
- })
+ })
+ )
})
const testCases = [
@@ -712,33 +700,29 @@ describe("/automations", () => {
it.each(testCases)(
"$description",
async ({ filters, row, oldRow, expectToRun }) => {
- let automation = await updateRowAutomationWithFilters(
- config.getAppId(),
- table._id!
- )
- automation.definition.trigger.inputs = {
+ let req = updateRowAutomationWithFilters(config.getAppId(), table._id!)
+ req.definition.trigger.inputs = {
tableId: table._id,
filters,
}
- automation = await config.createAutomation(automation)
- const inputs = {
- row: {
- tableId: table._id,
- ...row,
- },
+ const { automation } = await config.api.automation.post(req)
+ const res = await config.api.automation.test(automation._id!, {
+ fields: {},
oldRow: {
tableId: table._id,
...oldRow,
},
- }
+ row: {
+ tableId: table._id,
+ ...row,
+ },
+ })
- const res = await testAutomation(config, automation, inputs)
-
- if (expectToRun) {
- expect(res.body.steps[1].outputs.success).toEqual(true)
+ if (isDidNotTriggerResponse(res)) {
+ expect(expectToRun).toEqual(false)
} else {
- expect(res.body.outputs.success).toEqual(false)
+ expect(res.steps[1].outputs.success).toEqual(expectToRun)
}
}
)
diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.ts b/packages/server/src/api/routes/tests/utilities/TestFunctions.ts
index 9d5417d041..a232fec859 100644
--- a/packages/server/src/api/routes/tests/utilities/TestFunctions.ts
+++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.ts
@@ -53,15 +53,6 @@ export const clearAllApps = async (
})
}
-export const clearAllAutomations = async (config: TestConfiguration) => {
- const { automations } = await config.getAllAutomations()
- for (let auto of automations) {
- await context.doInAppContext(config.getAppId(), async () => {
- await config.deleteAutomation(auto)
- })
- }
-}
-
export const wipeDb = async () => {
const couchInfo = db.getCouchInfo()
const nano = Nano({
diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts
index 2d36e7855b..1f464b2ea4 100644
--- a/packages/server/src/tests/utilities/TestConfiguration.ts
+++ b/packages/server/src/tests/utilities/TestConfiguration.ts
@@ -258,7 +258,7 @@ export default class TestConfiguration {
}
}
- async withApp(app: App | string, f: () => Promise) {
+ async withApp(app: App | string, f: () => Promise) {
const oldAppId = this.appId
this.appId = typeof app === "string" ? app : app.appId
try {
@@ -268,6 +268,10 @@ export default class TestConfiguration {
}
}
+ async withProdApp(f: () => Promise) {
+ return await this.withApp(this.getProdAppId(), f)
+ }
+
// UTILS
_req | void, Res>(
diff --git a/packages/server/src/tests/utilities/api/automation.ts b/packages/server/src/tests/utilities/api/automation.ts
index 3f51385251..c4438560ae 100644
--- a/packages/server/src/tests/utilities/api/automation.ts
+++ b/packages/server/src/tests/utilities/api/automation.ts
@@ -1,8 +1,17 @@
import {
Automation,
+ CreateAutomationResponse,
+ DeleteAutomationResponse,
FetchAutomationResponse,
+ GetAutomationActionDefinitionsResponse,
+ GetAutomationStepDefinitionsResponse,
+ GetAutomationTriggerDefinitionsResponse,
TestAutomationRequest,
TestAutomationResponse,
+ TriggerAutomationRequest,
+ TriggerAutomationResponse,
+ UpdateAutomationRequest,
+ UpdateAutomationResponse,
} from "@budibase/types"
import { Expectations, TestAPI } from "./base"
@@ -20,6 +29,39 @@ export class AutomationAPI extends TestAPI {
return result
}
+ getActions = async (
+ expectations?: Expectations
+ ): Promise => {
+ return await this._get(
+ `/api/automations/actions/list`,
+ {
+ expectations,
+ }
+ )
+ }
+
+ getTriggers = async (
+ expectations?: Expectations
+ ): Promise => {
+ return await this._get(
+ `/api/automations/triggers/list`,
+ {
+ expectations,
+ }
+ )
+ }
+
+ getDefinitions = async (
+ expectations?: Expectations
+ ): Promise => {
+ return await this._get(
+ `/api/automations/definitions/list`,
+ {
+ expectations,
+ }
+ )
+ }
+
fetch = async (
expectations?: Expectations
): Promise => {
@@ -31,11 +73,14 @@ export class AutomationAPI extends TestAPI {
post = async (
body: Automation,
expectations?: Expectations
- ): Promise => {
- const result = await this._post(`/api/automations`, {
- body,
- expectations,
- })
+ ): Promise => {
+ const result = await this._post(
+ `/api/automations`,
+ {
+ body,
+ expectations,
+ }
+ )
return result
}
@@ -52,4 +97,40 @@ export class AutomationAPI extends TestAPI {
}
)
}
+
+ trigger = async (
+ id: string,
+ body: TriggerAutomationRequest,
+ expectations?: Expectations
+ ): Promise => {
+ return await this._post(
+ `/api/automations/${id}/trigger`,
+ {
+ expectations,
+ body,
+ }
+ )
+ }
+
+ update = async (
+ body: UpdateAutomationRequest,
+ expectations?: Expectations
+ ): Promise => {
+ return await this._put(`/api/automations`, {
+ body,
+ expectations,
+ })
+ }
+
+ delete = async (
+ automation: Automation,
+ expectations?: Expectations
+ ): Promise => {
+ return await this._delete(
+ `/api/automations/${automation._id!}/${automation._rev!}`,
+ {
+ expectations,
+ }
+ )
+ }
}
diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts
index c5eede18d6..2fdf726b6c 100644
--- a/packages/server/src/tests/utilities/api/index.ts
+++ b/packages/server/src/tests/utilities/api/index.ts
@@ -19,43 +19,43 @@ import { PluginAPI } from "./plugin"
import { WebhookAPI } from "./webhook"
export default class API {
- table: TableAPI
- legacyView: LegacyViewAPI
- viewV2: ViewV2API
- row: RowAPI
- permission: PermissionAPI
- datasource: DatasourceAPI
- screen: ScreenAPI
application: ApplicationAPI
- backup: BackupAPI
attachment: AttachmentAPI
- user: UserAPI
+ automation: AutomationAPI
+ backup: BackupAPI
+ datasource: DatasourceAPI
+ legacyView: LegacyViewAPI
+ permission: PermissionAPI
+ plugin: PluginAPI
query: QueryAPI
roles: RoleAPI
- templates: TemplateAPI
+ row: RowAPI
rowAction: RowActionAPI
- automation: AutomationAPI
- plugin: PluginAPI
+ screen: ScreenAPI
+ table: TableAPI
+ templates: TemplateAPI
+ user: UserAPI
+ viewV2: ViewV2API
webhook: WebhookAPI
constructor(config: TestConfiguration) {
- this.table = new TableAPI(config)
- this.legacyView = new LegacyViewAPI(config)
- this.viewV2 = new ViewV2API(config)
- this.row = new RowAPI(config)
- this.permission = new PermissionAPI(config)
- this.datasource = new DatasourceAPI(config)
- this.screen = new ScreenAPI(config)
this.application = new ApplicationAPI(config)
- this.backup = new BackupAPI(config)
this.attachment = new AttachmentAPI(config)
- this.user = new UserAPI(config)
+ this.automation = new AutomationAPI(config)
+ this.backup = new BackupAPI(config)
+ this.datasource = new DatasourceAPI(config)
+ this.legacyView = new LegacyViewAPI(config)
+ this.permission = new PermissionAPI(config)
+ this.plugin = new PluginAPI(config)
this.query = new QueryAPI(config)
this.roles = new RoleAPI(config)
- this.templates = new TemplateAPI(config)
+ this.row = new RowAPI(config)
this.rowAction = new RowActionAPI(config)
- this.automation = new AutomationAPI(config)
- this.plugin = new PluginAPI(config)
+ this.screen = new ScreenAPI(config)
+ this.table = new TableAPI(config)
+ this.templates = new TemplateAPI(config)
+ this.user = new UserAPI(config)
+ this.viewV2 = new ViewV2API(config)
this.webhook = new WebhookAPI(config)
}
}
diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts
index 3058a706c1..0c74a0faa2 100644
--- a/packages/server/src/tests/utilities/structures.ts
+++ b/packages/server/src/tests/utilities/structures.ts
@@ -34,6 +34,7 @@ import {
Webhook,
WebhookActionType,
BuiltinPermissionID,
+ DeepPartial,
} from "@budibase/types"
import { LoopInput } from "../../definitions/automations"
import { merge } from "lodash"
@@ -184,21 +185,12 @@ export function newAutomation({
steps,
trigger,
}: { steps?: AutomationStep[]; trigger?: AutomationTrigger } = {}) {
- const automation = basicAutomation()
-
- if (trigger) {
- automation.definition.trigger = trigger
- } else {
- automation.definition.trigger = automationTrigger()
- }
-
- if (steps) {
- automation.definition.steps = steps
- } else {
- automation.definition.steps = [automationStep()]
- }
-
- return automation
+ return basicAutomation({
+ definition: {
+ steps: steps || [automationStep()],
+ trigger: trigger || automationTrigger(),
+ },
+ })
}
export function rowActionAutomation() {
@@ -211,8 +203,8 @@ export function rowActionAutomation() {
return automation
}
-export function basicAutomation(appId?: string): Automation {
- return {
+export function basicAutomation(opts?: DeepPartial): Automation {
+ const baseAutomation: Automation = {
name: "My Automation",
screenId: "kasdkfldsafkl",
live: true,
@@ -241,8 +233,9 @@ export function basicAutomation(appId?: string): Automation {
steps: [],
},
type: "automation",
- appId: appId!,
+ appId: "appId",
}
+ return merge(baseAutomation, opts)
}
export function basicCronAutomation(appId: string, cron: string): Automation {
@@ -387,16 +380,21 @@ export function loopAutomation(
return automation as Automation
}
-export function collectAutomation(tableId?: string): Automation {
- const automation: any = {
+export function collectAutomation(opts?: DeepPartial): Automation {
+ const baseAutomation: Automation = {
+ appId: "appId",
name: "looping",
type: "automation",
definition: {
steps: [
{
id: "b",
- type: "ACTION",
+ name: "b",
+ tagline: "An automation action step",
+ icon: "Icon",
+ type: AutomationStepType.ACTION,
internal: true,
+ description: "Execute script",
stepId: AutomationActionStepId.EXECUTE_SCRIPT,
inputs: {
code: "return [1,2,3]",
@@ -405,8 +403,12 @@ export function collectAutomation(tableId?: string): Automation {
},
{
id: "c",
- type: "ACTION",
+ name: "c",
+ type: AutomationStepType.ACTION,
+ tagline: "An automation action step",
+ icon: "Icon",
internal: true,
+ description: "Collect",
stepId: AutomationActionStepId.COLLECT,
inputs: {
collection: "{{ literal steps.1.value }}",
@@ -416,24 +418,28 @@ export function collectAutomation(tableId?: string): Automation {
],
trigger: {
id: "a",
- type: "TRIGGER",
+ type: AutomationStepType.TRIGGER,
event: AutomationEventType.ROW_SAVE,
stepId: AutomationTriggerStepId.ROW_SAVED,
+ name: "trigger Step",
+ tagline: "An automation trigger",
+ description: "A trigger",
+ icon: "Icon",
inputs: {
- tableId,
+ tableId: "tableId",
},
schema: TRIGGER_DEFINITIONS.ROW_SAVED.schema,
},
},
}
- return automation
+ return merge(baseAutomation, opts)
}
-export function filterAutomation(appId: string, tableId?: string): Automation {
+export function filterAutomation(opts?: DeepPartial): Automation {
const automation: Automation = {
name: "looping",
type: "automation",
- appId,
+ appId: "appId",
definition: {
steps: [
{
@@ -459,13 +465,13 @@ export function filterAutomation(appId: string, tableId?: string): Automation {
event: AutomationEventType.ROW_SAVE,
stepId: AutomationTriggerStepId.ROW_SAVED,
inputs: {
- tableId: tableId!,
+ tableId: "tableId",
},
schema: TRIGGER_DEFINITIONS.ROW_SAVED.schema,
},
},
}
- return automation
+ return merge(automation, opts)
}
export function updateRowAutomationWithFilters(
diff --git a/packages/types/src/api/web/app/automation.ts b/packages/types/src/api/web/app/automation.ts
index b97dee0baf..f72966d100 100644
--- a/packages/types/src/api/web/app/automation.ts
+++ b/packages/types/src/api/web/app/automation.ts
@@ -75,6 +75,7 @@ export interface TestAutomationRequest {
revision?: string
fields: Record
row?: Row
+ oldRow?: Row
}
export type TestAutomationResponse = AutomationResults | DidNotTriggerResponse