From 9a2de11203b8cfb28f465b007b23663564ce1b1d Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Fri, 7 Jun 2024 09:35:18 +0100 Subject: [PATCH] Allow users to only specify a binding when adding attachments (#13819) * add ability for user to toggle bindable input for attachment * error handling for missing keys * improve error handling for smtp attachments * remove log * add test * fixing some pr comments * update test --- .../SetupPanel/AutomationBlockSetup.svelte | 87 +++++++++--- .../automation/SetupPanel/RowSelector.svelte | 25 ++-- .../SetupPanel/RowSelectorTypes.svelte | 133 ++++++++++++------ .../server/src/automations/automationUtils.ts | 16 ++- .../server/src/automations/steps/createRow.ts | 1 - .../src/automations/steps/sendSmtpEmail.ts | 8 ++ .../src/automations/tests/createRow.spec.ts | 27 ++++ 7 files changed, 226 insertions(+), 71 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 85ae1924d0..5d2e4ee28d 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -15,6 +15,7 @@ Checkbox, DatePicker, DrawerContent, + Toggle, } from "@budibase/bbui" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import { automationStore, selectedAutomation, tables } from "stores/builder" @@ -118,7 +119,6 @@ searchableSchema: true, }).schema } - try { if (isTestModal) { let newTestData = { schema } @@ -385,6 +385,16 @@ return params } + function toggleAttachmentBinding(e, key) { + onChange( + { + detail: "", + }, + key + ) + onChange({ detail: { useAttachmentBinding: e.detail } }, "meta") + } + onMount(async () => { try { await environment.loadVariables() @@ -462,27 +472,64 @@
-
- - onChange( - { - detail: e.detail.map(({ name, value }) => ({ - url: name, - filename: value, - })), - }, - key - )} - object={handleAttachmentParams(inputData[key])} - allowJS - {bindings} - keyBindings - customButtonText={"Add attachment"} - keyPlaceholder={"URL"} - valuePlaceholder={"Filename"} +
+ toggleAttachmentBinding(e, key)} />
+ +
+ {#if !inputData?.meta?.useAttachmentBinding} + + onChange( + { + detail: e.detail.map(({ name, value }) => ({ + url: name, + filename: value, + })), + }, + key + )} + object={handleAttachmentParams(inputData[key])} + allowJS + {bindings} + keyBindings + customButtonText={"Add attachment"} + keyPlaceholder={"URL"} + valuePlaceholder={"Filename"} + /> + {:else if isTestModal} + onChange(e, key)} + {bindings} + updateOnChange={false} + /> + {:else} +
+ onChange(e, key)} + {bindings} + updateOnChange={false} + placeholder={value.customType === "queryLimit" + ? queryLimit + : ""} + drawerLeft="260px" + /> +
+ {/if} +
{:else if value.customType === "filters"} Define filters diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index b5a54138ca..bd3bcda774 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -10,12 +10,12 @@ import { TableNames } from "constants" const dispatch = createEventDispatcher() - export let value export let meta export let bindings export let isTestModal export let isUpdateRow + $: parsedBindings = bindings.map(binding => { let clone = Object.assign({}, binding) clone.icon = "ShareAndroid" @@ -94,17 +94,22 @@ dispatch("change", newValue) } - const onChangeSetting = (e, field) => { - let fields = {} - fields[field] = { - clearRelationships: e.detail, + const onChangeSetting = (field, key, value) => { + let newField = {} + newField[field] = { + [key]: value, } + + let updatedFields = { + ...meta?.fields, + ...newField, + } + dispatch("change", { key: "meta", - fields, + fields: updatedFields, }) } - // Ensure any nullish tableId values get set to empty string so // that the select works $: if (value?.tableId == null) value = { tableId: "" } @@ -157,6 +162,9 @@ bindings={parsedBindings} {value} {onChange} + useAttachmentBinding={meta?.fields?.[field] + ?.useAttachmentBinding} + {onChangeSetting} /> {/if} @@ -167,7 +175,8 @@ value={meta.fields?.[field]?.clearRelationships} text={"Clear relationships if empty?"} size={"S"} - on:change={e => onChangeSetting(e, field)} + on:change={e => + onChangeSetting(field, "clearRelationships", e.detail)} /> {/if} diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index 0a27360347..a43ff35c80 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -1,5 +1,11 @@ {#if schemaHasOptions(schema) && schema.type !== "array"} @@ -108,38 +131,65 @@ useLabel={false} /> {:else if attachmentTypes.includes(schema.type)} -
- - onChange( - { - detail: - schema.type === FieldType.ATTACHMENT_SINGLE || - schema.type === FieldType.SIGNATURE_SINGLE - ? e.detail.length > 0 - ? { - url: e.detail[0].name, - filename: e.detail[0].value, - } - : {} - : e.detail.map(({ name, value }) => ({ - url: name, - filename: value, - })), - }, - field - )} - object={handleAttachmentParams(value[field])} - allowJS - {bindings} - keyBindings - customButtonText={"Add attachment"} - keyPlaceholder={"URL"} - valuePlaceholder={"Filename"} - actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE || - schema.type === FieldType.SIGNATURE) && - Object.keys(value[field]).length >= 1} - /> +
+
+ handleToggleChange(field, e)} + /> +
+ {#if !useAttachmentBinding} +
+ { + onChange( + { + detail: + schema.type === FieldType.ATTACHMENT_SINGLE || + schema.type === FieldType.SIGNATURE_SINGLE + ? e.detail.length > 0 + ? { + url: e.detail[0].name, + filename: e.detail[0].value, + } + : {} + : e.detail.map(({ name, value }) => ({ + url: name, + filename: value, + })), + }, + field + ) + }} + object={handleAttachmentParams(value[field])} + allowJS + {bindings} + keyBindings + customButtonText={"Add attachment"} + keyPlaceholder={"URL"} + valuePlaceholder={"Filename"} + actionButtonDisabled={(schema.type === FieldType.ATTACHMENT_SINGLE || + schema.type === FieldType.SIGNATURE) && + Object.keys(value[field]).length >= 1} + /> +
+ {:else} +
+ onChange(e, field)} + type="string" + bindings={parsedBindings} + allowJS={true} + updateOnChange={false} + title={schema.name} + /> +
+ {/if}
{:else if ["string", "number", "bigint", "barcodeqr", "array"].includes(schema.type)} - .attachment-field-spacinng { + .attachment-field-spacing, + .json-input-spacing { margin-top: var(--spacing-s); margin-bottom: var(--spacing-l); } diff --git a/packages/server/src/automations/automationUtils.ts b/packages/server/src/automations/automationUtils.ts index de6e1b3d88..5467e0757c 100644 --- a/packages/server/src/automations/automationUtils.ts +++ b/packages/server/src/automations/automationUtils.ts @@ -99,6 +99,15 @@ export function getError(err: any) { return typeof err !== "string" ? err.toString() : err } +export function guardAttachment(attachmentObject: any) { + if (!("url" in attachmentObject) || !("filename" in attachmentObject)) { + const providedKeys = Object.keys(attachmentObject).join(", ") + throw new Error( + `Attachments must have both "url" and "filename" keys. You have provided: ${providedKeys}` + ) + } +} + export async function sendAutomationAttachmentsToStorage( tableId: string, row: Row @@ -116,9 +125,15 @@ export async function sendAutomationAttachmentsToStorage( schema?.type === FieldType.ATTACHMENT_SINGLE || schema?.type === FieldType.SIGNATURE_SINGLE ) { + if (Array.isArray(value)) { + value.forEach(item => guardAttachment(item)) + } else { + guardAttachment(value) + } attachmentRows[prop] = value } } + for (const [prop, attachments] of Object.entries(attachmentRows)) { if (Array.isArray(attachments)) { if (attachments.length) { @@ -133,7 +148,6 @@ export async function sendAutomationAttachmentsToStorage( return row } - async function generateAttachmentRow(attachment: AutomationAttachment) { const prodAppId = context.getProdAppId() diff --git a/packages/server/src/automations/steps/createRow.ts b/packages/server/src/automations/steps/createRow.ts index 5b5084b465..c7f5fcff3b 100644 --- a/packages/server/src/automations/steps/createRow.ts +++ b/packages/server/src/automations/steps/createRow.ts @@ -90,7 +90,6 @@ export async function run({ inputs, appId, emitter }: AutomationStepInput) { tableId: inputs.row.tableId, }, }) - try { inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row) inputs.row = await sendAutomationAttachmentsToStorage( diff --git a/packages/server/src/automations/steps/sendSmtpEmail.ts b/packages/server/src/automations/steps/sendSmtpEmail.ts index 31a7759dea..bcb1699c6b 100644 --- a/packages/server/src/automations/steps/sendSmtpEmail.ts +++ b/packages/server/src/automations/steps/sendSmtpEmail.ts @@ -118,6 +118,14 @@ export async function run({ inputs }: AutomationStepInput) { } to = to || undefined + if (attachments) { + if (Array.isArray(attachments)) { + attachments.forEach(item => automationUtils.guardAttachment(item)) + } else { + automationUtils.guardAttachment(attachments) + } + } + try { let response = await sendSmtpEmail({ to, diff --git a/packages/server/src/automations/tests/createRow.spec.ts b/packages/server/src/automations/tests/createRow.spec.ts index e78236c5ac..62e9e24f9e 100644 --- a/packages/server/src/automations/tests/createRow.spec.ts +++ b/packages/server/src/automations/tests/createRow.spec.ts @@ -128,4 +128,31 @@ describe("test the create row action", () => { expect(objectData).toBeDefined() expect(objectData.ContentLength).toBeGreaterThan(0) }) + + it("should check that attachment without the correct keys throws an error", async () => { + let attachmentTable = await config.createTable( + basicTableWithAttachmentField() + ) + + let attachmentRow: any = { + tableId: attachmentTable._id, + } + + let filename = "test2.txt" + let presignedUrl = await uploadTestFile(filename) + let attachmentObject = { + wrongKey: presignedUrl, + anotherWrongKey: filename, + } + + attachmentRow.single_file_attachment = attachmentObject + const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row: attachmentRow, + }) + + expect(res.success).toEqual(false) + expect(res.response).toEqual( + 'Error: Attachments must have both "url" and "filename" keys. You have provided: wrongKey, anotherWrongKey' + ) + }) })