From 1e033c3618c688728c6b293df98f458a05a115ac Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 1 Feb 2024 14:45:16 +0000 Subject: [PATCH 01/30] 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 32e750afd6c7ed08a55971e71ce7206c12e5f3d4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 1 Feb 2024 17:37:46 +0000 Subject: [PATCH 02/30] A small ux improvement to the automation scripting system. --- .../SetupPanel/AutomationBlockSetup.svelte | 101 ++++++++++++------ .../common/CodeEditor/CodeEditor.svelte | 3 +- .../src/components/common/CodeEditor/index.js | 7 +- .../common/bindings/BindingPanel.svelte | 31 ++---- .../src/components/common/bindings/utils.js | 67 ++++++------ 5 files changed, 115 insertions(+), 94 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index a9e7ab0d39..d8b41dc94d 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -33,6 +33,8 @@ import Editor from "components/integration/QueryEditor.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" + import BindingPicker from "components/common/bindings/BindingPicker.svelte" + import { BindingHelpers } from "components/common/bindings/utils" import { bindingsToCompletions, hbAutocomplete, @@ -56,7 +58,7 @@ let drawer let fillWidth = true let inputData - let codeBindingOpen = false + let insertAtPos, getCaretPosition $: filters = lookForFilters(schemaProperties) || [] $: tempFilters = filters $: stepId = block.stepId @@ -75,6 +77,10 @@ $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW $: codeMode = stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS + $: bindingsHelpers = new BindingHelpers(getCaretPosition, insertAtPos, { + disableWrapping: true, + }) + $: editingJs = codeMode === EditorModes.JS $: stepCompletions = codeMode === EditorModes.Handlebars @@ -539,39 +545,51 @@ /> {:else if value.customType === "code"} - {#if codeMode == EditorModes.JS} - (codeBindingOpen = !codeBindingOpen)} - quiet - icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} - > - Bindings - - {#if codeBindingOpen} -
{JSON.stringify(bindings, null, 2)}
- {/if} - {/if} - { - // need to pass without the value inside - onChange({ detail: e.detail }, key) - inputData[key] = e.detail - }} - completions={stepCompletions} - mode={codeMode} - autocompleteEnabled={codeMode != EditorModes.JS} - height={500} - /> -
- {#if codeMode == EditorModes.Handlebars} - -
-
- Add available bindings by typing - }} - -
+
+
+ { + // need to pass without the value inside + onChange({ detail: e.detail }, key) + inputData[key] = e.detail + }} + completions={stepCompletions} + mode={codeMode} + autocompleteEnabled={codeMode !== EditorModes.JS} + bind:getCaretPosition + bind:insertAtPos + height={500} + /> +
+ {#if codeMode === EditorModes.Handlebars} + +
+
+ Add available bindings by typing + }} + +
+
+ {/if} +
+
+ {#if editingJs} +
+ + bindingsHelpers.onSelectBinding( + inputData[key], + binding, + { + js: true, + decode: false, + } + )} + mode="javascript" + />
{/if}
@@ -658,4 +676,19 @@ .test :global(.drawer) { width: 10000px !important; } + + .js-editor { + display: flex; + flex-direction: row; + flex-grow: 1; + width: 100%; + } + + .js-code { + flex: 7; + } + + .js-binding-picker { + flex: 3; + } diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index a39634f9a3..e906ef445d 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -54,6 +54,7 @@ export let placeholder = null export let autocompleteEnabled = true export let autofocus = false + export let jsBindingWrapping = true // Export a function to expose caret position export const getCaretPosition = () => { @@ -187,7 +188,7 @@ ) complete.push( EditorView.inputHandler.of((view, from, to, insert) => { - if (insert === "$") { + if (jsBindingWrapping && insert === "$") { let { text } = view.state.doc.lineAt(from) const left = from ? text.substring(0, from) : "" diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index da0e727e4e..38d377b47a 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -286,13 +286,14 @@ export const hbInsert = (value, from, to, text) => { return parsedInsert } -export function jsInsert(value, from, to, text, { helper } = {}) { +export function jsInsert(value, from, to, text, { helper, disableWrapping }) { let parsedInsert = "" const left = from ? value.substring(0, from) : "" const right = to ? value.substring(to) : "" - - if (helper) { + if (disableWrapping) { + parsedInsert = text + } else if (helper) { parsedInsert = `helpers.${text}()` } else if (!left.includes('$("') || !right.includes('")')) { parsedInsert = `$("${text}")` diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 548a71b483..e6bd2fe86a 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -29,10 +29,9 @@ hbAutocomplete, EditorModes, bindingsToCompletions, - hbInsert, - jsInsert, } from "../CodeEditor" import BindingPicker from "./BindingPicker.svelte" + import { BindingHelpers } from "./utils" const dispatch = createEventDispatcher() @@ -60,8 +59,10 @@ let targetMode = null $: usingJS = mode === "JavaScript" - $: editorMode = mode == "JavaScript" ? EditorModes.JS : EditorModes.Handlebars + $: editorMode = + mode === "JavaScript" ? EditorModes.JS : EditorModes.Handlebars $: bindingCompletions = bindingsToCompletions(bindings, editorMode) + $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) const updateValue = val => { valid = isValid(readableToRuntimeBinding(bindings, val)) @@ -70,31 +71,13 @@ } } - // Adds a JS/HBS helper to the expression const onSelectHelper = (helper, js) => { - const pos = getCaretPosition() - const { start, end } = pos - if (js) { - let js = decodeJSBinding(jsValue) - const insertVal = jsInsert(js, start, end, helper.text, { helper: true }) - insertAtPos({ start, end, value: insertVal }) - } else { - const insertVal = hbInsert(hbsValue, start, end, helper.text) - insertAtPos({ start, end, value: insertVal }) - } + bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, js) } - // Adds a data binding to the expression const onSelectBinding = (binding, { forceJS } = {}) => { - const { start, end } = getCaretPosition() - if (usingJS || forceJS) { - let js = decodeJSBinding(jsValue) - const insertVal = jsInsert(js, start, end, binding.readableBinding) - insertAtPos({ start, end, value: insertVal }) - } else { - const insertVal = hbInsert(hbsValue, start, end, binding.readableBinding) - insertAtPos({ start, end, value: insertVal }) - } + const js = usingJS || forceJS + bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js }) } const onChangeMode = e => { diff --git a/packages/builder/src/components/common/bindings/utils.js b/packages/builder/src/components/common/bindings/utils.js index 8d414ffed3..428e092842 100644 --- a/packages/builder/src/components/common/bindings/utils.js +++ b/packages/builder/src/components/common/bindings/utils.js @@ -1,38 +1,41 @@ -export function addHBSBinding(value, caretPos, binding) { - binding = typeof binding === "string" ? binding : binding.path - value = value == null ? "" : value +import { decodeJSBinding } from "@budibase/string-templates" +import { hbInsert, jsInsert } from "components/common/CodeEditor" - const left = caretPos?.start ? value.substring(0, caretPos.start) : "" - const right = caretPos?.end ? value.substring(caretPos.end) : "" - if (!left.includes("{{") || !right.includes("}}")) { - binding = `{{ ${binding} }}` +export class BindingHelpers { + constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) { + this.getCaretPosition = getCaretPosition + this.insertAtPos = insertAtPos + this.disableWrapping = disableWrapping } - if (caretPos.start) { - value = - value.substring(0, caretPos.start) + - binding + - value.substring(caretPos.end, value.length) - } else { - value += binding - } - return value -} -export function addJSBinding(value, caretPos, binding, { helper } = {}) { - binding = typeof binding === "string" ? binding : binding.path - value = value == null ? "" : value - if (!helper) { - binding = `$("${binding}")` - } else { - binding = `helpers.${binding}()` + // Adds a JS/HBS helper to the expression + onSelectHelper(value, helper, { js }) { + const pos = this.getCaretPosition() + const { start, end } = pos + if (js) { + const jsVal = decodeJSBinding(value) + const insertVal = jsInsert(jsVal, start, end, helper.text, { + helper: true, + }) + this.insertAtPos({ start, end, value: insertVal }) + } else { + const insertVal = hbInsert(value, start, end, helper.text) + this.insertAtPos({ start, end, value: insertVal }) + } } - if (caretPos.start) { - value = - value.substring(0, caretPos.start) + - binding + - value.substring(caretPos.end, value.length) - } else { - value += binding + + // Adds a data binding to the expression + onSelectBinding(value, binding, { js, decode }) { + const { start, end } = this.getCaretPosition() + if (js) { + const jsVal = decode ? decodeJSBinding(value) : value + const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, { + disableWrapping: this.disableWrapping, + }) + this.insertAtPos({ start, end, value: insertVal }) + } else { + const insertVal = hbInsert(value, start, end, binding.readableBinding) + this.insertAtPos({ start, end, value: insertVal }) + } } - return value } From 163c10c71e1a59253ea4caee25ec15d5fdc5c246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Feb 2024 09:58:15 +0000 Subject: [PATCH 03/30] Bump nodemailer from 6.7.2 to 6.9.9 in /packages/worker Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.7.2 to 6.9.9. - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.7.2...v6.9.9) --- updated-dependencies: - dependency-name: nodemailer dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- packages/worker/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/package.json b/packages/worker/package.json index 2e3200fda5..6c4cc6fe5b 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -63,7 +63,7 @@ "koa-useragent": "^4.1.0", "lodash": "4.17.21", "node-fetch": "2.6.7", - "nodemailer": "6.7.2", + "nodemailer": "6.9.9", "passport-google-oauth": "2.0.0", "passport-local": "1.0.0", "pouchdb": "7.3.0", From dab066e61b8e91165b52a5762f2f02d8de4e12bf Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 2 Feb 2024 11:11:55 +0000 Subject: [PATCH 04/30] Quick fix. --- .../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 e6bd2fe86a..0ef34a36ad 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -72,7 +72,7 @@ } const onSelectHelper = (helper, js) => { - bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, js) + bindingHelpers.onSelectHelper(js ? jsValue : hbsValue, helper, { js }) } const onSelectBinding = (binding, { forceJS } = {}) => { From cadce52e1bd1b9f89d22da95aa580e6959e4255a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 2 Feb 2024 11:28:54 +0000 Subject: [PATCH 05/30] Changing how optional decoding works. --- .../automation/SetupPanel/AutomationBlockSetup.svelte | 2 +- packages/builder/src/components/common/bindings/utils.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index d8b41dc94d..213e1e0e54 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -585,7 +585,7 @@ binding, { js: true, - decode: false, + dontDecode: true, } )} mode="javascript" diff --git a/packages/builder/src/components/common/bindings/utils.js b/packages/builder/src/components/common/bindings/utils.js index 428e092842..a086cd0394 100644 --- a/packages/builder/src/components/common/bindings/utils.js +++ b/packages/builder/src/components/common/bindings/utils.js @@ -9,11 +9,11 @@ export class BindingHelpers { } // Adds a JS/HBS helper to the expression - onSelectHelper(value, helper, { js }) { + onSelectHelper(value, helper, { js, dontDecode }) { const pos = this.getCaretPosition() const { start, end } = pos if (js) { - const jsVal = decodeJSBinding(value) + const jsVal = dontDecode ? value : decodeJSBinding(value) const insertVal = jsInsert(jsVal, start, end, helper.text, { helper: true, }) @@ -25,10 +25,10 @@ export class BindingHelpers { } // Adds a data binding to the expression - onSelectBinding(value, binding, { js, decode }) { + onSelectBinding(value, binding, { js, dontDecode }) { const { start, end } = this.getCaretPosition() if (js) { - const jsVal = decode ? decodeJSBinding(value) : value + const jsVal = dontDecode ? value : decodeJSBinding(value) const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, { disableWrapping: this.disableWrapping, }) From d6bf33bce753081ba72c5312bf4dd9b6c800fd06 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 2 Feb 2024 14:59:45 +0000 Subject: [PATCH 06/30] Update data binding generation to match how context is provided by components, respecting branching due to local context --- .../src/builderStore/componentUtils.js | 61 +++++++++++++++++-- .../builder/src/builderStore/dataBinding.js | 43 +++++++++---- packages/client/manifest.json | 3 +- .../client/src/components/app/Repeater.svelte | 7 +-- .../src/components/context/Provider.svelte | 4 +- packages/client/src/constants.js | 5 -- packages/client/src/sdk.js | 6 +- packages/frontend-core/src/constants.js | 5 ++ 8 files changed, 102 insertions(+), 32 deletions(-) diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js index 733ce0948e..8a7cd93d0f 100644 --- a/packages/builder/src/builderStore/componentUtils.js +++ b/packages/builder/src/builderStore/componentUtils.js @@ -7,6 +7,9 @@ import { findHBSBlocks, } from "@budibase/string-templates" import { capitalise } from "helpers" +import { Constants } from "@budibase/frontend-core" + +const { ContextScopes } = Constants /** * Recursively searches for a specific component ID @@ -263,11 +266,59 @@ export const getComponentName = component => { if (component == null) { return "" } - const components = get(store)?.components || {} const componentDefinition = components[component._component] || {} - const name = - componentDefinition.friendlyName || componentDefinition.name || "" - - return name + return componentDefinition.friendlyName || componentDefinition.name || "" +} + +/** + * Recurses through the component tree and builds a tree of contexts provided + * by components. + */ +export const buildContextTree = ( + rootComponent, + tree = { root: [] }, + currentBranch = "root" +) => { + // Sanity check + if (!rootComponent) { + return tree + } + + // Process this component's contexts + const def = store.actions.components.getDefinition(rootComponent._component) + if (def?.context) { + tree[currentBranch].push(rootComponent._id) + const contexts = Array.isArray(def.context) ? def.context : [def.context] + + // If we provide local context, start a new branch for our children + if (contexts.some(context => context.scope === ContextScopes.Local)) { + currentBranch = rootComponent._id + tree[rootComponent._id] = [] + } + } + + // Process children + if (rootComponent._children) { + rootComponent._children.forEach(child => { + buildContextTree(child, tree, currentBranch) + }) + } + + return tree +} + +/** + * Generates a lookup map of which content branch all components in a component + * tree are inside. + */ +export const buildContextTreeLookupMap = rootComponent => { + const tree = buildContextTree(rootComponent) + let map = {} + Object.entries(tree).forEach(([branch, ids]) => { + ids.forEach(id => { + map[id] = branch + }) + }) + return map } diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 86aecd466f..951d615e7a 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -1,6 +1,8 @@ import { cloneDeep } from "lodash/fp" import { get } from "svelte/store" import { + buildContextTree, + buildContextTreeLookupMap, findAllComponents, findAllMatchingComponents, findComponent, @@ -20,11 +22,13 @@ import { encodeJSBinding, } from "@budibase/string-templates" import { TableNames } from "../constants" -import { JSONUtils } from "@budibase/frontend-core" +import { JSONUtils, Constants } from "@budibase/frontend-core" import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json" import { environment, licensing } from "stores/portal" import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils" +const { ContextScopes } = Constants + // Regex to match all instances of template strings const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_JS = /\$\("([^")]+)"\)/g @@ -214,20 +218,27 @@ export const getComponentContexts = ( return [] } let map = {} + const componentPath = findComponentPath(asset.props, componentId) + const componentPathIds = componentPath.map(component => component._id) + const contextTreeLookupMap = buildContextTreeLookupMap(asset.props) // Processes all contexts exposed by a component const processContexts = scope => component => { - const def = store.actions.components.getDefinition(component._component) + // Sanity check + const def = store.actions.components.getDefinition(component?._component) if (!def?.context) { return } - if (!map[component._id]) { - map[component._id] = { - component, - definition: def, - contexts: [], - } + + // Filter out global contexts not in the same branch. + // Global contexts are only valid if their branch root is an ancestor of + // this component. + const branch = contextTreeLookupMap[component._id] + if (branch !== "root" && !componentPathIds.includes(branch)) { + return } + + // Process all contexts provided by this component const contexts = Array.isArray(def.context) ? def.context : [def.context] contexts.forEach(context => { // Ensure type matches @@ -235,7 +246,7 @@ export const getComponentContexts = ( return } // Ensure scope matches - let contextScope = context.scope || "global" + let contextScope = context.scope || ContextScopes.Global if (contextScope !== scope) { return } @@ -243,17 +254,23 @@ export const getComponentContexts = ( if (!isContextCompatibleWithComponent(context, component)) { return } + if (!map[component._id]) { + map[component._id] = { + component, + definition: def, + contexts: [], + } + } map[component._id].contexts.push(context) }) } // Process all global contexts const allComponents = findAllComponents(asset.props) - allComponents.forEach(processContexts("global")) + allComponents.forEach(processContexts(ContextScopes.Global)) - // Process all local contexts - const localComponents = findComponentPath(asset.props, componentId) - localComponents.forEach(processContexts("local")) + // Process all local contexts in the immediate tree + componentPath.forEach(processContexts(ContextScopes.Local)) // Exclude self if required if (!options?.includeSelf) { diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 5bbf465766..febc03085e 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -4720,7 +4720,8 @@ } ], "context": { - "type": "schema" + "type": "schema", + "scope": "local" } }, "daterangepicker": { diff --git a/packages/client/src/components/app/Repeater.svelte b/packages/client/src/components/app/Repeater.svelte index 8796078311..2d07342cf5 100644 --- a/packages/client/src/components/app/Repeater.svelte +++ b/packages/client/src/components/app/Repeater.svelte @@ -2,7 +2,9 @@ import { getContext } from "svelte" import Placeholder from "./Placeholder.svelte" import Container from "./Container.svelte" - import { ContextScopes } from "constants" + + const { Provider, ContextScopes } = getContext("sdk") + const component = getContext("component") export let dataProvider export let noRowsMessage @@ -12,9 +14,6 @@ export let gap export let scope = ContextScopes.Local - const { Provider } = getContext("sdk") - const component = getContext("component") - $: rows = dataProvider?.rows ?? [] $: loaded = dataProvider?.loaded ?? true diff --git a/packages/client/src/components/context/Provider.svelte b/packages/client/src/components/context/Provider.svelte index ad5b580c4f..fe4463821e 100644 --- a/packages/client/src/components/context/Provider.svelte +++ b/packages/client/src/components/context/Provider.svelte @@ -1,9 +1,11 @@ - + From bfc0cd305e1c140a52655c1fb2dcc8cebe87fe75 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 6 Feb 2024 09:47:50 +0000 Subject: [PATCH 26/30] Bump version to 2.17.6 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index c5c7f533ad..ee152030df 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.17.5", + "version": "2.17.6", "npmClient": "yarn", "packages": [ "packages/*", From 8bb25c471504134c0004e988a80acc59ad720c44 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Tue, 6 Feb 2024 10:47:47 +0000 Subject: [PATCH 27/30] More MongoDB query tests. --- .../api/routes/tests/queries/mongodb.spec.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts index 8184f40b88..a9c35cba7d 100644 --- a/packages/server/src/api/routes/tests/queries/mongodb.spec.ts +++ b/packages/server/src/api/routes/tests/queries/mongodb.spec.ts @@ -154,6 +154,55 @@ describe("/queries", () => { expect(result.data).toEqual([{ _id: expect.anything(), name: "one" }]) }) + it("should execute a findOneAndUpdate query", async () => { + const query = await createQuery({ + fields: { + json: { + filter: { name: { $eq: "one" } }, + update: { $set: { name: "newName" } }, + }, + extra: { + actionType: "findOneAndUpdate", + }, + }, + }) + + const result = await config.api.query.execute(query._id!) + + expect(result.data).toEqual([ + { + lastErrorObject: { n: 1, updatedExisting: true }, + ok: 1, + value: { _id: expect.anything(), name: "one" }, + }, + ]) + + await withCollection(async collection => { + expect(await collection.countDocuments()).toBe(5) + + const doc = await collection.findOne({ name: { $eq: "newName" } }) + expect(doc).toEqual({ + _id: expect.anything(), + name: "newName", + }) + }) + }) + + it("should execute a distinct query", async () => { + const query = await createQuery({ + fields: { + json: "name", + extra: { + actionType: "distinct", + }, + }, + }) + + const result = await config.api.query.execute(query._id!) + const values = result.data.map(o => o.value).sort() + expect(values).toEqual(["five", "four", "one", "three", "two"]) + }) + it("should execute a create query with parameters", async () => { const query = await createQuery({ fields: { From e27772d93f16bfc2ece25690f230de8d2340e63c Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Tue, 6 Feb 2024 13:08:37 +0000 Subject: [PATCH 28/30] Allow logo in top navigation to be a link (#12868) * Add support for logo link * Add link to logo --------- Co-authored-by: Andrew Kingston --- .../_components/Navigation/index.svelte | 63 ++++++++++++++----- .../client/src/components/app/Layout.svelte | 30 ++++++++- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte index 383026c4f8..f9f994ce67 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte @@ -15,10 +15,15 @@ Checkbox, notifications, Select, + Combobox, } from "@budibase/bbui" import { selectedScreen, store } from "builderStore" import { DefaultAppTheme } from "constants" + $: screenRouteOptions = $store.screens + .map(screen => screen.routing?.route) + .filter(x => x != null) + const updateShowNavigation = async e => { await store.actions.screens.updateSetting( get(selectedScreen), @@ -107,23 +112,6 @@ on:change={e => update("navWidth", e.detail)} /> {/if} -
- -
- update("hideLogo", !e.detail)} - /> - {#if !$store.navigation.hideLogo} -
- -
- update("logoUrl", e.detail)} - updateOnChange={false} - /> - {/if}
@@ -160,6 +148,47 @@ />
+ +
+
+
+ Logo +
+
+
+ +
+ update("hideLogo", !e.detail)} + /> + {#if !$store.navigation.hideLogo} +
+ +
+ update("logoUrl", e.detail)} + updateOnChange={false} + /> +
+ +
+ update("logoLinkUrl", e.detail)} + options={screenRouteOptions} + /> +
+ +
+ update("openLogoLinkInNewTab", !!e.detail)} + /> + {/if} +
+
{/if} diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index bdab0dd9ab..2ca57a3dde 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -33,6 +33,8 @@ export let navTextColor export let navWidth export let pageWidth + export let logoLinkUrl + export let openLogoLinkInNewTab export let embedded = false @@ -150,6 +152,16 @@ } return style } + + const getSanitizedUrl = (url, openInNewTab) => { + if (!isInternal(url)) { + return ensureExternal(url) + } + if (openInNewTab) { + return `#${url}` + } + return url + }