diff --git a/README.md b/README.md index 9392278379..7aa5146b9a 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,10 @@ Watch "releases" of this repo to get notified of major updates, and give the sta

+### Stargazers over time + +[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment. @@ -107,6 +111,8 @@ Budibase wants to make sure anyone can use the tools we develop and we know a lo Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting). +[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb®ion=nyc1&refcode=0caaa6085a82&image=budibase-20-04) + ## 🎓 Learning Budibase @@ -154,6 +160,7 @@ If you have a question or would like to talk with other Budibase users, please h ![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield) + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 4d338cb221..bab7f61df1 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -1,8 +1,11 @@ version: "3" +# optional ports are specified throughout for more advanced use cases. + services: app-service: restart: always + #build: ./build/server image: budibase/budibase-apps ports: - "${APP_PORT}:4002" @@ -20,6 +23,7 @@ services: worker-service: restart: always + #build: ./build/worker image: budibase/budibase-worker ports: - "${WORKER_PORT}:4003" @@ -62,7 +66,7 @@ services: - ./envoy.yaml:/etc/envoy/envoy.yaml ports: - "${MAIN_PORT}:10000" - - "9901:9901" + #- "9901:9901" depends_on: - minio-service - worker-service @@ -77,8 +81,8 @@ services: - COUCHDB_USER=${COUCH_DB_USER} ports: - "${COUCH_DB_PORT}:5984" - - "4369:4369" - - "9100:9100" + #- "4369:4369" + #- "9100:9100" volumes: - couchdb_data:/couchdb diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 3c816cb1ca..11f5c81b99 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -20,6 +20,11 @@ static_resources: route: cluster: app-service prefix_rewrite: "/" + + # special case for presenting our static self hosting page + - match: { path: "/" } + route: + cluster: app-service # special case for when API requests are made, can just forward, not to minio - match: { prefix: "/api/" } diff --git a/packages/builder/package.json b/packages/builder/package.json index f8a2f6b60d..0e6eae7a7c 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -63,7 +63,7 @@ } }, "dependencies": { - "@budibase/bbui": "^1.55.2", + "@budibase/bbui": "^1.56.0", "@budibase/client": "^0.6.2", "@budibase/colorpicker": "^1.0.1", "@budibase/string-templates": "^0.6.2", diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 65241b7875..6eb6e6a0d8 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp" import { get } from "svelte/store" import { backendUiStore, store } from "builderStore" import { findComponentPath } from "./storeUtils" +import { makePropSafe } from "@budibase/string-templates" import { TableNames } from "../constants" // Regex to match all instances of template strings @@ -128,7 +129,9 @@ export const getContextBindings = (rootComponent, componentId) => { contextBindings.push({ type: "context", - runtimeBinding: `${component._id}.${runtimeBoundKey}`, + runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe( + runtimeBoundKey + )}`, readableBinding: `${component._instanceName}.${table.name}.${key}`, fieldSchema, providerId: component._id, @@ -197,43 +200,52 @@ export const getSchemaForDatasource = datasource => { } /** - * Converts a readable data binding into a runtime data binding + * utility function for the readableToRuntimeBinding and runtimeToReadableBinding. */ -export function readableToRuntimeBinding(bindableProperties, textWithBindings) { +function bindingReplacement(bindableProperties, textWithBindings, convertTo) { + const convertFrom = + convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding" if (typeof textWithBindings !== "string") { return textWithBindings } + const convertFromProps = bindableProperties + .map(el => el[convertFrom]) + .sort((a, b) => { + return b.length - a.length + }) const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || [] let result = textWithBindings - boundValues.forEach(boundValue => { - const binding = bindableProperties.find(({ readableBinding }) => { - return boundValue === `{{ ${readableBinding} }}` - }) - if (binding) { - result = result.replace(boundValue, `{{ ${binding.runtimeBinding} }}`) + for (let boundValue of boundValues) { + let newBoundValue = boundValue + for (let from of convertFromProps) { + if (newBoundValue.includes(from)) { + const binding = bindableProperties.find(el => el[convertFrom] === from) + newBoundValue = newBoundValue.replace(from, binding[convertTo]) + } } - }) + result = result.replace(boundValue, newBoundValue) + } return result } +/** + * Converts a readable data binding into a runtime data binding + */ +export function readableToRuntimeBinding(bindableProperties, textWithBindings) { + return bindingReplacement( + bindableProperties, + textWithBindings, + "runtimeBinding" + ) +} + /** * Converts a runtime data binding into a readable data binding */ export function runtimeToReadableBinding(bindableProperties, textWithBindings) { - if (typeof textWithBindings !== "string") { - return textWithBindings - } - const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || [] - let result = textWithBindings - boundValues.forEach(boundValue => { - const binding = bindableProperties.find(({ runtimeBinding }) => { - return boundValue === `{{ ${runtimeBinding} }}` - }) - // Show invalid bindings as invalid rather than a long ID - result = result.replace( - boundValue, - `{{ ${binding?.readableBinding ?? "Invalid binding"} }}` - ) - }) - return result + return bindingReplacement( + bindableProperties, + textWithBindings, + "readableBinding" + ) } diff --git a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js index 02502dbb44..dc629d057f 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js +++ b/packages/builder/src/builderStore/store/screenTemplates/rowDetailScreen.js @@ -2,6 +2,7 @@ import sanitizeUrl from "./utils/sanitizeUrl" import { rowListUrl } from "./rowListScreen" import { Screen } from "./utils/Screen" import { Component } from "./utils/Component" +import { makePropSafe } from "@budibase/string-templates" import { makeBreadcrumbContainer, makeTitleContainer, @@ -58,8 +59,8 @@ function generateTitleContainer(table, title, formId) { onClick: [ { parameters: { - rowId: `{{ ${formId}._id }}`, - revId: `{{ ${formId}._rev }}`, + rowId: `{{ ${makePropSafe(formId)}._id }}`, + revId: `{{ ${makePropSafe(formId)}._rev }}`, tableId: table._id, }, "##eventHandlerType": "Delete Row", @@ -113,7 +114,7 @@ const createScreen = table => { const formId = form._json._id const rowDetailId = screen._json.props._id const heading = table.primaryDisplay - ? `{{ ${rowDetailId}.${table.primaryDisplay} }}` + ? `{{ ${makePropSafe(rowDetailId)}.${makePropSafe(table.primaryDisplay)} }}` : null form .addChild(makeBreadcrumbContainer(table.name, heading || "Edit")) diff --git a/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte b/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte index a326f60a08..24b6042940 100644 --- a/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte +++ b/packages/builder/src/components/automation/SetupPanel/GenericBindingPopover.svelte @@ -3,6 +3,7 @@ import { TextArea, Label, + Input, Heading, Body, Spacer, @@ -10,6 +11,9 @@ Popover, } from "@budibase/bbui" import { createEventDispatcher } from "svelte" + import { isValid } from "@budibase/string-templates" + import { handlebarsCompletions } from "constants/completions" + const dispatch = createEventDispatcher() export let value = "" @@ -18,9 +22,14 @@ export let align export let popover = null + let helpers = handlebarsCompletions() let getCaretPosition + let validity = true + let search = "" $: categories = Object.entries(groupBy("category", bindings)) + $: value && checkValid() + $: searchRgx = new RegExp(search, "ig") function onClickBinding(binding) { const position = getCaretPosition() @@ -34,18 +43,27 @@ value += toAdd } } + + function checkValid() { + validity = isValid(value) + }
Available bindings + + +
{#each categories as [categoryName, bindings]} {categoryName} - {#each bindings as binding} + {#each bindings.filter(binding => + binding.label.match(searchRgx) + ) as binding}
onClickBinding(binding)}> {binding.label} {binding.type} @@ -56,6 +74,17 @@
{/each} {/each} + Helpers + + {#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper} +
onClickBinding(helper)}> + {helper.label} +
+
+ {@html helper.description || ''} +
+
+ {/each}
@@ -70,11 +99,20 @@ bind:getCaretPosition bind:value placeholder="Add options from the left, type text, or do both" /> + {#if !validity} +

+ Current Handlebars syntax is invalid, please check the guide + here + for more details. +

+ {/if}
Learn more about binding - +
@@ -152,4 +190,14 @@ align-items: center; margin-top: var(--spacing-m); } + + .syntax-error { + color: var(--red); + font-size: 12px; + } + + .syntax-error a { + color: var(--red); + text-decoration: underline; + } diff --git a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte index 981c1297e6..d8f094b3cb 100644 --- a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte @@ -9,7 +9,7 @@ let permissions = [] let selectedRole = {} let errors = [] - let builtInRoles = ['Admin', 'Power', 'Basic', 'Public'] + let builtInRoles = ["Admin", "Power", "Basic", "Public"] $: selectedRoleId = selectedRole._id $: otherRoles = $backendUiStore.roles.filter( role => role._id !== selectedRoleId @@ -103,7 +103,11 @@ {/each} {#if selectedRole} - + + {#if context} Columns
    - {#each context as { readableBinding }} + {#each context.filter(context => + context.readableBinding.match(searchRgx) + ) as { readableBinding }}
  • addToText(readableBinding)}> {readableBinding}
  • {/each}
{/if} + {#if instance} Components +
    - {#each instance as { readableBinding }} + {#each instance.filter(instance => + instance.readableBinding.match(searchRgx) + ) as { readableBinding }}
  • addToText(readableBinding)}> {readableBinding}
  • {/each}
{/if} + + Helpers + +
    + {#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper} +
  • addToText(helper.text)}> +
    + +
    + {@html helper.description} +
    +
    +
  • + {/each} +