diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 8a9d1e59ea0..c6f760bd0ea 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/builder/src/stores/builder/automations.js b/packages/builder/src/stores/builder/automations.js index b2335ff7e52..a2ee4b3cccf 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) }, diff --git a/packages/server/src/sdk/app/automations/crud.ts b/packages/server/src/sdk/app/automations/crud.ts index 90fe6c16c42..59b6b5dc2a0 100644 --- a/packages/server/src/sdk/app/automations/crud.ts +++ b/packages/server/src/sdk/app/automations/crud.ts @@ -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 00000000000..124a5d9276e --- /dev/null +++ b/packages/server/src/sdk/app/automations/tests/index.spec.ts @@ -0,0 +1,55 @@ +import { sample } 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.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 = structures.newAutomation() + + const keyToUse = sample(Object.keys(getStep(automation).inputs))! + getStep(automation).inputs[keyToUse] = "anyValue" + + const response = await automationSdk.create(automation) + + const update = { ...response } + getStep(update).inputs[keyToUse] = "anyUpdatedValue" + const result = await automationSdk.update(update) + expect(getStep(result).inputs[keyToUse]).toEqual("anyUpdatedValue") + }) + }) + + 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 = structures.newAutomation() + getStep(automation).schema.inputs.properties["readonlyProperty"] = { + readonly: true, + } + getStep(automation).inputs["readonlyProperty"] = "anyValue" + + const response = await automationSdk.create(automation) + + const update = { ...response } + getStep(update).inputs["readonlyProperty"] = "anyUpdatedValue" + await expect(automationSdk.update(update)).rejects.toThrow( + "Field readonlyProperty is readonly and it cannot be modified" + ) + }) + }) + }) +}) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 6a6e5670f47..df5625cce3a 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", @@ -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 3407b1a6fd9..f29a2241cd4 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 {