From 6571d61b8634ffe2479bec33ec3e5716667b5792 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 18 Jul 2024 10:41:20 +0200 Subject: [PATCH 01/77] Persist automation id in row action --- packages/types/src/documents/app/rowAction.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/documents/app/rowAction.ts b/packages/types/src/documents/app/rowAction.ts index ea55d5dcd2..84fa0e7f00 100644 --- a/packages/types/src/documents/app/rowAction.ts +++ b/packages/types/src/documents/app/rowAction.ts @@ -6,6 +6,7 @@ export interface TableRowActions extends Document { string, { name: string + automationId: string } > } From faf1d678fa70ae2d22e120d6ff277290d27ccdf1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 18 Jul 2024 12:55:33 +0200 Subject: [PATCH 02/77] Create automation --- packages/server/src/sdk/app/rowActions.ts | 46 ++++++++++++++++++- .../types/src/documents/app/automation.ts | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 8bff216ab9..59c6d9bd40 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -2,10 +2,16 @@ import { context, HTTPError, utils } from "@budibase/backend-core" import { generateRowActionsID } from "../../db/utils" import { + AutomationCustomIOType, + AutomationIOType, + AutomationStepType, + AutomationTriggerStepId, SEPARATOR, TableRowActions, VirtualDocumentType, } from "@budibase/types" +import automations from "./automations" +import tables from "./tables" function ensureUniqueAndThrow( doc: TableRowActions, @@ -39,10 +45,48 @@ export async function create(tableId: string, rowAction: { name: string }) { doc = { _id: rowActionsId, actions: {} } } + const { name: tableName } = await tables.getTable(tableId) + ensureUniqueAndThrow(doc, action.name) + const automation = await automations.create({ + name: `${tableName}: ${action.name}`, + appId: context.getAppId()!, + definition: { + trigger: { + type: AutomationStepType.TRIGGER, + id: "TODO id", + tagline: "TODO tagline", + name: "Row Action", + description: "TODO description", + icon: "Workflow", + stepId: AutomationTriggerStepId.ROW_ACTION, + inputs: { + tableId, + }, + schema: { + inputs: { + properties: { + tableId: { + type: AutomationIOType.STRING, + customType: AutomationCustomIOType.TABLE, + title: "Table", + }, + }, + required: ["tableId"], + }, + outputs: { properties: {} }, + }, + }, + steps: [], + }, + }) + const newId = `${VirtualDocumentType.ROW_ACTION}${SEPARATOR}${utils.newid()}` - doc.actions[newId] = action + doc.actions[newId] = { + name: action.name, + automationId: automation._id!, + } await db.put(doc) return { diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 0432c11851..3407b1a6fd 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -45,6 +45,7 @@ export enum AutomationTriggerStepId { WEBHOOK = "WEBHOOK", APP = "APP", CRON = "CRON", + ROW_ACTION = "ROW_ACTION", } export enum AutomationStepType { From 2970bfc48a59a902389693539cd116f3f727d93a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 18 Jul 2024 17:11:11 +0200 Subject: [PATCH 03/77] Fix update and tests --- .../src/api/routes/tests/rowAction.spec.ts | 31 ++++++++++++++++--- packages/server/src/sdk/app/rowActions.ts | 5 ++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index efdfdd2392..4df9f630c5 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -1,7 +1,11 @@ import _ from "lodash" import tk from "timekeeper" -import { CreateRowActionRequest, RowActionResponse } from "@budibase/types" +import { + CreateRowActionRequest, + DocumentType, + RowActionResponse, +} from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" @@ -90,6 +94,9 @@ describe("/rowsActions", () => { ...rowAction, id: res.id, tableId: tableId, + automationId: expect.stringMatching( + `^${DocumentType.AUTOMATION}_.+` + ), }, }, }) @@ -129,9 +136,24 @@ describe("/rowsActions", () => { expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { - [responses[0].id]: { ...rowActions[0], id: responses[0].id, tableId }, - [responses[1].id]: { ...rowActions[1], id: responses[1].id, tableId }, - [responses[2].id]: { ...rowActions[2], id: responses[2].id, tableId }, + [responses[0].id]: { + ...rowActions[0], + id: responses[0].id, + tableId, + automationId: expect.any(String), + }, + [responses[1].id]: { + ...rowActions[1], + id: responses[1].id, + tableId, + automationId: expect.any(String), + }, + [responses[2].id]: { + ...rowActions[2], + id: responses[2].id, + tableId, + automationId: expect.any(String), + }, }, }) }) @@ -172,6 +194,7 @@ describe("/rowsActions", () => { id: res.id, tableId: tableId, ...rowAction, + automationId: expect.any(String), }, }, }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 59c6d9bd40..80266fe572 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -125,7 +125,10 @@ export async function update( ensureUniqueAndThrow(actionsDoc, action.name, rowActionId) - actionsDoc.actions[rowActionId] = action + actionsDoc.actions[rowActionId] = { + automationId: actionsDoc.actions[rowActionId].automationId, + ...action, + } const db = context.getAppDB() await db.put(actionsDoc) From 4a8f15995c7a5305dae3d3894e802fe218a12ac2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 10:47:02 +0200 Subject: [PATCH 04/77] Type sdk couchdb fields --- packages/server/src/sdk/app/automations/crud.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/server/src/sdk/app/automations/crud.ts b/packages/server/src/sdk/app/automations/crud.ts index c0f3df6f28..90fe6c16c4 100644 --- a/packages/server/src/sdk/app/automations/crud.ts +++ b/packages/server/src/sdk/app/automations/crud.ts @@ -11,6 +11,11 @@ import { import { definitions } from "../../../automations/triggerInfo" import automations from "." +interface PersistedAutomation extends Automation { + _id: string + _rev: string +} + function getDb() { return context.getAppDB() } @@ -71,7 +76,7 @@ async function handleStepEvents( export async function fetch() { const db = getDb() - const response = await db.allDocs( + const response = await db.allDocs( getAutomationParams(null, { include_docs: true, }) @@ -81,7 +86,7 @@ export async function fetch() { export async function get(automationId: string) { const db = getDb() - const result = await db.get(automationId) + const result = await db.get(automationId) return result } From eaa38c5c2d4ee91302b61c9124cd489081e628ea Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 10:56:36 +0200 Subject: [PATCH 05/77] Return automationid from row action api --- .../src/api/controllers/rowAction/crud.ts | 17 +++++-- .../src/api/routes/tests/rowAction.spec.ts | 46 +++++++++---------- packages/server/src/sdk/app/rowActions.ts | 10 ++-- .../src/tests/utilities/api/automation.ts | 17 +++++++ .../server/src/tests/utilities/api/index.ts | 3 ++ packages/types/src/api/web/app/rowAction.ts | 1 + 6 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 packages/server/src/tests/utilities/api/automation.ts diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 640bc35378..bd5326d957 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -31,7 +31,12 @@ export async function find(ctx: Ctx) { actions: Object.entries(actions).reduce>( (acc, [key, action]) => ({ ...acc, - [key]: { id: key, tableId: table._id!, ...action }, + [key]: { + id: key, + tableId: table._id!, + name: action.name, + automationId: action.automationId, + }, }), {} ), @@ -50,7 +55,9 @@ export async function create( ctx.body = { tableId: table._id!, - ...createdAction, + id: createdAction.id, + name: createdAction.name, + automationId: createdAction.automationId, } ctx.status = 201 } @@ -61,13 +68,15 @@ export async function update( const table = await getTable(ctx) const { actionId } = ctx.params - const actions = await sdk.rowActions.update(table._id!, actionId, { + const action = await sdk.rowActions.update(table._id!, actionId, { name: ctx.request.body.name, }) ctx.body = { tableId: table._id!, - ...actions, + id: action.id, + name: action.name, + automationId: action.automationId, } } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 4df9f630c5..a77f6bdd1a 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -9,6 +9,9 @@ import { import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" +const expectAutomationId = () => + expect.stringMatching(`^${DocumentType.AUTOMATION}_.+`) + describe("/rowsActions", () => { const config = setup.getConfig() @@ -83,20 +86,19 @@ describe("/rowsActions", () => { }) expect(res).toEqual({ + name: rowAction.name, id: expect.stringMatching(/^row_action_\w+/), tableId: tableId, - ...rowAction, + automationId: expectAutomationId(), }) expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { [res.id]: { - ...rowAction, + name: rowAction.name, id: res.id, tableId: tableId, - automationId: expect.stringMatching( - `^${DocumentType.AUTOMATION}_.+` - ), + automationId: expectAutomationId(), }, }, }) @@ -104,19 +106,13 @@ describe("/rowsActions", () => { it("trims row action names", async () => { const name = " action name " - const res = await createRowAction( - tableId, - { name }, - { - status: 201, - } - ) + const res = await createRowAction(tableId, { name }, { status: 201 }) - expect(res).toEqual({ - id: expect.stringMatching(/^row_action_\w+/), - tableId: tableId, + expect(res).toEqual( + expect.objectContaining({ name: "action name", }) + ) expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { @@ -137,19 +133,19 @@ describe("/rowsActions", () => { expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { [responses[0].id]: { - ...rowActions[0], + name: rowActions[0].name, id: responses[0].id, tableId, automationId: expect.any(String), }, [responses[1].id]: { - ...rowActions[1], + name: rowActions[1].name, id: responses[1].id, tableId, automationId: expect.any(String), }, [responses[2].id]: { - ...rowActions[2], + name: rowActions[2].name, id: responses[2].id, tableId, automationId: expect.any(String), @@ -174,7 +170,7 @@ describe("/rowsActions", () => { it("ignores not valid row action data", async () => { const rowAction = createRowActionRequest() const dirtyRowAction = { - ...rowAction, + name: rowAction.name, id: generator.guid(), valueToIgnore: generator.string(), } @@ -183,17 +179,18 @@ describe("/rowsActions", () => { }) expect(res).toEqual({ + name: rowAction.name, id: expect.any(String), tableId, - ...rowAction, + automationId: expectAutomationId(), }) expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { [res.id]: { + name: rowAction.name, id: res.id, tableId: tableId, - ...rowAction, automationId: expect.any(String), }, }, @@ -287,7 +284,6 @@ describe("/rowsActions", () => { const updatedName = generator.string() const res = await config.api.rowAction.update(tableId, actionId, { - ...actionData, name: updatedName, }) @@ -295,14 +291,17 @@ describe("/rowsActions", () => { id: actionId, tableId, name: updatedName, + automationId: actionData.automationId, }) expect(await config.api.rowAction.find(tableId)).toEqual( expect.objectContaining({ actions: expect.objectContaining({ [actionId]: { - ...actionData, name: updatedName, + id: actionData.id, + tableId: actionData.tableId, + automationId: actionData.automationId, }, }), }) @@ -319,7 +318,6 @@ describe("/rowsActions", () => { ) const res = await config.api.rowAction.update(tableId, rowAction.id, { - ...rowAction, name: " action name ", }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 80266fe572..dfb6975c48 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -91,7 +91,7 @@ export async function create(tableId: string, rowAction: { name: string }) { return { id: newId, - ...action, + ...doc.actions[newId], } } @@ -135,20 +135,24 @@ export async function update( return { id: rowActionId, - ...action, + ...actionsDoc.actions[rowActionId], } } export async function remove(tableId: string, rowActionId: string) { const actionsDoc = await get(tableId) - if (!actionsDoc.actions[rowActionId]) { + const rowAction = actionsDoc.actions[rowActionId] + if (!rowAction) { throw new HTTPError( `Row action '${rowActionId}' not found in '${tableId}'`, 400 ) } + const { automationId } = rowAction + const automation = await automations.get(automationId) + await automations.remove(automation._id, automation._rev) delete actionsDoc.actions[rowActionId] const db = context.getAppDB() diff --git a/packages/server/src/tests/utilities/api/automation.ts b/packages/server/src/tests/utilities/api/automation.ts new file mode 100644 index 0000000000..9620e2011c --- /dev/null +++ b/packages/server/src/tests/utilities/api/automation.ts @@ -0,0 +1,17 @@ +import { Automation } from "@budibase/types" +import { Expectations, TestAPI } from "./base" + +export class AutomationAPI extends TestAPI { + get = async ( + automationId: string, + expectations?: Expectations + ): Promise => { + const result = await this._get( + `/api/automations/${automationId}`, + { + expectations, + } + ) + return result + } +} diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index a19b68a872..36a6ed0222 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -14,6 +14,7 @@ import { QueryAPI } from "./query" import { RoleAPI } from "./role" import { TemplateAPI } from "./template" import { RowActionAPI } from "./rowAction" +import { AutomationAPI } from "./automation" export default class API { table: TableAPI @@ -31,6 +32,7 @@ export default class API { roles: RoleAPI templates: TemplateAPI rowAction: RowActionAPI + automation: AutomationAPI constructor(config: TestConfiguration) { this.table = new TableAPI(config) @@ -48,5 +50,6 @@ export default class API { this.roles = new RoleAPI(config) this.templates = new TemplateAPI(config) this.rowAction = new RowActionAPI(config) + this.automation = new AutomationAPI(config) } } diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index ba95ba6b95..305c42b473 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -7,6 +7,7 @@ export interface UpdateRowActionRequest extends RowActionData {} export interface RowActionResponse extends RowActionData { id: string tableId: string + automationId: string } export interface RowActionsResponse { From 292c87350aa97358357d3aeb10c48ff12d0c8e5e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 10:58:09 +0200 Subject: [PATCH 06/77] Check automation creation --- .../server/src/api/routes/tests/rowAction.spec.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index a77f6bdd1a..9eadd7f4c8 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -110,8 +110,8 @@ describe("/rowsActions", () => { expect(res).toEqual( expect.objectContaining({ - name: "action name", - }) + name: "action name", + }) ) expect(await config.api.rowAction.find(tableId)).toEqual({ @@ -233,6 +233,17 @@ describe("/rowsActions", () => { await createRowAction(otherTable._id!, { name: action.name }) }) + + it("an automation is created when creating a new row action", async () => { + const action1 = await createRowAction(tableId, createRowActionRequest()) + const action2 = await createRowAction(tableId, createRowActionRequest()) + + for (const automationId of [action1.automationId, action2.automationId]) { + expect( + await config.api.automation.get(automationId, { status: 200 }) + ).toEqual(expect.objectContaining({ _id: automationId })) + } + }) }) describe("find", () => { From 491266c7ba31c4b835344fc1ceccfb41a61c544a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 11:01:09 +0200 Subject: [PATCH 07/77] Add more tests --- .../src/api/routes/tests/rowAction.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 9eadd7f4c8..f9e1c502f4 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -440,5 +440,26 @@ describe("/rowsActions", () => { status: 400, }) }) + + it("deletes the linked automation", async () => { + const actions: RowActionResponse[] = [] + for (const rowAction of createRowActionRequests(3)) { + actions.push(await createRowAction(tableId, rowAction)) + } + + const actionToDelete = _.sample(actions)! + await config.api.rowAction.delete(tableId, actionToDelete.id, { + status: 204, + }) + + await config.api.automation.get(actionToDelete.automationId, { + status: 404, + }) + for (const action of actions.filter(a => a.id !== actionToDelete.id)) { + await config.api.automation.get(action.automationId, { + status: 200, + }) + } + }) }) }) From 67619364b957edcd6bbfd91d23f25ced9f24b023 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 11:15:17 +0200 Subject: [PATCH 08/77] Use expectAutomationId() --- packages/server/src/api/routes/tests/rowAction.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index f9e1c502f4..5e043cb42c 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -136,19 +136,19 @@ describe("/rowsActions", () => { name: rowActions[0].name, id: responses[0].id, tableId, - automationId: expect.any(String), + automationId: expectAutomationId(), }, [responses[1].id]: { name: rowActions[1].name, id: responses[1].id, tableId, - automationId: expect.any(String), + automationId: expectAutomationId(), }, [responses[2].id]: { name: rowActions[2].name, id: responses[2].id, tableId, - automationId: expect.any(String), + automationId: expectAutomationId(), }, }, }) @@ -191,7 +191,7 @@ describe("/rowsActions", () => { name: rowAction.name, id: res.id, tableId: tableId, - automationId: expect.any(String), + automationId: expectAutomationId(), }, }, }) From 1843233168c3ac95f1bc9489b50da301a25268ec Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 11:18:09 +0200 Subject: [PATCH 09/77] Undefined context checks --- packages/server/src/sdk/app/rowActions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index dfb6975c48..6a6e5670f4 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -49,9 +49,14 @@ export async function create(tableId: string, rowAction: { name: string }) { ensureUniqueAndThrow(doc, action.name) + const appId = context.getAppId() + if (!appId) { + throw new Error("Could not get the current appId") + } + const automation = await automations.create({ name: `${tableName}: ${action.name}`, - appId: context.getAppId()!, + appId, definition: { trigger: { type: AutomationStepType.TRIGGER, From b75c8b32e3454076fdb0a2cb2a9803520e95f91c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 18 Jul 2024 13:34:15 +0200 Subject: [PATCH 10/77] Field readonly --- .../automation/SetupPanel/AutomationBlockSetup.svelte | 6 ++++++ packages/server/src/sdk/app/rowActions.ts | 1 + packages/types/src/documents/app/automation.ts | 1 + 3 files changed, 8 insertions(+) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 8a9d1e59ea..c6f760bd0e 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -869,6 +869,7 @@ options={value.enum} getOptionLabel={(x, idx) => value.pretty ? value.pretty[idx] : x} + disabled={value.readonly} /> {:else if value.type === "json"} onChange({ [key]: e.detail })} + readOnly={value.readonly} /> {:else if value.type === "boolean"}
@@ -884,6 +886,7 @@ text={value.title} value={inputData[key]} on:change={e => onChange({ [key]: e.detail })} + disabled={value.readonly} />
{:else if value.type === "date"} @@ -897,6 +900,7 @@ allowJS={true} updateOnChange={false} drawerLeft="260px" + disabled={value.readonly} > onChange({ [key]: e.detail })} value={inputData[key]} options={Object.keys(table?.schema || {})} + disabled={value.readonly} /> {:else if value.type === "attachment" || value.type === "signature_single"}
@@ -1021,6 +1026,7 @@ {isTrigger} value={inputData[key]} on:change={e => onChange({ [key]: e.detail })} + disabled={value.readonly} /> {:else if value.customType === "webhookUrl"} diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 6a6e5670f4..1846ff4659 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -76,6 +76,7 @@ export async function create(tableId: string, rowAction: { name: string }) { type: AutomationIOType.STRING, customType: AutomationCustomIOType.TABLE, title: "Table", + readonly: true, }, }, required: ["tableId"], diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index 3407b1a6fd..f29a2241cd 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -153,6 +153,7 @@ interface BaseIOStructure { [key: string]: BaseIOStructure } required?: string[] + readonly?: true } export interface InputOutputBlock { From a799bc1c716e42cd9ce93aa22e4ad69902987a9d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 15:36:06 +0200 Subject: [PATCH 11/77] Change --- packages/server/src/sdk/app/rowActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 1846ff4659..df5625cce3 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -60,7 +60,7 @@ export async function create(tableId: string, rowAction: { name: string }) { definition: { trigger: { type: AutomationStepType.TRIGGER, - id: "TODO id", + id: "trigger", tagline: "TODO tagline", name: "Row Action", description: "TODO description", From f91ec1de21cc93fbdd82b8aefe63faf7008e2fcd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 15:52:29 +0200 Subject: [PATCH 12/77] Guard readonly and test --- .../server/src/sdk/app/automations/crud.ts | 32 ++++++++++- .../sdk/app/automations/tests/index.spec.ts | 54 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/sdk/app/automations/tests/index.spec.ts diff --git a/packages/server/src/sdk/app/automations/crud.ts b/packages/server/src/sdk/app/automations/crud.ts index 90fe6c16c4..7a2f4c12d0 100644 --- a/packages/server/src/sdk/app/automations/crud.ts +++ b/packages/server/src/sdk/app/automations/crud.ts @@ -11,7 +11,7 @@ import { import { definitions } from "../../../automations/triggerInfo" import automations from "." -interface PersistedAutomation extends Automation { +type PersistedAutomation = Automation & { _id: string _rev: string } @@ -125,6 +125,9 @@ export async function update(automation: Automation) { const db = getDb() const oldAutomation = await db.get(automation._id) + + guardInvalidUpdatesAndThrow(automation, oldAutomation) + automation = cleanAutomationInputs(automation) automation = await checkForWebhooks({ oldAuto: oldAutomation, @@ -251,3 +254,30 @@ async function checkForWebhooks({ oldAuto, newAuto }: any) { } return newAuto } +function guardInvalidUpdatesAndThrow( + automation: Automation, + oldAutomation: Automation +) { + const stepDefinitions = [ + automation.definition.trigger, + ...automation.definition.steps, + ] + const oldStepDefinitions = [ + oldAutomation.definition.trigger, + ...oldAutomation.definition.steps, + ] + for (const step of stepDefinitions) { + const readonlyFields = Object.keys( + step.schema.inputs.properties || {} + ).filter(k => step.schema.inputs.properties[k].readonly) + readonlyFields.forEach(readonlyField => { + const oldStep = oldStepDefinitions.find(i => i.id === step.id) + if (step.inputs[readonlyField] !== oldStep?.inputs[readonlyField]) { + throw new HTTPError( + `Field ${readonlyField} is readonly and it cannot be modified`, + 400 + ) + } + }) + } +} diff --git a/packages/server/src/sdk/app/automations/tests/index.spec.ts b/packages/server/src/sdk/app/automations/tests/index.spec.ts new file mode 100644 index 0000000000..295ab690d1 --- /dev/null +++ b/packages/server/src/sdk/app/automations/tests/index.spec.ts @@ -0,0 +1,54 @@ +import _ from "lodash/fp" +import { Automation } from "@budibase/types" +import automationSdk from "../" +import { structures } from "../../../../api/routes/tests/utilities" +import TestConfiguration from "../../../../tests/utilities/TestConfiguration" + +describe("automation sdk", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.init() + }) + + describe("update", () => { + it("can update input fields", async () => { + await config.doInContext(config.getAppId(), async () => { + const automation: Automation = structures.newAutomation() + const keyToUse = _.sample( + Object.keys(automation.definition.trigger.inputs) + )! + automation.definition.trigger.inputs[keyToUse] = "anyValue" + + const response = await automationSdk.create(automation) + + const update = { ...response } + update.definition.trigger.inputs[keyToUse] = "anyUpdatedValue" + const result = await automationSdk.update(update) + expect(result.definition.trigger.inputs[keyToUse]).toEqual( + "anyUpdatedValue" + ) + }) + }) + + it("cannot update readonly fields", async () => { + await config.doInContext(config.getAppId(), async () => { + const automation: Automation = { ...structures.newAutomation() } + automation.definition.trigger.schema.inputs.properties[ + "readonlyProperty" + ] = { + readonly: true, + } + automation.definition.trigger.inputs["readonlyProperty"] = "anyValue" + + const response = await automationSdk.create(automation) + + const update = { ...response } + update.definition.trigger.inputs["readonlyProperty"] = "anyUpdatedValue" + await expect(automationSdk.update(update)).rejects.toThrow( + "Field readonlyProperty is readonly and it cannot be modified" + ) + }) + }) + }) +}) From 08a6ac39190fbc9e529aa1ce89d2e1f99855b4b7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 16:15:31 +0200 Subject: [PATCH 13/77] Add extra tests --- .../sdk/app/automations/tests/index.spec.ts | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/server/src/sdk/app/automations/tests/index.spec.ts b/packages/server/src/sdk/app/automations/tests/index.spec.ts index 295ab690d1..124a5d9276 100644 --- a/packages/server/src/sdk/app/automations/tests/index.spec.ts +++ b/packages/server/src/sdk/app/automations/tests/index.spec.ts @@ -1,4 +1,4 @@ -import _ from "lodash/fp" +import { sample } from "lodash/fp" import { Automation } from "@budibase/types" import automationSdk from "../" import { structures } from "../../../../api/routes/tests/utilities" @@ -12,39 +12,40 @@ describe("automation sdk", () => { }) describe("update", () => { - it("can update input fields", async () => { + it.each([ + ["trigger", (a: Automation) => a.definition.trigger], + ["step", (a: Automation) => a.definition.steps[0]], + ])("can update input fields (for a %s)", async (_, getStep) => { await config.doInContext(config.getAppId(), async () => { - const automation: Automation = structures.newAutomation() - const keyToUse = _.sample( - Object.keys(automation.definition.trigger.inputs) - )! - automation.definition.trigger.inputs[keyToUse] = "anyValue" + const automation = structures.newAutomation() + + const keyToUse = sample(Object.keys(getStep(automation).inputs))! + getStep(automation).inputs[keyToUse] = "anyValue" const response = await automationSdk.create(automation) const update = { ...response } - update.definition.trigger.inputs[keyToUse] = "anyUpdatedValue" + getStep(update).inputs[keyToUse] = "anyUpdatedValue" const result = await automationSdk.update(update) - expect(result.definition.trigger.inputs[keyToUse]).toEqual( - "anyUpdatedValue" - ) + expect(getStep(result).inputs[keyToUse]).toEqual("anyUpdatedValue") }) }) - it("cannot update readonly fields", async () => { + it.each([ + ["trigger", (a: Automation) => a.definition.trigger], + ["step", (a: Automation) => a.definition.steps[0]], + ])("cannot update readonly fields (for a %s)", async (_, getStep) => { await config.doInContext(config.getAppId(), async () => { - const automation: Automation = { ...structures.newAutomation() } - automation.definition.trigger.schema.inputs.properties[ - "readonlyProperty" - ] = { + const automation = structures.newAutomation() + getStep(automation).schema.inputs.properties["readonlyProperty"] = { readonly: true, } - automation.definition.trigger.inputs["readonlyProperty"] = "anyValue" + getStep(automation).inputs["readonlyProperty"] = "anyValue" const response = await automationSdk.create(automation) const update = { ...response } - update.definition.trigger.inputs["readonlyProperty"] = "anyUpdatedValue" + getStep(update).inputs["readonlyProperty"] = "anyUpdatedValue" await expect(automationSdk.update(update)).rejects.toThrow( "Field readonlyProperty is readonly and it cannot be modified" ) From 6c67aaf3a0790fc30d43aded3ee1d32eeb5b2669 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 16:39:04 +0200 Subject: [PATCH 14/77] Undo --- packages/server/src/sdk/app/automations/crud.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/sdk/app/automations/crud.ts b/packages/server/src/sdk/app/automations/crud.ts index 7a2f4c12d0..59b6b5dc2a 100644 --- a/packages/server/src/sdk/app/automations/crud.ts +++ b/packages/server/src/sdk/app/automations/crud.ts @@ -11,7 +11,7 @@ import { import { definitions } from "../../../automations/triggerInfo" import automations from "." -type PersistedAutomation = Automation & { +interface PersistedAutomation extends Automation { _id: string _rev: string } From b9443906dcb48ec31419e98256dc97ce3add9507 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 19 Jul 2024 16:49:30 +0200 Subject: [PATCH 15/77] Fix --- packages/builder/src/stores/builder/automations.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/automations.js b/packages/builder/src/stores/builder/automations.js index b2335ff7e5..a2ee4b3ccc 100644 --- a/packages/builder/src/stores/builder/automations.js +++ b/packages/builder/src/stores/builder/automations.js @@ -308,7 +308,9 @@ const automationActions = store => ({ if (!automation) { return } - delete newAutomation.definition.stepNames[blockId] + if (newAutomation.definition.stepNames) { + delete newAutomation.definition.stepNames[blockId] + } await store.actions.save(newAutomation) }, From e0d3855945660bd50173fe23e405f48cb5621362 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 18 Jul 2024 16:28:15 +0200 Subject: [PATCH 16/77] Display row action trigger info --- .../AutomationBuilder/FlowChart/FlowItem.svelte | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 811909845a..503cc39187 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -14,7 +14,9 @@ notifications, Label, AbsTooltip, + InlineAlert, } from "@budibase/bbui" + import { AutomationTriggerStepId } from "@budibase/types" import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import ActionModal from "./ActionModal.svelte" @@ -49,6 +51,8 @@ $: isAppAction && setPermissions(role) $: isAppAction && getPermissions(automationId) + $: isRowAction = block?.stepId === AutomationTriggerStepId.ROW_ACTION + async function setPermissions(role) { if (!role || !automationId) { return @@ -183,6 +187,12 @@ {block} {webhookModal} /> + {#if isRowAction && isTrigger} + + {/if} {#if lastStep}