From ed403fd79daebbb8649566c5d10dfd7bbe1192a1 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 8 Dec 2023 13:45:51 +0000 Subject: [PATCH 01/84] WIP --- packages/builder/src/builderStore/store/frontend.js | 8 +++++++- .../components/common/bindings/BindingPanel.svelte | 3 +++ .../components/common/bindings/BindingPicker.svelte | 13 +++++++++---- .../common/bindings/ClientBindingPanel.svelte | 3 ++- .../common/bindings/DrawerBindableInput.svelte | 4 +++- .../design/settings/controls/PropertyControl.svelte | 2 ++ .../Component/ComponentSettingsSection.svelte | 9 +++++++++ .../design/[screenId]/_components/AppPreview.svelte | 10 ++++++++++ packages/client/src/index.js | 6 ++++++ 9 files changed, 51 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 2e22276e50..83f72669ee 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -88,7 +88,7 @@ const INITIAL_FRONTEND_STATE = { selectedLayoutId: null, // Client state - selectedComponentInstance: null, + selectedComponentContext: null, // Onboarding onboarding: false, @@ -504,6 +504,12 @@ export const getFrontendStore = () => { return state }) }, + setSelectedComponentContext: context => { + store.update(state => { + state.selectedComponentContext = context + return state + }) + }, }, layouts: { select: layoutId => { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 4df26c5d03..927b5cb82a 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -45,6 +45,7 @@ export let valid export let allowJS = false export let allowHelpers = true + export let context = null const drawerActions = getContext("drawer-actions") const bindingDrawerActions = getContext("binding-drawer-actions") @@ -250,6 +251,7 @@ import groupBy from "lodash/fp/groupBy" - import { convertToJS } from "@budibase/string-templates" + import { convertToJS, processStringSync } from "@budibase/string-templates" import { Input, Layout, ActionButton, Icon, Popover } from "@budibase/bbui" import { handlebarsCompletions } from "constants/completions" @@ -9,6 +9,7 @@ export let bindings export let mode export let allowHelpers + export let context = null let search = "" let popover @@ -95,6 +96,9 @@ {#if hoverTarget.example}
{hoverTarget.example}
{/if} + {#if hoverTarget.val} +
{hoverTarget.val}
+ {/if} @@ -165,13 +169,14 @@
  • { + const hbs = `{{ ${binding.runtimeBinding} }}` + const val = processStringSync(hbs, context) + console.log(binding.runtimeBinding, val) popoverAnchor = e.target - if (!binding.description) { - return - } hoverTarget = { title: binding.display?.name || binding.fieldSchema?.name, description: binding.description, + val, } popover.show() e.stopPropagation() diff --git a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte index 74e14574ab..a46c18af27 100644 --- a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte @@ -6,7 +6,7 @@ export let value = "" export let allowJS = false export let allowHelpers = true - + export let context = null $: enrichedBindings = enrichBindings(bindings) // Ensure bindings have the correct categories @@ -24,6 +24,7 @@ (tempValue = event.detail)} + {context} {bindings} {allowJS} {allowHelpers} diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index a6f3d1b218..2195e8a1b9 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -24,6 +24,7 @@ export let propertyFocus = false export let info = null export let disableBindings = false + export let context = null $: nullishValue = value == null || value === "" $: allBindings = getAllBindings(bindings, componentBindings, nested) @@ -97,6 +98,7 @@ onChange={handleChange} bindings={allBindings} name={key} + {context} {nested} {key} {type} 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 6093d2a45e..eb69756626 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 @@ -8,6 +8,7 @@ import { getComponentForSetting } from "components/design/settings/componentSettings" import InfoDisplay from "./InfoDisplay.svelte" import analytics, { Events } from "analytics" + import { onMount } from "svelte" export let componentDefinition export let componentInstance @@ -26,6 +27,7 @@ tag, includeHidden ) + $: context = $store.selectedComponentContext const getSections = (instance, definition, isScreen, tag, includeHidden) => { const settings = definition?.settings ?? [] @@ -145,6 +147,12 @@ } return shouldDisplay(instance, setting) } + + onMount(() => { + store.actions.preview.sendEvent("request-context") + }) + + $: console.log(context) {#each sections as section, idx (section.name)} @@ -191,6 +199,7 @@ min: setting.min ?? null, max: setting.max ?? null, }} + {context} {bindings} {componentBindings} {componentInstance} 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..a6820cb551 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 @@ -181,6 +181,16 @@ } else if (type === "add-parent-component") { const { componentId, parentType } = data await store.actions.components.addParent(componentId, parentType) + } else if (type === "provide-context") { + let context = data?.context + if (context) { + try { + context = JSON.parse(context) + } catch (error) { + context = null + } + } + store.actions.preview.setSelectedComponentContext(context) } else { console.warn(`Client sent unknown event type: ${type}`) } diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 415d9fa5f2..2d2419eeca 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -75,6 +75,12 @@ const loadBudibase = async () => { } else { dndStore.actions.reset() } + } else if (type === "request-context") { + const { selectedComponentInstance } = get(componentStore) + const context = selectedComponentInstance?.getDataContext() + eventStore.actions.dispatchEvent("provide-context", { + context: JSON.stringify(context), + }) } } From a268e5560718e317180f8da7c71667b218c59cc1 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 30 Jan 2024 17:31:11 +0000 Subject: [PATCH 02/84] Plumbing for showing a maintenance page when SQS is required but missing. --- .../builder/src/pages/builder/_layout.svelte | 4 +++ .../pages/builder/maintenance/index.svelte | 27 +++++++++++++++++++ .../portal/_components/BudibaseLogo.svelte | 13 +++++++++ packages/builder/src/stores/portal/admin.js | 2 ++ packages/types/src/core/installation.ts | 4 +++ .../src/api/controllers/system/environment.ts | 19 ++++++++++--- .../routes/system/tests/environment.spec.ts | 4 +-- 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 packages/builder/src/pages/builder/maintenance/index.svelte create mode 100644 packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 62d3951fb5..95ca05b87b 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -71,6 +71,10 @@ await auth.getSelf() await admin.init() + if ($admin.maintenance.length > 0) { + $redirect("./maintenance") + } + if ($auth.user) { await licensing.init() } diff --git a/packages/builder/src/pages/builder/maintenance/index.svelte b/packages/builder/src/pages/builder/maintenance/index.svelte new file mode 100644 index 0000000000..bdadd98c42 --- /dev/null +++ b/packages/builder/src/pages/builder/maintenance/index.svelte @@ -0,0 +1,27 @@ + + +{#each $admin.maintenance as maintenance} + {#if maintenance.type === MaintenanceType.SQS_MISSING} + + + + Please upgrade your Budibase installation + + We've detected that the version of Budibase you're using depends on a + more recent version of the CouchDB database than what you have + installed. + + + To resolve this, you can either rollback to a previous version of + Budibase, or follow the migration guide here + to update to a later version of CouchDB. + + + + {/if} +{/each} diff --git a/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte b/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte new file mode 100644 index 0000000000..8276510201 --- /dev/null +++ b/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte @@ -0,0 +1,13 @@ + + +Budibase Logo $goto("./apps")} /> + + diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 2106acac27..29d4585c06 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, + maintenance: [], offlineMode: false, } @@ -48,6 +49,7 @@ export function createAdminStore() { store.isDev = environment.isDev store.baseUrl = environment.baseUrl store.offlineMode = environment.offlineMode + store.maintenance = environment.maintenance return store }) } diff --git a/packages/types/src/core/installation.ts b/packages/types/src/core/installation.ts index 7679290f36..ec89e439d9 100644 --- a/packages/types/src/core/installation.ts +++ b/packages/types/src/core/installation.ts @@ -2,3 +2,7 @@ export enum ServiceType { WORKER = "worker", APPS = "apps", } + +export enum MaintenanceType { + SQS_MISSING = "sqs_missing", +} diff --git a/packages/worker/src/api/controllers/system/environment.ts b/packages/worker/src/api/controllers/system/environment.ts index bf9270607f..6171ccf4db 100644 --- a/packages/worker/src/api/controllers/system/environment.ts +++ b/packages/worker/src/api/controllers/system/environment.ts @@ -1,10 +1,18 @@ -import { Ctx } from "@budibase/types" +import { Ctx, MaintenanceType } from "@budibase/types" import env from "../../../environment" import { env as coreEnv } from "@budibase/backend-core" import nodeFetch from "node-fetch" +// When we come to move to SQS fully and move away from Clouseau, we will need +// to flip this to true (or remove it entirely). This will then be used to +// determine if we should show the maintenance page that links to the SQS +// migration docs. +const sqsRequired = false + let sqsAvailable: boolean async function isSqsAvailable() { + // We cache this value for the duration of the Node process because we don't + // want every page load to be making this relatively expensive check. if (sqsAvailable !== undefined) { return sqsAvailable } @@ -21,6 +29,10 @@ async function isSqsAvailable() { } } +async function isSqsMissing() { + return sqsRequired && !(await isSqsAvailable()) +} + export const fetch = async (ctx: Ctx) => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, @@ -33,8 +45,9 @@ export const fetch = async (ctx: Ctx) => { } if (env.SELF_HOSTED) { - ctx.body.infrastructure = { - sqs: await isSqsAvailable(), + ctx.body.maintenance = [] + if (await isSqsMissing()) { + ctx.body.maintenance.push({ type: MaintenanceType.SQS_MISSING }) } } } diff --git a/packages/worker/src/api/routes/system/tests/environment.spec.ts b/packages/worker/src/api/routes/system/tests/environment.spec.ts index 2efbfa07c9..4dee77d3a9 100644 --- a/packages/worker/src/api/routes/system/tests/environment.spec.ts +++ b/packages/worker/src/api/routes/system/tests/environment.spec.ts @@ -40,9 +40,7 @@ describe("/api/system/environment", () => { multiTenancy: true, baseUrl: "http://localhost:10000", offlineMode: false, - infrastructure: { - sqs: false, - }, + maintenance: [], }) }) }) From 281b88a86adac77f06e6295cb584095aec87d046 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 31 Jan 2024 15:54:36 +0000 Subject: [PATCH 03/84] Respond to PR feedback. --- packages/worker/src/api/controllers/system/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/src/api/controllers/system/environment.ts b/packages/worker/src/api/controllers/system/environment.ts index 6171ccf4db..203d3d41ff 100644 --- a/packages/worker/src/api/controllers/system/environment.ts +++ b/packages/worker/src/api/controllers/system/environment.ts @@ -42,10 +42,10 @@ export const fetch = async (ctx: Ctx) => { disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL, baseUrl: env.PLATFORM_URL, isDev: env.isDev() && !env.isTest(), + maintenance: [], } if (env.SELF_HOSTED) { - ctx.body.maintenance = [] if (await isSqsMissing()) { ctx.body.maintenance.push({ type: MaintenanceType.SQS_MISSING }) } From b52ba43979b2d7a397356300c5e291edf0d3b35a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 31 Jan 2024 15:56:17 +0000 Subject: [PATCH 04/84] Fix tests. --- packages/worker/src/api/routes/system/tests/environment.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/worker/src/api/routes/system/tests/environment.spec.ts b/packages/worker/src/api/routes/system/tests/environment.spec.ts index 4dee77d3a9..dbe9be7374 100644 --- a/packages/worker/src/api/routes/system/tests/environment.spec.ts +++ b/packages/worker/src/api/routes/system/tests/environment.spec.ts @@ -27,6 +27,7 @@ describe("/api/system/environment", () => { multiTenancy: true, baseUrl: "http://localhost:10000", offlineMode: false, + maintenance: [], }) }) From 06556325a1595389faf5db9d829b0e3d14782c4a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 1 Feb 2024 14:45:16 +0000 Subject: [PATCH 05/84] Remove proxying of context changes up the chain --- .../src/components/context/Provider.svelte | 4 +- packages/client/src/stores/context.js | 53 +++++-------------- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/packages/client/src/components/context/Provider.svelte b/packages/client/src/components/context/Provider.svelte index 1b6a073512..ad5b580c4f 100644 --- a/packages/client/src/components/context/Provider.svelte +++ b/packages/client/src/components/context/Provider.svelte @@ -33,7 +33,7 @@ const provideData = newData => { const dataKey = JSON.stringify(newData) if (dataKey !== lastDataKey) { - context.actions.provideData(providerKey, newData, scope) + context.actions.provideData(providerKey, newData) lastDataKey = dataKey } } @@ -43,7 +43,7 @@ if (actionsKey !== lastActionsKey) { lastActionsKey = actionsKey newActions?.forEach(({ type, callback, metadata }) => { - context.actions.provideAction(providerKey, type, callback, scope) + context.actions.provideAction(providerKey, type, callback) // Register any "refresh datasource" actions with a singleton store // so we can easily refresh data at all levels for any datasource diff --git a/packages/client/src/stores/context.js b/packages/client/src/stores/context.js index e54c773591..c1ec18ef13 100644 --- a/packages/client/src/stores/context.js +++ b/packages/client/src/stores/context.js @@ -1,5 +1,4 @@ import { writable, derived } from "svelte/store" -import { ContextScopes } from "constants" export const createContextStore = parentContext => { const context = writable({}) @@ -20,60 +19,34 @@ export const createContextStore = parentContext => { } // Provide some data in context - const provideData = (providerId, data, scope = ContextScopes.Global) => { + const provideData = (providerId, data) => { if (!providerId || data === undefined) { return } - // Proxy message up the chain if we have a parent and are providing global - // context - if (scope === ContextScopes.Global && parentContext) { - parentContext.actions.provideData(providerId, data, scope) - } - // Otherwise this is either the context root, or we're providing a local // context override, so we need to update the local context instead - else { - context.update(state => { - state[providerId] = data - return state - }) - broadcastChange(providerId) - } + context.update(state => { + state[providerId] = data + return state + }) + broadcastChange(providerId) } // Provides some action in context - const provideAction = ( - providerId, - actionType, - callback, - scope = ContextScopes.Global - ) => { + const provideAction = (providerId, actionType, callback) => { if (!providerId || !actionType) { return } - // Proxy message up the chain if we have a parent and are providing global - // context - if (scope === ContextScopes.Global && parentContext) { - parentContext.actions.provideAction( - providerId, - actionType, - callback, - scope - ) - } - // Otherwise this is either the context root, or we're providing a local // context override, so we need to update the local context instead - else { - const key = `${providerId}_${actionType}` - context.update(state => { - state[key] = callback - return state - }) - broadcastChange(key) - } + const key = `${providerId}_${actionType}` + context.update(state => { + state[key] = callback + return state + }) + broadcastChange(key) } const observeChanges = callback => { From a880c5e62a5aceb3c4a207ad55cedaee9d6834d8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 2 Feb 2024 10:27:23 +0000 Subject: [PATCH 06/84] Update outside popover styles --- packages/bbui/src/Actions/position_dropdown.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index cc169eac09..14bb209b97 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -38,8 +38,9 @@ export default function positionDropdown(element, opts) { styles = customUpdate(anchorBounds, elementBounds, styles) } else { // Determine vertical styles - if (align === "right-outside") { - styles.top = anchorBounds.top + if (align === "right-outside" || align === "left-outside") { + styles.top = anchorBounds.bottom - elementBounds.height + styles.maxHeight = maxHeight } else if ( window.innerHeight - anchorBounds.bottom < (maxHeight || 100) From daec133f792e89499700510949a0d3b15aac6262 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 2 Feb 2024 10:27:29 +0000 Subject: [PATCH 07/84] Add live eval of bindings --- .../common/bindings/BindingPanel.svelte | 37 +++++++- .../common/bindings/BindingPicker.svelte | 91 +++++++++---------- .../Component/ComponentSettingsSection.svelte | 2 - 3 files changed, 79 insertions(+), 51 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 1dba55c733..3b64c918d0 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -15,6 +15,7 @@ decodeJSBinding, encodeJSBinding, convertToJS, + processStringSync, } from "@budibase/string-templates" import { readableToRuntimeBinding, @@ -59,15 +60,21 @@ let hbsValue = initialValueJS ? null : value let sidebar = true let targetMode = null + let expressionResult $: usingJS = mode === "JavaScript" - $: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars + $: editorMode = + mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars $: bindingCompletions = bindingsToCompletions(bindings, editorMode) + $: runtimeExpression = readableToRuntimeBinding(bindings, value) + $: expressionResult = processStringSync(runtimeExpression || "", context) const updateValue = val => { - valid = isValid(readableToRuntimeBinding(bindings, val)) + const runtimeExpression = readableToRuntimeBinding(bindings, val) + valid = isValid(runtimeExpression) if (valid) { dispatch("change", val) + expressionResult = processStringSync(runtimeExpression || "", context) } } @@ -114,7 +121,7 @@ } const switchMode = () => { - if (targetMode == "Text") { + if (targetMode === "Text") { jsValue = null updateValue(jsValue) } else { @@ -204,6 +211,11 @@ autofocus={autofocusEditor} /> + {#if expressionResult} +
    + {expressionResult} +
    + {/if}
  • { - const hbs = `{{ ${binding.runtimeBinding} }}` - const val = processStringSync(hbs, context) - console.log(binding.runtimeBinding, val) + let val = getBindingValue(binding) + if (val === "") { + val = " " + } popoverAnchor = e.target hoverTarget = { - title: binding.display?.name || binding.fieldSchema?.name, - description: binding.description, - val, + code: val, } popover.show() e.stopPropagation() @@ -224,19 +215,15 @@ {#each filteredHelpers as helper}
  • addHelper(helper, mode.name == "javascript")} + on:click={() => addHelper(helper, mode.name === "javascript")} on:mouseenter={e => { popoverAnchor = e.target if (!helper.displayText && helper.description) { return } hoverTarget = { - title: helper.displayText, description: helper.description, - example: getHelperExample( - helper, - mode.name == "javascript" - ), + code: getHelperExample(helper, mode.name === "javascript"), } popover.show() e.stopPropagation() @@ -397,4 +384,16 @@ margin-left: 2px; font-weight: 600; } + + .helper pre { + padding: 0; + margin: 0; + font-size: 12px; + white-space: pre-wrap; + word-break: break-all; + } + .helper :global(p) { + padding: 0; + margin: 0; + } 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 a18f21ae1d..c096ef58ec 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 @@ -150,8 +150,6 @@ onMount(() => { store.actions.preview.sendEvent("request-context") }) - - $: console.log(context) {#each sections as section, idx (section.name)} From f3f5532ad630e9560063f968d35cbe635cd63f37 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 14 Feb 2024 16:32:28 +0000 Subject: [PATCH 08/84] Fix merge issues --- .../_components/Component/ComponentSettingsSection.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 36ef6a7526..c7fe7b6b33 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 @@ -1,7 +1,7 @@ From 461418390d6a72bbb012d88d85543c6b7d18979b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 12:36:03 +0000 Subject: [PATCH 09/84] Change how context is passed through to binding drawers to ensure it's always available --- .../common/bindings/BindingPanel.svelte | 20 +++++++++---------- .../common/bindings/BindingPicker.svelte | 17 ++++++++-------- .../common/bindings/ClientBindingPanel.svelte | 4 ++-- .../bindings/DrawerBindableInput.svelte | 2 -- .../ButtonConfiguration.svelte | 1 + .../settings/controls/PropertyControl.svelte | 2 -- .../Component/ComponentSettingsSection.svelte | 2 -- 7 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index cb564593a0..d7b0e6e65c 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -135,13 +135,10 @@ selected={mode} on:select={onChangeMode} beforeSwitch={selectedMode => { - if (selectedMode == mode) { + if (selectedMode === mode) { return true } - - //Get the current mode value const editorValue = usingJS ? decodeJSBinding(jsValue) : hbsValue - if (editorValue) { targetMode = selectedMode return false @@ -204,9 +201,9 @@
    Current Handlebars syntax is invalid, please check the guide - here + + here + for more details.
    {:else} @@ -523,9 +520,10 @@ border-radius: var(--border-radius-s); font-family: monospace; border: 1px solid var(--spectrum-global-color-gray-300); - max-height: 200px; - overflow: auto; - white-space: pre; - word-wrap: anywhere; + overflow-y: scroll; + overflow-x: hidden; + white-space: pre-wrap; + word-wrap: break-word; + max-height: 92px; } diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 8ae236a7ab..cb990b277a 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -10,7 +10,6 @@ export let mode export let allowHelpers export let context = null - export let noPaddingTop = false let search = "" let popover @@ -70,6 +69,8 @@ return names } + $: console.log(context) + const getBindingValue = binding => { const hbs = `{{ ${binding.runtimeBinding} }}` return processStringSync(hbs, context) @@ -165,15 +166,13 @@ class="binding" on:mouseenter={e => { let val = getBindingValue(binding) - if (val === "") { - val = " " + if (val !== "") { + popoverAnchor = e.target + hoverTarget = { + code: val, + } + popover.show() } - popoverAnchor = e.target - hoverTarget = { - code: val, - } - popover.show() - e.stopPropagation() }} on:mouseleave={() => { popover.hide() diff --git a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte index 4bf05190db..39d8aaf2f5 100644 --- a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte @@ -1,12 +1,12 @@ import { helpers } from "@budibase/shared-core" import { DetailSummary, notifications } from "@budibase/bbui" - import { componentStore, previewStore } from "stores/builder" + import { componentStore } from "stores/builder" import PropertyControl from "components/design/settings/controls/PropertyControl.svelte" import ResetFieldsButton from "components/design/settings/controls/ResetFieldsButton.svelte" import EjectBlockButton from "components/design/settings/controls/EjectBlockButton.svelte" import { getComponentForSetting } from "components/design/settings/componentSettings" import InfoDisplay from "./InfoDisplay.svelte" import analytics, { Events } from "analytics" - import { onMount } from "svelte" export let componentDefinition export let componentInstance @@ -145,10 +144,6 @@ } return shouldDisplay(instance, setting) } - - onMount(() => { - previewStore.sendEvent("request-context") - }) {#each sections as section, idx (section.name)} diff --git a/packages/builder/src/stores/builder/preview.js b/packages/builder/src/stores/builder/preview.js index eca69d56d7..4923185ee7 100644 --- a/packages/builder/src/stores/builder/preview.js +++ b/packages/builder/src/stores/builder/preview.js @@ -60,6 +60,10 @@ export const createPreviewStore = () => { }) } + const requestComponentContext = () => { + sendEvent("request-context") + } + return { subscribe: store.subscribe, setDevice, @@ -69,6 +73,7 @@ export const createPreviewStore = () => { stopDrag, showPreview, setSelectedComponentContext, + requestComponentContext, } } diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index c2bc9f3989..46a507387d 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -53,7 +53,7 @@ export const getAdditionalDataContext = () => { const rows = get(grid?.getContext()?.rows) const goldenRow = generateGoldenSample(rows) - const id = [get(component).id] + const id = get(component).id return { [id]: goldenRow, eventContext: { diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index 008fa7e730..bd2b69d352 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -4,6 +4,7 @@ import BlockComponent from "components/BlockComponent.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { enrichSearchColumns, enrichFilter } from "utils/blocks.js" + import { get } from "svelte/store" export let title export let dataSource @@ -31,7 +32,9 @@ export let linkColumn export let noRowsMessage - const { fetchDatasourceSchema } = getContext("sdk") + const context = getContext("context") + const { fetchDatasourceSchema, generateGoldenSample } = getContext("sdk") + const component = getContext("component") let formId let dataProviderId @@ -62,6 +65,16 @@ }, ] + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const rows = get(context)[dataProviderId]?.rows || [] + const goldenRow = generateGoldenSample(rows) + const id = get(component).id + return { + [`${id}-repeater`]: goldenRow, + } + } + // Builds a full details page URL for the card title const buildFullCardUrl = (link, url, repeaterId, linkColumn) => { if (!link || !url || !repeaterId) { From 5c4e797251a0ceca04ed1735688536d012652ea5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 14:23:17 +0000 Subject: [PATCH 12/84] Provide additional context from repeater blocks --- .../src/components/app/blocks/RepeaterBlock.svelte | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/client/src/components/app/blocks/RepeaterBlock.svelte b/packages/client/src/components/app/blocks/RepeaterBlock.svelte index 30fbdddcdc..878b827c78 100644 --- a/packages/client/src/components/app/blocks/RepeaterBlock.svelte +++ b/packages/client/src/components/app/blocks/RepeaterBlock.svelte @@ -4,6 +4,7 @@ import Placeholder from "components/app/Placeholder.svelte" import { getContext } from "svelte" import { makePropSafe as safe } from "@budibase/string-templates" + import { get } from "svelte/store" export let dataSource export let filter @@ -18,8 +19,20 @@ export let gap const component = getContext("component") + const context = getContext("context") + const { generateGoldenSample } = getContext("sdk") let providerId + + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const rows = get(context)[providerId]?.rows || [] + const goldenRow = generateGoldenSample(rows) + const id = get(component).id + return { + [`${id}-repeater`]: goldenRow, + } + } From 840f499b47d110ebc77726d95b14835ef9df5370 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 14:33:40 +0000 Subject: [PATCH 13/84] Provide additional data context from form blocks --- .../app/blocks/MultiStepFormblock.svelte | 14 ++- .../app/blocks/form/FormBlock.svelte | 88 +++++++++++-------- 2 files changed, 62 insertions(+), 40 deletions(-) diff --git a/packages/client/src/components/app/blocks/MultiStepFormblock.svelte b/packages/client/src/components/app/blocks/MultiStepFormblock.svelte index b24c2f418a..1a0fbbd50d 100644 --- a/packages/client/src/components/app/blocks/MultiStepFormblock.svelte +++ b/packages/client/src/components/app/blocks/MultiStepFormblock.svelte @@ -5,7 +5,7 @@ import { builderStore } from "stores" import { Utils } from "@budibase/frontend-core" import FormBlockWrapper from "./form/FormBlockWrapper.svelte" - import { writable } from "svelte/store" + import { get, writable } from "svelte/store" export let actionType export let rowId @@ -15,7 +15,7 @@ export let buttonPosition = "bottom" export let size - const { fetchDatasourceSchema } = getContext("sdk") + const { fetchDatasourceSchema, generateGoldenSample } = getContext("sdk") const component = getContext("component") const context = getContext("context") @@ -45,6 +45,16 @@ $: enrichedSteps = enrichSteps(steps, schema, $component.id, $currentStep) $: updateCurrentStep(enrichedSteps, $builderStore, $component) + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const id = get(component).id + const rows = get(context)[`${id}-provider`]?.rows || [] + const goldenRow = generateGoldenSample(rows) + return { + [`${id}-repeater`]: goldenRow, + } + } + const updateCurrentStep = (steps, builderStore, component) => { const { componentId, step } = builderStore.metadata || {} diff --git a/packages/client/src/components/app/blocks/form/FormBlock.svelte b/packages/client/src/components/app/blocks/form/FormBlock.svelte index cdf1a05628..8c084a71ab 100644 --- a/packages/client/src/components/app/blocks/form/FormBlock.svelte +++ b/packages/client/src/components/app/blocks/form/FormBlock.svelte @@ -3,6 +3,7 @@ import InnerFormBlock from "./InnerFormBlock.svelte" import { Utils } from "@budibase/frontend-core" import FormBlockWrapper from "./FormBlockWrapper.svelte" + import { get } from "svelte/store" export let actionType export let dataSource @@ -11,7 +12,6 @@ export let fields export let buttons export let buttonPosition - export let title export let description export let rowId @@ -25,8 +25,56 @@ export let saveButtonLabel export let deleteButtonLabel - const { fetchDatasourceSchema } = getContext("sdk") + const { fetchDatasourceSchema, generateGoldenSample } = getContext("sdk") const component = getContext("component") + const context = getContext("context") + + let schema + + $: formattedFields = convertOldFieldFormat(fields) + $: fieldsOrDefault = getDefaultFields(formattedFields, schema) + $: fetchSchema(dataSource) + // We could simply spread $$props into the inner form and append our + // additions, but that would create svelte warnings about unused props and + // make maintenance in future more confusing as we typically always have a + // proper mapping of schema settings to component exports, without having to + // search multiple files + $: innerProps = { + dataSource, + actionUrl, + actionType, + size, + disabled, + fields: fieldsOrDefault, + title, + description, + schema, + notificationOverride, + buttons: + buttons || + Utils.buildFormBlockButtonConfig({ + _id: $component.id, + showDeleteButton, + showSaveButton, + saveButtonLabel, + deleteButtonLabel, + notificationOverride, + actionType, + actionUrl, + dataSource, + }), + buttonPosition: buttons ? buttonPosition : "top", + } + + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const id = get(component).id + const rows = get(context)[`${id}-provider`]?.rows || [] + const goldenRow = generateGoldenSample(rows) + return { + [`${id}-repeater`]: goldenRow, + } + } const convertOldFieldFormat = fields => { if (!fields) { @@ -68,42 +116,6 @@ return [...fields, ...defaultFields].filter(field => field.active) } - let schema - - $: formattedFields = convertOldFieldFormat(fields) - $: fieldsOrDefault = getDefaultFields(formattedFields, schema) - $: fetchSchema(dataSource) - // We could simply spread $$props into the inner form and append our - // additions, but that would create svelte warnings about unused props and - // make maintenance in future more confusing as we typically always have a - // proper mapping of schema settings to component exports, without having to - // search multiple files - $: innerProps = { - dataSource, - actionUrl, - actionType, - size, - disabled, - fields: fieldsOrDefault, - title, - description, - schema, - notificationOverride, - buttons: - buttons || - Utils.buildFormBlockButtonConfig({ - _id: $component.id, - showDeleteButton, - showSaveButton, - saveButtonLabel, - deleteButtonLabel, - notificationOverride, - actionType, - actionUrl, - dataSource, - }), - buttonPosition: buttons ? buttonPosition : "top", - } const fetchSchema = async () => { schema = (await fetchDatasourceSchema(dataSource)) || {} } From 53bb890d3d9f8ec922b3f9c07261e02e08c989a3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 14:36:28 +0000 Subject: [PATCH 14/84] Provide additional context from row explorer block --- .../src/components/app/blocks/RowExplorer.svelte | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/app/blocks/RowExplorer.svelte b/packages/client/src/components/app/blocks/RowExplorer.svelte index 8fadcb5006..1e2357713a 100644 --- a/packages/client/src/components/app/blocks/RowExplorer.svelte +++ b/packages/client/src/components/app/blocks/RowExplorer.svelte @@ -3,25 +3,35 @@ import BlockComponent from "components/BlockComponent.svelte" import { makePropSafe as safe } from "@budibase/string-templates" import { generate } from "shortid" + import { get } from "svelte/store" + import { getContext } from "svelte" export let dataSource export let height - export let cardTitle export let cardSubtitle export let cardDescription export let cardImageURL export let cardSearchField - export let detailFields export let detailTitle - export let noRowsMessage const stateKey = generate() + const context = getContext("context") + const { generateGoldenSample } = getContext("sdk") let listDataProviderId let listRepeaterId + + // Provide additional data context for live binding eval + export const getAdditionalDataContext = () => { + const rows = get(context)[listDataProviderId]?.rows || [] + const goldenRow = generateGoldenSample(rows) + return { + [listRepeaterId]: goldenRow, + } + } From 86695c0ee49fb41cf6117c48e9a1388d757f0ad0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 16:24:31 +0000 Subject: [PATCH 15/84] Add syntax highlighting to live binding eval --- packages/builder/package.json | 1 + .../common/bindings/BindingPanel.svelte | 25 +++++++++++++-- .../common/bindings/BindingPicker.svelte | 31 +++++++++++++++---- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index 52db8e11bc..3bf9ab4442 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -70,6 +70,7 @@ "dayjs": "^1.10.8", "downloadjs": "1.4.7", "fast-json-patch": "^3.1.1", + "json-format-highlight": "^1.0.4", "lodash": "4.17.21", "posthog-js": "^1.36.0", "remixicon": "2.5.0", diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index d7b0e6e65c..f0ded4a67b 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -33,6 +33,7 @@ } from "../CodeEditor" import BindingPicker from "./BindingPicker.svelte" import { BindingHelpers } from "./utils" + import formatHighlight from "json-format-highlight" const dispatch = createEventDispatcher() @@ -123,6 +124,24 @@ onSelectBinding("", { forceJS: true }) } + const highlight = json => { + // Attempt to parse and then stringify, in case this is valid JSON + try { + json = JSON.stringify(JSON.parse(json), null, 2) + } catch (err) { + // Ignore + } + + return formatHighlight(json, { + keyColor: "#e06c75", + numberColor: "#e5c07b", + stringColor: "#98c379", + trueColor: "#d19a66", + falseColor: "#d19a66", + nullColor: "#c678dd", + }) + } + onMount(() => { valid = isValid(readableToRuntimeBinding(bindings, value)) }) @@ -192,7 +211,7 @@ {#if expressionResult}
    - {expressionResult} + {@html highlight(expressionResult)}
    {/if} {#if expressionResult}
    - {expressionResult} + {@html highlight(expressionResult)}
    {/if} {/if} {#if hoverTarget.code} -
    {hoverTarget.code}
    +
    {@html highlight(hoverTarget.code)}
    {/if} @@ -387,8 +400,14 @@ padding: 0; margin: 0; font-size: 12px; - white-space: pre-wrap; - word-break: break-all; + white-space: pre; + text-overflow: ellipsis; + overflow: hidden; + } + .helper pre :global(span) { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; } .helper :global(p) { padding: 0; From 14b2bfa8d690802877058b3b5b1faf7b6ad2582d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 16 Feb 2024 16:24:46 +0000 Subject: [PATCH 16/84] Update lock --- yarn.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yarn.lock b/yarn.lock index 1937482837..3e147eed34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13765,6 +13765,11 @@ json-buffer@3.0.1: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-format-highlight@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/json-format-highlight/-/json-format-highlight-1.0.4.tgz#2e44277edabcec79a3d2c84e984c62e2258037b9" + integrity sha512-RqenIjKr1I99XfXPAml9G7YlEZg/GnsH7emWyWJh2yuGXqHW8spN7qx6/ME+MoIBb35/fxrMC9Jauj6nvGe4Mg== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" From 433c3a6306fdd161da70d145b4ac73885f215757 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 19 Feb 2024 16:22:23 +0000 Subject: [PATCH 17/84] Debounce hiding binding values to enable interacting with them --- packages/bbui/src/Popover/Popover.svelte | 2 + .../common/bindings/BindingPicker.svelte | 51 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 5066e3aa05..7ff5ae5f03 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -101,6 +101,8 @@ role="presentation" style="height: {customHeight}; --customZindex: {customZindex};" transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }} + on:mouseenter + on:mouseleave > diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 4f08cd5306..342855f427 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -18,6 +18,7 @@ let hoverTarget let helpers = handlebarsCompletions() let selectedCategory + let hideTimeout $: bindingIcons = bindings?.reduce((acc, ele) => { if (ele.icon) { @@ -86,6 +87,37 @@ nullColor: "#c678dd", }) } + + const showPopover = (target, binding) => { + if (hideTimeout) { + clearTimeout(hideTimeout) + hideTimeout = null + } + let val = getBindingValue(binding) + if (val !== "") { + popoverAnchor = target + hoverTarget = { + code: val, + } + popover.show() + } + } + + const hidePopover = () => { + hideTimeout = setTimeout(() => { + popover.hide() + popoverAnchor = null + hoverTarget = null + hideTimeout = null + }, 100) + } + + const stopHiding = () => { + if (hideTimeout) { + clearTimeout(hideTimeout) + hideTimeout = null + } + }
    @@ -175,21 +209,8 @@ {#each category.bindings as binding}
  • { - let val = getBindingValue(binding) - if (val !== "") { - popoverAnchor = e.target - hoverTarget = { - code: val, - } - popover.show() - } - }} - on:mouseleave={() => { - popover.hide() - popoverAnchor = null - hoverTarget = null - }} + on:mouseenter={e => showPopover(e.target, binding)} + on:mouseleave={hidePopover} on:focus={() => {}} on:blur={() => {}} on:click={() => addBinding(binding)} From 602f35537d9f0438014c0600ae3afcb89f6a94d8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 19 Feb 2024 16:28:23 +0000 Subject: [PATCH 18/84] Support custom min widths for popovers --- packages/bbui/src/Actions/position_dropdown.js | 3 ++- packages/bbui/src/Popover/Popover.svelte | 3 ++- .../src/components/common/bindings/BindingPicker.svelte | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 14bb209b97..31a1044ba4 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -15,6 +15,7 @@ export default function positionDropdown(element, opts) { align, maxHeight, maxWidth, + minWidth, useAnchorWidth, offset = 5, customUpdate, @@ -28,7 +29,7 @@ export default function positionDropdown(element, opts) { const elementBounds = element.getBoundingClientRect() let styles = { maxHeight: null, - minWidth: null, + minWidth, maxWidth, left: null, top: null, diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 7ff5ae5f03..407fb0b153 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -12,6 +12,7 @@ export let anchor export let align = "right" export let portalTarget + export let minWidth export let maxWidth export let maxHeight export let open = false @@ -21,7 +22,6 @@ export let customHeight export let animate = true export let customZindex - export let handlePostionUpdate export let showPopover = true export let clickOutsideOverride = false @@ -86,6 +86,7 @@ align, maxHeight, maxWidth, + minWidth, useAnchorWidth, offset, customUpdate: handlePostionUpdate, diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 342855f427..9c0522ce5a 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -124,11 +124,11 @@ align="left-outside" bind:this={popover} anchor={popoverAnchor} - maxWidth={600} + minWidth={0} + maxWidth={480} maxHeight={300} dismissible={false} on:mouseenter={stopHiding} - on:mouseleave={hidePopover} >
    From 7a278234b55d82b4407e51edc4d46b0b7c342eb5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 19 Feb 2024 16:30:29 +0000 Subject: [PATCH 19/84] Suppress warning --- .../src/components/common/bindings/BindingPicker.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 9c0522ce5a..60ca6abf7c 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -139,7 +139,10 @@
    {/if} {#if hoverTarget.code} -
    {@html highlight(hoverTarget.code)}
    +
    +          
    +          {@html highlight(hoverTarget.code)}
    +        
    {/if} From ca3f464523f4f57351db2f979c6392da4297756e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 20 Feb 2024 10:11:27 +0000 Subject: [PATCH 20/84] Tidy up logic for showing and hiding popovers for bindings and helpers --- .../common/CodeEditor/CodeEditor.svelte | 22 ++++- .../src/components/common/CodeEditor/index.js | 5 + .../common/bindings/BindingPanel.svelte | 45 +++++++-- .../common/bindings/BindingPicker.svelte | 93 +++++++------------ 4 files changed, 91 insertions(+), 74 deletions(-) diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index f4fa762bce..b169cd2f7c 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -117,7 +117,7 @@ const indentWithTabCustom = { key: "Tab", run: view => { - if (completionStatus(view.state) == "active") { + if (completionStatus(view.state) === "active") { acceptCompletion(view) return true } @@ -131,7 +131,7 @@ } const buildKeymap = () => { - const baseMap = [ + return [ ...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, @@ -139,7 +139,6 @@ ...completionKeymap, indentWithTabCustom, ] - return baseMap } const buildBaseExtensions = () => { @@ -215,7 +214,7 @@ ) } - if (mode.name == "javascript") { + if (mode.name === "javascript") { complete.push(javascript()) complete.push(highlightWhitespace()) complete.push(lineNumbers()) @@ -321,4 +320,19 @@ border-radius: var(--border-radius-s); padding: 4px 6px; } + + .code-editor :global(.binding__example) { + padding: 0; + margin: 0; + font-size: 12px; + white-space: pre; + text-overflow: ellipsis; + overflow: hidden; + max-height: 480px; + } + .code-editor :global(.binding__example span) { + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; + } diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index 0d71a475f0..6f55f42169 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -255,6 +255,11 @@ export const buildBindingInfoNode = (completion, binding) => { const ele = document.createElement("div") ele.classList.add("info-bubble") + if (binding.valueHTML) { + ele.innerHTML = `
    ${binding.valueHTML}
    ` + return ele + } + const exampleNodeHtml = binding.readableBinding ? `
    {{ ${binding.readableBinding} }}
    ` : "" diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index f0ded4a67b..3708f83c61 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -62,16 +62,45 @@ let targetMode = null let expressionResult + $: enrichedBindings = enrichBindings(bindings, context) $: usingJS = mode === "JavaScript" $: editorMode = mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars - $: bindingCompletions = bindingsToCompletions(bindings, editorMode) - $: runtimeExpression = readableToRuntimeBinding(bindings, value) + $: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode) + $: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value) $: expressionResult = processStringSync(runtimeExpression || "", context) $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) + const getBindingValue = (binding, context) => { + const hbs = `{{ literal ${binding.runtimeBinding} }}` + const res = processStringSync(hbs, context) + return JSON.stringify(res, null, 2) + } + + const highlightJSON = json => { + return formatHighlight(json, { + keyColor: "#e06c75", + numberColor: "#e5c07b", + stringColor: "#98c379", + trueColor: "#d19a66", + falseColor: "#d19a66", + nullColor: "#c678dd", + }) + } + + const enrichBindings = (bindings, context) => { + return bindings.map(binding => { + const value = getBindingValue(binding, context) + return { + ...binding, + value, + valueHTML: highlightJSON(value), + } + }) + } + const updateValue = val => { - const runtimeExpression = readableToRuntimeBinding(bindings, val) + const runtimeExpression = readableToRuntimeBinding(enrichedBindings, val) valid = isValid(runtimeExpression) if (valid) { dispatch("change", val) @@ -116,9 +145,9 @@ } const convert = () => { - const runtime = readableToRuntimeBinding(bindings, hbsValue) + const runtime = readableToRuntimeBinding(enrichedBindings, hbsValue) const runtimeJs = encodeJSBinding(convertToJS(runtime)) - jsValue = runtimeToReadableBinding(bindings, runtimeJs) + jsValue = runtimeToReadableBinding(enrichedBindings, runtimeJs) hbsValue = null mode = "JavaScript" onSelectBinding("", { forceJS: true }) @@ -143,7 +172,7 @@ } onMount(() => { - valid = isValid(readableToRuntimeBinding(bindings, value)) + valid = isValid(readableToRuntimeBinding(enrichedBindings, value)) }) @@ -261,7 +290,7 @@ {#if sidebar}
    { - const hbs = `{{ literal ${binding.runtimeBinding} }}` - const res = processStringSync(hbs, context) - return JSON.stringify(res, null, 2) + const showBindingPopover = (binding, target) => { + stopHidingPopover() + popoverAnchor = target + hoverTarget = { + code: binding.valueHTML, + } + popover.show() } - const highlight = json => { - return formatHighlight(json, { - keyColor: "#e06c75", - numberColor: "#e5c07b", - stringColor: "#98c379", - trueColor: "#d19a66", - falseColor: "#d19a66", - nullColor: "#c678dd", - }) - } - - const showPopover = (target, binding) => { - if (hideTimeout) { - clearTimeout(hideTimeout) - hideTimeout = null + const showHelperPopover = (helper, target) => { + stopHidingPopover() + if (!helper.displayText && helper.description) { + return } - let val = getBindingValue(binding) - if (val !== "") { - popoverAnchor = target - hoverTarget = { - code: val, - } - popover.show() + popoverAnchor = target + hoverTarget = { + description: helper.description, + code: getHelperExample(helper, mode.name === "javascript"), } + popover.show() } const hidePopover = () => { @@ -112,7 +102,7 @@ }, 100) } - const stopHiding = () => { + const stopHidingPopover = () => { if (hideTimeout) { clearTimeout(hideTimeout) hideTimeout = null @@ -126,9 +116,10 @@ anchor={popoverAnchor} minWidth={0} maxWidth={480} - maxHeight={300} + maxHeight={480} dismissible={false} - on:mouseenter={stopHiding} + on:mouseenter={stopHidingPopover} + on:mouseleave={hidePopover} >
    @@ -139,10 +130,8 @@
    {/if} {#if hoverTarget.code} -
    -          
    -          {@html highlight(hoverTarget.code)}
    -        
    + +
    {@html hoverTarget.code}
    {/if}
    @@ -212,10 +201,8 @@ {#each category.bindings as binding}
  • showPopover(e.target, binding)} + on:mouseenter={e => showBindingPopover(binding, e.target)} on:mouseleave={hidePopover} - on:focus={() => {}} - on:blur={() => {}} on:click={() => addBinding(binding)} > @@ -227,7 +214,6 @@ {binding.readableBinding} {/if} - {#if binding.display?.type || binding.fieldSchema?.type} @@ -250,26 +236,9 @@ {#each filteredHelpers as helper}
  • showHelperPopover(helper, e.target)} + on:mouseleave={hidePopover} on:click={() => addHelper(helper, mode.name === "javascript")} - on:mouseenter={e => { - popoverAnchor = e.target - if (!helper.displayText && helper.description) { - return - } - hoverTarget = { - description: helper.description, - code: getHelperExample(helper, mode.name === "javascript"), - } - popover.show() - e.stopPropagation() - }} - on:mouseleave={() => { - popover.hide() - popoverAnchor = null - hoverTarget = null - }} - on:focus={() => {}} - on:blur={() => {}} > {helper.displayText} @@ -287,16 +256,16 @@ From 0217bac2672fda9d765da9ee3f059512fe8c24f8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 22 Feb 2024 15:05:21 +0000 Subject: [PATCH 22/84] Add multiple style improvements to drawers --- packages/bbui/src/Drawer/Drawer.svelte | 18 +- packages/bbui/src/Drawer/DrawerContent.svelte | 14 +- .../SetupPanel/AutomationBlockSetup.svelte | 2 +- .../common/CodeEditor/CodeEditor.svelte | 13 +- .../src/components/common/CodeEditor/index.js | 17 + .../common/bindings/BindingPanel.svelte | 703 +++++++----------- ...gPicker.svelte => BindingSidePanel.svelte} | 220 +++--- .../bindings/DrawerBindableInput.svelte | 4 - .../bindings/EvaluationSidePanel.svelte | 122 +++ 9 files changed, 568 insertions(+), 545 deletions(-) rename packages/builder/src/components/common/bindings/{BindingPicker.svelte => BindingSidePanel.svelte} (66%) create mode 100644 packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 8976bfb81e..00891c6bdc 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -9,7 +9,7 @@ export let title export let fillWidth export let left = "314px" - export let width = "calc(100% - 626px)" + export let width = "calc(100% - 648px)" export let headless = false const dispatch = createEventDispatcher() @@ -68,10 +68,7 @@ {#if !headless}
    - {title} - - - + {title}
    @@ -85,10 +82,6 @@ {/if} diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte similarity index 66% rename from packages/builder/src/components/common/bindings/BindingPicker.svelte rename to packages/builder/src/components/common/bindings/BindingSidePanel.svelte index d1baa03999..b74f3c6fcd 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -137,123 +137,129 @@
    - - {#if selectedCategory} -
    - { - selectedCategory = null - }} - > - Back - -
    - {/if} - - {#if !selectedCategory} - - {/if} - - {#if !selectedCategory && !search} -
      - {#each categoryNames as categoryName} -
    • + + {#if selectedCategory} +
      + { - selectedCategory = categoryName + selectedCategory = null }} > - - {categoryName} - -
    • - {/each} -
    - {/if} + Back + + + {/if} - {#if selectedCategory || search} - {#each filteredCategories as category} - {#if category.bindings?.length} -
    -
    - {category.name} -
    -
      - {#each category.bindings as binding} -
    • showBindingPopover(binding, e.target)} - on:mouseleave={hidePopover} - on:click={() => addBinding(binding)} - > - - {#if binding.display?.name} - {binding.display.name} - {:else if binding.fieldSchema?.name} - {binding.fieldSchema?.name} - {:else} - {binding.readableBinding} - {/if} - - {#if binding.display?.type || binding.fieldSchema?.type} - - - {binding.display?.type || binding.fieldSchema?.type} - + {#if !selectedCategory} + + {/if} + + {#if !selectedCategory && !search} +
        + {#each categoryNames as categoryName} +
      • { + selectedCategory = categoryName + }} + > + + {categoryName} + +
      • + {/each} +
      + {/if} + + {#if selectedCategory || search} + {#each filteredCategories as category} + {#if category.bindings?.length} +
      +
      + {category.name} +
      +
        + {#each category.bindings as binding} +
      • showBindingPopover(binding, e.target)} + on:mouseleave={hidePopover} + on:click={() => addBinding(binding)} + > + + {#if binding.display?.name} + {binding.display.name} + {:else if binding.fieldSchema?.name} + {binding.fieldSchema?.name} + {:else} + {binding.readableBinding} + {/if} - {/if} -
      • - {/each} -
      -
      - {/if} - {/each} + {#if binding.display?.type || binding.fieldSchema?.type} + + + {binding.display?.type || binding.fieldSchema?.type} + + + {/if} +
    • + {/each} +
    +
    + {/if} + {/each} - {#if selectedCategory === "Helpers" || search} - {#if filteredHelpers?.length} -
    -
    Helpers
    -
      - {#each filteredHelpers as helper} -
    • showHelperPopover(helper, e.target)} - on:mouseleave={hidePopover} - on:click={() => addHelper(helper, mode.name === "javascript")} - > - {helper.displayText} - - function - -
    • - {/each} -
    -
    + {#if selectedCategory === "Helpers" || search} + {#if filteredHelpers?.length} +
    +
    Helpers
    +
      + {#each filteredHelpers as helper} +
    • showHelperPopover(helper, e.target)} + on:mouseleave={hidePopover} + on:click={() => addHelper(helper, mode.name === "javascript")} + > + {helper.displayText} + + function + +
    • + {/each} +
    +
    + {/if} {/if} {/if} - {/if} -
    + + From 8320c50c9686602084240fcc8adb2b449d840dd8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 22 Feb 2024 16:44:18 +0000 Subject: [PATCH 23/84] Fix multiple style issues with codemirror --- packages/builder/package.json | 2 +- .../common/CodeEditor/CodeEditor.svelte | 44 ++++++++++++++++++- .../src/components/common/CodeEditor/index.js | 13 ------ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index 3bf9ab4442..4871833d48 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -66,7 +66,7 @@ "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "@zerodevx/svelte-json-view": "^1.0.7", - "codemirror": "^5.59.0", + "codemirror": "^6.0.1", "dayjs": "^1.10.8", "downloadjs": "1.4.7", "fast-json-patch": "^3.1.1", diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 0d8798d0f2..9341711022 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -299,12 +299,53 @@ diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index d68857b420..2c8c6144fb 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -140,21 +140,19 @@
    {#if selectedCategory} -
    - { - selectedCategory = null - }} - > - Back - +
    + (selectedCategory = null)} + /> + {selectedCategory}
    {/if} {#if !selectedCategory} -
    - - {#if !$modal && depth > 0} -
    +
    + {#if $modal} +
    {/if} +
    0} + class:modal={$modal} + transition:slide|local + {style} + > +
    +
    {title || "Bindings"}
    +
    + + +
    +
    + + {#if !$modal && depth > 0} +
    + {/if} +
    {/if} From 334c6de3045a73109d370c44873000524aaf6e6f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 27 Feb 2024 14:34:47 +0000 Subject: [PATCH 48/84] Attempt to make CreateEditColumn slightly readable by grouping variables as done everywhere else --- .../DataTable/modals/CreateEditColumn.svelte | 158 ++++++++---------- 1 file changed, 73 insertions(+), 85 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 09245b06c5..b3fbd7132d 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -49,43 +49,21 @@ const dispatch = createEventDispatcher() const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"] - const { dispatch: gridDispatch } = getContext("grid") + const { dispatch: gridDispatch, rows } = getContext("grid") export let field let mounted = false - const fieldDefinitions = Object.values(FIELDS).reduce( - // Storing the fields by complex field id - (acc, field) => ({ - ...acc, - [makeFieldId(field.type, field.subtype)]: field, - }), - {} - ) - - function makeFieldId(type, subtype, autocolumn) { - // don't make field IDs for auto types - if (type === AUTO_TYPE || autocolumn) { - return type.toUpperCase() - } else { - return `${type}${subtype || ""}`.toUpperCase() - } - } - let originalName let linkEditDisabled let primaryDisplay let indexes = [...($tables.selected.indexes || [])] let isCreating = undefined - let relationshipPart1 = PrettyRelationshipDefinitions.Many let relationshipPart2 = PrettyRelationshipDefinitions.One - let relationshipTableIdPrimary = null let relationshipTableIdSecondary = null - let table = $tables.selected - let confirmDeleteDialog let savingColumn let deleteColName @@ -99,11 +77,6 @@ } let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions) let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions) - - $: if (primaryDisplay) { - editableColumn.constraints.presence = { allowEmpty: false } - } - let relationshipMap = { [RelationshipType.ONE_TO_MANY]: { part1: PrettyRelationshipDefinitions.MANY, @@ -118,7 +91,11 @@ part2: PrettyRelationshipDefinitions.MANY, }, } + let autoColumnInfo = getAutoColumnInformation() + $: if (primaryDisplay) { + editableColumn.constraints.presence = { allowEmpty: false } + } $: { // this parses any changes the user has made when creating a new internal relationship // into what we expect the schema to look like @@ -148,6 +125,74 @@ editableColumn.tableId = relationshipTableIdSecondary } } + $: initialiseField(field, savingColumn) + $: checkConstraints(editableColumn) + $: required = !!editableColumn?.constraints?.presence || primaryDisplay + $: uneditable = + $tables.selected?._id === TableNames.USERS && + UNEDITABLE_USER_FIELDS.includes(editableColumn.name) + $: invalid = + !editableColumn?.name || + (editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) || + Object.keys(errors).length !== 0 + $: errors = checkErrors(editableColumn) + $: datasource = $datasources.list.find( + source => source._id === table?.sourceId + ) + $: tableAutoColumnsTypes = getTableAutoColumnTypes($tables?.selected) + $: availableAutoColumns = Object.keys(autoColumnInfo).reduce((acc, key) => { + if (!tableAutoColumnsTypes.includes(key)) { + acc[key] = autoColumnInfo[key] + } + return acc + }, {}) + $: availableAutoColumnKeys = availableAutoColumns + ? Object.keys(availableAutoColumns) + : [] + $: autoColumnOptions = editableColumn.autocolumn + ? autoColumnInfo + : availableAutoColumns + // used to select what different options can be displayed for column type + $: canBeDisplay = + editableColumn?.type !== LINK_TYPE && + editableColumn?.type !== AUTO_TYPE && + editableColumn?.type !== JSON_TYPE && + !editableColumn.autocolumn + $: canBeRequired = + editableColumn?.type !== LINK_TYPE && + !uneditable && + editableColumn?.type !== AUTO_TYPE && + !editableColumn.autocolumn + $: externalTable = table.sourceType === DB_TYPE_EXTERNAL + // in the case of internal tables the sourceId will just be undefined + $: tableOptions = $tables.list.filter( + opt => + opt.sourceType === table.sourceType && table.sourceId === opt.sourceId + ) + $: typeEnabled = + !originalName || + (originalName && + SWITCHABLE_TYPES.indexOf(editableColumn.type) !== -1 && + !editableColumn?.autocolumn) + + const fieldDefinitions = Object.values(FIELDS).reduce( + // Storing the fields by complex field id + (acc, field) => ({ + ...acc, + [makeFieldId(field.type, field.subtype)]: field, + }), + {} + ) + + function makeFieldId(type, subtype, autocolumn) { + // don't make field IDs for auto types + if (type === AUTO_TYPE || autocolumn) { + return type.toUpperCase() + } else { + return `${type}${subtype || ""}`.toUpperCase() + } + } + const initialiseField = (field, savingColumn) => { isCreating = !field if (field && !savingColumn) { @@ -187,22 +232,6 @@ } } - $: initialiseField(field, savingColumn) - - $: checkConstraints(editableColumn) - $: required = !!editableColumn?.constraints?.presence || primaryDisplay - $: uneditable = - $tables.selected?._id === TableNames.USERS && - UNEDITABLE_USER_FIELDS.includes(editableColumn.name) - $: invalid = - !editableColumn?.name || - (editableColumn?.type === LINK_TYPE && !editableColumn?.tableId) || - Object.keys(errors).length !== 0 - $: errors = checkErrors(editableColumn) - $: datasource = $datasources.list.find( - source => source._id === table?.sourceId - ) - const getTableAutoColumnTypes = table => { return Object.keys(table?.schema).reduce((acc, key) => { let fieldSchema = table?.schema[key] @@ -213,47 +242,6 @@ }, []) } - let autoColumnInfo = getAutoColumnInformation() - - $: tableAutoColumnsTypes = getTableAutoColumnTypes($tables?.selected) - $: availableAutoColumns = Object.keys(autoColumnInfo).reduce((acc, key) => { - if (!tableAutoColumnsTypes.includes(key)) { - acc[key] = autoColumnInfo[key] - } - return acc - }, {}) - - $: availableAutoColumnKeys = availableAutoColumns - ? Object.keys(availableAutoColumns) - : [] - - $: autoColumnOptions = editableColumn.autocolumn - ? autoColumnInfo - : availableAutoColumns - - // used to select what different options can be displayed for column type - $: canBeDisplay = - editableColumn?.type !== LINK_TYPE && - editableColumn?.type !== AUTO_TYPE && - editableColumn?.type !== JSON_TYPE && - !editableColumn.autocolumn - $: canBeRequired = - editableColumn?.type !== LINK_TYPE && - !uneditable && - editableColumn?.type !== AUTO_TYPE && - !editableColumn.autocolumn - $: externalTable = table.sourceType === DB_TYPE_EXTERNAL - // in the case of internal tables the sourceId will just be undefined - $: tableOptions = $tables.list.filter( - opt => - opt.sourceType === table.sourceType && table.sourceId === opt.sourceId - ) - $: typeEnabled = - !originalName || - (originalName && - SWITCHABLE_TYPES.indexOf(editableColumn.type) !== -1 && - !editableColumn?.autocolumn) - async function saveColumn() { savingColumn = true if (errors?.length) { From 08ad9d9c4e13f165ae7af9b60349124e3fd75276 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 27 Feb 2024 14:44:25 +0000 Subject: [PATCH 49/84] Add binding eval for non relationship fields when editing formula columns --- .../DataTable/modals/CreateEditColumn.svelte | 5 ++ .../bindings/DrawerBindableInput.svelte | 2 + .../common/bindings/ServerBindingPanel.svelte | 2 + packages/client/src/sdk.js | 10 ++-- packages/client/src/utils/components.js | 49 ------------------- packages/frontend-core/src/utils/index.js | 1 + packages/frontend-core/src/utils/rows.js | 48 ++++++++++++++++++ 7 files changed, 65 insertions(+), 52 deletions(-) create mode 100644 packages/frontend-core/src/utils/rows.js diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index b3fbd7132d..c1d1a5655a 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -36,6 +36,8 @@ import { ValidColumnNameRegex } from "@budibase/shared-core" import { FieldType, FieldSubtype, SourceName } from "@budibase/types" import RelationshipSelector from "components/common/RelationshipSelector.svelte" + import { RowUtils } from "@budibase/frontend-core" + import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte" const AUTO_TYPE = FIELDS.AUTO.type const FORMULA_TYPE = FIELDS.FORMULA.type @@ -93,6 +95,7 @@ } let autoColumnInfo = getAutoColumnInformation() + $: rowGoldenSample = RowUtils.generateGoldenSample($rows) $: if (primaryDisplay) { editableColumn.constraints.presence = { allowEmpty: false } } @@ -667,6 +670,7 @@
    { @@ -677,6 +681,7 @@ }} bindings={getBindings({ table })} allowJS + context={rowGoldenSample} />
    diff --git a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte index e61376daa0..e3b8615103 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte @@ -23,6 +23,7 @@ export let key export let disableBindings = false export let forceModal = false + export let context = null const dispatch = createEventDispatcher() @@ -102,6 +103,7 @@ {bindings} {allowJS} {allowHelpers} + {context} /> diff --git a/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte b/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte index 213e5bbf1d..fdc8254a18 100644 --- a/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ServerBindingPanel.svelte @@ -5,6 +5,7 @@ export let valid export let value = "" export let allowJS = false + export let context = null $: enrichedBindings = enrichBindings(bindings) @@ -23,5 +24,6 @@ bindings={enrichedBindings} {value} {allowJS} + {context} on:change /> diff --git a/packages/client/src/sdk.js b/packages/client/src/sdk.js index c0b99cd131..d86d635970 100644 --- a/packages/client/src/sdk.js +++ b/packages/client/src/sdk.js @@ -28,9 +28,13 @@ import { ActionTypes } from "./constants" import { fetchDatasourceSchema } from "./utils/schema.js" import { getAPIKey } from "./utils/api.js" import { enrichButtonActions } from "./utils/buttonActions.js" -import { generateGoldenSample } from "./utils/components.js" import { processStringSync, makePropSafe } from "@budibase/string-templates" -import { fetchData, LuceneUtils, Constants } from "@budibase/frontend-core" +import { + fetchData, + LuceneUtils, + Constants, + RowUtils, +} from "@budibase/frontend-core" export default { API, @@ -66,7 +70,7 @@ export default { processStringSync, makePropSafe, createContextStore, - generateGoldenSample, + generateGoldenSample: RowUtils.generateGoldenSample, // Components Provider, diff --git a/packages/client/src/utils/components.js b/packages/client/src/utils/components.js index 70f80e4329..1812175c2c 100644 --- a/packages/client/src/utils/components.js +++ b/packages/client/src/utils/components.js @@ -82,52 +82,3 @@ export const findComponentParent = (rootComponent, id, parentComponent) => { } return null } - -/** - * Util to check is a given value is "better" than another. "Betterness" is - * defined as presence and length. - */ -const isBetterSample = (newValue, oldValue) => { - // Prefer non-null values - if (oldValue == null && newValue != null) { - return true - } - - // Don't change type - const oldType = typeof oldValue - const newType = typeof newValue - if (oldType !== newType) { - return false - } - - // Prefer longer values - if (newType === "string" && newValue.length > oldValue.length) { - return true - } - if ( - newType === "object" && - Object.keys(newValue).length > Object.keys(oldValue).length - ) { - return true - } - - return false -} - -/** - * Generates a best-case example object of the provided samples. - * The generated sample does not necessarily exist - it simply is a sample that - * contains "good" examples for every property of all the samples. - * The generate sample will have a value for all keys across all samples. - */ -export const generateGoldenSample = samples => { - let goldenSample = {} - samples?.forEach(sample => { - Object.keys(sample).forEach(key => { - if (isBetterSample(sample[key], goldenSample[key])) { - goldenSample[key] = sample[key] - } - }) - }) - return goldenSample -} diff --git a/packages/frontend-core/src/utils/index.js b/packages/frontend-core/src/utils/index.js index 3f00c00e47..98998b7f0e 100644 --- a/packages/frontend-core/src/utils/index.js +++ b/packages/frontend-core/src/utils/index.js @@ -3,6 +3,7 @@ export * as JSONUtils from "./json" export * as CookieUtils from "./cookies" export * as RoleUtils from "./roles" export * as Utils from "./utils" +export * as RowUtils from "./rows" export { memo, derivedMemo } from "./memo" export { createWebsocket } from "./websocket" export * from "./download" diff --git a/packages/frontend-core/src/utils/rows.js b/packages/frontend-core/src/utils/rows.js new file mode 100644 index 0000000000..ea43d63734 --- /dev/null +++ b/packages/frontend-core/src/utils/rows.js @@ -0,0 +1,48 @@ +/** + * Util to check is a given value is "better" than another. "Betterness" is + * defined as presence and length. + */ +const isBetterSample = (newValue, oldValue) => { + // Prefer non-null values + if (oldValue == null && newValue != null) { + return true + } + + // Don't change type + const oldType = typeof oldValue + const newType = typeof newValue + if (oldType !== newType) { + return false + } + + // Prefer longer values + if (newType === "string" && newValue.length > oldValue.length) { + return true + } + if ( + newType === "object" && + Object.keys(newValue).length > Object.keys(oldValue).length + ) { + return true + } + + return false +} + +/** + * Generates a best-case example object of the provided samples. + * The generated sample does not necessarily exist - it simply is a sample that + * contains "good" examples for every property of all the samples. + * The generate sample will have a value for all keys across all samples. + */ +export const generateGoldenSample = samples => { + let goldenSample = {} + samples?.slice(0, 100).forEach(sample => { + Object.keys(sample).forEach(key => { + if (isBetterSample(sample[key], goldenSample[key])) { + goldenSample[key] = sample[key] + } + }) + }) + return goldenSample +} From 47cc9557c42dd4cd82a6087f781ffd8d489abdd8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 16:29:32 +0000 Subject: [PATCH 50/84] Improve drawer state management between instances --- packages/bbui/src/Drawer/Drawer.svelte | 9 ++++++++- .../src/components/common/bindings/BindingPanel.svelte | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 672795e078..118a7dcd8e 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -8,6 +8,7 @@ // Context level stores to keep drawers in sync const openDrawers = writable([]) const modal = writable(false) + const resizable = writable(true) const drawerLeft = writable(null) const drawerWidth = writable(null) @@ -45,8 +46,13 @@ return } observer?.disconnect() + + // Reset state observer = null modal.set(false) + resizable.set(true) + drawerLeft.set(null) + drawerWidth.set(null) } @@ -93,6 +99,7 @@ } if (forceModal) { modal.set(true) + resizable.set(false) } observe() visible = true @@ -114,7 +121,7 @@ hide, show, modal, - forceModal, + resizable, }) const easeInOutQuad = x => { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index ba07b6f254..9a2a55cd9e 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -34,6 +34,7 @@ import { capitalise } from "helpers" import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import { Utils } from "@budibase/frontend-core" + import { get } from "svelte/store" const dispatch = createEventDispatcher() @@ -213,7 +214,7 @@ {/each} - {#if drawerContext && !drawerContext.forceModal} + {#if drawerContext && get(drawerContext.resizable)} Date: Thu, 29 Feb 2024 16:29:59 +0000 Subject: [PATCH 51/84] Remove testing components --- .../builder/src/components/common/bindings/BindingPanel.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 9a2a55cd9e..15cbec667a 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -32,7 +32,6 @@ import { BindingHelpers } from "./utils" import formatHighlight from "json-format-highlight" import { capitalise } from "helpers" - import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" import { Utils } from "@budibase/frontend-core" import { get } from "svelte/store" @@ -201,7 +200,6 @@ {capitalise(tab)} {/each} -
    {#each sideTabs as tab} From 5010c4fe4ef69d4b846e5cb374f25c0991486b08 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 17:00:06 +0000 Subject: [PATCH 52/84] Improve handling of nullish expression to provide a more reliable match to the real evaluation --- .../common/bindings/BindingPanel.svelte | 24 +++++++------------ .../common/bindings/BindingSidePanel.svelte | 2 +- .../bindings/EvaluationSidePanel.svelte | 12 ++++++---- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 15cbec667a..2e4a7a63a8 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -12,13 +12,9 @@ isValid, decodeJSBinding, encodeJSBinding, - convertToJS, processStringSync, } from "@budibase/string-templates" - import { - readableToRuntimeBinding, - runtimeToReadableBinding, - } from "dataBinding" + import { readableToRuntimeBinding } from "dataBinding" import CodeEditor from "../CodeEditor/CodeEditor.svelte" import { getHelperCompletions, @@ -93,7 +89,8 @@ } const getBindingValue = (binding, context) => { - const hbs = `{{ literal ${binding.runtimeBinding} }}` + const js = `return $("${binding.runtimeBinding}")` + const hbs = encodeJSBinding(js) const res = processStringSync(hbs, context) return JSON.stringify(res, null, 2) } @@ -171,15 +168,6 @@ updateValue(jsValue) } - const convert = () => { - const runtime = readableToRuntimeBinding(enrichedBindings, hbsValue) - const runtimeJs = encodeJSBinding(convertToJS(runtime)) - jsValue = runtimeToReadableBinding(enrichedBindings, runtimeJs) - hbsValue = null - mode = "JavaScript" - onSelectBinding("", { forceJS: true }) - } - onMount(() => { valid = isValid(readableToRuntimeBinding(enrichedBindings, value)) }) @@ -296,7 +284,11 @@ mode={editorMode} /> {:else if sidePanel === SidePanels.Evaluation} - + {/if}
    diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index c5020afee7..87fcb7cb5b 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -71,7 +71,7 @@ } const showBindingPopover = (binding, target) => { - if (!context) { + if (!context || !binding.value || binding.value === "") { return } stopHidingPopover() diff --git a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte index 3d80b05b07..37f9deda14 100644 --- a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte @@ -6,13 +6,17 @@ export let expressionResult export let evaluating = false + export let expression = null $: error = expressionResult === "Error while executing JS" - $: empty = expressionResult == null || expressionResult === "" + $: empty = expression == null || expression?.trim() === "" $: success = !error && !empty $: highlightedResult = highlight(expressionResult) const highlight = json => { + if (json == null) { + return "" + } // Attempt to parse and then stringify, in case this is valid JSON try { json = JSON.stringify(JSON.parse(json), null, 2) @@ -67,10 +71,10 @@
    - {#if expressionResult} - {@html highlightedResult} - {:else} + {#if empty} Your expression will be evaluated here + {:else} + {@html highlightedResult} {/if}
    From e53676791a8d8b9dfd47759bb02242287ca3be7f Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 17:04:01 +0000 Subject: [PATCH 53/84] Fix font size of code block in helper popovers --- .../src/components/common/bindings/BindingSidePanel.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index 87fcb7cb5b..4815353cff 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -123,7 +123,6 @@ maxHeight={480} dismissible={false} on:mouseenter={stopHidingPopover} - on:mouseleave={hidePopover} >
    {#if hoverTarget.description} @@ -414,4 +413,7 @@ padding: 0; margin: 0; } + .binding-popover.helper :global(code) { + font-size: 12px; + } From b159258fb5c814bb9c512973a73e9832c6682068 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 17:04:39 +0000 Subject: [PATCH 54/84] Restore mouse functionality --- .../src/components/common/bindings/BindingSidePanel.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index 4815353cff..3a3c3c4034 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -123,6 +123,7 @@ maxHeight={480} dismissible={false} on:mouseenter={stopHidingPopover} + on:mouseleave={hidePopover} >
    {#if hoverTarget.description} From 3aed79ad03a5f36d1e9d0c7646dea82289e29365 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 19:08:29 +0000 Subject: [PATCH 55/84] Remove helpers subheading --- .../src/components/common/bindings/BindingSidePanel.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index 3a3c3c4034..1595aed3b5 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -237,7 +237,6 @@ {#if selectedCategory === "Helpers" || search} {#if filteredHelpers?.length}
    -
    Helpers
      {#each filteredHelpers as helper}
    • Date: Thu, 29 Feb 2024 19:40:21 +0000 Subject: [PATCH 56/84] Lint, remove drawer modal border, bump account portal --- packages/account-portal | 2 +- packages/bbui/src/Drawer/Drawer.svelte | 1 + packages/builder/src/components/common/CodeEditor/index.js | 1 - .../builder/src/components/common/bindings/BindingPanel.svelte | 2 -- .../[application]/design/[screenId]/_components/AppPanel.svelte | 2 +- 5 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/account-portal b/packages/account-portal index 8c446c4ba3..19f7a5829f 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 8c446c4ba385592127fa31755d3b64467b291882 +Subproject commit 19f7a5829f4d23cbc694136e45d94482a59a475a diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 118a7dcd8e..606b4e28cd 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -211,6 +211,7 @@ width: 70vw; bottom: 15vh; height: 70vh; + border: none; } .drawer.stacked { transform: translateY(calc(-1 * 1024px * (1 - var(--scale-factor)))) diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index 962e0bd393..82c8ddf647 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -1,4 +1,3 @@ -import { EditorView } from "@codemirror/view" import { getManifest } from "@budibase/string-templates" import sanitizeHtml from "sanitize-html" import { groupBy } from "lodash" diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 2e4a7a63a8..a3d57e1d5c 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -225,7 +225,6 @@ ...getHelperCompletions(editorMode), ]), ]} - height="100%" autofocus={autofocusEditor} placeholder="Add bindings by typing {{ or use the menu on the right" /> @@ -242,7 +241,6 @@ mode={EditorModes.JS} bind:getCaretPosition bind:insertAtPos - height="100%" autofocus={autofocusEditor} placeholder="Add bindings by typing $ or use the menu on the right" /> diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index c90c4ea599..4617814485 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -33,7 +33,7 @@ flex-direction: column; justify-content: flex-start; align-items: stretch; - padding: 9px var(--spacing-m); + padding: 9px 10px 12px 10px; position: relative; transition: width 360ms ease-out; } From 10b5ab34cfa10cb1ad6c278879fc66b6815c6628 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 19:51:25 +0000 Subject: [PATCH 57/84] Re-add border for drawer modals --- packages/bbui/src/Drawer/Drawer.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 606b4e28cd..118a7dcd8e 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -211,7 +211,6 @@ width: 70vw; bottom: 15vh; height: 70vh; - border: none; } .drawer.stacked { transform: translateY(calc(-1 * 1024px * (1 - var(--scale-factor)))) From d2cdee13aec50d912fea881cf90ad8562a38c088 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 29 Feb 2024 20:18:20 +0000 Subject: [PATCH 58/84] Update automation code editors to work with new components --- .../SetupPanel/AutomationBlockSetup.svelte | 24 ++++--------------- .../common/CodeEditor/CodeEditor.svelte | 15 ++++++++---- .../common/bindings/BindingPanel.svelte | 9 ------- .../common/bindings/BindingSidePanel.svelte | 6 ++--- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 2434002d52..aff9e43aa7 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -547,7 +547,7 @@ {:else if value.customType === "code"}
      -
      +
      { @@ -560,20 +560,10 @@ autocompleteEnabled={codeMode !== EditorModes.JS} bind:getCaretPosition bind:insertAtPos - height={500} + placeholder={codeMode === EditorModes.Handlebars + ? "Add bindings by typing {{" + : null} /> -
      - {#if codeMode === EditorModes.Handlebars} - -
      -
      - Add available bindings by typing - }} - -
      -
      - {/if} -
      {#if editingJs}
      @@ -650,11 +640,6 @@ width: 320px; } - .messaging { - display: flex; - align-items: center; - margin-top: var(--spacing-xl); - } .fields { display: flex; flex-direction: column; @@ -666,7 +651,6 @@ .block-field { display: flex; /* Use Flexbox */ justify-content: space-between; - align-items: center; flex-direction: row; /* Arrange label and field side by side */ align-items: center; /* Align vertically in the center */ gap: 10px; /* Add some space between label and field */ diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 58942e5091..080476a9b7 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -47,7 +47,6 @@ export let label export let completions = [] - export let height = 200 export let resize = "none" export let mode = EditorModes.Handlebars export let value = "" @@ -241,8 +240,6 @@ } } - $: editorHeight = typeof height === "number" ? `${height}px` : height - // Init when all elements are ready $: if (mounted && !isEditorInitialised) { isEditorInitialised = true @@ -284,14 +281,22 @@ diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 2c8d310619..9a2f0addbc 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,5 +1,6 @@ import ClientApp from "./components/ClientApp.svelte" import UpdatingApp from "./components/UpdatingApp.svelte" +import MaintenanceScreen from "./components/MaintenanceScreen.svelte" import { builderStore, appStore, @@ -65,6 +66,15 @@ const loadBudibase = async () => { await environmentStore.actions.fetchEnvironment() } + const maintenanceList = get(environmentStore)?.maintenance + if (maintenanceList?.length > 0) { + new MaintenanceScreen({ + target: window.document.body, + props: { maintenanceList }, + }) + return + } + // Register handler for runtime events from the builder window.handleBuilderRuntimeEvent = (type, data) => { if (!window["##BUDIBASE_IN_BUILDER##"]) { From 8eee45b44ac4035b91d8f54e2082d2bafbd7a6e5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 10:23:52 +0000 Subject: [PATCH 73/84] Lint --- .../builder/src/components/common/bindings/BindingPanel.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index d0f88f8029..aa765c03f6 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -7,7 +7,7 @@ Body, Button, } from "@budibase/bbui" - import { createEventDispatcher, getContext, onMount } from "svelte" + import { createEventDispatcher, getContext } from "svelte" import { decodeJSBinding, encodeJSBinding, From ed2926d924d7eceb8a8ada8d91672b8c4dbdccc0 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 13 Mar 2024 16:08:21 +0000 Subject: [PATCH 74/84] Significantly reduce reliance on the config.* methods on row.spec.ts, making them more independent. --- .../server/src/api/routes/tests/row.spec.ts | 606 ++++++++---------- 1 file changed, 275 insertions(+), 331 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index d50dd8a3d9..cf4e7c26d0 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -7,6 +7,7 @@ import { context, InternalTable, roles, tenancy } from "@budibase/backend-core" import { quotas } from "@budibase/pro" import { AutoFieldSubType, + Datasource, DeleteRow, FieldSchema, FieldType, @@ -24,6 +25,7 @@ import { StaticQuotaName, Table, TableSourceType, + ViewV2, } from "@budibase/types" import { expectAnyExternalColsAttributes, @@ -32,7 +34,7 @@ import { mocks, structures, } from "@budibase/backend-core/tests" -import _ from "lodash" +import _, { merge } from "lodash" import * as uuid from "uuid" const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString() @@ -51,12 +53,21 @@ describe.each([ ["mssql", databaseTestProviders.mssql], ["mariadb", databaseTestProviders.mariadb], ])("/rows (%s)", (__, dsProvider) => { - const isInternal = !dsProvider - + const isInternal = dsProvider === undefined const request = setup.getRequest() const config = setup.getConfig() + let table: Table - let tableId: string + let datasource: Datasource | undefined + + beforeAll(async () => { + await config.init() + if (dsProvider) { + datasource = await config.createDatasource({ + datasource: await dsProvider.datasource(), + }) + } + }) afterAll(async () => { if (dsProvider) { @@ -65,24 +76,17 @@ describe.each([ setup.afterAll() }) - beforeAll(async () => { - await config.init() - - if (dsProvider) { - await config.createDatasource({ - datasource: await dsProvider.datasource(), - }) - } - }) - - const generateTableConfig: () => SaveTableRequest = () => { - return { + function saveTableRequest( + ...overrides: Partial[] + ): SaveTableRequest { + const req: SaveTableRequest = { name: uuid.v4().substring(0, 16), type: "table", + sourceType: datasource + ? TableSourceType.EXTERNAL + : TableSourceType.INTERNAL, + sourceId: datasource ? datasource._id! : INTERNAL_TABLE_SOURCE_ID, primary: ["id"], - primaryDisplay: "name", - sourceType: TableSourceType.INTERNAL, - sourceId: INTERNAL_TABLE_SOURCE_ID, schema: { id: { type: FieldType.AUTO, @@ -92,22 +96,36 @@ describe.each([ presence: true, }, }, - name: { - type: FieldType.STRING, - name: "name", - constraints: { - type: "string", + }, + } + return merge(req, ...overrides) + } + + function defaultTable( + ...overrides: Partial[] + ): SaveTableRequest { + return saveTableRequest( + { + primaryDisplay: "name", + schema: { + name: { + type: FieldType.STRING, + name: "name", + constraints: { + type: "string", + }, }, - }, - description: { - type: FieldType.STRING, - name: "description", - constraints: { - type: "string", + description: { + type: FieldType.STRING, + name: "description", + constraints: { + type: "string", + }, }, }, }, - } + ...overrides + ) } beforeEach(async () => { @@ -148,9 +166,7 @@ describe.each([ } beforeAll(async () => { - const tableConfig = generateTableConfig() - let table = await createTable(tableConfig) - tableId = table._id! + table = await config.api.table.save(defaultTable()) }) describe("save, load, update", () => { @@ -158,13 +174,13 @@ describe.each([ const rowUsage = await getRowUsage() const res = await request - .post(`/api/${tableId}/rows`) - .send(basicRow(tableId)) + .post(`/api/${table._id!}/rows`) + .send(basicRow(table._id!)) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect((res as any).res.statusMessage).toEqual( - `${config.table!.name} saved successfully` + `${table.name} saved successfully` ) expect(res.body.name).toEqual("Test Contact") expect(res.body._rev).toBeDefined() @@ -174,13 +190,10 @@ describe.each([ it("Increment row autoId per create row request", async () => { const rowUsage = await getRowUsage() - const tableConfig = generateTableConfig() - const newTable = await createTable( - { - ...tableConfig, + const newTable = await config.api.table.save( + saveTableRequest({ name: "TestTableAuto", schema: { - ...tableConfig.schema, "Row ID": { name: "Row ID", type: FieldType.NUMBER, @@ -197,37 +210,25 @@ describe.each([ }, }, }, - }, - { skipReassigning: true } + }) ) - const ids = [1, 2, 3] - - // Performing several create row requests should increment the autoID fields accordingly - const createRow = async (id: number) => { - const res = await config.api.row.save(newTable._id!, { - name: "row_" + id, - }) - expect(res.name).toEqual("row_" + id) - expect(res._rev).toBeDefined() - expect(res["Row ID"]).toEqual(id) + let previousId = 0 + for (let i = 0; i < 10; i++) { + const row = await config.api.row.save(newTable._id!, {}) + expect(row["Row ID"]).toBeGreaterThan(previousId) + previousId = row["Row ID"] } - - for (let i = 0; i < ids.length; i++) { - await createRow(ids[i]) - } - - await assertRowUsage(rowUsage + ids.length) + await assertRowUsage(rowUsage + 10) }) it("updates a row successfully", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() - const res = await config.api.row.save(tableId, { + const res = await config.api.row.save(table._id!, { _id: existing._id, _rev: existing._rev, - tableId, name: "Updated Name", }) @@ -236,9 +237,9 @@ describe.each([ }) it("should load a row", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) - const res = await config.api.row.get(tableId, existing._id!) + const res = await config.api.row.get(table._id!, existing._id!) expect(res).toEqual({ ...existing, @@ -247,29 +248,22 @@ describe.each([ }) it("should list all rows for given tableId", async () => { - const table = await createTable(generateTableConfig(), { - skipReassigning: true, - }) - const tableId = table._id! - const newRow = { - tableId, - name: "Second Contact", - description: "new", - } - const firstRow = await config.createRow({ tableId }) - await config.createRow(newRow) + const table = await config.api.table.save(defaultTable()) + const rows = await Promise.all([ + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), + ]) - const res = await config.api.row.fetch(tableId) - - expect(res.length).toBe(2) - expect(res.find((r: Row) => r.name === newRow.name)).toBeDefined() - expect(res.find((r: Row) => r.name === firstRow.name)).toBeDefined() + const res = await config.api.row.fetch(table._id!) + expect(res.map(r => r._id)).toEqual( + expect.arrayContaining(rows.map(r => r._id)) + ) }) it("load should return 404 when row does not exist", async () => { - await config.createRow() - - await config.api.row.get(tableId, "1234567", { + const table = await config.api.table.save(defaultTable()) + await config.api.row.save(table._id!, {}) + await config.api.row.get(table._id!, "1234567", { status: 404, }) }) @@ -459,7 +453,8 @@ describe.each([ }, }) - const createViewResponse = await config.createView({ + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, name: uuid.v4(), schema: { Country: { @@ -496,25 +491,25 @@ describe.each([ let otherTable: Table beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) - const otherTableConfig = generateTableConfig() - // need a short name of table here - for relationship tests - otherTableConfig.name = "a" - otherTableConfig.schema.relationship = { - name: "relationship", - relationshipType: RelationshipType.ONE_TO_MANY, - type: FieldType.LINK, - tableId: table._id!, - fieldName: "relationship", - } - otherTable = await createTable(otherTableConfig) - // need to set the config back to the original table - config.table = table + table = await config.api.table.save(defaultTable()) + otherTable = await config.api.table.save( + defaultTable({ + name: "a", + schema: { + relationship: { + name: "relationship", + relationshipType: RelationshipType.ONE_TO_MANY, + type: FieldType.LINK, + tableId: table._id!, + fieldName: "relationship", + }, + }, + }) + ) }) it("should update only the fields that are supplied", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() @@ -536,7 +531,7 @@ describe.each([ }) it("should throw an error when given improper types", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() await config.api.row.patch( @@ -628,12 +623,11 @@ describe.each([ describe("destroy", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should be able to delete a row", async () => { - const createdRow = await config.createRow() + const createdRow = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() const res = await config.api.row.bulkDelete(table._id!, { @@ -644,7 +638,7 @@ describe.each([ }) it("should be able to bulk delete rows, including a row that doesn't exist", async () => { - const createdRow = await config.createRow() + const createdRow = await config.api.row.save(table._id!, {}) const res = await config.api.row.bulkDelete(table._id!, { rows: [createdRow, { _id: "9999999" }], @@ -657,8 +651,7 @@ describe.each([ describe("validate", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should return no errors on valid row", async () => { @@ -690,13 +683,12 @@ describe.each([ describe("bulkDelete", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should be able to delete a bulk set of rows", async () => { - const row1 = await config.createRow() - const row2 = await config.createRow() + const row1 = await config.api.row.save(table._id!, {}) + const row2 = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() const res = await config.api.row.bulkDelete(table._id!, { @@ -710,9 +702,9 @@ describe.each([ it("should be able to delete a variety of row set types", async () => { const [row1, row2, row3] = await Promise.all([ - config.createRow(), - config.createRow(), - config.createRow(), + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), ]) const rowUsage = await getRowUsage() @@ -726,7 +718,7 @@ describe.each([ }) it("should accept a valid row object and delete the row", async () => { - const row1 = await config.createRow() + const row1 = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() const res = await config.api.row.delete(table._id!, row1 as DeleteRow) @@ -768,12 +760,11 @@ describe.each([ isInternal && describe("fetchView", () => { beforeEach(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should be able to fetch tables contents via 'view'", async () => { - const row = await config.createRow() + const row = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() const rows = await config.api.legacyView.get(table._id!) @@ -797,7 +788,7 @@ describe.each([ filters: [], schema: {}, }) - const row = await config.createRow() + const row = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() const rows = await config.api.legacyView.get(view.name) @@ -810,45 +801,34 @@ describe.each([ describe("fetchEnrichedRows", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should allow enriching some linked rows", async () => { const { linkedTable, firstRow, secondRow } = await tenancy.doInTenant( config.getTenantId(), async () => { - const linkedTable = await config.createLinkedTable( - RelationshipType.ONE_TO_MANY, - ["link"], - { - // Making sure that the combined table name + column name is within postgres limits - name: uuid.v4().replace(/-/g, "").substring(0, 16), - type: "table", - primary: ["id"], - primaryDisplay: "id", + const linkedTable = await config.api.table.save( + defaultTable({ schema: { - id: { - type: FieldType.AUTO, - name: "id", - autocolumn: true, - constraints: { - presence: true, - }, + link: { + name: "link", + fieldName: "link", + type: FieldType.LINK, + relationshipType: RelationshipType.ONE_TO_MANY, + tableId: table._id!, }, }, - } + }) ) - const firstRow = await config.createRow({ + const firstRow = await config.api.row.save(table._id!, { name: "Test Contact", description: "original description", - tableId: table._id, }) - const secondRow = await config.createRow({ + const secondRow = await config.api.row.save(linkedTable._id!, { name: "Test 2", description: "og desc", link: [{ _id: firstRow._id }], - tableId: linkedTable._id, }) return { linkedTable, firstRow, secondRow } } @@ -882,8 +862,7 @@ describe.each([ isInternal && describe("attachments", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should allow enriching attachment rows", async () => { @@ -912,12 +891,11 @@ describe.each([ describe("exportData", () => { beforeAll(async () => { - const tableConfig = generateTableConfig() - table = await createTable(tableConfig) + table = await config.api.table.save(defaultTable()) }) it("should allow exporting all columns", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) const res = await config.api.row.exportRows(table._id!, { rows: [existing._id!], }) @@ -935,7 +913,7 @@ describe.each([ }) it("should allow exporting only certain columns", async () => { - const existing = await config.createRow() + const existing = await config.api.row.save(table._id!, {}) const res = await config.api.row.exportRows(table._id!, { rows: [existing._id!], columns: ["_id"], @@ -952,21 +930,10 @@ describe.each([ describe("view 2.0", () => { async function userTable(): Promise { - return { + return saveTableRequest({ name: `users_${uuid.v4()}`, - sourceId: INTERNAL_TABLE_SOURCE_ID, - sourceType: TableSourceType.INTERNAL, type: "table", - primary: ["id"], schema: { - id: { - type: FieldType.AUTO, - name: "id", - autocolumn: true, - constraints: { - presence: true, - }, - }, name: { type: FieldType.STRING, name: "name", @@ -988,7 +955,7 @@ describe.each([ name: "jobTitle", }, }, - } + }) } const randomRowData = () => ({ @@ -1001,8 +968,9 @@ describe.each([ describe("create", () => { it("should persist a new row with only the provided view fields", async () => { - const table = await createTable(await userTable()) - const view = await config.createView({ + const table = await config.api.table.save(await userTable()) + const view = await config.api.viewV2.create({ + tableId: table._id!, schema: { name: { visible: true }, surname: { visible: true }, @@ -1036,9 +1004,10 @@ describe.each([ describe("patch", () => { it("should update only the view fields for a row", async () => { - const table = await createTable(await userTable()) + const table = await config.api.table.save(await userTable()) const tableId = table._id! - const view = await config.createView({ + const view = await config.api.viewV2.create({ + tableId: tableId, schema: { name: { visible: true }, address: { visible: true }, @@ -1077,16 +1046,17 @@ describe.each([ describe("destroy", () => { it("should be able to delete a row", async () => { - const table = await createTable(await userTable()) + const table = await config.api.table.save(await userTable()) const tableId = table._id! - const view = await config.createView({ + const view = await config.api.viewV2.create({ + tableId: tableId, schema: { name: { visible: true }, address: { visible: true }, }, }) - const createdRow = await config.createRow() + const createdRow = await config.api.row.save(table._id!, {}) const rowUsage = await getRowUsage() await config.api.row.bulkDelete(view.id, { rows: [createdRow] }) @@ -1099,9 +1069,10 @@ describe.each([ }) it("should be able to delete multiple rows", async () => { - const table = await createTable(await userTable()) + const table = await config.api.table.save(await userTable()) const tableId = table._id! - const view = await config.createView({ + const view = await config.api.viewV2.create({ + tableId: tableId, schema: { name: { visible: true }, address: { visible: true }, @@ -1109,9 +1080,9 @@ describe.each([ }) const rows = await Promise.all([ - config.createRow(), - config.createRow(), - config.createRow(), + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), + config.api.row.save(table._id!, {}), ]) const rowUsage = await getRowUsage() @@ -1130,46 +1101,39 @@ describe.each([ }) describe("view search", () => { + let table: Table const viewSchema = { age: { visible: true }, name: { visible: true } } - async function userTable(): Promise
      { - return { - name: `users_${uuid.v4()}`, - sourceId: INTERNAL_TABLE_SOURCE_ID, - sourceType: TableSourceType.INTERNAL, - type: "table", - primary: ["id"], - schema: { - id: { - type: FieldType.AUTO, - name: "id", - autocolumn: true, - constraints: { - presence: true, + + beforeAll(async () => { + table = await config.api.table.save( + saveTableRequest({ + name: `users_${uuid.v4()}`, + schema: { + name: { + type: FieldType.STRING, + name: "name", + constraints: { type: "string" }, + }, + age: { + type: FieldType.NUMBER, + name: "age", + constraints: {}, }, }, - name: { - type: FieldType.STRING, - name: "name", - constraints: { type: "string" }, - }, - age: { - type: FieldType.NUMBER, - name: "age", - constraints: {}, - }, - }, - } - } + }) + ) + }) it("returns empty rows from view when no schema is passed", async () => { - const table = await createTable(await userTable()) const rows = await Promise.all( Array.from({ length: 10 }, () => config.api.row.save(table._id!, { tableId: table._id }) ) ) - const createViewResponse = await config.createView() + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, + }) const response = await config.api.viewV2.search(createViewResponse.id) expect(response.rows).toHaveLength(10) @@ -1193,8 +1157,6 @@ describe.each([ }) it("searching respects the view filters", async () => { - const table = await createTable(await userTable()) - await Promise.all( Array.from({ length: 10 }, () => config.api.row.save(table._id!, { @@ -1215,7 +1177,8 @@ describe.each([ ) ) - const createViewResponse = await config.createView({ + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, query: [ { operator: SearchQueryOperators.EQUAL, field: "age", value: 40 }, ], @@ -1316,8 +1279,9 @@ describe.each([ ] describe("sorting", () => { + let table: Table beforeAll(async () => { - const table = await createTable(await userTable()) + table = await config.api.table.save(await userTable()) const users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 }, @@ -1337,7 +1301,8 @@ describe.each([ it.each(sortTestOptions)( "allow sorting (%s)", async (sortParams, expected) => { - const createViewResponse = await config.createView({ + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, sort: sortParams, schema: viewSchema, }) @@ -1356,7 +1321,8 @@ describe.each([ it.each(sortTestOptions)( "allow override the default view sorting (%s)", async (sortParams, expected) => { - const createViewResponse = await config.createView({ + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, sort: { field: "name", order: SortOrder.ASCENDING, @@ -1384,7 +1350,7 @@ describe.each([ }) it("when schema is defined, defined columns and row attributes are returned", async () => { - const table = await createTable(await userTable()) + const table = await config.api.table.save(await userTable()) const rows = await Promise.all( Array.from({ length: 10 }, () => config.api.row.save(table._id!, { @@ -1395,7 +1361,8 @@ describe.each([ ) ) - const view = await config.createView({ + const view = await config.api.viewV2.create({ + tableId: table._id!, schema: { name: { visible: true } }, }) const response = await config.api.viewV2.search(view.id) @@ -1415,21 +1382,25 @@ describe.each([ }) it("views without data can be returned", async () => { - const table = await createTable(await userTable()) - - const createViewResponse = await config.createView() + const table = await config.api.table.save(await userTable()) + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, + }) const response = await config.api.viewV2.search(createViewResponse.id) - expect(response.rows).toHaveLength(0) }) it("respects the limit parameter", async () => { - await createTable(await userTable()) - await Promise.all(Array.from({ length: 10 }, () => config.createRow())) + const table = await config.api.table.save(await userTable()) + await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) + ) const limit = generator.integer({ min: 1, max: 8 }) - const createViewResponse = await config.createView() + const createViewResponse = await config.api.viewV2.create({ + tableId: table._id!, + }) const response = await config.api.viewV2.search(createViewResponse.id, { limit, query: {}, @@ -1439,56 +1410,49 @@ describe.each([ }) it("can handle pagination", async () => { - await createTable(await userTable()) - await Promise.all(Array.from({ length: 10 }, () => config.createRow())) - - const createViewResponse = await config.createView() - const allRows = (await config.api.viewV2.search(createViewResponse.id)) - .rows - - const firstPageResponse = await config.api.viewV2.search( - createViewResponse.id, - { - paginate: true, - limit: 4, - query: {}, - } + const table = await config.api.table.save(await userTable()) + await Promise.all( + Array.from({ length: 10 }, () => config.api.row.save(table._id!, {})) ) - expect(firstPageResponse).toEqual({ - rows: expect.arrayContaining(allRows.slice(0, 4)), + const view = await config.api.viewV2.create({ + tableId: table._id!, + }) + const rows = (await config.api.viewV2.search(view.id)).rows + + const page1 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + query: {}, + }) + expect(page1).toEqual({ + rows: expect.arrayContaining(rows.slice(0, 4)), totalRows: isInternal ? 10 : undefined, hasNextPage: true, bookmark: expect.anything(), }) - const secondPageResponse = await config.api.viewV2.search( - createViewResponse.id, - { - paginate: true, - limit: 4, - bookmark: firstPageResponse.bookmark, + const page2 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + bookmark: page1.bookmark, - query: {}, - } - ) - expect(secondPageResponse).toEqual({ - rows: expect.arrayContaining(allRows.slice(4, 8)), + query: {}, + }) + expect(page2).toEqual({ + rows: expect.arrayContaining(rows.slice(4, 8)), totalRows: isInternal ? 10 : undefined, hasNextPage: true, bookmark: expect.anything(), }) - const lastPageResponse = await config.api.viewV2.search( - createViewResponse.id, - { - paginate: true, - limit: 4, - bookmark: secondPageResponse.bookmark, - query: {}, - } - ) - expect(lastPageResponse).toEqual({ - rows: expect.arrayContaining(allRows.slice(8)), + const page3 = await config.api.viewV2.search(view.id, { + paginate: true, + limit: 4, + bookmark: page2.bookmark, + query: {}, + }) + expect(page3).toEqual({ + rows: expect.arrayContaining(rows.slice(8)), totalRows: isInternal ? 10 : undefined, hasNextPage: false, bookmark: expect.anything(), @@ -1513,19 +1477,20 @@ describe.each([ }) describe("permissions", () => { - let viewId: string - let tableId: string + let table: Table + let view: ViewV2 beforeAll(async () => { - await createTable(await userTable()) + table = await config.api.table.save(await userTable()) await Promise.all( - Array.from({ length: 10 }, () => config.createRow()) + Array.from({ length: 10 }, () => + config.api.row.save(table._id!, {}) + ) ) - const createViewResponse = await config.createView() - - tableId = table._id! - viewId = createViewResponse.id + view = await config.api.viewV2.create({ + tableId: table._id!, + }) }) beforeEach(() => { @@ -1534,7 +1499,7 @@ describe.each([ it("does not allow public users to fetch by default", async () => { await config.publish() - await config.api.viewV2.publicSearch(viewId, undefined, { + await config.api.viewV2.publicSearch(view.id, undefined, { status: 403, }) }) @@ -1543,11 +1508,11 @@ describe.each([ await config.api.permission.add({ roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, level: PermissionLevel.READ, - resourceId: viewId, + resourceId: view.id, }) await config.publish() - const response = await config.api.viewV2.publicSearch(viewId) + const response = await config.api.viewV2.publicSearch(view.id) expect(response.rows).toHaveLength(10) }) @@ -1556,11 +1521,11 @@ describe.each([ await config.api.permission.add({ roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, level: PermissionLevel.READ, - resourceId: tableId, + resourceId: table._id!, }) await config.publish() - const response = await config.api.viewV2.publicSearch(viewId) + const response = await config.api.viewV2.publicSearch(view.id) expect(response.rows).toHaveLength(10) }) @@ -1569,16 +1534,16 @@ describe.each([ await config.api.permission.add({ roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, level: PermissionLevel.READ, - resourceId: tableId, + resourceId: table._id!, }) await config.api.permission.add({ roleId: roles.BUILTIN_ROLE_IDS.POWER, level: PermissionLevel.READ, - resourceId: viewId, + resourceId: view.id, }) await config.publish() - await config.api.viewV2.publicSearch(viewId, undefined, { + await config.api.viewV2.publicSearch(view.id, undefined, { status: 403, }) }) @@ -1589,18 +1554,8 @@ describe.each([ let o2mTable: Table let m2mTable: Table beforeAll(async () => { - o2mTable = await createTable( - { ...generateTableConfig(), name: "o2m" }, - { - skipReassigning: true, - } - ) - m2mTable = await createTable( - { ...generateTableConfig(), name: "m2m" }, - { - skipReassigning: true, - } - ) + o2mTable = await config.api.table.save(defaultTable({ name: "o2m" })) + m2mTable = await config.api.table.save(defaultTable({ name: "m2m" })) }) describe.each([ @@ -1662,21 +1617,9 @@ describe.each([ let m2mData: Row[] beforeAll(async () => { - const tableConfig = generateTableConfig() - - if (config.datasource) { - tableConfig.sourceId = config.datasource._id! - if (config.datasource.plus) { - tableConfig.sourceType = TableSourceType.EXTERNAL - } - } - const table = await config.api.table.save({ - ...tableConfig, - schema: { - ...tableConfig.schema, - ...relSchema(), - }, - }) + const table = await config.api.table.save( + defaultTable({ schema: relSchema() }) + ) tableId = table._id! o2mData = [ @@ -1994,20 +1937,23 @@ describe.each([ }) describe("Formula fields", () => { - let relationshipTable: Table, tableId: string, relatedRow: Row + let table: Table + let otherTable: Table + let relatedRow: Row beforeAll(async () => { - const otherTableId = config.table!._id! - const cfg = generateTableConfig() - relationshipTable = await config.createLinkedTable( - RelationshipType.ONE_TO_MANY, - ["links"], - { - ...cfg, - // needs to be a short name + otherTable = await config.api.table.save(defaultTable()) + table = await config.api.table.save( + saveTableRequest({ name: "b", schema: { - ...cfg.schema, + links: { + name: "links", + fieldName: "links", + type: FieldType.LINK, + tableId: otherTable._id!, + relationshipType: RelationshipType.ONE_TO_MANY, + }, formula: { name: "formula", type: FieldType.FORMULA, @@ -2015,25 +1961,23 @@ describe.each([ formulaType: FormulaType.DYNAMIC, }, }, - } + }) ) - tableId = relationshipTable._id! - - relatedRow = await config.api.row.save(otherTableId, { + relatedRow = await config.api.row.save(otherTable._id!, { name: generator.word(), description: generator.paragraph(), }) - await config.api.row.save(tableId, { + await config.api.row.save(table._id!, { name: generator.word(), description: generator.paragraph(), - tableId, + tableId: table._id!, links: [relatedRow._id], }) }) it("should be able to search for rows containing formulas", async () => { - const { rows } = await config.api.row.search(tableId) + const { rows } = await config.api.row.search(table._id!) expect(rows.length).toBe(1) expect(rows[0].links.length).toBe(1) const row = rows[0] @@ -2054,22 +1998,22 @@ describe.each([ ` ).toString("base64") - const table = await config.createTable({ - name: "table", - type: "table", - schema: { - text: { - name: "text", - type: FieldType.STRING, + const table = await config.api.table.save( + saveTableRequest({ + schema: { + text: { + name: "text", + type: FieldType.STRING, + }, + formula: { + name: "formula", + type: FieldType.FORMULA, + formula: `{{ js "${js}"}}`, + formulaType: FormulaType.DYNAMIC, + }, }, - formula: { - name: "formula", - type: FieldType.FORMULA, - formula: `{{ js "${js}"}}`, - formulaType: FormulaType.DYNAMIC, - }, - }, - }) + }) + ) await config.api.row.save(table._id!, { text: "foo" }) const { rows } = await config.api.row.search(table._id!) From d3c8a28ab6da370d2f1c6b790b2e9d29c8043056 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 13 Mar 2024 16:20:45 +0000 Subject: [PATCH 75/84] Remove more old testing practices. --- .../server/src/api/routes/tests/row.spec.ts | 143 ++++++------------ 1 file changed, 50 insertions(+), 93 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index cf4e7c26d0..09866a7917 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -44,8 +44,6 @@ jest.unmock("mysql2") jest.unmock("mysql2/promise") jest.unmock("mssql") -const { basicRow } = setup.structures - describe.each([ ["internal", undefined], ["postgres", databaseTestProviders.postgres], @@ -54,7 +52,6 @@ describe.each([ ["mariadb", databaseTestProviders.mariadb], ])("/rows (%s)", (__, dsProvider) => { const isInternal = dsProvider === undefined - const request = setup.getRequest() const config = setup.getConfig() let table: Table @@ -172,18 +169,11 @@ describe.each([ describe("save, load, update", () => { it("returns a success message when the row is created", async () => { const rowUsage = await getRowUsage() - - const res = await request - .post(`/api/${table._id!}/rows`) - .send(basicRow(table._id!)) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect((res as any).res.statusMessage).toEqual( - `${table.name} saved successfully` - ) - expect(res.body.name).toEqual("Test Contact") - expect(res.body._rev).toBeDefined() + const row = await config.api.row.save(table._id!, { + name: "Test Contact", + }) + expect(row.name).toEqual("Test Contact") + expect(row._rev).toBeDefined() await assertRowUsage(rowUsage + 1) }) @@ -1638,37 +1628,33 @@ describe.each([ }) it("can save a row when relationship fields are empty", async () => { - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), - } - const row = await config.api.row.save(tableId, rowData) + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", + }) expect(row).toEqual({ - name: rowData.name, - description: rowData.description, - tableId, _id: expect.any(String), _rev: expect.any(String), id: isInternal ? undefined : expect.any(Number), type: isInternal ? "row" : undefined, + name: "foo", + description: "bar", + tableId, }) }) it("can save a row with a single relationship field", async () => { const user = _.sample(o2mData)! - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", user: [user], - } - const row = await config.api.row.save(tableId, rowData) + }) expect(row).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, user: [user].map(u => resultMapper(u)), _id: expect.any(String), @@ -1681,17 +1667,15 @@ describe.each([ it("can save a row with a multiple relationship field", async () => { const selectedUsers = _.sampleSize(m2mData, 2) - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", users: selectedUsers, - } - const row = await config.api.row.save(tableId, rowData) + }) expect(row).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, users: expect.arrayContaining(selectedUsers.map(u => resultMapper(u))), _id: expect.any(String), @@ -1702,17 +1686,15 @@ describe.each([ }) it("can retrieve rows with no populated relationships", async () => { - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), - } - const row = await config.api.row.save(tableId, rowData) + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", + }) const retrieved = await config.api.row.get(tableId, row._id!) expect(retrieved).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, user: undefined, users: undefined, @@ -1727,19 +1709,17 @@ describe.each([ const user1 = _.sample(o2mData)! const [user2, user3] = _.sampleSize(m2mData, 2) - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", users: [user2, user3], user: [user1], - } - const row = await config.api.row.save(tableId, rowData) + }) const retrieved = await config.api.row.get(tableId, row._id!) expect(retrieved).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, user: expect.arrayContaining([user1].map(u => resultMapper(u))), users: expect.arrayContaining([user2, user3].map(u => resultMapper(u))), @@ -1755,13 +1735,11 @@ describe.each([ const user = _.sample(o2mData)! const [users1, users2, users3] = _.sampleSize(m2mData, 3) - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", users: [users1, users2], - } - const row = await config.api.row.save(tableId, rowData) + }) const updatedRow = await config.api.row.save(tableId, { ...row, @@ -1769,8 +1747,8 @@ describe.each([ users: [users3, users1], }) expect(updatedRow).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, user: expect.arrayContaining([user].map(u => resultMapper(u))), users: expect.arrayContaining( @@ -1786,14 +1764,11 @@ describe.each([ it("can wipe an existing populated relationships in row", async () => { const [user1, user2] = _.sampleSize(m2mData, 2) - - const rowData = { - ...basicRow(tableId), - name: generator.name(), - description: generator.name(), + const row = await config.api.row.save(tableId, { + name: "foo", + description: "bar", users: [user1, user2], - } - const row = await config.api.row.save(tableId, rowData) + }) const updatedRow = await config.api.row.save(tableId, { ...row, @@ -1801,8 +1776,8 @@ describe.each([ users: null, }) expect(updatedRow).toEqual({ - name: rowData.name, - description: rowData.description, + name: "foo", + description: "bar", tableId, _id: row._id, _rev: expect.any(String), @@ -1815,28 +1790,19 @@ describe.each([ const [user1] = _.sampleSize(o2mData, 1) const [users1, users2, users3] = _.sampleSize(m2mData, 3) - const rows: { - name: string - description: string - user?: Row[] - users?: Row[] - tableId: string - }[] = [ + const rows = [ { - ...basicRow(tableId), name: generator.name(), description: generator.name(), users: [users1, users2], }, { - ...basicRow(tableId), name: generator.name(), description: generator.name(), user: [user1], users: [users1, users3], }, { - ...basicRow(tableId), name: generator.name(), description: generator.name(), users: [users3], @@ -1874,28 +1840,19 @@ describe.each([ const [user1] = _.sampleSize(o2mData, 1) const [users1, users2, users3] = _.sampleSize(m2mData, 3) - const rows: { - name: string - description: string - user?: Row[] - users?: Row[] - tableId: string - }[] = [ + const rows = [ { - ...basicRow(tableId), name: generator.name(), description: generator.name(), users: [users1, users2], }, { - ...basicRow(tableId), name: generator.name(), description: generator.name(), user: [user1], users: [users1, users3], }, { - ...basicRow(tableId), name: generator.name(), description: generator.name(), users: [users3], From 68777b08439223ac43f7d1671043f153f3846589 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 13 Mar 2024 16:32:33 +0000 Subject: [PATCH 76/84] Remove all uses of config.createTable --- .../server/src/api/routes/tests/row.spec.ts | 267 ++++++++---------- 1 file changed, 122 insertions(+), 145 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 09866a7917..613928712c 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -32,7 +32,6 @@ import { expectAnyInternalColsAttributes, generator, mocks, - structures, } from "@budibase/backend-core/tests" import _, { merge } from "lodash" import * as uuid from "uuid" @@ -149,19 +148,6 @@ describe.each([ } : undefined - async function createTable( - cfg: Omit, - opts?: { skipReassigning: boolean } - ) { - let table - if (dsProvider) { - table = await config.createExternalTable(cfg, opts) - } else { - table = await config.createTable(cfg, opts) - } - return table - } - beforeAll(async () => { table = await config.api.table.save(defaultTable()) }) @@ -308,45 +294,49 @@ describe.each([ inclusion: ["Alpha", "Beta", "Gamma"], }, } - const table = await createTable({ - name: "TestTable2", - type: "table", - schema: { - name: str, - stringUndefined: str, - stringNull: str, - stringString: str, - numberEmptyString: number, - numberNull: number, - numberUndefined: number, - numberString: number, - numberNumber: number, - datetimeEmptyString: datetime, - datetimeNull: datetime, - datetimeUndefined: datetime, - datetimeString: datetime, - datetimeDate: datetime, - boolNull: bool, - boolEmpty: bool, - boolUndefined: bool, - boolString: bool, - boolBool: bool, - attachmentNull: attachment, - attachmentUndefined: attachment, - attachmentEmpty: attachment, - attachmentEmptyArrayStr: attachment, - arrayFieldEmptyArrayStr: arrayField, - arrayFieldArrayStrKnown: arrayField, - arrayFieldNull: arrayField, - arrayFieldUndefined: arrayField, - optsFieldEmptyStr: optsField, - optsFieldUndefined: optsField, - optsFieldNull: optsField, - optsFieldStrKnown: optsField, - }, - }) + const table = await config.api.table.save( + saveTableRequest({ + name: "TestTable2", + type: "table", + schema: { + name: str, + stringUndefined: str, + stringNull: str, + stringString: str, + numberEmptyString: number, + numberNull: number, + numberUndefined: number, + numberString: number, + numberNumber: number, + datetimeEmptyString: datetime, + datetimeNull: datetime, + datetimeUndefined: datetime, + datetimeString: datetime, + datetimeDate: datetime, + boolNull: bool, + boolEmpty: bool, + boolUndefined: bool, + boolString: bool, + boolBool: bool, + attachmentNull: attachment, + attachmentUndefined: attachment, + attachmentEmpty: attachment, + attachmentEmptyArrayStr: attachment, + arrayFieldEmptyArrayStr: arrayField, + arrayFieldArrayStrKnown: arrayField, + arrayFieldNull: arrayField, + arrayFieldUndefined: arrayField, + optsFieldEmptyStr: optsField, + optsFieldUndefined: optsField, + optsFieldNull: optsField, + optsFieldStrKnown: optsField, + }, + }) + ) - const row = { + const datetimeStr = "1984-04-20T00:00:00.000Z" + + const row = await config.api.row.save(table._id!, { name: "Test Row", stringUndefined: undefined, stringNull: null, @@ -359,8 +349,8 @@ describe.each([ datetimeEmptyString: "", datetimeNull: null, datetimeUndefined: undefined, - datetimeString: "1984-04-20T00:00:00.000Z", - datetimeDate: new Date("1984-04-20"), + datetimeString: datetimeStr, + datetimeDate: new Date(datetimeStr), boolNull: null, boolEmpty: "", boolUndefined: undefined, @@ -379,69 +369,58 @@ describe.each([ optsFieldUndefined: undefined, optsFieldNull: null, optsFieldStrKnown: "Alpha", - } + }) - const createdRow = await config.createRow(row) - const id = createdRow._id! - - const saved = await config.api.row.get(table._id!, id) - - expect(saved.stringUndefined).toBe(undefined) - expect(saved.stringNull).toBe(null) - expect(saved.stringString).toBe("i am a string") - expect(saved.numberEmptyString).toBe(null) - expect(saved.numberNull).toBe(null) - expect(saved.numberUndefined).toBe(undefined) - expect(saved.numberString).toBe(123) - expect(saved.numberNumber).toBe(123) - expect(saved.datetimeEmptyString).toBe(null) - expect(saved.datetimeNull).toBe(null) - expect(saved.datetimeUndefined).toBe(undefined) - expect(saved.datetimeString).toBe( - new Date(row.datetimeString).toISOString() - ) - expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString()) - expect(saved.boolNull).toBe(null) - expect(saved.boolEmpty).toBe(null) - expect(saved.boolUndefined).toBe(undefined) - expect(saved.boolString).toBe(true) - expect(saved.boolBool).toBe(true) - expect(saved.attachmentNull).toEqual([]) - expect(saved.attachmentUndefined).toBe(undefined) - expect(saved.attachmentEmpty).toEqual([]) - expect(saved.attachmentEmptyArrayStr).toEqual([]) - expect(saved.arrayFieldEmptyArrayStr).toEqual([]) - expect(saved.arrayFieldNull).toEqual([]) - expect(saved.arrayFieldUndefined).toEqual(undefined) - expect(saved.optsFieldEmptyStr).toEqual(null) - expect(saved.optsFieldUndefined).toEqual(undefined) - expect(saved.optsFieldNull).toEqual(null) - expect(saved.arrayFieldArrayStrKnown).toEqual(["One"]) - expect(saved.optsFieldStrKnown).toEqual("Alpha") + expect(row.stringUndefined).toBe(undefined) + expect(row.stringNull).toBe(null) + expect(row.stringString).toBe("i am a string") + expect(row.numberEmptyString).toBe(null) + expect(row.numberNull).toBe(null) + expect(row.numberUndefined).toBe(undefined) + expect(row.numberString).toBe(123) + expect(row.numberNumber).toBe(123) + expect(row.datetimeEmptyString).toBe(null) + expect(row.datetimeNull).toBe(null) + expect(row.datetimeUndefined).toBe(undefined) + expect(row.datetimeString).toBe(new Date(datetimeStr).toISOString()) + expect(row.datetimeDate).toBe(new Date(datetimeStr).toISOString()) + expect(row.boolNull).toBe(null) + expect(row.boolEmpty).toBe(null) + expect(row.boolUndefined).toBe(undefined) + expect(row.boolString).toBe(true) + expect(row.boolBool).toBe(true) + expect(row.attachmentNull).toEqual([]) + expect(row.attachmentUndefined).toBe(undefined) + expect(row.attachmentEmpty).toEqual([]) + expect(row.attachmentEmptyArrayStr).toEqual([]) + expect(row.arrayFieldEmptyArrayStr).toEqual([]) + expect(row.arrayFieldNull).toEqual([]) + expect(row.arrayFieldUndefined).toEqual(undefined) + expect(row.optsFieldEmptyStr).toEqual(null) + expect(row.optsFieldUndefined).toEqual(undefined) + expect(row.optsFieldNull).toEqual(null) + expect(row.arrayFieldArrayStrKnown).toEqual(["One"]) + expect(row.optsFieldStrKnown).toEqual("Alpha") }) }) describe("view save", () => { it("views have extra data trimmed", async () => { - const table = await createTable({ - type: "table", - name: "orders", - primary: ["OrderID"], - schema: { - Country: { - type: FieldType.STRING, - name: "Country", + const table = await config.api.table.save( + saveTableRequest({ + name: "orders", + schema: { + Country: { + type: FieldType.STRING, + name: "Country", + }, + Story: { + type: FieldType.STRING, + name: "Story", + }, }, - OrderID: { - type: FieldType.NUMBER, - name: "OrderID", - }, - Story: { - type: FieldType.STRING, - name: "Story", - }, - }, - }) + }) + ) const createViewResponse = await config.api.viewV2.create({ tableId: table._id!, @@ -450,9 +429,6 @@ describe.each([ Country: { visible: true, }, - OrderID: { - visible: true, - }, }, }) @@ -468,8 +444,8 @@ describe.each([ expect(row.Story).toBeUndefined() expect(row).toEqual({ ...defaultRowFields, - OrderID: createRowResponse.OrderID, Country: "Aussy", + id: createRowResponse.id, _id: createRowResponse._id, _rev: createRowResponse._rev, tableId: table._id, @@ -857,7 +833,7 @@ describe.each([ it("should allow enriching attachment rows", async () => { const table = await config.createAttachmentTable() - const attachmentId = `${structures.uuid()}.csv` + const attachmentId = `${uuid.v4()}.csv` const row = await config.createRow({ name: "test", description: "test", @@ -1998,22 +1974,23 @@ describe.each([ ` ).toString("base64") - const table = await config.createTable({ - name: "table", - type: "table", - schema: { - text: { - name: "text", - type: FieldType.STRING, + const table = await config.api.table.save( + saveTableRequest({ + name: "table", + schema: { + text: { + name: "text", + type: FieldType.STRING, + }, + formula: { + name: "formula", + type: FieldType.FORMULA, + formula: `{{ js "${js}"}}`, + formulaType: FormulaType.DYNAMIC, + }, }, - formula: { - name: "formula", - type: FieldType.FORMULA, - formula: `{{ js "${js}"}}`, - formulaType: FormulaType.DYNAMIC, - }, - }, - }) + }) + ) for (let i = 0; i < 10; i++) { await config.api.row.save(table._id!, { text: "foo" }) @@ -2051,22 +2028,22 @@ describe.each([ it("should not carry over context between formulas", async () => { const js = Buffer.from(`return $("[text]");`).toString("base64") - const table = await config.createTable({ - name: "table", - type: "table", - schema: { - text: { - name: "text", - type: FieldType.STRING, + const table = await config.api.table.save( + saveTableRequest({ + schema: { + text: { + name: "text", + type: FieldType.STRING, + }, + formula: { + name: "formula", + type: FieldType.FORMULA, + formula: `{{ js "${js}"}}`, + formulaType: FormulaType.DYNAMIC, + }, }, - formula: { - name: "formula", - type: FieldType.FORMULA, - formula: `{{ js "${js}"}}`, - formulaType: FormulaType.DYNAMIC, - }, - }, - }) + }) + ) for (let i = 0; i < 10; i++) { await config.api.row.save(table._id!, { text: `foo${i}` }) From 76ecfedaedd43f29b9a4ece1afaa8862b1177688 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 13 Mar 2024 16:44:05 +0000 Subject: [PATCH 77/84] Reduce reliance on config a bit more. --- packages/server/src/api/routes/tests/row.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 613928712c..22dca18023 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -832,9 +832,19 @@ describe.each([ }) it("should allow enriching attachment rows", async () => { - const table = await config.createAttachmentTable() + const table = await config.api.table.save( + defaultTable({ + schema: { + attachment: { + type: FieldType.ATTACHMENT, + name: "attachment", + constraints: { type: "array", presence: false }, + }, + }, + }) + ) const attachmentId = `${uuid.v4()}.csv` - const row = await config.createRow({ + const row = await config.api.row.save(table._id!, { name: "test", description: "test", attachment: [ From 3da22617350d1a23c5dca0cfe7739dab4f84fb1a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 13 Mar 2024 17:42:24 +0000 Subject: [PATCH 78/84] Convert view.spec.js to TypeScript, remove reliace on TestConfiguration. --- .../tests/__snapshots__/view.spec.js.snap | 38 -- .../tests/{view.spec.js => view.spec.ts} | 326 +++++++++--------- .../src/tests/utilities/api/legacyView.ts | 34 +- packages/types/src/documents/app/view.ts | 1 + 4 files changed, 201 insertions(+), 198 deletions(-) delete mode 100644 packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap rename packages/server/src/api/routes/tests/{view.spec.js => view.spec.ts} (59%) diff --git a/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap b/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap deleted file mode 100644 index 357285a69a..0000000000 --- a/packages/server/src/api/routes/tests/__snapshots__/view.spec.js.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`/views query returns data for the created view 1`] = ` -[ - { - "avg": 2333.3333333333335, - "count": 3, - "group": null, - "max": 4000, - "min": 1000, - "sum": 7000, - "sumsqr": 21000000, - }, -] -`; - -exports[`/views query returns data for the created view using a group by 1`] = ` -[ - { - "avg": 1500, - "count": 2, - "group": "One", - "max": 2000, - "min": 1000, - "sum": 3000, - "sumsqr": 5000000, - }, - { - "avg": 4000, - "count": 1, - "group": "Two", - "max": 4000, - "min": 4000, - "sum": 4000, - "sumsqr": 16000000, - }, -] -`; diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.ts similarity index 59% rename from packages/server/src/api/routes/tests/view.spec.js rename to packages/server/src/api/routes/tests/view.spec.ts index 92ff097899..2e8c71b812 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.ts @@ -1,30 +1,38 @@ -const setup = require("./utilities") -const { events } = require("@budibase/backend-core") +import { events } from "@budibase/backend-core" +import * as setup from "./utilities" +import { + FieldType, + INTERNAL_TABLE_SOURCE_ID, + SaveTableRequest, + Table, + TableSourceType, + View, + ViewCalculation, +} from "@budibase/types" -function priceTable() { - return { - name: "table", - type: "table", - key: "name", - schema: { - Price: { - type: "number", - constraints: {}, - }, - Category: { +const priceTable: SaveTableRequest = { + name: "table", + type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, + sourceType: TableSourceType.INTERNAL, + schema: { + Price: { + name: "Price", + type: FieldType.NUMBER, + }, + Category: { + name: "Category", + type: FieldType.STRING, + constraints: { type: "string", - constraints: { - type: "string", - }, }, }, - } + }, } describe("/views", () => { - let request = setup.getRequest() let config = setup.getConfig() - let table + let table: Table afterAll(setup.afterAll) @@ -33,38 +41,34 @@ describe("/views", () => { }) beforeEach(async () => { - table = await config.createTable(priceTable()) + table = await config.api.table.save(priceTable) }) - const saveView = async view => { - const viewToSave = { + const saveView = async (view?: Partial) => { + const viewToSave: View = { name: "TestView", field: "Price", - calculation: "stats", - tableId: table._id, + calculation: ViewCalculation.STATISTICS, + tableId: table._id!, + filters: [], + schema: {}, ...view, } - return request - .post(`/api/views`) - .send(viewToSave) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) + return config.api.legacyView.save(viewToSave) } describe("create", () => { it("returns a success message when the view is successfully created", async () => { const res = await saveView() - expect(res.body.tableId).toBe(table._id) expect(events.view.created).toBeCalledTimes(1) }) it("creates a view with a calculation", async () => { jest.clearAllMocks() - const res = await saveView({ calculation: "count" }) + const view = await saveView({ calculation: ViewCalculation.COUNT }) - expect(res.body.tableId).toBe(table._id) + expect(view.tableId).toBe(table._id) expect(events.view.created).toBeCalledTimes(1) expect(events.view.updated).not.toBeCalled() expect(events.view.calculationCreated).toBeCalledTimes(1) @@ -78,8 +82,8 @@ describe("/views", () => { it("creates a view with a filter", async () => { jest.clearAllMocks() - const res = await saveView({ - calculation: null, + const view = await saveView({ + calculation: undefined, filters: [ { value: "1", @@ -89,7 +93,7 @@ describe("/views", () => { ], }) - expect(res.body.tableId).toBe(table._id) + expect(view.tableId).toBe(table._id) expect(events.view.created).toBeCalledTimes(1) expect(events.view.updated).not.toBeCalled() expect(events.view.calculationCreated).not.toBeCalled() @@ -101,52 +105,41 @@ describe("/views", () => { }) it("updates the table row with the new view metadata", async () => { - const res = await request - .post(`/api/views`) - .send({ - name: "TestView", - field: "Price", - calculation: "stats", - tableId: table._id, + await saveView() + const updatedTable = await config.api.table.get(table._id!) + expect(updatedTable.views).toEqual( + expect.objectContaining({ + TestView: expect.objectContaining({ + field: "Price", + calculation: "stats", + tableId: table._id, + filters: [], + schema: { + sum: { + type: "number", + }, + min: { + type: "number", + }, + max: { + type: "number", + }, + count: { + type: "number", + }, + sumsqr: { + type: "number", + }, + avg: { + type: "number", + }, + field: { + type: "string", + }, + }, + }), }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.tableId).toBe(table._id) - - const updatedTable = await config.getTable(table._id) - const expectedObj = expect.objectContaining({ - TestView: expect.objectContaining({ - field: "Price", - calculation: "stats", - tableId: table._id, - filters: [], - schema: { - sum: { - type: "number", - }, - min: { - type: "number", - }, - max: { - type: "number", - }, - count: { - type: "number", - }, - sumsqr: { - type: "number", - }, - avg: { - type: "number", - }, - field: { - type: "string", - }, - }, - }), - }) - expect(updatedTable.views).toEqual(expectedObj) + ) }) }) @@ -168,10 +161,10 @@ describe("/views", () => { }) it("updates a view calculation", async () => { - await saveView({ calculation: "sum" }) + await saveView({ calculation: ViewCalculation.SUM }) jest.clearAllMocks() - await saveView({ calculation: "count" }) + await saveView({ calculation: ViewCalculation.COUNT }) expect(events.view.created).not.toBeCalled() expect(events.view.updated).toBeCalledTimes(1) @@ -184,10 +177,10 @@ describe("/views", () => { }) it("deletes a view calculation", async () => { - await saveView({ calculation: "sum" }) + await saveView({ calculation: ViewCalculation.SUM }) jest.clearAllMocks() - await saveView({ calculation: null }) + await saveView({ calculation: undefined }) expect(events.view.created).not.toBeCalled() expect(events.view.updated).toBeCalledTimes(1) @@ -258,100 +251,98 @@ describe("/views", () => { describe("fetch", () => { beforeEach(async () => { - table = await config.createTable(priceTable()) + table = await config.api.table.save(priceTable) }) it("returns only custom views", async () => { - await config.createLegacyView({ + await saveView({ name: "TestView", field: "Price", - calculation: "stats", + calculation: ViewCalculation.STATISTICS, tableId: table._id, }) - const res = await request - .get(`/api/views`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.length).toBe(1) - expect(res.body.find(({ name }) => name === "TestView")).toBeDefined() + const views = await config.api.legacyView.fetch() + expect(views.length).toBe(1) + expect(views.find(({ name }) => name === "TestView")).toBeDefined() }) }) describe("query", () => { it("returns data for the created view", async () => { - await config.createLegacyView({ + await saveView({ name: "TestView", field: "Price", - calculation: "stats", - tableId: table._id, + calculation: ViewCalculation.STATISTICS, + tableId: table._id!, }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 1000, }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 2000, }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 4000, }) - const res = await request - .get(`/api/views/TestView?calculation=stats`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.length).toBe(1) - expect(res.body).toMatchSnapshot() + const rows = await config.api.legacyView.get("TestView", { + calculation: ViewCalculation.STATISTICS, + }) + expect(rows.length).toBe(1) + expect(rows[0]).toEqual({ + avg: 2333.3333333333335, + count: 3, + group: null, + max: 4000, + min: 1000, + sum: 7000, + sumsqr: 21000000, + }) }) it("returns data for the created view using a group by", async () => { - await config.createLegacyView({ - calculation: "stats", + await saveView({ + calculation: ViewCalculation.STATISTICS, name: "TestView", field: "Price", groupBy: "Category", tableId: table._id, }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 1000, Category: "One", }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 2000, Category: "One", }) - await config.createRow({ - tableId: table._id, + await config.api.row.save(table._id!, { Price: 4000, Category: "Two", }) - const res = await request - .get(`/api/views/TestView?calculation=stats&group=Category`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - - expect(res.body.length).toBe(2) - expect(res.body).toMatchSnapshot() + const rows = await config.api.legacyView.get("TestView", { + calculation: ViewCalculation.STATISTICS, + group: "Category", + }) + expect(rows.length).toBe(2) + expect(rows[0]).toEqual({ + avg: 1500, + count: 2, + group: "One", + max: 2000, + min: 1000, + sum: 3000, + sumsqr: 5000000, + }) }) }) describe("destroy", () => { it("should be able to delete a view", async () => { - const table = await config.createTable(priceTable()) - const view = await config.createLegacyView() - const res = await request - .delete(`/api/views/${view.name}`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.map).toBeDefined() - expect(res.body.meta.tableId).toEqual(table._id) + const table = await config.api.table.save(priceTable) + const view = await saveView({ tableId: table._id }) + const deletedView = await config.api.legacyView.destroy(view.name!) + expect(deletedView.map).toBeDefined() + expect(deletedView.meta?.tableId).toEqual(table._id) expect(events.view.deleted).toBeCalledTimes(1) }) }) @@ -362,33 +353,44 @@ describe("/views", () => { }) const setupExport = async () => { - const table = await config.createTable() - await config.createRow({ name: "test-name", description: "ùúûü" }) + const table = await config.api.table.save({ + name: "test-table", + type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, + sourceType: TableSourceType.INTERNAL, + schema: { + name: { + name: "name", + type: FieldType.STRING, + }, + description: { + name: "description", + type: FieldType.STRING, + }, + }, + }) + await config.api.row.save(table._id!, { + name: "test-name", + description: "ùúûü", + }) return table } - const exportView = async (viewName, format) => { - return request - .get(`/api/views/export?view=${viewName}&format=${format}`) - .set(config.defaultHeaders()) - .expect(200) - } - - const assertJsonExport = res => { - const rows = JSON.parse(res.text) + const assertJsonExport = (res: string) => { + const rows = JSON.parse(res) expect(rows.length).toBe(1) expect(rows[0].name).toBe("test-name") expect(rows[0].description).toBe("ùúûü") } - const assertCSVExport = res => { - expect(res.text).toBe(`"name","description"\n"test-name","ùúûü"`) + const assertCSVExport = (res: string) => { + expect(res).toBe(`"name","description"\n"test-name","ùúûü"`) } it("should be able to export a table as JSON", async () => { const table = await setupExport() - const res = await exportView(table._id, "json") + const res = await config.api.legacyView.export(table._id!, "json") assertJsonExport(res) expect(events.table.exported).toBeCalledTimes(1) @@ -398,7 +400,7 @@ describe("/views", () => { it("should be able to export a table as CSV", async () => { const table = await setupExport() - const res = await exportView(table._id, "csv") + const res = await config.api.legacyView.export(table._id!, "csv") assertCSVExport(res) expect(events.table.exported).toBeCalledTimes(1) @@ -407,10 +409,15 @@ describe("/views", () => { it("should be able to export a view as JSON", async () => { let table = await setupExport() - const view = await config.createLegacyView() - table = await config.getTable(table._id) + const view = await config.api.legacyView.save({ + name: "test-view", + tableId: table._id!, + filters: [], + schema: {}, + }) + table = await config.api.table.get(table._id!) - let res = await exportView(view.name, "json") + let res = await config.api.legacyView.export(view.name!, "json") assertJsonExport(res) expect(events.view.exported).toBeCalledTimes(1) @@ -419,10 +426,15 @@ describe("/views", () => { it("should be able to export a view as CSV", async () => { let table = await setupExport() - const view = await config.createLegacyView() - table = await config.getTable(table._id) + const view = await config.api.legacyView.save({ + name: "test-view", + tableId: table._id!, + filters: [], + schema: {}, + }) + table = await config.api.table.get(table._id!) - let res = await exportView(view.name, "csv") + let res = await config.api.legacyView.export(view.name!, "csv") assertCSVExport(res) expect(events.view.exported).toBeCalledTimes(1) diff --git a/packages/server/src/tests/utilities/api/legacyView.ts b/packages/server/src/tests/utilities/api/legacyView.ts index 38ef70d62a..19a6eceb3b 100644 --- a/packages/server/src/tests/utilities/api/legacyView.ts +++ b/packages/server/src/tests/utilities/api/legacyView.ts @@ -1,8 +1,36 @@ import { Expectations, TestAPI } from "./base" -import { Row } from "@budibase/types" +import { Row, View, ViewCalculation } from "@budibase/types" export class LegacyViewAPI extends TestAPI { - get = async (id: string, expectations?: Expectations) => { - return await this._get(`/api/views/${id}`, { expectations }) + get = async ( + id: string, + query: { calculation: ViewCalculation; group?: string }, + expectations?: Expectations + ) => { + return await this._get(`/api/views/${id}`, { query, expectations }) + } + + save = async (body: View, expectations?: Expectations) => { + return await this._post(`/api/views/`, { body, expectations }) + } + + fetch = async (expectations?: Expectations) => { + return await this._get(`/api/views`, { expectations }) + } + + destroy = async (id: string, expectations?: Expectations) => { + return await this._delete(`/api/views/${id}`, { expectations }) + } + + export = async ( + viewName: string, + format: "json" | "csv" | "jsonWithSchema", + expectations?: Expectations + ) => { + const response = await this._requestRaw("get", `/api/views/export`, { + query: { view: viewName, format }, + expectations, + }) + return response.text } } diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index b5a22ec592..7b93d24f3d 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -30,6 +30,7 @@ export interface View { map?: string reduce?: any meta?: ViewTemplateOpts + groupBy?: string } export interface ViewV2 { From fcd7ee10f1bcc7ba76fb0f78e8f23362abd73323 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 14 Mar 2024 09:57:00 +0000 Subject: [PATCH 79/84] Create app user facing maintenance page. --- .../pages/builder/maintenance/index.svelte | 6 + .../client/src/components/ClientApp.svelte | 210 +++++++++--------- .../src/components/MaintenanceScreen.svelte | 31 +-- packages/client/src/index.js | 10 - 4 files changed, 132 insertions(+), 125 deletions(-) diff --git a/packages/builder/src/pages/builder/maintenance/index.svelte b/packages/builder/src/pages/builder/maintenance/index.svelte index 95080d0563..1735a93e84 100644 --- a/packages/builder/src/pages/builder/maintenance/index.svelte +++ b/packages/builder/src/pages/builder/maintenance/index.svelte @@ -3,6 +3,12 @@ import { Heading, Body, Button, Layout } from "@budibase/bbui" import { admin } from "stores/portal" import BudibaseLogo from "../portal/_components/BudibaseLogo.svelte" + + $: { + if ($admin.maintenance.length === 0) { + window.location = "/builder" + } + }
      diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 4efa8af4e6..7e30e21ae8 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -17,6 +17,7 @@ appStore, devToolsStore, devToolsEnabled, + environmentStore, } from "stores" import NotificationDisplay from "components/overlay/NotificationDisplay.svelte" import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte" @@ -36,6 +37,7 @@ import DevToolsHeader from "components/devtools/DevToolsHeader.svelte" import DevTools from "components/devtools/DevTools.svelte" import FreeFooter from "components/FreeFooter.svelte" + import MaintenanceScreen from "components/MaintenanceScreen.svelte" import licensing from "../licensing" // Provide contexts @@ -111,122 +113,128 @@ class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}" class:builder={$builderStore.inBuilder} > - - - - - - - - {#key $builderStore.selectedComponentId} - {#if $builderStore.inBuilder} - - {/if} - {/key} - - -
      - -
      - {#if showDevTools} - + {#if $environmentStore.maintenance.length > 0} + + {:else} + + + + + + + + {#key $builderStore.selectedComponentId} + {#if $builderStore.inBuilder} + {/if} + {/key} -
      - {#if permissionError} -
      - - - {@html ErrorSVG} - - You don't have permission to use this app - - - Ask your administrator to grant you access - - -
      - {:else if !$screenStore.activeLayout} -
      - - - {@html ErrorSVG} - - Something went wrong rendering your app - - - Get in touch with support if this issue persists - - -
      - {:else if embedNoScreens} -
      - - - {@html ErrorSVG} - - This Budibase app is not publicly accessible - - -
      - {:else} - - {#key $screenStore.activeLayout._id} - - {/key} + +
      + +
      + {#if showDevTools} + + {/if} - + {@html ErrorSVG} + + You don't have permission to use this app + + + Ask your administrator to grant you access + + +
      + {:else if !$screenStore.activeLayout} +
      + + + {@html ErrorSVG} + + Something went wrong rendering your app + + + Get in touch with support if this issue persists + + +
      + {:else if embedNoScreens} +
      + + + {@html ErrorSVG} + + This Budibase app is not publicly accessible + + +
      + {:else} + + {#key $screenStore.activeLayout._id} + + {/key} + + -
      +
      - - - {#if !$builderStore.inBuilder && licensing.logoEnabled()} - + + {#if $appStore.isDevApp} + + {/if} + {#if $builderStore.inBuilder || $devToolsStore.allowSelection} + + {/if} + {#if $builderStore.inBuilder} + + {/if}
      - - - {#if $appStore.isDevApp} - - {/if} - {#if $builderStore.inBuilder || $devToolsStore.allowSelection} - - {/if} - {#if $builderStore.inBuilder} - - - {/if} -
      - - - - - + + + + + + {/if}
      {/if} diff --git a/packages/client/src/components/MaintenanceScreen.svelte b/packages/client/src/components/MaintenanceScreen.svelte index b43525fdb6..529f20781a 100644 --- a/packages/client/src/components/MaintenanceScreen.svelte +++ b/packages/client/src/components/MaintenanceScreen.svelte @@ -1,6 +1,17 @@ + @@ -11,22 +22,14 @@ {#each maintenanceList as maintenance} {#if maintenance.type === MaintenanceType.SQS_MISSING} - Please upgrade your Budibase installation + Budibase installation requires maintenance - We've detected that the version of Budibase you're using depends - on a more recent version of the CouchDB database than what you - have installed. - - - To resolve this, you can either rollback to a previous version of - Budibase, or follow the migration guide to update to a later - version of CouchDB. + The administrator of this Budibase installation needs to take + actions to update components that are out of date. Please contact + them and show them this warning. More information will be + available when they log into their account. - {/if} {/each}
      diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 9a2f0addbc..2c8d310619 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,6 +1,5 @@ import ClientApp from "./components/ClientApp.svelte" import UpdatingApp from "./components/UpdatingApp.svelte" -import MaintenanceScreen from "./components/MaintenanceScreen.svelte" import { builderStore, appStore, @@ -66,15 +65,6 @@ const loadBudibase = async () => { await environmentStore.actions.fetchEnvironment() } - const maintenanceList = get(environmentStore)?.maintenance - if (maintenanceList?.length > 0) { - new MaintenanceScreen({ - target: window.document.body, - props: { maintenanceList }, - }) - return - } - // Register handler for runtime events from the builder window.handleBuilderRuntimeEvent = (type, data) => { if (!window["##BUDIBASE_IN_BUILDER##"]) { From 62cec7289dcb3758c38e70d539c1e9531c4dbc80 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 14 Mar 2024 11:03:43 +0100 Subject: [PATCH 80/84] Use guid instead of words --- packages/backend-core/src/cache/tests/docWritethrough.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/cache/tests/docWritethrough.spec.ts b/packages/backend-core/src/cache/tests/docWritethrough.spec.ts index 5fe09b95ff..1ae85cfd0b 100644 --- a/packages/backend-core/src/cache/tests/docWritethrough.spec.ts +++ b/packages/backend-core/src/cache/tests/docWritethrough.spec.ts @@ -32,7 +32,7 @@ describe("docWritethrough", () => { describe("patch", () => { function generatePatchObject(fieldCount: number) { - const keys = generator.unique(() => generator.word(), fieldCount) + const keys = generator.unique(() => generator.guid(), fieldCount) return keys.reduce((acc, c) => { acc[c] = generator.word() return acc From c8f37e16e567af9c8f9376f17eff99da44814018 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 14 Mar 2024 10:23:42 +0000 Subject: [PATCH 81/84] CSS tweaks. --- .../pages/builder/maintenance/index.svelte | 1 + .../src/components/MaintenanceScreen.svelte | 56 ++++++------------- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/packages/builder/src/pages/builder/maintenance/index.svelte b/packages/builder/src/pages/builder/maintenance/index.svelte index 1735a93e84..e4c379885a 100644 --- a/packages/builder/src/pages/builder/maintenance/index.svelte +++ b/packages/builder/src/pages/builder/maintenance/index.svelte @@ -51,6 +51,7 @@ flex-direction: row; align-items: center; justify-content: center; + padding: var(--spacing-l); } .hero { margin: var(--spacing-l); diff --git a/packages/client/src/components/MaintenanceScreen.svelte b/packages/client/src/components/MaintenanceScreen.svelte index 529f20781a..a44c1292dd 100644 --- a/packages/client/src/components/MaintenanceScreen.svelte +++ b/packages/client/src/components/MaintenanceScreen.svelte @@ -16,28 +16,24 @@ export let maintenanceList -
      -
      -
      - {#each maintenanceList as maintenance} - {#if maintenance.type === MaintenanceType.SQS_MISSING} - - Budibase installation requires maintenance - - The administrator of this Budibase installation needs to take - actions to update components that are out of date. Please contact - them and show them this warning. More information will be - available when they log into their account. - - - {/if} - {/each} -
      -
      +
      + {#each maintenanceList as maintenance} + {#if maintenance.type === MaintenanceType.SQS_MISSING} + + Budibase installation requires maintenance + + The administrator of this Budibase installation needs to take actions + to update components that are out of date. Please contact them and + show them this warning. More information will be available when they + log into their account. + + + {/if} + {/each}
      From 5918dfebbaecd6a7d79f82bd93d7dbb5f4a1c2bd Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 14 Mar 2024 10:25:41 +0000 Subject: [PATCH 82/84] Silence lint warnings. --- .../src/pages/builder/portal/_components/BudibaseLogo.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte b/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte index 8276510201..b0dc4cda50 100644 --- a/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte +++ b/packages/builder/src/pages/builder/portal/_components/BudibaseLogo.svelte @@ -3,6 +3,8 @@ import { goto } from "@roxi/routify" + + Budibase Logo $goto("./apps")} />