From 8d700655ba66aae7bc4397d1eabd06741fc7b5cf Mon Sep 17 00:00:00 2001 From: Duarte Velez Grilo <duartegrilo@gmail.com> Date: Wed, 18 Oct 2023 16:06:51 +0100 Subject: [PATCH 01/47] Update manifest.json Added used camera preference option. --- packages/client/manifest.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 8d0a4e456f..f294dbbc80 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3419,6 +3419,22 @@ "value": "custom" } }, + { + "type": "select", + "label": "Preferred camera", + "key": "preferredCamera", + "defaultValue": "environment", + "options": [ + { + "label": "Front", + "value": "user" + }, + { + "label": "Back", + "value": "environment" + } + ] + }, { "type": "event", "label": "On change", From 5e4821e6a5e22473e869bf71f2499649ae453d26 Mon Sep 17 00:00:00 2001 From: Duarte Velez Grilo <duartegrilo@gmail.com> Date: Wed, 18 Oct 2023 16:12:12 +0100 Subject: [PATCH 02/47] Update CodeScannerField.svelte Added preferred camera setting. --- .../client/src/components/app/forms/CodeScannerField.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/src/components/app/forms/CodeScannerField.svelte b/packages/client/src/components/app/forms/CodeScannerField.svelte index c408f78d7c..1a57d8fbe2 100644 --- a/packages/client/src/components/app/forms/CodeScannerField.svelte +++ b/packages/client/src/components/app/forms/CodeScannerField.svelte @@ -14,6 +14,7 @@ export let beepOnScan export let beepFrequency export let customFrequency + export let preferredCamera let fieldState let fieldApi @@ -48,6 +49,7 @@ {beepOnScan} {beepFrequency} {customFrequency} + {preferredCamera} /> {/if} </Field> From ad3a80c629e4748bc1f27bb4193f8912100b9d33 Mon Sep 17 00:00:00 2001 From: Duarte Velez Grilo <duartegrilo@gmail.com> Date: Wed, 18 Oct 2023 16:13:50 +0100 Subject: [PATCH 03/47] Update CodeScanner.svelte Added preferred camera setting. --- packages/client/src/components/app/forms/CodeScanner.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/app/forms/CodeScanner.svelte b/packages/client/src/components/app/forms/CodeScanner.svelte index 04d6919157..891154939f 100644 --- a/packages/client/src/components/app/forms/CodeScanner.svelte +++ b/packages/client/src/components/app/forms/CodeScanner.svelte @@ -11,6 +11,7 @@ export let beepOnScan = false export let beepFrequency = 2637 export let customFrequency = 1046 + export let preferredCamera = "environment" const dispatch = createEventDispatcher() @@ -20,7 +21,7 @@ let cameraEnabled let cameraStarted = false let html5QrCode - let cameraSetting = { facingMode: "environment" } + let cameraSetting = { facingMode: "{preferredCamera}" } let cameraConfig = { fps: 25, qrbox: { width: 250, height: 250 }, From 92e091818b20113ecc332da17d227ca0247e1217 Mon Sep 17 00:00:00 2001 From: Duarte Velez Grilo <duartegrilo@gmail.com> Date: Thu, 19 Oct 2023 11:11:10 +0100 Subject: [PATCH 04/47] Update CodeScanner.svelte Removed quotes ("") from variable reference. I believe this is what is causing the test failure. --- packages/client/src/components/app/forms/CodeScanner.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/app/forms/CodeScanner.svelte b/packages/client/src/components/app/forms/CodeScanner.svelte index 891154939f..f68f0791f6 100644 --- a/packages/client/src/components/app/forms/CodeScanner.svelte +++ b/packages/client/src/components/app/forms/CodeScanner.svelte @@ -21,7 +21,7 @@ let cameraEnabled let cameraStarted = false let html5QrCode - let cameraSetting = { facingMode: "{preferredCamera}" } + let cameraSetting = { facingMode: {preferredCamera} } let cameraConfig = { fps: 25, qrbox: { width: 250, height: 250 }, From 02fb5f3865c9ebb453a5889cd0c459139b98140a Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Mon, 4 Sep 2023 08:14:31 +0100 Subject: [PATCH 05/47] Add prototype of form block field layout --- packages/client/manifest.json | 364 ++++++++++++++++++ .../app/blocks/form/InnerFormBlock.svelte | 35 +- .../app/forms/AttachmentField.svelte | 2 + .../components/app/forms/DateTimeField.svelte | 2 + .../src/components/app/forms/Field.svelte | 79 ++-- .../app/forms/MultiFieldSelect.svelte | 2 + .../components/app/forms/OptionsField.svelte | 2 + .../components/app/forms/StringField.svelte | 2 + 8 files changed, 448 insertions(+), 40 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 8d0a4e456f..faf279194f 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2597,6 +2597,34 @@ "barTitle": "Justify text" } ] + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -2654,6 +2682,34 @@ "type": "validation/number", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -2706,6 +2762,34 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -2763,6 +2847,34 @@ "type": "validation/string", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -2931,6 +3043,34 @@ "type": "validation/string", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3093,6 +3233,34 @@ "type": "validation/array", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3173,6 +3341,34 @@ "type": "validation/boolean", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3252,6 +3448,34 @@ "type": "validation/string", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3333,6 +3557,34 @@ "type": "validation/datetime", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3434,6 +3686,34 @@ "type": "validation/string", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3610,6 +3890,34 @@ "type": "validation/attachment", "label": "Validation", "key": "validation" + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3678,6 +3986,34 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, @@ -3730,6 +4066,34 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] }, diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index ec5daa21b1..8e6984b182 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -2,6 +2,7 @@ import BlockComponent from "components/BlockComponent.svelte" import Placeholder from "components/app/Placeholder.svelte" import { makePropSafe as safe } from "@budibase/string-templates" + import { getContext } from "svelte" export let dataSource export let actionUrl @@ -32,6 +33,7 @@ barcodeqr: "codescanner", bb_reference: "bbreferencefield", } + const context = getContext("context") let formId @@ -213,16 +215,18 @@ </BlockComponent> {/if} {#key fields} - <BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}> - {#each fields as field, idx} - {#if getComponentForField(field) && field.active} - <BlockComponent - type={getComponentForField(field)} - props={getPropsForField(field)} - order={idx} - /> - {/if} - {/each} + <BlockComponent type="container"> + <div class="fields" class:mobile={$context.device.mobile}> + {#each fields as field, idx} + {#if getComponentForField(field) && field.active} + <BlockComponent + type={getComponentForField(field)} + props={getPropsForField(field)} + order={idx} + /> + {/if} + {/each} + </div> </BlockComponent> {/key} </BlockComponent> @@ -232,3 +236,14 @@ text="Choose your table and add some fields to your form to get started" /> {/if} + +<style> + .fields { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 8px 16px; + } + .fields.mobile :global(.spectrum-Form-item) { + grid-column: span 6 !important; + } +</style> diff --git a/packages/client/src/components/app/forms/AttachmentField.svelte b/packages/client/src/components/app/forms/AttachmentField.svelte index e24115ebc0..bc788a726d 100644 --- a/packages/client/src/components/app/forms/AttachmentField.svelte +++ b/packages/client/src/components/app/forms/AttachmentField.svelte @@ -11,6 +11,7 @@ export let extensions export let onChange export let maximum = undefined + export let span let fieldState let fieldApi @@ -72,6 +73,7 @@ {field} {disabled} {validation} + {span} type="attachment" bind:fieldState bind:fieldApi diff --git a/packages/client/src/components/app/forms/DateTimeField.svelte b/packages/client/src/components/app/forms/DateTimeField.svelte index 6bcd20d250..661c0c2fad 100644 --- a/packages/client/src/components/app/forms/DateTimeField.svelte +++ b/packages/client/src/components/app/forms/DateTimeField.svelte @@ -13,6 +13,7 @@ export let validation export let defaultValue export let onChange + export let span let fieldState let fieldApi @@ -31,6 +32,7 @@ {disabled} {validation} {defaultValue} + {span} type="datetime" bind:fieldState bind:fieldApi diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index 5d4da5afef..d8b2c7a327 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -12,6 +12,7 @@ export let type export let disabled = false export let validation + export let span = 6 // Get contexts const formContext = getContext("form") @@ -62,40 +63,58 @@ }) </script> -<FieldGroupFallback> - <div class="spectrum-Form-item" use:styleable={$component.styles}> - {#key $component.editing} - <label - bind:this={labelNode} - contenteditable={$component.editing} - on:blur={$component.editing ? updateLabel : null} - class:hidden={!label} - for={fieldState?.fieldId} - class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`} - > - {label || " "} - </label> - {/key} - <div class="spectrum-Form-itemField"> - {#if !formContext} - <Placeholder text="Form components need to be wrapped in a form" /> - {:else if !fieldState} - <Placeholder /> - {:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)} - <Placeholder - text="This Field setting is the wrong data type for this component" - /> - {:else} - <slot /> - {#if fieldState.error} - <div class="error">{fieldState.error}</div> - {/if} +<div + class="spectrum-Form-item" + class:span-2={span === 2} + class:span-3={span === 3} + class:span-6={span === 6 || !span} + use:styleable={$component.styles} + class:above={labelPos === "above"} +> + {#key $component.editing} + <label + bind:this={labelNode} + contenteditable={$component.editing} + on:blur={$component.editing ? updateLabel : null} + class:hidden={!label} + for={fieldState?.fieldId} + class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`} + > + {label || " "} + </label> + {/key} + <div class="spectrum-Form-itemField"> + {#if !formContext} + <Placeholder text="Form components need to be wrapped in a form" /> + {:else if !fieldState} + <Placeholder /> + {:else if schemaType && schemaType !== type && !["options", "longform"].includes(type)} + <Placeholder + text="This Field setting is the wrong data type for this component" + /> + {:else} + <slot /> + {#if fieldState.error} + <div class="error">{fieldState.error}</div> {/if} - </div> + {/if} </div> -</FieldGroupFallback> +</div> <style> + .spectrum-Form-item.span-2 { + grid-column: span 2; + } + .spectrum-Form-item.span-3 { + grid-column: span 3; + } + .spectrum-Form-item.span-6 { + grid-column: span 6; + } + .spectrum-Form-item.above { + display: flex; + flex-direction: column; + } label { white-space: nowrap; } diff --git a/packages/client/src/components/app/forms/MultiFieldSelect.svelte b/packages/client/src/components/app/forms/MultiFieldSelect.svelte index 88e1ec5a8e..f3983a2284 100644 --- a/packages/client/src/components/app/forms/MultiFieldSelect.svelte +++ b/packages/client/src/components/app/forms/MultiFieldSelect.svelte @@ -17,6 +17,7 @@ export let onChange export let optionsType = "select" export let direction = "vertical" + export let span let fieldState let fieldApi @@ -56,6 +57,7 @@ {label} {disabled} {validation} + {span} defaultValue={expandedDefaultValue} type="array" bind:fieldState diff --git a/packages/client/src/components/app/forms/OptionsField.svelte b/packages/client/src/components/app/forms/OptionsField.svelte index 3c229c0509..bd8dc862ca 100644 --- a/packages/client/src/components/app/forms/OptionsField.svelte +++ b/packages/client/src/components/app/forms/OptionsField.svelte @@ -18,6 +18,7 @@ export let direction = "vertical" export let onChange export let sort = true + export let span let fieldState let fieldApi @@ -47,6 +48,7 @@ {disabled} {validation} {defaultValue} + {span} type="options" bind:fieldState bind:fieldApi diff --git a/packages/client/src/components/app/forms/StringField.svelte b/packages/client/src/components/app/forms/StringField.svelte index 26136b5d8d..073353d500 100644 --- a/packages/client/src/components/app/forms/StringField.svelte +++ b/packages/client/src/components/app/forms/StringField.svelte @@ -11,6 +11,7 @@ export let defaultValue = "" export let align export let onChange + export let span let fieldState let fieldApi @@ -29,6 +30,7 @@ {disabled} {validation} {defaultValue} + {span} type={type === "number" ? "number" : "string"} bind:fieldState bind:fieldApi From 291ecd45fe993dafa2198d78b35b4d895e6ddd7a Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Tue, 5 Sep 2023 11:01:01 +0100 Subject: [PATCH 06/47] Allow form block fields to be clicked from within the preview --- .../builder/src/builderStore/previewEvents.js | 13 +++++++++++++ .../FieldConfiguration/EditFieldPopover.svelte | 18 +++++++++++++++++- .../[screenId]/_components/AppPreview.svelte | 6 ++++++ .../src/components/BlockComponent.svelte | 7 ++++--- .../client/src/components/Component.svelte | 8 +++++++- .../app/blocks/form/InnerFormBlock.svelte | 6 ++++++ packages/client/src/index.js | 2 ++ packages/client/src/stores/builder.js | 4 ++++ packages/client/src/utils/styleable.js | 10 +++++++++- .../controllers/static/templates/preview.hbs | 2 ++ 10 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 packages/builder/src/builderStore/previewEvents.js diff --git a/packages/builder/src/builderStore/previewEvents.js b/packages/builder/src/builderStore/previewEvents.js new file mode 100644 index 0000000000..dfee80391f --- /dev/null +++ b/packages/builder/src/builderStore/previewEvents.js @@ -0,0 +1,13 @@ +let subscribers = [] + +export const onPreviewEvent = cb => { + subscribers.push(cb) + + return () => { + subscribers = subscribers.filter(callback => callback !== cb) + } +} + +export const emitPreviewEvent = event => { + subscribers.forEach(cb => cb(event)) +} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte index 7d2eaae478..29c1d21841 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -2,9 +2,10 @@ import { Icon, Popover, Layout } from "@budibase/bbui" import { store } from "builderStore" import { cloneDeep } from "lodash/fp" - import { createEventDispatcher } from "svelte" + import { createEventDispatcher, onDestroy, onMount } from "svelte" import ComponentSettingsSection from "../../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte" import { getContext } from "svelte" + import { onPreviewEvent } from "builderStore/previewEvents" export let anchor export let field @@ -61,6 +62,21 @@ dispatch("change", update) } + + const handlePreviewEvent = event => { + const { type, data } = event?.data || {} + if (type === "click-form-block-field") { + if (data.field === field.field) { + popover.show() + open = true + } else { + popover.hide() + open = false + } + } + } + + onMount(() => onPreviewEvent(handlePreviewEvent)) </script> <Icon diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 45fe005ceb..4934116104 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -14,6 +14,7 @@ import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw" import { findComponent, findComponentPath } from "builderStore/componentUtils" import { isActive, goto } from "@roxi/routify" + import { emitPreviewEvent } from "builderStore/previewEvents" let iframe let layout @@ -36,12 +37,14 @@ // Determine selected component ID $: selectedComponentId = $store.selectedComponentId + $: selectedBlockComponentId = $store.selectedBlockComponentId $: previewData = { appId: $store.appId, layout, screen, selectedComponentId, + selectedBlockComponentId, theme: $store.theme, customTheme: $store.customTheme, previewDevice: $store.previewDevice, @@ -93,6 +96,7 @@ // Await the event handler try { await handleBudibaseEvent(message) + emitPreviewEvent(message) } catch (error) { notifications.error(error || "Error handling event from app preview") } @@ -181,6 +185,8 @@ } else if (type === "add-parent-component") { const { componentId, parentType } = data await store.actions.components.addParent(componentId, parentType) + } else if (type === "click-form-block-field") { + // Swallow and let this be handled by form block settings } else { console.warn(`Client sent unknown event type: ${type}`) } diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index c9516b0d71..b3b6a486c8 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -11,6 +11,7 @@ export let name export let order = 0 export let containsSlot = false + export let onClick = null // ID is only exposed as a prop so that it can be bound to from parent // block components @@ -26,15 +27,15 @@ $: parentId = $component?.id $: inBuilder = $builderStore.inBuilder $: instance = { + ...props, _component: getComponent(type), _id: id, _instanceName: getInstanceName(name, type), + _containsSlot: containsSlot, _styles: { ...styles, normal: styles?.normal || {}, }, - _containsSlot: containsSlot, - ...props, } // Register this block component if we're inside the builder so it can be @@ -76,6 +77,6 @@ }) </script> -<Component {instance} isBlock> +<Component {instance} isBlock {onClick}> <slot /> </Component> diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 03aab21d38..67fb06261f 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -35,6 +35,7 @@ export let isLayout = false export let isRoot = false export let isBlock = false + export let onClick = null // Get parent contexts const context = getContext("context") @@ -131,7 +132,10 @@ // Interactive components can be selected, dragged and highlighted inside // the builder preview $: builderInteractive = - $builderStore.inBuilder && insideScreenslot && !isBlock && !instance.static + $builderStore.inBuilder && + insideScreenslot && + (!isBlock || onClick) && + !instance.static $: devToolsInteractive = $devToolsStore.allowSelection && !isBlock $: interactive = !isRoot && (builderInteractive || devToolsInteractive) $: editing = editable && selected && $builderStore.editMode @@ -194,6 +198,8 @@ interactive, draggable, editable, + isBlock, + onClick, }, empty: emptyState, selected, diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 8e6984b182..b8abec874a 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -34,6 +34,7 @@ bb_reference: "bbreferencefield", } const context = getContext("context") + const { builderStore } = getContext("sdk") let formId @@ -223,6 +224,11 @@ type={getComponentForField(field)} props={getPropsForField(field)} order={idx} + interactive + name={field?.field} + onClick={() => { + builderStore.actions.clickFormBlockField(field?.field) + }} /> {/if} {/each} diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 1550ba4d7b..0cf6800560 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -31,6 +31,8 @@ const loadBudibase = async () => { layout: window["##BUDIBASE_PREVIEW_LAYOUT##"], screen: window["##BUDIBASE_PREVIEW_SCREEN##"], selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"], + selectedBlockComponentId: + window["##BUDIBASE_SELECTED_BLOCK_COMPONENT_ID##"], previewId: window["##BUDIBASE_PREVIEW_ID##"], theme: window["##BUDIBASE_PREVIEW_THEME##"], customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"], diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 036558e8b2..c1865d608f 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -8,6 +8,7 @@ const createBuilderStore = () => { inBuilder: false, screen: null, selectedComponentId: null, + selectedBlockComponentId: null, editMode: false, previewId: null, theme: null, @@ -35,6 +36,9 @@ const createBuilderStore = () => { devToolsStore.actions.setAllowSelection(false) eventStore.actions.dispatchEvent("select-component", { id }) }, + clickFormBlockField: field => { + eventStore.actions.dispatchEvent("click-form-block-field", { field }) + }, updateProp: (prop, value) => { eventStore.actions.dispatchEvent("update-prop", { prop, value }) }, diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index f48eece89b..31c35c4483 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -40,6 +40,7 @@ export const styleable = (node, styles = {}) => { const componentId = newStyles.id const customStyles = newStyles.custom || "" + const { isBlock, onClick } = newStyles const normalStyles = { ...baseStyles, ...newStyles.normal } const hoverStyles = { ...normalStyles, @@ -67,7 +68,11 @@ export const styleable = (node, styles = {}) => { // Handler to select a component in the builder when clicking it in the // builder preview selectComponent = event => { - builderStore.actions.selectComponent(componentId) + if (isBlock && onClick) { + onClick() + } else { + builderStore.actions.selectComponent(componentId, isBlock) + } event.preventDefault() event.stopPropagation() return false @@ -76,6 +81,9 @@ export const styleable = (node, styles = {}) => { // Handler to start editing a component (if applicable) when double // clicking in the builder preview editComponent = event => { + if (isBlock) { + return + } if (newStyles.interactive && newStyles.editable) { builderStore.actions.setEditMode(true) } diff --git a/packages/server/src/api/controllers/static/templates/preview.hbs b/packages/server/src/api/controllers/static/templates/preview.hbs index 31bf0762e0..f28469ca22 100644 --- a/packages/server/src/api/controllers/static/templates/preview.hbs +++ b/packages/server/src/api/controllers/static/templates/preview.hbs @@ -63,6 +63,7 @@ // Extract data from message const { selectedComponentId, + selectedBlockComponentId, layout, screen, appId, @@ -81,6 +82,7 @@ window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout window["##BUDIBASE_PREVIEW_SCREEN##"] = screen window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId + window["##BUDIBASE_SELECTED_BLOCK_COMPONENT_ID##"] = selectedBlockComponentId window["##BUDIBASE_PREVIEW_ID##"] = Math.random() window["##BUDIBASE_PREVIEW_THEME##"] = theme window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme From 43663c6fbfc69fb6b09c5f5fa5a20de5ee91b099 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Tue, 5 Sep 2023 11:02:10 +0100 Subject: [PATCH 07/47] Prevent dragging interactive block components --- packages/client/src/components/Component.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 67fb06261f..467fee0468 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -144,6 +144,7 @@ interactive && !isLayout && !isRoot && + !isBlock && definition?.draggable !== false $: droppable = interactive $: builderHidden = From bffa8b1f0fbc9851bd8e194b28892eadf9856809 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Tue, 5 Sep 2023 11:11:43 +0100 Subject: [PATCH 08/47] Ensure only the selected block supports clicking on inner interactive components --- packages/client/src/components/BlockComponent.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index b3b6a486c8..8af3a8b75e 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -26,6 +26,7 @@ $: id = `${block.id}-${context ?? rand}` $: parentId = $component?.id $: inBuilder = $builderStore.inBuilder + $: blockSelected = $builderStore.selectedComponentId === block.id $: instance = { ...props, _component: getComponent(type), @@ -77,6 +78,6 @@ }) </script> -<Component {instance} isBlock {onClick}> +<Component {instance} isBlock onClick={blockSelected ? onClick : null}> <slot /> </Component> From 34a752dd41310ac778f83e7d47117f6f13599df4 Mon Sep 17 00:00:00 2001 From: Duarte Velez Grilo <duartegrilo@gmail.com> Date: Fri, 20 Oct 2023 12:46:43 +0100 Subject: [PATCH 09/47] Update CodeScanner.svelte Updated the code as suggested. Thanks! --- packages/client/src/components/app/forms/CodeScanner.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/app/forms/CodeScanner.svelte b/packages/client/src/components/app/forms/CodeScanner.svelte index f68f0791f6..f2e32004cf 100644 --- a/packages/client/src/components/app/forms/CodeScanner.svelte +++ b/packages/client/src/components/app/forms/CodeScanner.svelte @@ -21,7 +21,7 @@ let cameraEnabled let cameraStarted = false let html5QrCode - let cameraSetting = { facingMode: {preferredCamera} } + let cameraSetting = { facingMode: preferredCamera } let cameraConfig = { fps: 25, qrbox: { width: 250, height: 250 }, From 0e1cffa65657fc291078c4e9257473be404b778c Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Mon, 23 Oct 2023 09:20:05 +0100 Subject: [PATCH 10/47] Add span setting to relationship fielsd --- .../client/src/components/app/forms/RelationshipField.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 544a1a8434..cd97790e3f 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -18,6 +18,7 @@ export let filter export let datasourceType = "table" export let primaryDisplay + export let span let fieldState let fieldApi @@ -186,6 +187,7 @@ {validation} defaultValue={expandedDefaultValue} {type} + {span} bind:fieldState bind:fieldApi bind:fieldSchema From 2bf78db0a026dd7565e6ecdbba99011c6ca71d78 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 08:11:09 +0100 Subject: [PATCH 11/47] Add field layout setting to BB reference field --- packages/client/manifest.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 1fc25c5184..798e33f58d 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -6104,6 +6104,34 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "select", + "label": "Layout", + "key": "span", + "defaultValue": 6, + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "1 column", + "value": 6, + "barIcon": "Stop", + "barTitle": "1 column" + }, + { + "label": "2 columns", + "value": 3, + "barIcon": "ColumnTwoA", + "barTitle": "2 columns" + }, + { + "label": "3 columns", + "value": 2, + "barIcon": "ViewColumn", + "barTitle": "3 columns" + } + ] } ] } From 77cd7233152c6b84c6910c38045c1be5a89029d6 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 08:14:55 +0100 Subject: [PATCH 12/47] Remove label alignment option from form block to support custom field layouts --- packages/client/manifest.json | 16 ---------------- .../components/app/blocks/form/FormBlock.svelte | 2 -- .../app/blocks/form/InnerFormBlock.svelte | 1 - 3 files changed, 19 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 798e33f58d..48f3f19203 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5755,22 +5755,6 @@ "section": true, "name": "Fields", "settings": [ - { - "type": "select", - "label": "Align labels", - "key": "labelPosition", - "defaultValue": "left", - "options": [ - { - "label": "Left", - "value": "left" - }, - { - "label": "Above", - "value": "above" - } - ] - }, { "type": "select", "label": "Size", diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index f905227af9..e4d3b55eff 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -10,7 +10,6 @@ export let size export let disabled export let fields - export let labelPosition export let title export let description export let showDeleteButton @@ -97,7 +96,6 @@ size, disabled, fields: fieldsOrDefault, - labelPosition, title, description, saveButtonLabel: saveLabel, diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index eb93279397..60a3522216 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -10,7 +10,6 @@ export let size export let disabled export let fields - export let labelPosition export let title export let description export let saveButtonLabel From e2c2f5e2a6363a67c597e909c8e350e51b0a00cb Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 08:50:26 +0100 Subject: [PATCH 13/47] Fix some issues with dropzone height and ensure field alignment settings are ignored unless used inside a form block --- packages/bbui/src/Form/Core/Dropzone.svelte | 13 ++++--- .../app/blocks/form/InnerFormBlock.svelte | 2 +- .../app/forms/AttachmentField.svelte | 38 ++++++++----------- .../src/components/app/forms/Field.svelte | 6 +-- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index e9ee75bd8b..e2e25b4a04 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -384,7 +384,7 @@ } .compact .placeholder, .compact img { - margin: 10px 16px; + margin: 8px 16px; } .compact img { height: 90px; @@ -454,6 +454,12 @@ color: var(--red); } + .spectrum-Dropzone { + height: 220px; + } + .compact .spectrum-Dropzone { + height: 40px; + } .spectrum-Dropzone.disabled { pointer-events: none; background-color: var(--spectrum-global-color-gray-200); @@ -461,10 +467,6 @@ .disabled .spectrum-Heading--sizeL { color: var(--spectrum-alias-text-color-disabled); } - .compact .spectrum-Dropzone { - padding-top: 8px; - padding-bottom: 8px; - } .compact .spectrum-IllustratedMessage-description { margin: 0; } @@ -475,7 +477,6 @@ flex-wrap: wrap; justify-content: center; } - .tag { margin-top: 8px; } diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 60a3522216..502b3e0569 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -233,7 +233,7 @@ {/if} {#key fields} <BlockComponent type="container"> - <div class="fields" class:mobile={$context.device.mobile}> + <div class="form-block fields" class:mobile={$context.device.mobile}> {#each fields as field, idx} {#if getComponentForField(field) && field.active} <BlockComponent diff --git a/packages/client/src/components/app/forms/AttachmentField.svelte b/packages/client/src/components/app/forms/AttachmentField.svelte index bc788a726d..f28d4801d4 100644 --- a/packages/client/src/components/app/forms/AttachmentField.svelte +++ b/packages/client/src/components/app/forms/AttachmentField.svelte @@ -79,27 +79,19 @@ bind:fieldApi defaultValue={[]} > - <div class="minHeightWrapper"> - {#if fieldState} - <CoreDropzone - value={fieldState.value} - disabled={fieldState.disabled} - error={fieldState.error} - on:change={handleChange} - {processFiles} - {deleteAttachments} - {handleFileTooLarge} - {handleTooManyFiles} - {maximum} - {extensions} - {compact} - /> - {/if} - </div> + {#if fieldState} + <CoreDropzone + value={fieldState.value} + disabled={fieldState.disabled} + error={fieldState.error} + on:change={handleChange} + {processFiles} + {deleteAttachments} + {handleFileTooLarge} + {handleTooManyFiles} + {maximum} + {extensions} + {compact} + /> + {/if} </Field> - -<style> - .minHeightWrapper { - min-height: 80px; - } -</style> diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index d8b2c7a327..83db76b473 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -102,13 +102,13 @@ </div> <style> - .spectrum-Form-item.span-2 { + :global(.form-block .spectrum-Form-item.span-2) { grid-column: span 2; } - .spectrum-Form-item.span-3 { + :global(.form-block .spectrum-Form-item.span-3) { grid-column: span 3; } - .spectrum-Form-item.span-6 { + :global(.form-block .spectrum-Form-item.span-6) { grid-column: span 6; } .spectrum-Form-item.above { From b1f233784916f4b089c9af3a708a5379a90205ac Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 11:42:01 +0100 Subject: [PATCH 14/47] Hide field layout settings for normal field components, and only show them when editing from within a form block --- .../EditFieldPopover.svelte | 1 + .../Component/ComponentSettingsSection.svelte | 25 +++++++++++++++---- packages/client/manifest.json | 14 +++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte index 29c1d21841..e6fb82bedd 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -134,6 +134,7 @@ <span>{field.field}</span> </div> <ComponentSettingsSection + includeHidden componentInstance={pseudoComponentInstance} componentDefinition={parsedComponentDef} isScreen={false} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index f833464d8c..f78aeaa192 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -16,10 +16,16 @@ export let isScreen = false export let onUpdateSetting export let showSectionTitle = true + export let includeHidden = false - $: sections = getSections(componentInstance, componentDefinition, isScreen) + $: sections = getSections( + componentInstance, + componentDefinition, + isScreen, + includeHidden + ) - const getSections = (instance, definition, isScreen) => { + const getSections = (instance, definition, isScreen, includeHidden) => { const settings = definition?.settings ?? [] const generalSettings = settings.filter(setting => !setting.section) const customSections = settings.filter(setting => setting.section) @@ -38,7 +44,12 @@ return } section.settings.forEach(setting => { - setting.visible = canRenderControl(instance, setting, isScreen) + setting.visible = canRenderControl( + instance, + setting, + isScreen, + includeHidden + ) }) section.visible = section.name === "General" || @@ -108,16 +119,20 @@ }) } - const canRenderControl = (instance, setting, isScreen) => { + const canRenderControl = (instance, setting, isScreen, includeHidden) => { // Prevent rendering on click setting for screens if (setting?.type === "event" && isScreen) { return false } + // Check we have a component to render for this setting const control = getComponentForSetting(setting) if (!control) { return false } - + // Check if setting is hidden + if (setting.hidden && !includeHidden) { + return false + } return shouldDisplay(instance, setting) } </script> diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 48f3f19203..c74ec7dea8 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2603,6 +2603,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -2688,6 +2689,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -2768,6 +2770,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -2853,6 +2856,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3049,6 +3053,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3239,6 +3244,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3347,6 +3353,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3454,6 +3461,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3563,6 +3571,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3692,6 +3701,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3896,6 +3906,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -3992,6 +4003,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -4072,6 +4084,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ @@ -6094,6 +6107,7 @@ "label": "Layout", "key": "span", "defaultValue": 6, + "hidden": true, "showInBar": true, "barStyle": "buttons", "options": [ From 77fa373a9077da9bbbcf41451ac78387ade4dd70 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 15:09:17 +0100 Subject: [PATCH 15/47] Remove ability to select fields in preview because it is not compatible with field configuration changes --- .../builder/src/builderStore/previewEvents.js | 13 ---------- .../controls/EditComponentPopover.svelte | 25 +++---------------- .../[screenId]/_components/AppPreview.svelte | 6 ----- .../src/components/BlockComponent.svelte | 4 +-- .../client/src/components/Component.svelte | 7 +----- packages/client/src/index.js | 2 -- packages/client/src/stores/builder.js | 4 --- packages/client/src/utils/styleable.js | 8 ++---- .../controllers/static/templates/preview.hbs | 2 -- 9 files changed, 8 insertions(+), 63 deletions(-) delete mode 100644 packages/builder/src/builderStore/previewEvents.js diff --git a/packages/builder/src/builderStore/previewEvents.js b/packages/builder/src/builderStore/previewEvents.js deleted file mode 100644 index dfee80391f..0000000000 --- a/packages/builder/src/builderStore/previewEvents.js +++ /dev/null @@ -1,13 +0,0 @@ -let subscribers = [] - -export const onPreviewEvent = cb => { - subscribers.push(cb) - - return () => { - subscribers = subscribers.filter(callback => callback !== cb) - } -} - -export const emitPreviewEvent = event => { - subscribers.forEach(cb => cb(event)) -} diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte b/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte index d963ad04ed..26c1ced502 100644 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte +++ b/packages/builder/src/components/design/settings/controls/EditComponentPopover.svelte @@ -2,10 +2,9 @@ import { Icon, Popover, Layout } from "@budibase/bbui" import { store } from "builderStore" import { cloneDeep } from "lodash/fp" - import { createEventDispatcher, onMount } from "svelte" + import { createEventDispatcher } from "svelte" import ComponentSettingsSection from "../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte" import { getContext } from "svelte" - import { onPreviewEvent } from "builderStore/previewEvents" export let anchor export let componentInstance @@ -21,7 +20,7 @@ let open = false // Auto hide the component when another item is selected - $: if (open && $draggable.selected != componentInstance._id) { + $: if (open && $draggable.selected !== componentInstance._id) { popover.hide() } @@ -79,22 +78,6 @@ return { ...cfg, left, top } } - - const handlePreviewEvent = event => { - const { type, data } = event?.data || {} - if (type === "click-form-block-field") { - console.log(data.field) - if (data.field === "asdasd") { - popover.show() - open = true - } else { - popover.hide() - open = false - } - } - } - - onMount(() => onPreviewEvent(handlePreviewEvent)) </script> <Icon @@ -117,13 +100,13 @@ }} on:close={() => { open = false - if ($draggable.selected == componentInstance._id) { + if ($draggable.selected === componentInstance._id) { $draggable.actions.select() } }} {anchor} align="left-outside" - showPopover={drawers.length == 0} + showPopover={drawers.length === 0} clickOutsideOverride={drawers.length > 0} maxHeight={600} handlePostionUpdate={customPositionHandler} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 4934116104..45fe005ceb 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -14,7 +14,6 @@ import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw" import { findComponent, findComponentPath } from "builderStore/componentUtils" import { isActive, goto } from "@roxi/routify" - import { emitPreviewEvent } from "builderStore/previewEvents" let iframe let layout @@ -37,14 +36,12 @@ // Determine selected component ID $: selectedComponentId = $store.selectedComponentId - $: selectedBlockComponentId = $store.selectedBlockComponentId $: previewData = { appId: $store.appId, layout, screen, selectedComponentId, - selectedBlockComponentId, theme: $store.theme, customTheme: $store.customTheme, previewDevice: $store.previewDevice, @@ -96,7 +93,6 @@ // Await the event handler try { await handleBudibaseEvent(message) - emitPreviewEvent(message) } catch (error) { notifications.error(error || "Error handling event from app preview") } @@ -185,8 +181,6 @@ } else if (type === "add-parent-component") { const { componentId, parentType } = data await store.actions.components.addParent(componentId, parentType) - } else if (type === "click-form-block-field") { - // Swallow and let this be handled by form block settings } else { console.warn(`Client sent unknown event type: ${type}`) } diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index 8af3a8b75e..12555a7fb5 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -11,7 +11,6 @@ export let name export let order = 0 export let containsSlot = false - export let onClick = null // ID is only exposed as a prop so that it can be bound to from parent // block components @@ -26,7 +25,6 @@ $: id = `${block.id}-${context ?? rand}` $: parentId = $component?.id $: inBuilder = $builderStore.inBuilder - $: blockSelected = $builderStore.selectedComponentId === block.id $: instance = { ...props, _component: getComponent(type), @@ -78,6 +76,6 @@ }) </script> -<Component {instance} isBlock onClick={blockSelected ? onClick : null}> +<Component {instance} isBlock> <slot /> </Component> diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 467fee0468..63ce8dc152 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -35,7 +35,6 @@ export let isLayout = false export let isRoot = false export let isBlock = false - export let onClick = null // Get parent contexts const context = getContext("context") @@ -132,10 +131,7 @@ // Interactive components can be selected, dragged and highlighted inside // the builder preview $: builderInteractive = - $builderStore.inBuilder && - insideScreenslot && - (!isBlock || onClick) && - !instance.static + $builderStore.inBuilder && insideScreenslot && !isBlock && !instance.static $: devToolsInteractive = $devToolsStore.allowSelection && !isBlock $: interactive = !isRoot && (builderInteractive || devToolsInteractive) $: editing = editable && selected && $builderStore.editMode @@ -200,7 +196,6 @@ draggable, editable, isBlock, - onClick, }, empty: emptyState, selected, diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 0cf6800560..1550ba4d7b 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -31,8 +31,6 @@ const loadBudibase = async () => { layout: window["##BUDIBASE_PREVIEW_LAYOUT##"], screen: window["##BUDIBASE_PREVIEW_SCREEN##"], selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"], - selectedBlockComponentId: - window["##BUDIBASE_SELECTED_BLOCK_COMPONENT_ID##"], previewId: window["##BUDIBASE_PREVIEW_ID##"], theme: window["##BUDIBASE_PREVIEW_THEME##"], customTheme: window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"], diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index c1865d608f..036558e8b2 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -8,7 +8,6 @@ const createBuilderStore = () => { inBuilder: false, screen: null, selectedComponentId: null, - selectedBlockComponentId: null, editMode: false, previewId: null, theme: null, @@ -36,9 +35,6 @@ const createBuilderStore = () => { devToolsStore.actions.setAllowSelection(false) eventStore.actions.dispatchEvent("select-component", { id }) }, - clickFormBlockField: field => { - eventStore.actions.dispatchEvent("click-form-block-field", { field }) - }, updateProp: (prop, value) => { eventStore.actions.dispatchEvent("update-prop", { prop, value }) }, diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index 31c35c4483..3fccae0be5 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -40,7 +40,7 @@ export const styleable = (node, styles = {}) => { const componentId = newStyles.id const customStyles = newStyles.custom || "" - const { isBlock, onClick } = newStyles + const { isBlock } = newStyles const normalStyles = { ...baseStyles, ...newStyles.normal } const hoverStyles = { ...normalStyles, @@ -68,11 +68,7 @@ export const styleable = (node, styles = {}) => { // Handler to select a component in the builder when clicking it in the // builder preview selectComponent = event => { - if (isBlock && onClick) { - onClick() - } else { - builderStore.actions.selectComponent(componentId, isBlock) - } + builderStore.actions.selectComponent(componentId) event.preventDefault() event.stopPropagation() return false diff --git a/packages/server/src/api/controllers/static/templates/preview.hbs b/packages/server/src/api/controllers/static/templates/preview.hbs index f28469ca22..31bf0762e0 100644 --- a/packages/server/src/api/controllers/static/templates/preview.hbs +++ b/packages/server/src/api/controllers/static/templates/preview.hbs @@ -63,7 +63,6 @@ // Extract data from message const { selectedComponentId, - selectedBlockComponentId, layout, screen, appId, @@ -82,7 +81,6 @@ window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout window["##BUDIBASE_PREVIEW_SCREEN##"] = screen window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId - window["##BUDIBASE_SELECTED_BLOCK_COMPONENT_ID##"] = selectedBlockComponentId window["##BUDIBASE_PREVIEW_ID##"] = Math.random() window["##BUDIBASE_PREVIEW_THEME##"] = theme window["##BUDIBASE_PREVIEW_CUSTOM_THEME##"] = customTheme From 4fd1c26bbde7b53347bb49e9acce9fe65edec946 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 15:13:49 +0100 Subject: [PATCH 16/47] Clean up --- .../src/components/app/blocks/form/InnerFormBlock.svelte | 4 ---- packages/client/src/components/app/forms/Field.svelte | 1 - 2 files changed, 5 deletions(-) diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index 502b3e0569..f5e0d236a3 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -34,7 +34,6 @@ bb_reference: "bbreferencefield", } const context = getContext("context") - const { builderStore } = getContext("sdk") let formId @@ -242,9 +241,6 @@ order={idx} interactive name={field?.field} - onClick={() => { - builderStore.actions.clickFormBlockField(field?.field) - }} /> {/if} {/each} diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index 83db76b473..f6fbe37681 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -1,6 +1,5 @@ <script> import Placeholder from "../Placeholder.svelte" - import FieldGroupFallback from "./FieldGroupFallback.svelte" import { getContext, onDestroy } from "svelte" export let label From de55445168785ae2a64abebda435bdcd5b9c57a3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Fri, 27 Oct 2023 16:06:06 +0100 Subject: [PATCH 17/47] Update pro --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index d24c0dc3a3..5ed0ee2aca 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit d24c0dc3a30014cbe61860252aa48104cad36376 +Subproject commit 5ed0ee2aca9d754d80cd46bae412b24621afa47e From 5c049e0465ca27ed078e3ffda4c5c91998ba97cd Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Wed, 1 Nov 2023 13:54:18 +0000 Subject: [PATCH 18/47] LongFormField readonly prop --- .../bbui/src/Form/Core/RichTextField.svelte | 2 ++ packages/bbui/src/Form/Core/TextArea.svelte | 2 ++ .../bbui/src/Markdown/MarkdownEditor.svelte | 5 ++- packages/client/manifest.json | 33 +++++++++++++++++++ .../components/app/forms/LongFormField.svelte | 3 ++ .../components/app/forms/StringField.svelte | 2 ++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/bbui/src/Form/Core/RichTextField.svelte b/packages/bbui/src/Form/Core/RichTextField.svelte index f964405f0d..3e0b0caf4d 100644 --- a/packages/bbui/src/Form/Core/RichTextField.svelte +++ b/packages/bbui/src/Form/Core/RichTextField.svelte @@ -4,6 +4,7 @@ export let value = "" export let placeholder = null export let disabled = false + export let readonly = false export let error = null export let height = null export let id = null @@ -20,6 +21,7 @@ {fullScreenOffset} {disabled} {easyMDEOptions} + {readonly} on:change /> </div> diff --git a/packages/bbui/src/Form/Core/TextArea.svelte b/packages/bbui/src/Form/Core/TextArea.svelte index 465212cd44..be7eed466d 100644 --- a/packages/bbui/src/Form/Core/TextArea.svelte +++ b/packages/bbui/src/Form/Core/TextArea.svelte @@ -5,6 +5,7 @@ export let value = "" export let placeholder = null export let disabled = false + export let readonly = false export let error = null export let id = null export let height = null @@ -61,6 +62,7 @@ class="spectrum-Textfield-input" style={align ? `text-align: ${align}` : ""} {disabled} + {readonly} {id} on:focus={() => (focus = true)} on:blur={onChange} diff --git a/packages/bbui/src/Markdown/MarkdownEditor.svelte b/packages/bbui/src/Markdown/MarkdownEditor.svelte index 7fb6414ad8..27035d8033 100644 --- a/packages/bbui/src/Markdown/MarkdownEditor.svelte +++ b/packages/bbui/src/Markdown/MarkdownEditor.svelte @@ -8,6 +8,7 @@ export let id = null export let fullScreenOffset = 0 export let disabled = false + export let readonly = false export let easyMDEOptions const dispatch = createEventDispatcher() @@ -19,6 +20,7 @@ // control $: checkValue(value) $: mde?.codemirror.on("change", debouncedUpdate) + $: mde?.codemirror.setOption("readOnly", readonly) const checkValue = val => { if (mde && val !== latestValue) { @@ -43,7 +45,7 @@ const debouncedUpdate = debounce(update, 250) </script> -{#key height} +{#key (height, readonly)} <SpectrumMDE bind:mde scroll={true} @@ -54,6 +56,7 @@ easyMDEOptions={{ initialValue: value, placeholder, + toolbar: readonly ? false : undefined, ...easyMDEOptions, }} /> diff --git a/packages/client/manifest.json b/packages/client/manifest.json index eef1e50b7c..6e175c44de 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2589,6 +2589,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "text", "label": "Initial form step", @@ -2738,6 +2749,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "validation/string", "label": "Validation", @@ -3427,6 +3449,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "validation/string", "label": "Validation", diff --git a/packages/client/src/components/app/forms/LongFormField.svelte b/packages/client/src/components/app/forms/LongFormField.svelte index 8d94f83319..8482a6a68e 100644 --- a/packages/client/src/components/app/forms/LongFormField.svelte +++ b/packages/client/src/components/app/forms/LongFormField.svelte @@ -8,6 +8,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let validation export let defaultValue = "" export let format = "auto" @@ -71,6 +72,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} + {readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} @@ -88,6 +90,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} + {readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} diff --git a/packages/client/src/components/app/forms/StringField.svelte b/packages/client/src/components/app/forms/StringField.svelte index 26136b5d8d..624611c733 100644 --- a/packages/client/src/components/app/forms/StringField.svelte +++ b/packages/client/src/components/app/forms/StringField.svelte @@ -11,6 +11,7 @@ export let defaultValue = "" export let align export let onChange + export let readonly = false let fieldState let fieldApi @@ -44,6 +45,7 @@ {placeholder} {type} {align} + {readonly} /> {/if} </Field> From 5c36d70a0116624acadc20865a7bba6f76d46d1d Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Wed, 1 Nov 2023 14:56:28 +0000 Subject: [PATCH 19/47] Pickers readonly prop --- .../bbui/src/Form/Core/CheckboxGroup.svelte | 6 +++++ packages/bbui/src/Form/Core/RadioGroup.svelte | 6 +++++ packages/client/manifest.json | 22 +++++++++++++++++++ .../app/forms/MultiFieldSelect.svelte | 3 +++ .../components/app/forms/OptionsField.svelte | 3 +++ 5 files changed, 40 insertions(+) diff --git a/packages/bbui/src/Form/Core/CheckboxGroup.svelte b/packages/bbui/src/Form/Core/CheckboxGroup.svelte index 2b8a1e438a..faf37f3ad8 100644 --- a/packages/bbui/src/Form/Core/CheckboxGroup.svelte +++ b/packages/bbui/src/Form/Core/CheckboxGroup.svelte @@ -8,6 +8,7 @@ export let options = [] export let error = null export let disabled = false + export let readonly = false export let getOptionLabel = option => option export let getOptionValue = option => option @@ -40,6 +41,11 @@ > <input on:change={onChange} + on:click={e => { + if (readonly) { + e.preventDefault() + } + }} type="checkbox" class="spectrum-Checkbox-input" value={optionValue} diff --git a/packages/bbui/src/Form/Core/RadioGroup.svelte b/packages/bbui/src/Form/Core/RadioGroup.svelte index f7afc10bbc..fc99fafd40 100644 --- a/packages/bbui/src/Form/Core/RadioGroup.svelte +++ b/packages/bbui/src/Form/Core/RadioGroup.svelte @@ -8,6 +8,7 @@ export let options = [] export let error = null export let disabled = false + export let readonly = false export let getOptionLabel = option => option export let getOptionValue = option => option export let getOptionTitle = option => option @@ -43,6 +44,11 @@ > <input on:change={onChange} + on:click={e => { + if (readonly) { + e.preventDefault() + } + }} bind:group={value} value={getOptionValue(option)} type="radio" diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 6e175c44de..90c897139c 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3071,6 +3071,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "select", "label": "Options source", @@ -3196,6 +3207,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "select", "label": "Type", diff --git a/packages/client/src/components/app/forms/MultiFieldSelect.svelte b/packages/client/src/components/app/forms/MultiFieldSelect.svelte index 88e1ec5a8e..cb4879f86e 100644 --- a/packages/client/src/components/app/forms/MultiFieldSelect.svelte +++ b/packages/client/src/components/app/forms/MultiFieldSelect.svelte @@ -6,6 +6,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let validation export let defaultValue export let optionsSource = "schema" @@ -71,6 +72,7 @@ getOptionValue={flatOptions ? x => x : x => x.value} id={fieldState.fieldId} disabled={fieldState.disabled} + {readonly} on:change={handleChange} {placeholder} {options} @@ -81,6 +83,7 @@ value={fieldState.value || []} id={fieldState.fieldId} disabled={fieldState.disabled} + {readonly} error={fieldState.error} {options} {direction} diff --git a/packages/client/src/components/app/forms/OptionsField.svelte b/packages/client/src/components/app/forms/OptionsField.svelte index 3c229c0509..c01827471a 100644 --- a/packages/client/src/components/app/forms/OptionsField.svelte +++ b/packages/client/src/components/app/forms/OptionsField.svelte @@ -6,6 +6,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let optionsType = "select" export let validation export let defaultValue @@ -58,6 +59,7 @@ value={fieldState.value} id={fieldState.fieldId} disabled={fieldState.disabled} + {readonly} error={fieldState.error} {options} {placeholder} @@ -72,6 +74,7 @@ value={fieldState.value} id={fieldState.fieldId} disabled={fieldState.disabled} + {readonly} error={fieldState.error} {options} {direction} From 56d5a0b8f6fbc0e265bdc0068f253d601d56c62a Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Wed, 1 Nov 2023 16:01:45 +0000 Subject: [PATCH 20/47] Further read only settings --- packages/bbui/src/Form/Core/Checkbox.svelte | 6 ++ packages/bbui/src/Form/Core/DatePicker.svelte | 4 +- packages/bbui/src/Form/DatePicker.svelte | 2 + packages/client/manifest.json | 57 ++++++++++++++++++- .../components/app/forms/BooleanField.svelte | 2 + .../components/app/forms/DateTimeField.svelte | 2 + .../src/components/app/forms/JSONField.svelte | 2 + .../app/forms/RelationshipField.svelte | 2 + 8 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/bbui/src/Form/Core/Checkbox.svelte b/packages/bbui/src/Form/Core/Checkbox.svelte index 3efc737bfb..3eaaf4dede 100644 --- a/packages/bbui/src/Form/Core/Checkbox.svelte +++ b/packages/bbui/src/Form/Core/Checkbox.svelte @@ -8,6 +8,7 @@ export let id = null export let text = null export let disabled = false + export let readonly = false export let size export let indeterminate = false @@ -29,6 +30,11 @@ checked={value} {disabled} on:change={onChange} + on:click={e => { + if (readonly) { + e.preventDefault() + } + }} type="checkbox" class="spectrum-Checkbox-input" {id} diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index 7ce15292be..786aee40b6 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -9,6 +9,7 @@ export let id = null export let disabled = false + export let readonly = false export let error = null export let enableTime = true export let value = null @@ -186,7 +187,7 @@ > <div id={flatpickrId} - class:is-disabled={disabled} + class:is-disabled={disabled || readonly} class:is-invalid={!!error} class="flatpickr spectrum-InputGroup spectrum-Datepicker" class:is-focused={open} @@ -211,6 +212,7 @@ {/if} <input {disabled} + {readonly} data-input type="text" class="spectrum-Textfield-input spectrum-InputGroup-input" diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte index 04ce8b5467..f17871a576 100644 --- a/packages/bbui/src/Form/DatePicker.svelte +++ b/packages/bbui/src/Form/DatePicker.svelte @@ -7,6 +7,7 @@ export let label = null export let labelPosition = "above" export let disabled = false + export let readonly = false export let error = null export let enableTime = true export let timeOnly = false @@ -33,6 +34,7 @@ <DatePicker {error} {disabled} + {readonly} {value} {placeholder} {enableTime} diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 90c897139c..7559825267 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2851,6 +2851,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "validation/number", "label": "Validation", @@ -3392,6 +3403,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "validation/boolean", "label": "Validation", @@ -3563,6 +3585,17 @@ "key": "disabled", "defaultValue": false }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } + }, { "type": "validation/datetime", "label": "Validation", @@ -3836,7 +3869,7 @@ }, { "type": "boolean", - "label": "Disabled", + "label": "Read only", "key": "disabled", "defaultValue": false }, @@ -3912,6 +3945,17 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } } ] }, @@ -3964,6 +4008,17 @@ "label": "Disabled", "key": "disabled", "defaultValue": false + }, + { + "type": "boolean", + "label": "Read only", + "key": "readonly", + "defaultValue": false, + "dependsOn": { + "setting": "disabled", + "value": true, + "invert": true + } } ] }, diff --git a/packages/client/src/components/app/forms/BooleanField.svelte b/packages/client/src/components/app/forms/BooleanField.svelte index a65d041c29..9635a7d7b5 100644 --- a/packages/client/src/components/app/forms/BooleanField.svelte +++ b/packages/client/src/components/app/forms/BooleanField.svelte @@ -6,6 +6,7 @@ export let label export let text export let disabled = false + export let readonly = false export let size export let validation export let defaultValue @@ -49,6 +50,7 @@ <CoreCheckbox value={fieldState.value} disabled={fieldState.disabled} + {readonly} error={fieldState.error} id={fieldState.fieldId} {size} diff --git a/packages/client/src/components/app/forms/DateTimeField.svelte b/packages/client/src/components/app/forms/DateTimeField.svelte index 6bcd20d250..8124c7861a 100644 --- a/packages/client/src/components/app/forms/DateTimeField.svelte +++ b/packages/client/src/components/app/forms/DateTimeField.svelte @@ -6,6 +6,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let enableTime = true export let timeOnly = false export let time24hr = false @@ -40,6 +41,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} + {readonly} error={fieldState.error} id={fieldState.fieldId} appendTo={document.getElementById("flatpickr-root")} diff --git a/packages/client/src/components/app/forms/JSONField.svelte b/packages/client/src/components/app/forms/JSONField.svelte index c80060d3d6..1ee09c8460 100644 --- a/packages/client/src/components/app/forms/JSONField.svelte +++ b/packages/client/src/components/app/forms/JSONField.svelte @@ -7,6 +7,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let defaultValue = "" export let onChange @@ -60,6 +61,7 @@ value={serialiseValue(fieldState.value)} on:change={handleChange} disabled={fieldState.disabled} + {readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 544a1a8434..9d2d6adf0f 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -11,6 +11,7 @@ export let label export let placeholder export let disabled = false + export let readonly = false export let validation export let autocomplete = true export let defaultValue @@ -200,6 +201,7 @@ on:loadMore={loadMore} id={fieldState.fieldId} disabled={fieldState.disabled} + {readonly} error={fieldState.error} getOptionLabel={getDisplayName} getOptionValue={option => option._id} From 33e37261b2e8a16404efb68467eb27eea7e42ac7 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Wed, 1 Nov 2023 16:27:52 +0000 Subject: [PATCH 21/47] Use fieldState --- .../client/src/components/app/forms/AttachmentField.svelte | 4 +++- packages/client/src/components/app/forms/BooleanField.svelte | 3 ++- .../client/src/components/app/forms/CodeScannerField.svelte | 4 +++- .../client/src/components/app/forms/DateTimeField.svelte | 3 ++- packages/client/src/components/app/forms/Field.svelte | 2 ++ packages/client/src/components/app/forms/Form.svelte | 4 +++- packages/client/src/components/app/forms/InnerForm.svelte | 3 +++ packages/client/src/components/app/forms/JSONField.svelte | 3 ++- .../client/src/components/app/forms/LongFormField.svelte | 5 +++-- .../client/src/components/app/forms/MultiFieldSelect.svelte | 5 +++-- packages/client/src/components/app/forms/OptionsField.svelte | 5 +++-- .../client/src/components/app/forms/RelationshipField.svelte | 3 ++- packages/client/src/components/app/forms/StringField.svelte | 5 +++-- 13 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/client/src/components/app/forms/AttachmentField.svelte b/packages/client/src/components/app/forms/AttachmentField.svelte index e24115ebc0..861d881733 100644 --- a/packages/client/src/components/app/forms/AttachmentField.svelte +++ b/packages/client/src/components/app/forms/AttachmentField.svelte @@ -6,6 +6,7 @@ export let field export let label export let disabled = false + export let readonly = false export let compact = false export let validation export let extensions @@ -71,6 +72,7 @@ {label} {field} {disabled} + {readonly} {validation} type="attachment" bind:fieldState @@ -81,7 +83,7 @@ {#if fieldState} <CoreDropzone value={fieldState.value} - disabled={fieldState.disabled} + disabled={fieldState.disabled || fieldState.readonly} error={fieldState.error} on:change={handleChange} {processFiles} diff --git a/packages/client/src/components/app/forms/BooleanField.svelte b/packages/client/src/components/app/forms/BooleanField.svelte index 9635a7d7b5..1f59ddcfa6 100644 --- a/packages/client/src/components/app/forms/BooleanField.svelte +++ b/packages/client/src/components/app/forms/BooleanField.svelte @@ -40,6 +40,7 @@ {label} {field} {disabled} + {readonly} {validation} defaultValue={isTruthy(defaultValue)} type="boolean" @@ -50,7 +51,7 @@ <CoreCheckbox value={fieldState.value} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} {size} diff --git a/packages/client/src/components/app/forms/CodeScannerField.svelte b/packages/client/src/components/app/forms/CodeScannerField.svelte index c408f78d7c..a9c115e852 100644 --- a/packages/client/src/components/app/forms/CodeScannerField.svelte +++ b/packages/client/src/components/app/forms/CodeScannerField.svelte @@ -6,6 +6,7 @@ export let label export let type = "barcodeqr" export let disabled = false + export let readonly = false export let validation export let defaultValue = "" export let onChange @@ -32,6 +33,7 @@ {label} {field} {disabled} + {readonly} {validation} {defaultValue} {type} @@ -42,7 +44,7 @@ <CodeScanner value={fieldState.value} on:change={handleUpdate} - disabled={fieldState.disabled} + disabled={fieldState.disabled || fieldState.readonly} {allowManualEntry} scanButtonText={scanText} {beepOnScan} diff --git a/packages/client/src/components/app/forms/DateTimeField.svelte b/packages/client/src/components/app/forms/DateTimeField.svelte index 8124c7861a..22a76e5ef2 100644 --- a/packages/client/src/components/app/forms/DateTimeField.svelte +++ b/packages/client/src/components/app/forms/DateTimeField.svelte @@ -30,6 +30,7 @@ {label} {field} {disabled} + {readonly} {validation} {defaultValue} type="datetime" @@ -41,7 +42,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} appendTo={document.getElementById("flatpickr-root")} diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index 5d4da5afef..16115583a7 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -11,6 +11,7 @@ export let defaultValue export let type export let disabled = false + export let readonly = false export let validation // Get contexts @@ -29,6 +30,7 @@ type, defaultValue, disabled, + readonly, validation, formStep ) diff --git a/packages/client/src/components/app/forms/Form.svelte b/packages/client/src/components/app/forms/Form.svelte index 87883fe4b6..1a740585f3 100644 --- a/packages/client/src/components/app/forms/Form.svelte +++ b/packages/client/src/components/app/forms/Form.svelte @@ -8,6 +8,7 @@ export let theme export let size export let disabled = false + export let readonly = false export let actionType = "Create" export let initialFormStep = 1 @@ -39,7 +40,7 @@ $: schemaKey = generateSchemaKey(schema) $: initialValues = getInitialValues(actionType, dataSource, $context) $: resetKey = Helpers.hashString( - schemaKey + JSON.stringify(initialValues) + disabled + schemaKey + JSON.stringify(initialValues) + disabled + readonly ) // Returns the closes data context which isn't a built in context @@ -97,6 +98,7 @@ {theme} {size} {disabled} + {readonly} {actionType} {schema} {table} diff --git a/packages/client/src/components/app/forms/InnerForm.svelte b/packages/client/src/components/app/forms/InnerForm.svelte index 4dacf36244..6ebe9de7ec 100644 --- a/packages/client/src/components/app/forms/InnerForm.svelte +++ b/packages/client/src/components/app/forms/InnerForm.svelte @@ -6,6 +6,7 @@ export let dataSource export let disabled = false + export let readonly = false export let initialValues export let size export let schema @@ -148,6 +149,7 @@ type, defaultValue = null, fieldDisabled = false, + fieldReadOnly = false, validationRules, step = 1 ) => { @@ -205,6 +207,7 @@ error: initialError, disabled: disabled || fieldDisabled || (isAutoColumn && !editAutoColumns), + readonly: readonly || fieldReadOnly, defaultValue, validator, lastUpdate: Date.now(), diff --git a/packages/client/src/components/app/forms/JSONField.svelte b/packages/client/src/components/app/forms/JSONField.svelte index 1ee09c8460..cf96f54a23 100644 --- a/packages/client/src/components/app/forms/JSONField.svelte +++ b/packages/client/src/components/app/forms/JSONField.svelte @@ -49,6 +49,7 @@ {label} {field} {disabled} + {readonly} {validation} {defaultValue} type="json" @@ -61,7 +62,7 @@ value={serialiseValue(fieldState.value)} on:change={handleChange} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} diff --git a/packages/client/src/components/app/forms/LongFormField.svelte b/packages/client/src/components/app/forms/LongFormField.svelte index 8482a6a68e..a9087a0a9c 100644 --- a/packages/client/src/components/app/forms/LongFormField.svelte +++ b/packages/client/src/components/app/forms/LongFormField.svelte @@ -59,6 +59,7 @@ {label} {field} {disabled} + {readonly} {validation} {defaultValue} type="longform" @@ -72,7 +73,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} @@ -90,7 +91,7 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} diff --git a/packages/client/src/components/app/forms/MultiFieldSelect.svelte b/packages/client/src/components/app/forms/MultiFieldSelect.svelte index cb4879f86e..4ee691061a 100644 --- a/packages/client/src/components/app/forms/MultiFieldSelect.svelte +++ b/packages/client/src/components/app/forms/MultiFieldSelect.svelte @@ -56,6 +56,7 @@ {field} {label} {disabled} + {readonly} {validation} defaultValue={expandedDefaultValue} type="array" @@ -72,7 +73,7 @@ getOptionValue={flatOptions ? x => x : x => x.value} id={fieldState.fieldId} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} on:change={handleChange} {placeholder} {options} @@ -83,7 +84,7 @@ value={fieldState.value || []} id={fieldState.fieldId} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} {options} {direction} diff --git a/packages/client/src/components/app/forms/OptionsField.svelte b/packages/client/src/components/app/forms/OptionsField.svelte index c01827471a..dc18df8dbe 100644 --- a/packages/client/src/components/app/forms/OptionsField.svelte +++ b/packages/client/src/components/app/forms/OptionsField.svelte @@ -46,6 +46,7 @@ {field} {label} {disabled} + {readonly} {validation} {defaultValue} type="options" @@ -59,7 +60,7 @@ value={fieldState.value} id={fieldState.fieldId} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} {options} {placeholder} @@ -74,7 +75,7 @@ value={fieldState.value} id={fieldState.fieldId} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} {options} {direction} diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 9d2d6adf0f..10fcaa904f 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -184,6 +184,7 @@ {label} {field} {disabled} + {readonly} {validation} defaultValue={expandedDefaultValue} {type} @@ -201,7 +202,7 @@ on:loadMore={loadMore} id={fieldState.fieldId} disabled={fieldState.disabled} - {readonly} + readonly={fieldState.readonly} error={fieldState.error} getOptionLabel={getDisplayName} getOptionValue={option => option._id} diff --git a/packages/client/src/components/app/forms/StringField.svelte b/packages/client/src/components/app/forms/StringField.svelte index 624611c733..674be9f1b2 100644 --- a/packages/client/src/components/app/forms/StringField.svelte +++ b/packages/client/src/components/app/forms/StringField.svelte @@ -7,11 +7,11 @@ export let placeholder export let type = "text" export let disabled = false + export let readonly = false export let validation export let defaultValue = "" export let align export let onChange - export let readonly = false let fieldState let fieldApi @@ -28,6 +28,7 @@ {label} {field} {disabled} + {readonly} {validation} {defaultValue} type={type === "number" ? "number" : "string"} @@ -40,12 +41,12 @@ value={fieldState.value} on:change={handleChange} disabled={fieldState.disabled} + readonly={fieldState.readonly} error={fieldState.error} id={fieldState.fieldId} {placeholder} {type} {align} - {readonly} /> {/if} </Field> From 5923ae2983d588d1f1d5a5fd3550a04cda47d671 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Wed, 1 Nov 2023 16:40:23 +0000 Subject: [PATCH 22/47] Make form block view readonly --- packages/bbui/src/Markdown/MarkdownEditor.svelte | 2 +- packages/client/manifest.json | 7 +------ .../src/components/app/blocks/form/InnerFormBlock.svelte | 3 ++- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/bbui/src/Markdown/MarkdownEditor.svelte b/packages/bbui/src/Markdown/MarkdownEditor.svelte index 27035d8033..225d25a0eb 100644 --- a/packages/bbui/src/Markdown/MarkdownEditor.svelte +++ b/packages/bbui/src/Markdown/MarkdownEditor.svelte @@ -56,7 +56,7 @@ easyMDEOptions={{ initialValue: value, placeholder, - toolbar: readonly ? false : undefined, + toolbar: disabled || readonly ? false : undefined, ...easyMDEOptions, }} /> diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 7559825267..749e2cf194 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -5640,12 +5640,7 @@ "type": "boolean", "label": "Disabled", "key": "disabled", - "defaultValue": false, - "dependsOn": { - "setting": "actionType", - "value": "View", - "invert": true - } + "defaultValue": false } ] }, diff --git a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte index f7e9a0d2ed..b2c9888c73 100644 --- a/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/InnerFormBlock.svelte @@ -136,7 +136,8 @@ actionType: actionType === "Create" ? "Create" : "Update", dataSource, size, - disabled: disabled || actionType === "View", + disabled, + readonly: !disabled && actionType === "View", }} styles={{ normal: { From 738082dc27e564530e7af107c99beb7c27956733 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Thu, 2 Nov 2023 15:49:16 +0000 Subject: [PATCH 23/47] Remove readonly key --- packages/bbui/src/Markdown/MarkdownEditor.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bbui/src/Markdown/MarkdownEditor.svelte b/packages/bbui/src/Markdown/MarkdownEditor.svelte index 225d25a0eb..6c711c9d28 100644 --- a/packages/bbui/src/Markdown/MarkdownEditor.svelte +++ b/packages/bbui/src/Markdown/MarkdownEditor.svelte @@ -45,7 +45,7 @@ const debouncedUpdate = debounce(update, 250) </script> -{#key (height, readonly)} +{#key height} <SpectrumMDE bind:mde scroll={true} From 12a7811847fd15cdd8e2e5b3c64e939460f54a56 Mon Sep 17 00:00:00 2001 From: Michael Drury <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 13:05:23 +0000 Subject: [PATCH 24/47] Revert "Reverting changes to bull parameters" --- packages/backend-core/src/index.ts | 1 + .../backend-core/src/queue/inMemoryQueue.ts | 2 +- packages/backend-core/src/queue/queue.ts | 20 ++++++-- packages/backend-core/src/utils/Duration.ts | 49 +++++++++++++++++++ packages/backend-core/src/utils/index.ts | 1 + .../src/utils/tests/Duration.spec.ts | 19 +++++++ 6 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 packages/backend-core/src/utils/Duration.ts create mode 100644 packages/backend-core/src/utils/tests/Duration.spec.ts diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index ffffd8240a..c7cf9f56cc 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -30,6 +30,7 @@ export * as timers from "./timers" export { default as env } from "./environment" export * as blacklist from "./blacklist" export * as docUpdates from "./docUpdates" +export * from "./utils/Duration" export { SearchParams } from "./db" // Add context to tenancy for backwards compatibility // only do this for external usages to prevent internal diff --git a/packages/backend-core/src/queue/inMemoryQueue.ts b/packages/backend-core/src/queue/inMemoryQueue.ts index af2ec6dbaa..a8add7ecb6 100644 --- a/packages/backend-core/src/queue/inMemoryQueue.ts +++ b/packages/backend-core/src/queue/inMemoryQueue.ts @@ -36,7 +36,7 @@ class InMemoryQueue { * @param opts This is not used by the in memory queue as there is no real use * case when in memory, but is the same API as Bull */ - constructor(name: string, opts = null) { + constructor(name: string, opts?: any) { this._name = name this._opts = opts this._messages = [] diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index 0658147709..c0d1861de3 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -2,11 +2,18 @@ import env from "../environment" import { getRedisOptions } from "../redis/utils" import { JobQueue } from "./constants" import InMemoryQueue from "./inMemoryQueue" -import BullQueue from "bull" +import BullQueue, { QueueOptions } from "bull" import { addListeners, StalledFn } from "./listeners" +import { Duration } from "../utils" import * as timers from "../timers" +import * as Redis from "ioredis" -const CLEANUP_PERIOD_MS = 60 * 1000 +// the queue lock is held for 5 minutes +const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs() +// queue lock is refreshed every 30 seconds +const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs() +// cleanup the queue every 60 seconds +const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs() let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = [] let cleanupInterval: NodeJS.Timeout @@ -21,7 +28,14 @@ export function createQueue<T>( opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue<T> { const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() - const queueConfig: any = redisProtocolUrl || { redis: redisOpts } + const queueConfig: QueueOptions = { + redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions), + settings: { + maxStalledCount: 0, + lockDuration: QUEUE_LOCK_MS, + lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS, + }, + } let queue: any if (!env.isTest()) { queue = new BullQueue(jobQueue, queueConfig) diff --git a/packages/backend-core/src/utils/Duration.ts b/packages/backend-core/src/utils/Duration.ts new file mode 100644 index 0000000000..f376c2f7c7 --- /dev/null +++ b/packages/backend-core/src/utils/Duration.ts @@ -0,0 +1,49 @@ +export enum DurationType { + MILLISECONDS = "milliseconds", + SECONDS = "seconds", + MINUTES = "minutes", + HOURS = "hours", + DAYS = "days", +} + +const conversion: Record<DurationType, number> = { + milliseconds: 1, + seconds: 1000, + minutes: 60 * 1000, + hours: 60 * 60 * 1000, + days: 24 * 60 * 60 * 1000, +} + +export class Duration { + static convert(from: DurationType, to: DurationType, duration: number) { + const milliseconds = duration * conversion[from] + return milliseconds / conversion[to] + } + + static from(from: DurationType, duration: number) { + return { + to: (to: DurationType) => { + return Duration.convert(from, to, duration) + }, + toMs: () => { + return Duration.convert(from, DurationType.MILLISECONDS, duration) + }, + } + } + + static fromSeconds(duration: number) { + return Duration.from(DurationType.SECONDS, duration) + } + + static fromMinutes(duration: number) { + return Duration.from(DurationType.MINUTES, duration) + } + + static fromHours(duration: number) { + return Duration.from(DurationType.HOURS, duration) + } + + static fromDays(duration: number) { + return Duration.from(DurationType.DAYS, duration) + } +} diff --git a/packages/backend-core/src/utils/index.ts b/packages/backend-core/src/utils/index.ts index 318a7f13ba..ac17227459 100644 --- a/packages/backend-core/src/utils/index.ts +++ b/packages/backend-core/src/utils/index.ts @@ -1,3 +1,4 @@ export * from "./hashing" export * from "./utils" export * from "./stringUtils" +export * from "./Duration" diff --git a/packages/backend-core/src/utils/tests/Duration.spec.ts b/packages/backend-core/src/utils/tests/Duration.spec.ts new file mode 100644 index 0000000000..46b996f788 --- /dev/null +++ b/packages/backend-core/src/utils/tests/Duration.spec.ts @@ -0,0 +1,19 @@ +import { Duration, DurationType } from "../Duration" + +describe("duration", () => { + it("should convert minutes to milliseconds", () => { + expect(Duration.fromMinutes(5).toMs()).toBe(300000) + }) + + it("should convert seconds to milliseconds", () => { + expect(Duration.fromSeconds(30).toMs()).toBe(30000) + }) + + it("should convert days to milliseconds", () => { + expect(Duration.fromDays(1).toMs()).toBe(86400000) + }) + + it("should convert minutes to days", () => { + expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1) + }) +}) From a03a00c4af0e3deffb55c16c24c0a37748fae594 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Fri, 3 Nov 2023 14:08:46 +0000 Subject: [PATCH 25/47] Toggle preview --- packages/bbui/src/Markdown/MarkdownEditor.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/bbui/src/Markdown/MarkdownEditor.svelte b/packages/bbui/src/Markdown/MarkdownEditor.svelte index 6c711c9d28..888187c8da 100644 --- a/packages/bbui/src/Markdown/MarkdownEditor.svelte +++ b/packages/bbui/src/Markdown/MarkdownEditor.svelte @@ -20,7 +20,9 @@ // control $: checkValue(value) $: mde?.codemirror.on("change", debouncedUpdate) - $: mde?.codemirror.setOption("readOnly", readonly) + $: if (readonly || disabled) { + mde?.togglePreview() + } const checkValue = val => { if (mde && val !== latestValue) { From 86f7bd192fb4c6bbe12636e94cca69dc28957d23 Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 14:55:56 +0000 Subject: [PATCH 26/47] Moving audit log init to be part of the server startup. --- packages/worker/src/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index 3905a21c73..4b1d11ecf7 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -31,10 +31,6 @@ import destroyable from "server-destroy" import { initPro } from "./initPro" import { handleScimBody } from "./middleware/handleScimBody" -// configure events to use the pro audit log write -// can't integrate directly into backend-core due to cyclic issues -events.processors.init(proSdk.auditLogs.write) - if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE) { console.warn( "Warning: ENABLE_SSO_MAINTENANCE_MODE is set. It is recommended this flag is disabled if maintenance is not in progress" @@ -93,6 +89,9 @@ export default server.listen(parseInt(env.PORT || "4002"), async () => { console.log(`Worker running on ${JSON.stringify(server.address())}`) await initPro() await redis.init() + // configure events to use the pro audit log write + // can't integrate directly into backend-core due to cyclic issues + await events.processors.init(proSdk.auditLogs.write) }) process.on("uncaughtException", err => { From f8f1ec4ce930f93e57a74fa57679b9cc582168db Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 17:17:20 +0000 Subject: [PATCH 27/47] Removing old redisProtocol string - it is causing confusion and should not be necessary. --- packages/backend-core/src/queue/queue.ts | 5 ++-- packages/backend-core/src/redis/redis.ts | 4 +-- packages/backend-core/src/redis/utils.ts | 38 +++++++++++++----------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index c0d1861de3..b460a7312b 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -6,7 +6,6 @@ import BullQueue, { QueueOptions } from "bull" import { addListeners, StalledFn } from "./listeners" import { Duration } from "../utils" import * as timers from "../timers" -import * as Redis from "ioredis" // the queue lock is held for 5 minutes const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs() @@ -27,9 +26,9 @@ export function createQueue<T>( jobQueue: JobQueue, opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue<T> { - const { opts: redisOpts, redisProtocolUrl } = getRedisOptions() + const { opts: redisOpts } = getRedisOptions() const queueConfig: QueueOptions = { - redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions), + redis: redisOpts, settings: { maxStalledCount: 0, lockDuration: QUEUE_LOCK_MS, diff --git a/packages/backend-core/src/redis/redis.ts b/packages/backend-core/src/redis/redis.ts index d1e2d8989e..1ae2bd7794 100644 --- a/packages/backend-core/src/redis/redis.ts +++ b/packages/backend-core/src/redis/redis.ts @@ -91,12 +91,10 @@ function init(selectDb = DEFAULT_SELECT_DB) { if (client) { client.disconnect() } - const { redisProtocolUrl, opts, host, port } = getRedisOptions() + const { opts, host, port } = getRedisOptions() if (CLUSTERED) { client = new RedisCore.Cluster([{ host, port }], opts) - } else if (redisProtocolUrl) { - client = new RedisCore(redisProtocolUrl) } else { client = new RedisCore(opts) } diff --git a/packages/backend-core/src/redis/utils.ts b/packages/backend-core/src/redis/utils.ts index 34b7275a2b..6cac7b2633 100644 --- a/packages/backend-core/src/redis/utils.ts +++ b/packages/backend-core/src/redis/utils.ts @@ -1,4 +1,5 @@ import env from "../environment" +import * as Redis from "ioredis" const SLOT_REFRESH_MS = 2000 const CONNECT_TIMEOUT_MS = 10000 @@ -74,28 +75,29 @@ export function getRedisOptions() { } const [host, port] = url.split(":") - let redisProtocolUrl - - // fully qualified redis URL - if (/rediss?:\/\//.test(env.REDIS_URL)) { - redisProtocolUrl = env.REDIS_URL - } - - const opts: any = { + let redisOpts: Redis.RedisOptions = { connectTimeout: CONNECT_TIMEOUT_MS, + port: parseInt(port), + host, + password, } + let opts: Redis.ClusterOptions | Redis.RedisOptions = redisOpts if (env.REDIS_CLUSTERED) { - opts.redisOptions = {} - opts.redisOptions.tls = {} - opts.redisOptions.password = password - opts.slotsRefreshTimeout = SLOT_REFRESH_MS - opts.dnsLookup = (address: string, callback: any) => callback(null, address) - } else { - opts.host = host - opts.port = port - opts.password = password + opts = { + connectTimeout: CONNECT_TIMEOUT_MS, + redisOptions: { + ...redisOpts, + tls: {}, + }, + slotsRefreshTimeout: SLOT_REFRESH_MS, + dnsLookup: (address: string, callback: any) => callback(null, address), + } as Redis.ClusterOptions + } + return { + opts, + host, + port: parseInt(port), } - return { opts, host, port: parseInt(port), redisProtocolUrl } } export function addDbPrefix(db: string, key: string) { From 001cf0130360ea5874fc4979bad65c8c557a1e74 Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 18:00:13 +0000 Subject: [PATCH 28/47] Updating public API rate limiting functionality to be better typed as well. --- packages/backend-core/src/redis/utils.ts | 3 +- .../server/src/api/routes/public/index.ts | 47 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/backend-core/src/redis/utils.ts b/packages/backend-core/src/redis/utils.ts index 6cac7b2633..e0bdcfcd20 100644 --- a/packages/backend-core/src/redis/utils.ts +++ b/packages/backend-core/src/redis/utils.ts @@ -43,7 +43,7 @@ export enum Databases { export enum SelectableDatabase { DEFAULT = 0, SOCKET_IO = 1, - UNUSED_1 = 2, + RATE_LIMITING = 2, UNUSED_2 = 3, UNUSED_3 = 4, UNUSED_4 = 5, @@ -96,6 +96,7 @@ export function getRedisOptions() { return { opts, host, + password, port: parseInt(port), } } diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts index 4cc1eff8a4..ab10b2ed74 100644 --- a/packages/server/src/api/routes/public/index.ts +++ b/packages/server/src/api/routes/public/index.ts @@ -15,6 +15,16 @@ import env from "../../../environment" const Router = require("@koa/router") const { RateLimit, Stores } = require("koa2-ratelimit") import { middleware, redis } from "@budibase/backend-core" +import { SelectableDatabase } from "@budibase/backend-core/src/redis/utils" + +interface KoaRateLimitOptions { + socket: { + host: string + port: number + } + password?: string + database?: number +} const PREFIX = "/api/public/v1" // allow a lot more requests when in test @@ -29,32 +39,21 @@ function getApiLimitPerSecond(): number { let rateLimitStore: any = null if (!env.isTest()) { - const REDIS_OPTS = redis.utils.getRedisOptions() - let options - if (REDIS_OPTS.redisProtocolUrl) { - // fully qualified redis URL - options = { - url: REDIS_OPTS.redisProtocolUrl, - } - } else { - options = { - socket: { - host: REDIS_OPTS.host, - port: REDIS_OPTS.port, - }, - } + const { password, host, port } = redis.utils.getRedisOptions() + let options: KoaRateLimitOptions = { + socket: { + host: host, + port: port, + }, + } - if (REDIS_OPTS.opts?.password || REDIS_OPTS.opts.redisOptions?.password) { - // @ts-ignore - options.password = - REDIS_OPTS.opts.password || REDIS_OPTS.opts.redisOptions.password - } + if (password) { + options.password = password + } - if (!env.REDIS_CLUSTERED) { - // @ts-ignore - // Can't set direct redis db in clustered env - options.database = 1 - } + if (!env.REDIS_CLUSTERED) { + // Can't set direct redis db in clustered env + options.database = SelectableDatabase.RATE_LIMITING } rateLimitStore = new Stores.Redis(options) RateLimit.defaultOptions({ From 7bf307b0c2e86284b93741e94e0e106261d2e638 Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 18:03:11 +0000 Subject: [PATCH 29/47] Further updates to typing. --- packages/backend-core/src/redis/utils.ts | 20 +++++++++++-------- .../server/src/api/routes/public/index.ts | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/backend-core/src/redis/utils.ts b/packages/backend-core/src/redis/utils.ts index e0bdcfcd20..5187fe13f8 100644 --- a/packages/backend-core/src/redis/utils.ts +++ b/packages/backend-core/src/redis/utils.ts @@ -59,7 +59,7 @@ export enum SelectableDatabase { UNUSED_14 = 15, } -export function getRedisOptions() { +export function getRedisConnectionDetails() { let password = env.REDIS_PASSWORD let url: string[] | string = env.REDIS_URL.split("//") // get rid of the protocol @@ -75,9 +75,18 @@ export function getRedisOptions() { } const [host, port] = url.split(":") + return { + host, + password, + port: parseInt(port), + } +} + +export function getRedisOptions() { + const { host, password, port } = getRedisConnectionDetails() let redisOpts: Redis.RedisOptions = { connectTimeout: CONNECT_TIMEOUT_MS, - port: parseInt(port), + port: port, host, password, } @@ -93,12 +102,7 @@ export function getRedisOptions() { dnsLookup: (address: string, callback: any) => callback(null, address), } as Redis.ClusterOptions } - return { - opts, - host, - password, - port: parseInt(port), - } + return opts } export function addDbPrefix(db: string, key: string) { diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts index ab10b2ed74..b37ed931fc 100644 --- a/packages/server/src/api/routes/public/index.ts +++ b/packages/server/src/api/routes/public/index.ts @@ -39,7 +39,7 @@ function getApiLimitPerSecond(): number { let rateLimitStore: any = null if (!env.isTest()) { - const { password, host, port } = redis.utils.getRedisOptions() + const { password, host, port } = redis.utils.getRedisConnectionDetails() let options: KoaRateLimitOptions = { socket: { host: host, From 08c4ba00975d074c10f01cabc79aa92089f8022b Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Fri, 3 Nov 2023 18:06:12 +0000 Subject: [PATCH 30/47] Updating redis option functions usage, as it is no longer returned as a part of the getRedisOptions response. --- packages/backend-core/src/queue/queue.ts | 2 +- packages/backend-core/src/redis/redis.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/queue/queue.ts b/packages/backend-core/src/queue/queue.ts index b460a7312b..0657437a3b 100644 --- a/packages/backend-core/src/queue/queue.ts +++ b/packages/backend-core/src/queue/queue.ts @@ -26,7 +26,7 @@ export function createQueue<T>( jobQueue: JobQueue, opts: { removeStalledCb?: StalledFn } = {} ): BullQueue.Queue<T> { - const { opts: redisOpts } = getRedisOptions() + const redisOpts = getRedisOptions() const queueConfig: QueueOptions = { redis: redisOpts, settings: { diff --git a/packages/backend-core/src/redis/redis.ts b/packages/backend-core/src/redis/redis.ts index 1ae2bd7794..6f1b573718 100644 --- a/packages/backend-core/src/redis/redis.ts +++ b/packages/backend-core/src/redis/redis.ts @@ -16,6 +16,7 @@ import { getRedisOptions, SEPARATOR, SelectableDatabase, + getRedisConnectionDetails, } from "./utils" import * as timers from "../timers" @@ -91,7 +92,8 @@ function init(selectDb = DEFAULT_SELECT_DB) { if (client) { client.disconnect() } - const { opts, host, port } = getRedisOptions() + const { host, port } = getRedisConnectionDetails() + const opts = getRedisOptions() if (CLUSTERED) { client = new RedisCore.Cluster([{ host, port }], opts) From c43bfda45df9acac45ca136156a41046547283b3 Mon Sep 17 00:00:00 2001 From: mike12345567 <me@michaeldrury.co.uk> Date: Mon, 6 Nov 2023 12:38:10 +0000 Subject: [PATCH 31/47] Fix for user invitations throwing a 501 error due to the way the search was being performed. --- .../app/[application]/_components/BuilderSidePanel.svelte | 4 ++-- packages/frontend-core/src/fetch/UserFetch.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index f9a40b09a6..a67c2d3c61 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -112,9 +112,9 @@ } await usersFetch.update({ query: { - appId: query || !filterByAppAccess ? null : prodAppId, - email: query, + string: { email: query }, }, + appId: query || !filterByAppAccess ? null : prodAppId, limit: 50, paginate: query || !filterByAppAccess ? null : false, }) diff --git a/packages/frontend-core/src/fetch/UserFetch.js b/packages/frontend-core/src/fetch/UserFetch.js index b1478c3a6d..65bfe36058 100644 --- a/packages/frontend-core/src/fetch/UserFetch.js +++ b/packages/frontend-core/src/fetch/UserFetch.js @@ -33,7 +33,7 @@ export default class UserFetch extends DataFetch { let finalQuery // convert old format to new one - we now allow use of the lucene format const { appId, paginated, ...rest } = query - if (!LuceneUtils.hasFilters(query) && rest.email) { + if (!LuceneUtils.hasFilters(query) && rest.email != null) { finalQuery = { string: { email: rest.email } } } else { finalQuery = rest From 6ee7ae953d9c23a1931ed8d0311f3a340d1bf7d2 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Mon, 6 Nov 2023 13:56:31 +0000 Subject: [PATCH 32/47] Don't trigger required validation early --- .../client/src/components/app/forms/RelationshipField.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 544a1a8434..2c567c49aa 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -137,7 +137,9 @@ typeof value === "object" ? value._id : value ) // Make sure field state is valid - fieldApi.setValue(values) + if (values?.length > 0) { + fieldApi.setValue(values) + } return values } From dbcbb2e6b7680ebb6cfb7d01b1d4663703a35c12 Mon Sep 17 00:00:00 2001 From: Sam Rose <hello@samwho.dev> Date: Mon, 6 Nov 2023 15:33:02 +0000 Subject: [PATCH 33/47] Add test to row patch endpoint, it succeeds. Problem must be elsewhere. --- .../server/src/api/routes/tests/row.spec.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 92d581d930..48f7ab4f09 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -563,6 +563,56 @@ describe.each([ await assertRowUsage(rowUsage) await assertQueryUsage(queryUsage) }) + + it("should not overwrite links if those links are not set", async () => { + let linkField: FieldSchema = { + type: FieldType.LINK, + name: "", + fieldName: "", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.ONE_TO_MANY, + tableId: InternalTable.USER_METADATA, + } + + let table = await config.api.table.create({ + name: "TestTable", + type: "table", + sourceType: TableSourceType.INTERNAL, + sourceId: INTERNAL_TABLE_SOURCE_ID, + schema: { + user1: { ...linkField, name: "user1", fieldName: "user1" }, + user2: { ...linkField, name: "user2", fieldName: "user2" }, + }, + }) + + let user1 = await config.createUser() + let user2 = await config.createUser() + + let row = await config.api.row.save(table._id!, { + user1: [{ _id: user1._id }], + user2: [{ _id: user2._id }], + }) + + let getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user1._id) + expect(getResp.body.user2[0]._id).toEqual(user2._id) + + let patchResp = await config.api.row.patch(table._id!, { + _id: row._id!, + _rev: row._rev!, + tableId: table._id!, + user1: [{ _id: user2._id }], + }) + expect(patchResp.user1[0]._id).toEqual(user2._id) + expect(patchResp.user2[0]._id).toEqual(user2._id) + + getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user2._id) + expect(getResp.body.user2[0]._id).toEqual(user2._id) + }) }) describe("destroy", () => { From b02512fd3c4203b6dc6697b5a6b7500e14612971 Mon Sep 17 00:00:00 2001 From: Sam Rose <hello@samwho.dev> Date: Mon, 6 Nov 2023 15:56:58 +0000 Subject: [PATCH 34/47] Create a test of a table with 2 link fields in updateRow.spec.ts. --- .../src/automations/tests/updateRow.spec.js | 44 ------- .../src/automations/tests/updateRow.spec.ts | 107 ++++++++++++++++++ .../src/automations/tests/utilities/index.ts | 6 +- 3 files changed, 110 insertions(+), 47 deletions(-) delete mode 100644 packages/server/src/automations/tests/updateRow.spec.js create mode 100644 packages/server/src/automations/tests/updateRow.spec.ts diff --git a/packages/server/src/automations/tests/updateRow.spec.js b/packages/server/src/automations/tests/updateRow.spec.js deleted file mode 100644 index 77383d80e9..0000000000 --- a/packages/server/src/automations/tests/updateRow.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -const setup = require("./utilities") - -describe("test the update row action", () => { - let table, row, inputs - let config = setup.getConfig() - - beforeAll(async () => { - await config.init() - table = await config.createTable() - row = await config.createRow() - inputs = { - rowId: row._id, - row: { - ...row, - name: "Updated name", - // put a falsy option in to be removed - description: "", - } - } - }) - - afterAll(setup.afterAll) - - it("should be able to run the action", async () => { - const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs) - expect(res.success).toEqual(true) - const updatedRow = await config.getRow(table._id, res.id) - expect(updatedRow.name).toEqual("Updated name") - expect(updatedRow.description).not.toEqual("") - }) - - it("should check invalid inputs return an error", async () => { - const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {}) - expect(res.success).toEqual(false) - }) - - it("should return an error when table doesn't exist", async () => { - const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { - row: { _id: "invalid" }, - rowId: "invalid", - }) - expect(res.success).toEqual(false) - }) -}) diff --git a/packages/server/src/automations/tests/updateRow.spec.ts b/packages/server/src/automations/tests/updateRow.spec.ts new file mode 100644 index 0000000000..63925b0d3b --- /dev/null +++ b/packages/server/src/automations/tests/updateRow.spec.ts @@ -0,0 +1,107 @@ +import { + FieldSchema, + FieldType, + INTERNAL_TABLE_SOURCE_ID, + InternalTable, + RelationshipType, + Row, + Table, + TableSourceType, +} from "@budibase/types" + +import * as setup from "./utilities" + +describe("test the update row action", () => { + let table: Table, row: Row, inputs: any + let config = setup.getConfig() + + beforeAll(async () => { + await config.init() + table = await config.createTable() + row = await config.createRow() + inputs = { + rowId: row._id, + row: { + ...row, + name: "Updated name", + // put a falsy option in to be removed + description: "", + }, + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs) + expect(res.success).toEqual(true) + const updatedRow = await config.getRow(table._id!, res.id) + expect(updatedRow.name).toEqual("Updated name") + expect(updatedRow.description).not.toEqual("") + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("should return an error when table doesn't exist", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { + row: { _id: "invalid" }, + rowId: "invalid", + }) + expect(res.success).toEqual(false) + }) + + it("should not overwrite links if those links are not set", async () => { + let linkField: FieldSchema = { + type: FieldType.LINK, + name: "", + fieldName: "", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.ONE_TO_MANY, + tableId: InternalTable.USER_METADATA, + } + + let table = await config.api.table.create({ + name: "TestTable", + type: "table", + sourceType: TableSourceType.INTERNAL, + sourceId: INTERNAL_TABLE_SOURCE_ID, + schema: { + user1: { ...linkField, name: "user1", fieldName: "user1" }, + user2: { ...linkField, name: "user2", fieldName: "user2" }, + }, + }) + + let user1 = await config.createUser() + let user2 = await config.createUser() + + let row = await config.api.row.save(table._id!, { + user1: [{ _id: user1._id }], + user2: [{ _id: user2._id }], + }) + + let getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user1._id) + expect(getResp.body.user2[0]._id).toEqual(user2._id) + + let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { + rowId: row._id, + row: { + _id: row._id, + _rev: row._rev, + tableId: row.tableId, + user1: [{ _id: user2._id }], + }, + }) + expect(stepResp.success).toEqual(true) + + getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user2._id) + expect(getResp.body.user2[0]._id).toEqual(user2._id) + }) +}) diff --git a/packages/server/src/automations/tests/utilities/index.ts b/packages/server/src/automations/tests/utilities/index.ts index 9ba4f950f3..cd3ea289ca 100644 --- a/packages/server/src/automations/tests/utilities/index.ts +++ b/packages/server/src/automations/tests/utilities/index.ts @@ -4,11 +4,11 @@ import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions" import emitter from "../../../events/index" import env from "../../../environment" -let config: any +let config: TestConfig -export function getConfig() { +export function getConfig(): TestConfig { if (!config) { - config = new TestConfig(false) + config = new TestConfig(true) } return config } From 2684b73768ce990b542a7575feeb864b1fbb613c Mon Sep 17 00:00:00 2001 From: Sam Rose <hello@samwho.dev> Date: Mon, 6 Nov 2023 16:40:27 +0000 Subject: [PATCH 35/47] Fix type error. --- packages/server/src/automations/tests/automation.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/automations/tests/automation.spec.ts b/packages/server/src/automations/tests/automation.spec.ts index 67ff6d40ec..c37c9cc7ce 100644 --- a/packages/server/src/automations/tests/automation.spec.ts +++ b/packages/server/src/automations/tests/automation.spec.ts @@ -36,7 +36,7 @@ describe("Run through some parts of the automations system", () => { it("should be able to init in builder", async () => { const automation: Automation = { ...basicAutomation(), - appId: config.appId, + appId: config.appId!, } const fields: any = { a: 1, appId: config.appId } await triggers.externalTrigger(automation, fields) From 9bb9fb55496b05ac81f379a106c1de9dc2b48f4d Mon Sep 17 00:00:00 2001 From: Dean <deanhannigan@gmail.com> Date: Mon, 6 Nov 2023 17:00:47 +0000 Subject: [PATCH 36/47] Reintroduce filtering and sorting for exported row data --- packages/server/src/api/controllers/row/index.ts | 4 +++- packages/server/src/sdk/app/rows/search.ts | 10 +++++++++- packages/server/src/sdk/app/rows/search/external.ts | 10 ++++++---- packages/server/src/sdk/app/rows/search/internal.ts | 9 +++++++-- packages/types/src/api/web/app/rows.ts | 3 +++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 1a6747a085..018283c8c5 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -254,7 +254,7 @@ export const exportRows = async ( const format = ctx.query.format - const { rows, columns, query } = ctx.request.body + const { rows, columns, query, sort, sortOrder } = ctx.request.body if (typeof format !== "string" || !exporters.isFormat(format)) { ctx.throw( 400, @@ -272,6 +272,8 @@ export const exportRows = async ( rowIds: rows, columns, query, + sort, + sortOrder, }) ctx.attachment(fileName) return apiFileReturn(content) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 31f7c74601..36ef538d8b 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,4 +1,10 @@ -import { Row, SearchFilters, SearchParams } from "@budibase/types" +import { + Row, + SearchFilters, + SearchParams, + SortOrder, + SortType, +} from "@budibase/types" import { isExternalTableID } from "../../../integrations/utils" import * as internal from "./search/internal" import * as external from "./search/external" @@ -32,6 +38,8 @@ export interface ExportRowsParams { rowIds?: string[] columns?: string[] query?: SearchFilters + sort?: string + sortOrder?: SortOrder } export interface ExportRowsResult { diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 981ae1bf8d..974900ba6d 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -98,12 +98,12 @@ export async function search(options: SearchParams) { export async function exportRows( options: ExportRowsParams ): Promise<ExportRowsResult> { - const { tableId, format, columns, rowIds } = options + const { tableId, format, columns, rowIds, query, sort, sortOrder } = options const { datasourceId, tableName } = breakExternalTableId(tableId) - let query: SearchFilters = {} + let requestQuery: SearchFilters = {} if (rowIds?.length) { - query = { + requestQuery = { oneOf: { _id: rowIds.map((row: string) => { const ids = JSON.parse( @@ -119,6 +119,8 @@ export async function exportRows( }), }, } + } else { + requestQuery = query || {} } const datasource = await sdk.datasources.get(datasourceId!) @@ -126,7 +128,7 @@ export async function exportRows( throw new HTTPError("Datasource has not been configured for plus API.", 400) } - let result = await search({ tableId, query }) + let result = await search({ tableId, query: requestQuery, sort, sortOrder }) let rows: Row[] = [] // Filter data to only specified columns if required diff --git a/packages/server/src/sdk/app/rows/search/internal.ts b/packages/server/src/sdk/app/rows/search/internal.ts index 1aec8a321e..e31bda1a15 100644 --- a/packages/server/src/sdk/app/rows/search/internal.ts +++ b/packages/server/src/sdk/app/rows/search/internal.ts @@ -84,7 +84,7 @@ export async function search(options: SearchParams) { export async function exportRows( options: ExportRowsParams ): Promise<ExportRowsResult> { - const { tableId, format, rowIds, columns, query } = options + const { tableId, format, rowIds, columns, query, sort, sortOrder } = options const db = context.getAppDB() const table = await sdk.tables.getTable(tableId) @@ -99,7 +99,12 @@ export async function exportRows( result = await outputProcessing(table, response) } else if (query) { - let searchResponse = await search({ tableId, query }) + let searchResponse = await search({ + tableId, + query, + sort, + sortOrder, + }) result = searchResponse.rows } diff --git a/packages/types/src/api/web/app/rows.ts b/packages/types/src/api/web/app/rows.ts index 62ea90a6a4..dad3286754 100644 --- a/packages/types/src/api/web/app/rows.ts +++ b/packages/types/src/api/web/app/rows.ts @@ -1,5 +1,6 @@ import { SearchFilters, SearchParams } from "../../../sdk" import { Row } from "../../../documents" +import { SortOrder } from "../../../api" import { ReadStream } from "fs" export interface SaveRowRequest extends Row {} @@ -34,6 +35,8 @@ export interface ExportRowsRequest { rows: string[] columns?: string[] query?: SearchFilters + sort?: string + sortOrder?: SortOrder } export type ExportRowsResponse = ReadStream From f21addeb7170f5e83a662e9fb05b6f3d712f8798 Mon Sep 17 00:00:00 2001 From: Sam Rose <hello@samwho.dev> Date: Mon, 6 Nov 2023 17:34:30 +0000 Subject: [PATCH 37/47] Add another test to make sure relationships are cleared when asked. --- .../src/automations/tests/updateRow.spec.ts | 70 +++++++++++++++++-- .../server/src/tests/utilities/api/table.ts | 10 ++- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/packages/server/src/automations/tests/updateRow.spec.ts b/packages/server/src/automations/tests/updateRow.spec.ts index 63925b0d3b..7e369f1ecb 100644 --- a/packages/server/src/automations/tests/updateRow.spec.ts +++ b/packages/server/src/automations/tests/updateRow.spec.ts @@ -10,6 +10,7 @@ import { } from "@budibase/types" import * as setup from "./utilities" +import * as uuid from "uuid" describe("test the update row action", () => { let table: Table, row: Row, inputs: any @@ -67,13 +68,13 @@ describe("test the update row action", () => { } let table = await config.api.table.create({ - name: "TestTable", + name: uuid.v4(), type: "table", sourceType: TableSourceType.INTERNAL, sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { - user1: { ...linkField, name: "user1", fieldName: "user1" }, - user2: { ...linkField, name: "user2", fieldName: "user2" }, + user1: { ...linkField, name: "user1", fieldName: uuid.v4() }, + user2: { ...linkField, name: "user2", fieldName: uuid.v4() }, }, }) @@ -95,7 +96,8 @@ describe("test the update row action", () => { _id: row._id, _rev: row._rev, tableId: row.tableId, - user1: [{ _id: user2._id }], + user1: [user2._id], + user2: "", }, }) expect(stepResp.success).toEqual(true) @@ -104,4 +106,64 @@ describe("test the update row action", () => { expect(getResp.body.user1[0]._id).toEqual(user2._id) expect(getResp.body.user2[0]._id).toEqual(user2._id) }) + + it("should overwrite links if those links are not set and we ask it do", async () => { + let linkField: FieldSchema = { + type: FieldType.LINK, + name: "", + fieldName: "", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.ONE_TO_MANY, + tableId: InternalTable.USER_METADATA, + } + + let table = await config.api.table.create({ + name: uuid.v4(), + type: "table", + sourceType: TableSourceType.INTERNAL, + sourceId: INTERNAL_TABLE_SOURCE_ID, + schema: { + user1: { ...linkField, name: "user1", fieldName: uuid.v4() }, + user2: { ...linkField, name: "user2", fieldName: uuid.v4() }, + }, + }) + + let user1 = await config.createUser() + let user2 = await config.createUser() + + let row = await config.api.row.save(table._id!, { + user1: [{ _id: user1._id }], + user2: [{ _id: user2._id }], + }) + + let getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user1._id) + expect(getResp.body.user2[0]._id).toEqual(user2._id) + + let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { + rowId: row._id, + row: { + _id: row._id, + _rev: row._rev, + tableId: row.tableId, + user1: [user2._id], + user2: "", + }, + meta: { + fields: { + user2: { + clearRelationships: true, + }, + }, + }, + }) + expect(stepResp.success).toEqual(true) + + getResp = await config.api.row.get(table._id!, row._id!) + expect(getResp.body.user1[0]._id).toEqual(user2._id) + expect(getResp.body.user2).toBeUndefined() + }) }) diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts index b80c940697..ffd9e19ee8 100644 --- a/packages/server/src/tests/utilities/api/table.ts +++ b/packages/server/src/tests/utilities/api/table.ts @@ -22,7 +22,15 @@ export class TableAPI extends TestAPI { .send(data) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(expectStatus) + + if (res.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + res.status + } with body ${JSON.stringify(res.body)}` + ) + } + return res.body } From a5246bc08aa8a335d1d06efa7bfbd395cdd39d49 Mon Sep 17 00:00:00 2001 From: Greg <129239095+gcglinton@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:53:22 -0500 Subject: [PATCH 38/47] Update nginx-ssl.conf --- hosting/letsencrypt/nginx-ssl.conf | 61 +++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/hosting/letsencrypt/nginx-ssl.conf b/hosting/letsencrypt/nginx-ssl.conf index 50c5e0198a..b3f51e5cc5 100644 --- a/hosting/letsencrypt/nginx-ssl.conf +++ b/hosting/letsencrypt/nginx-ssl.conf @@ -2,16 +2,18 @@ server { listen 443 ssl default_server; listen [::]:443 ssl default_server; server_name _; - ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem; - include /etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - + error_log /dev/stderr warn; + access_log /dev/stdout main; client_max_body_size 1000m; ignore_invalid_headers off; proxy_buffering off; # port_in_redirect off; + ssl_certificate /etc/letsencrypt/live/CUSTOM_DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/CUSTOM_DOMAIN/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/html; @@ -47,6 +49,24 @@ server { rewrite ^/worker/(.*)$ /$1 break; } + location /api/backups/ { + # calls to export apps are limited + limit_req zone=ratelimit burst=20 nodelay; + + # 1800s timeout for app export requests + proxy_read_timeout 1800s; + proxy_connect_timeout 1800s; + proxy_send_timeout 1800s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://127.0.0.1:4001; + } + location /api/ { # calls to the API are rate limited with bursting limit_req zone=ratelimit burst=20 nodelay; @@ -70,18 +90,49 @@ server { rewrite ^/db/(.*)$ /$1 break; } + location /socket/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_pass http://127.0.0.1:4001; + } + location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; proxy_connect_timeout 300; proxy_http_version 1.1; proxy_set_header Connection ""; chunked_transfer_encoding off; + proxy_pass http://127.0.0.1:9000; } + location /files/signed/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # IMPORTANT: Signed urls will inspect the host header of the request. + # Normally a signed url will need to be generated with a specified client host in mind. + # To support dynamic hosts, e.g. some unknown self-hosted installation url, + # use a predefined host header. The host 'minio-service' is also used at the time of url signing. + proxy_set_header Host minio-service; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://127.0.0.1:9000; + rewrite ^/files/signed/(.*)$ /$1 break; + } + client_header_timeout 60; client_body_timeout 60; keepalive_timeout 60; From 466d5350e23ea8cd5ac67eaa35e2bfb6374c691d Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Mon, 6 Nov 2023 19:50:27 +0000 Subject: [PATCH 39/47] Bump version to 2.13.1 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 611cf7d32b..2126adac29 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.0", + "version": "2.13.1", "npmClient": "yarn", "packages": [ "packages/*" From c169a7376499747dace7471320737cb22c121319 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan <mel@budibase.com> Date: Tue, 7 Nov 2023 09:19:29 +0000 Subject: [PATCH 40/47] readonly css --- packages/bbui/src/Form/Core/Checkbox.svelte | 9 ++++----- packages/bbui/src/Form/Core/CheckboxGroup.svelte | 9 ++++----- packages/bbui/src/Form/Core/RadioGroup.svelte | 9 ++++----- packages/client/src/components/app/forms/Field.svelte | 4 ++++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/bbui/src/Form/Core/Checkbox.svelte b/packages/bbui/src/Form/Core/Checkbox.svelte index 3eaaf4dede..e24f5669eb 100644 --- a/packages/bbui/src/Form/Core/Checkbox.svelte +++ b/packages/bbui/src/Form/Core/Checkbox.svelte @@ -25,16 +25,12 @@ class:is-invalid={!!error} class:checked={value} class:is-indeterminate={indeterminate} + class:readonly > <input checked={value} {disabled} on:change={onChange} - on:click={e => { - if (readonly) { - e.preventDefault() - } - }} type="checkbox" class="spectrum-Checkbox-input" {id} @@ -74,4 +70,7 @@ .spectrum-Checkbox-input { opacity: 0; } + .readonly { + pointer-events: none; + } </style> diff --git a/packages/bbui/src/Form/Core/CheckboxGroup.svelte b/packages/bbui/src/Form/Core/CheckboxGroup.svelte index faf37f3ad8..66ac55561b 100644 --- a/packages/bbui/src/Form/Core/CheckboxGroup.svelte +++ b/packages/bbui/src/Form/Core/CheckboxGroup.svelte @@ -35,17 +35,13 @@ title={getOptionLabel(option)} class="spectrum-Checkbox spectrum-FieldGroup-item" class:is-invalid={!!error} + class:readonly > <label class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-FieldGroup-item" > <input on:change={onChange} - on:click={e => { - if (readonly) { - e.preventDefault() - } - }} type="checkbox" class="spectrum-Checkbox-input" value={optionValue} @@ -72,4 +68,7 @@ .spectrum-Checkbox-input { opacity: 0; } + .readonly { + pointer-events: none; + } </style> diff --git a/packages/bbui/src/Form/Core/RadioGroup.svelte b/packages/bbui/src/Form/Core/RadioGroup.svelte index fc99fafd40..648c46b5fc 100644 --- a/packages/bbui/src/Form/Core/RadioGroup.svelte +++ b/packages/bbui/src/Form/Core/RadioGroup.svelte @@ -41,14 +41,10 @@ title={getOptionTitle(option)} class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized" class:is-invalid={!!error} + class:readonly > <input on:change={onChange} - on:click={e => { - if (readonly) { - e.preventDefault() - } - }} bind:group={value} value={getOptionValue(option)} type="radio" @@ -68,4 +64,7 @@ .spectrum-Radio-input { opacity: 0; } + .readonly { + pointer-events: none; + } </style> diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte index 1fa1aeda5a..8878b25989 100644 --- a/packages/client/src/components/app/forms/Field.svelte +++ b/packages/client/src/components/app/forms/Field.svelte @@ -78,6 +78,7 @@ contenteditable={$component.editing} on:blur={$component.editing ? updateLabel : null} class:hidden={!label} + class:readonly for={fieldState?.fieldId} class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelClass}`} > @@ -138,4 +139,7 @@ .spectrum-FieldLabel--left { padding-right: var(--spectrum-global-dimension-size-200); } + .readonly { + pointer-events: none; + } </style> From f28eb054f38b66c40b33d9b9f946481c15b3556f Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:59:51 +0000 Subject: [PATCH 41/47] Set the relationship type (#12275) * Set the relationship type * Add users type * Set user relationship type saveColumn * Set relationshipType for users type on save * Add relatioshipType to schema * Refactor * Check isUsersColumn * Make relationshipType optional --------- Co-authored-by: Michael Drury <me@michaeldrury.co.uk> --- .../backend/DataTable/modals/CreateEditColumn.svelte | 10 ++++++++++ packages/types/src/documents/app/table/schema.ts | 1 + 2 files changed, 11 insertions(+) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index d5a9aba488..4eb1f962f0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -44,6 +44,8 @@ const NUMBER_TYPE = FIELDS.NUMBER.type const JSON_TYPE = FIELDS.JSON.type const DATE_TYPE = FIELDS.DATETIME.type + const USER_TYPE = FIELDS.USER.subtype + const USERS_TYPE = FIELDS.USERS.subtype const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] @@ -287,6 +289,14 @@ if (saveColumn.type !== LINK_TYPE) { delete saveColumn.fieldName } + if (isUsersColumn(saveColumn)) { + if (saveColumn.subtype === USER_TYPE) { + saveColumn.relationshipType = RelationshipType.ONE_TO_MANY + } else if (saveColumn.subtype === USERS_TYPE) { + saveColumn.relationshipType = RelationshipType.MANY_TO_MANY + } + } + try { await tables.saveField({ originalName, diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 755ccf61e7..19a7303072 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -102,6 +102,7 @@ export interface BBReferenceFieldMetadata extends Omit<BaseFieldSchema, "subtype"> { type: FieldType.BB_REFERENCE subtype: FieldSubtype.USER | FieldSubtype.USERS + relationshipType?: RelationshipType } export interface FieldConstraints { From dc59245b39da5c4b8ed95cebba5931adf8028d4c Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 7 Nov 2023 12:20:21 +0000 Subject: [PATCH 42/47] Bump version to 2.13.2 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 2126adac29..bfcac5633c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.1", + "version": "2.13.2", "npmClient": "yarn", "packages": [ "packages/*" From 7dd4521ea83a3089902c66c8c9968668b7cbcf7a Mon Sep 17 00:00:00 2001 From: Adria Navarro <adria@budibase.com> Date: Tue, 7 Nov 2023 13:55:34 +0100 Subject: [PATCH 43/47] Fix fetching tag --- .github/workflows/tag-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index 78c07a037c..13d59d1019 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -45,8 +45,8 @@ jobs: BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"} ./versionCommit.sh $BUMP_TYPE - - new_version=$(./getCurrentVersion.sh) + cd .. + new_version=$(./scripts/getCurrentVersion.sh) echo "version=$new_version" >> $GITHUB_OUTPUT trigger-release: From cc5418e16e942267217ec62658ce191039634e37 Mon Sep 17 00:00:00 2001 From: Andrew Kingston <andrew@kingston.dev> Date: Tue, 7 Nov 2023 15:10:27 +0000 Subject: [PATCH 44/47] Update pull_request_template.md Remove colon in pull request template which break styling --- pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pull_request_template.md b/pull_request_template.md index 1dd1a1d45d..405059b2ab 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,7 +1,7 @@ ## Description _Describe the problem or feature in addition to a link to the relevant github issues._ -### Addresses: +## Addresses - `<Enter the Link to the issue(s) this PR addresses>` - ...more if required From 86cfd76b5a7edc02614431200a6640fc43b5421d Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 7 Nov 2023 16:37:31 +0000 Subject: [PATCH 45/47] Bump version to 2.13.3 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index bfcac5633c..25ec556c56 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.2", + "version": "2.13.3", "npmClient": "yarn", "packages": [ "packages/*" From 9d29e3dddab4daeb3b656e71a7fe1d5b7c726d74 Mon Sep 17 00:00:00 2001 From: Adria Navarro <adria@budibase.com> Date: Tue, 7 Nov 2023 18:31:44 +0100 Subject: [PATCH 46/47] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 3820c0c93a..cffa01ac67 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 3820c0c93a3e448e10a60a9feb5396844b537ca8 +Subproject commit cffa01ac67a2ca7401e848593f95f881e103a33d From 4d5d9a0685a9c340c9859414363bb8654b6e0ef6 Mon Sep 17 00:00:00 2001 From: Adria Navarro <adria@budibase.com> Date: Tue, 7 Nov 2023 18:42:41 +0100 Subject: [PATCH 47/47] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index cffa01ac67..ad9a0085be 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit cffa01ac67a2ca7401e848593f95f881e103a33d +Subproject commit ad9a0085bee0c4f3184acd86cadd872ea9917e88