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'
+ )
+ })
})