From b522726afcb3a9a519a513832bf75d86a37a006d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 28 Oct 2021 12:43:31 +0100 Subject: [PATCH 001/111] Allow in-preview editing of paragraphs and headings --- packages/client/manifest.json | 2 ++ .../client/src/components/Component.svelte | 25 ++++++++++++----- .../client/src/components/app/Heading.svelte | 19 ++++++++++--- .../client/src/components/app/Text.svelte | 19 ++++++++++--- .../src/components/preview/Indicator.svelte | 3 +-- .../preview/SelectionIndicator.svelte | 6 ++++- packages/client/src/stores/builder.js | 20 +++++++------- packages/client/src/utils/styleable.js | 27 +++++++++++++------ 8 files changed, 88 insertions(+), 33 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 2adfd96626..63ac7b1c7f 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -589,6 +589,7 @@ "icon": "TextParagraph", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", @@ -695,6 +696,7 @@ "description": "A component for displaying heading text", "illegalChildren": ["section"], "showSettingsBar": true, + "editable": true, "settings": [ { "type": "text", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index faf0226604..8200812477 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -60,21 +60,32 @@ $: instanceKey = JSON.stringify(rawProps) $: updateComponentProps(rawProps, instanceKey, $context) $: selected = - $builderStore.inBuilder && - $builderStore.selectedComponentId === instance._id + $builderStore.inBuilder && $builderStore.selectedComponentId === id $: inSelectedPath = $builderStore.selectedComponentPath?.includes(id) $: evaluateConditions(enrichedSettings?._conditions) $: componentSettings = { ...enrichedSettings, ...conditionalSettings } $: renderKey = `${propsHash}-${emptyState}` + $: editable = definition.editable + $: editing = editable && selected && $builderStore.editMode + $: draggable = interactive && !isLayout && !isScreen && !editing + $: droppable = interactive && !isLayout && !isScreen // Update component context $: componentStore.set({ id, children: children.length, - styles: { ...instance._styles, id, empty: emptyState, interactive }, + styles: { + ...instance._styles, + id, + empty: emptyState, + interactive, + draggable, + editable, + }, empty: emptyState, selected, name, + editing, }) const getRawProps = instance => { @@ -171,10 +182,6 @@ conditionalSettings = result.settingUpdates visible = nextVisible } - - // Drag and drop helper tags - $: draggable = interactive && !isLayout && !isScreen - $: droppable = interactive && !isLayout && !isScreen {#key renderKey} @@ -187,6 +194,7 @@ class:droppable class:empty class:interactive + class:editing data-id={id} data-name={name} > @@ -213,4 +221,7 @@ .draggable :global(*:hover) { cursor: grab; } + .editing :global(*:hover) { + cursor: auto; + } diff --git a/packages/client/src/components/app/Heading.svelte b/packages/client/src/components/app/Heading.svelte index bf4d902017..f3e283cf97 100644 --- a/packages/client/src/components/app/Heading.svelte +++ b/packages/client/src/components/app/Heading.svelte @@ -36,21 +36,34 @@ }, } } + + // Convert contenteditable HTML to text and save + const updateText = e => { + const html = e.target.innerHTML + const sanitized = html + .replace(/<\/div>
/gi, "\n") + .replace(/
/gi, "") + .replace(/<\/div>/gi, "") + .replace(/
/gi, "") + builderStore.actions.updateProp("text", sanitized) + } -

{componentText} -

+
diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte index 0671dce589..fd3f0c2a57 100644 --- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte @@ -1,21 +1,20 @@ - - - - updateRole(e.detail)} + options={$roles} + getOptionLabel={x => x.name} + getOptionValue={x => x._id} + /> +
{#if integrationInfo?.extra && query.queryVerb} { const response = await api.get(`/api/permission/${resourceId}`) - const json = await response.json() - return json + return await response.json() }, } } diff --git a/packages/server/src/api/routes/query.js b/packages/server/src/api/routes/query.js index 1cd6dcb1bb..3fca0c1e0f 100644 --- a/packages/server/src/api/routes/query.js +++ b/packages/server/src/api/routes/query.js @@ -66,6 +66,7 @@ router ) .get( "/api/queries/:queryId", + paramResource("queryId"), authorized(PermissionTypes.QUERY, PermissionLevels.READ), queryController.find ) From 935fc06edc971d4f716caf1f2f95db3de6d873f9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Nov 2021 12:00:29 +0000 Subject: [PATCH 010/111] Updating per review comments. --- packages/auth/src/db/utils.js | 18 ++++++++++++++++++ packages/server/src/api/controllers/user.js | 18 ++++++++---------- packages/server/src/api/routes/user.js | 2 +- packages/worker/src/utilities/appService.js | 21 ++++++++++++++------- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 03bd773922..b956089660 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -259,6 +259,24 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { } } +/** + * Utility function for getAllApps but filters to production apps only. + */ +exports.getDeployedAppIDs = async CouchDB => { + return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter( + id => !exports.isDevAppID(id) + ) +} + +/** + * Utility function for the inverse of above. + */ +exports.getDevAppIDs = async CouchDB => { + return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(id => + exports.isDevAppID(id) + ) +} + exports.dbExists = async (CouchDB, dbName) => { let exists = false try { diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 5faf821349..908018fe51 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -8,11 +8,7 @@ const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global") const { getFullUser } = require("../../utilities/users") const { isEqual } = require("lodash") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") -const { - getDevelopmentAppID, - getAllApps, - isDevAppID, -} = require("@budibase/auth/db") +const { getDevelopmentAppID, getDeployedAppIDs } = require("@budibase/auth/db") const { doesDatabaseExist } = require("../../utilities") const { UserStatus } = require("@budibase/auth/constants") @@ -78,8 +74,12 @@ exports.syncUser = async function (ctx) { try { user = await getRawGlobalUser(userId) } catch (err) { - user = {} - deleting = true + if (err && err.status === 404) { + user = {} + deleting = true + } else { + throw err + } } const roles = user.roles // remove props which aren't useful to metadata @@ -90,9 +90,7 @@ exports.syncUser = async function (ctx) { let prodAppIds // if they are a builder then get all production app IDs if ((user.builder && user.builder.global) || deleting) { - prodAppIds = (await getAllApps(CouchDB, { idsOnly: true })).filter( - id => !isDevAppID(id) - ) + prodAppIds = await getDeployedAppIDs(CouchDB) } else { prodAppIds = Object.entries(roles) .filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC) diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 43c08a7f33..a3043b5af1 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -35,7 +35,7 @@ router controller.destroyMetadata ) .post( - "/api/users/sync/:id", + "/api/users/metadata/sync/:id", authorized(PermissionTypes.USER, PermissionLevels.WRITE), controller.syncUser ) diff --git a/packages/worker/src/utilities/appService.js b/packages/worker/src/utilities/appService.js index 166a82f78b..92809c9046 100644 --- a/packages/worker/src/utilities/appService.js +++ b/packages/worker/src/utilities/appService.js @@ -4,7 +4,7 @@ const { getTenantId, isTenantIdSet } = require("@budibase/auth/tenancy") const { checkSlashesInUrl } = require("../utilities") const env = require("../environment") -exports.syncUserInApps = async userId => { +async function makeAppRequest(url, method, body) { if (env.isTest()) { return } @@ -13,12 +13,19 @@ exports.syncUserInApps = async userId => { if (isTenantIdSet()) { request.headers[Headers.TENANT_ID] = getTenantId() } - request.headers["Content-Type"] = "application/json" - request.body = JSON.stringify({}) - request.method = "POST" - const response = await fetch( - checkSlashesInUrl(env.APPS_URL + `/api/users/sync/${userId}`), - request + if (body) { + request.headers["Content-Type"] = "application/json" + request.body = JSON.stringify(body) + } + request.method = method + return fetch(checkSlashesInUrl(env.APPS_URL + url), request) +} + +exports.syncUserInApps = async userId => { + const response = await makeAppRequest( + `/api/users/metadata/sync/${userId}`, + "POST", + {} ) if (response.status !== 200) { throw "Unable to sync user." From eaaa134fb343bc755a5de9163e991518ffc3db60 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Wed, 10 Nov 2021 16:04:27 +0000 Subject: [PATCH 011/111] logic for saving of multiple screens --- .../FrontendNavigatePane.svelte | 28 ++++- .../NavigationSelectionModal.svelte | 45 +++++++- .../NavigationPanel/NewScreenModal.svelte | 105 +++++------------- .../NavigationPanel/ScreenNameModal.svelte | 19 ++++ 4 files changed, 112 insertions(+), 85 deletions(-) create mode 100644 packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte diff --git a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte index 274df8294c..ad62a7d6f4 100644 --- a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte +++ b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte @@ -13,6 +13,8 @@ import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte" import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui" + import NavigationSelectionModal from "./NavigationSelectionModal.svelte" + import ScreenNameModal from "./ScreenNameModal.svelte" const tabs = [ { @@ -26,7 +28,10 @@ ] let modal - let navSelectionModal + let navigationSelectionModal + let screenNameModal + let selectedScreens = [] + let screenName $: selected = tabs.find(t => t.key === $params.assetType)?.title || "Screens" const navigate = ({ detail }) => { @@ -86,9 +91,24 @@ - + + + + + + + + + + diff --git a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte index 4efd74b30b..22351d0a13 100644 --- a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte @@ -1,15 +1,56 @@ navSelectionModal.show()} + onCancel={() => (blankSelected ? screenNameModal.show() : modal.show())} size="M" + onConfirm={() => { + save(createdScreens.forEach(screen => save(screen))) + }} disabled={!selectedNav} > - import { store, allScreens } from "builderStore" + import { store } from "builderStore" import { tables } from "stores/backend" import { ModalContent, Body, Detail, Layout, Icon } from "@budibase/bbui" import getTemplates from "builderStore/store/screenTemplates" - const CONTAINER = "@budibase/standard-components/container" - const blankScreen = "createFromScratch" - let selectedScreens = [] - let navigationSelectionModal - let name = "" - let baseComponent = CONTAINER - let templateIndex - let draftScreen + export let screenNameModal + export let navigationSelectionModal + export let selectedScreens = [] - $: blankSelected = selectedScreens.includes(blankScreen) + const blankScreen = "createFromScratch" + $: blankSelected = selectedScreens.find(x => x.id === blankScreen) $: autoSelected = selectedScreens.length > 0 && !blankSelected $: templates = getTemplates($store, $tables.list) - $: route = !route && $allScreens.length === 0 ? "*" : route - $: { - if (templates && templateIndex === undefined) { - templateIndex = 0 - templateChanged(0) - } - } - - const templateChanged = newTemplateIndex => { - if (newTemplateIndex === undefined) return - draftScreen = templates[newTemplateIndex].create() - if (draftScreen.props._instanceName) { - name = draftScreen.props._instanceName - } - - if (draftScreen.props._component) { - baseComponent = draftScreen.props._component - } - - if (draftScreen.routing) { - route = draftScreen.routing.route - } - } - - /* - const save = async () => { - if (!route) { - routeError = "URL is required" + const toggleScreenSelection = table => { + if (selectedScreens.find(s => s.name.includes(table.name))) { + selectedScreens = selectedScreens.filter( + screen => !screen.name.includes(table.name) + ) } else { - if (routeExists(route, roleId)) { - routeError = "This URL is already taken for this access role" - } else { - routeError = "" - } - } - - if (routeError) return false - - draftScreen.props._instanceName = name - draftScreen.props._component = baseComponent - draftScreen.routing = { route, roleId } - - await store.actions.screens.create(draftScreen) - if (createLink) { - await store.actions.components.links.save(route, name) - } - await store.actions.routing.fetch() - - if (templateIndex !== undefined) { - const template = templates[templateIndex] - analytics.captureEvent(Events.SCREEN.CREATED, { - template: template.id || template.name, - }) - } - } -*/ - const toggleScreenSelection = template => { - if (selectedScreens.includes(template.id)) { - selectedScreens = selectedScreens.filter(screen => screen !== template.id) - } else { - selectedScreens = [template.id, ...selectedScreens] + const templates = getTemplates($store, $tables.list).filter(template => + template.name.includes(table.name) + ) + selectedScreens = [...templates, ...selectedScreens] } } @@ -86,7 +31,8 @@ title="Add screens" confirmText="Add Screens" cancelText="Cancel" - onConfirm={() => navigationSelectionModal.show()} + onConfirm={() => + autoSelected ? navigationSelectionModal.show() : screenNameModal.show()} disabled={!selectedScreens.length} size="L" > @@ -99,35 +45,36 @@ Blank screen
toggleScreenSelection({ id: blankScreen })} + class:selected={selectedScreens.find(x => x.id.includes(blankScreen))} + on:click={() => + toggleScreenSelection(templates.find(t => t.id === blankScreen))} class:disabled={autoSelected} >
Blank
- {#if selectedScreens.includes(blankScreen)} + {#if selectedScreens.find(x => x.id === blankScreen)} {/if}
Autogenerated Screens - {#each templates.filter(x => x.id !== blankScreen) as template} + {#each $tables.list.filter(table => table.type !== "external") as table}
toggleScreenSelection(template)} + class:selected={selectedScreens.find(x => x.name.includes(table.name))} + on:click={() => toggleScreenSelection(table)} class="item" >
- {template.name} + {table.name}
- {#if selectedScreens.includes(template.id)} + {#if selectedScreens.find(x => x.name.includes(table.name))} {/if}
diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte new file mode 100644 index 0000000000..e10ca7a4a1 --- /dev/null +++ b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte @@ -0,0 +1,19 @@ + + + modal.show()} + onConfirm={() => navigationSelectionModal.show()} + cancelText={"Back"} + disabled={!name.length} +> + + From 781e8359a1acb291983999bd1852697b5b4266a8 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Nov 2021 19:35:09 +0000 Subject: [PATCH 012/111] Adding worker-farm back to have a mechanism to run queries within which we can timeout. --- packages/server/package.json | 1 + packages/server/src/api/controllers/query.js | 74 ++----------------- packages/server/src/integrations/airtable.ts | 3 +- packages/server/src/integrations/arangodb.ts | 3 +- .../src/integrations/base/IntegrationBase.ts | 6 ++ .../src/integrations/base/datasourcePlus.ts | 3 +- packages/server/src/integrations/couchdb.ts | 3 +- packages/server/src/integrations/dynamodb.ts | 3 +- .../server/src/integrations/elasticsearch.ts | 3 +- packages/server/src/integrations/mongodb.ts | 3 +- packages/server/src/integrations/mysql.ts | 2 +- packages/server/src/integrations/rest.ts | 3 +- packages/server/src/integrations/s3.ts | 3 +- .../server/src/utilities/queryRunner/index.js | 31 ++++++++ .../src/utilities/queryRunner/runner.js | 63 ++++++++++++++++ packages/server/tsconfig.json | 2 +- packages/server/yarn.lock | 52 ++++++++----- 17 files changed, 159 insertions(+), 99 deletions(-) create mode 100644 packages/server/src/integrations/base/IntegrationBase.ts create mode 100644 packages/server/src/utilities/queryRunner/index.js create mode 100644 packages/server/src/utilities/queryRunner/runner.js diff --git a/packages/server/package.json b/packages/server/package.json index 484d9dcb4d..827a4739ef 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -120,6 +120,7 @@ "uuid": "3.3.2", "validate.js": "0.13.1", "vm2": "^3.9.3", + "worker-farm": "^1.7.0", "yargs": "13.2.4", "zlib": "1.0.5" }, diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 4383ff2910..f1d4a7a91c 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -1,10 +1,9 @@ const { processString } = require("@budibase/string-templates") const CouchDB = require("../../db") const { generateQueryID, getQueryParams } = require("../../db/utils") -const { integrations } = require("../../integrations") const { BaseQueryVerbs } = require("../../constants") const env = require("../../environment") -const ScriptRunner = require("../../utilities/scriptRunner") +const queryRunner = require("../../utilities/queryRunner") // simple function to append "readable" to all read queries function enrichQueries(input) { @@ -18,47 +17,6 @@ function enrichQueries(input) { return wasArray ? queries : queries[0] } -function formatResponse(resp) { - if (typeof resp === "string") { - try { - resp = JSON.parse(resp) - } catch (err) { - resp = { response: resp } - } - } - return resp -} - -async function runAndTransform( - integration, - queryVerb, - enrichedQuery, - transformer -) { - let rows = formatResponse(await integration[queryVerb](enrichedQuery)) - - // transform as required - if (transformer) { - const runner = new ScriptRunner(transformer, { data: rows }) - rows = runner.execute() - } - - // needs to an array for next step - if (!Array.isArray(rows)) { - rows = [rows] - } - - // map into JSON if just raw primitive here - if (rows.find(row => typeof row !== "object")) { - rows = rows.map(value => ({ value })) - } - - // get all the potential fields in the schema - let keys = rows.flatMap(Object.keys) - - return { rows, keys } -} - exports.fetch = async function (ctx) { const db = new CouchDB(ctx.appId) @@ -143,18 +101,11 @@ exports.preview = async function (ctx) { const datasource = await db.get(ctx.request.body.datasourceId) - const Integration = integrations[datasource.source] - - if (!Integration) { - ctx.throw(400, "Integration type does not exist.") - } - const { fields, parameters, queryVerb, transformer } = ctx.request.body const enrichedQuery = await enrichQueryFields(fields, parameters) - const integration = new Integration(datasource.config) - const { rows, keys } = await runAndTransform( - integration, + const { rows, keys } = await queryRunner( + datasource, queryVerb, enrichedQuery, transformer @@ -164,10 +115,6 @@ exports.preview = async function (ctx) { rows, schemaFields: [...new Set(keys)], } - // cleanup - if (integration.end) { - integration.end() - } } exports.execute = async function (ctx) { @@ -176,30 +123,19 @@ exports.execute = async function (ctx) { const query = await db.get(ctx.params.queryId) const datasource = await db.get(query.datasourceId) - const Integration = integrations[datasource.source] - - if (!Integration) { - ctx.throw(400, "Integration type does not exist.") - } - const enrichedQuery = await enrichQueryFields( query.fields, ctx.request.body.parameters ) - const integration = new Integration(datasource.config) // call the relevant CRUD method on the integration class - const { rows } = await runAndTransform( - integration, + const { rows } = await queryRunner( + datasource, query.queryVerb, enrichedQuery, query.transformer ) ctx.body = rows - // cleanup - if (integration.end) { - integration.end() - } } exports.destroy = async function (ctx) { diff --git a/packages/server/src/integrations/airtable.ts b/packages/server/src/integrations/airtable.ts index 7a80f51bd0..50c0a01147 100644 --- a/packages/server/src/integrations/airtable.ts +++ b/packages/server/src/integrations/airtable.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module AirtableModule { const Airtable = require("airtable") @@ -73,7 +74,7 @@ module AirtableModule { }, } - class AirtableIntegration { + class AirtableIntegration implements IntegrationBase { private config: AirtableConfig private client: any diff --git a/packages/server/src/integrations/arangodb.ts b/packages/server/src/integrations/arangodb.ts index 5fbe469870..8d2597b86b 100644 --- a/packages/server/src/integrations/arangodb.ts +++ b/packages/server/src/integrations/arangodb.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module ArangoModule { const { Database, aql } = require("arangojs") @@ -55,7 +56,7 @@ module ArangoModule { }, } - class ArangoDBIntegration { + class ArangoDBIntegration implements IntegrationBase { private config: ArangodbConfig private client: any diff --git a/packages/server/src/integrations/base/IntegrationBase.ts b/packages/server/src/integrations/base/IntegrationBase.ts new file mode 100644 index 0000000000..ce92a10fce --- /dev/null +++ b/packages/server/src/integrations/base/IntegrationBase.ts @@ -0,0 +1,6 @@ +export interface IntegrationBase { + create?(query: any): Promise<[any]> + read?(query: any): Promise<[any]> + update?(query: any): Promise<[any]> + delete?(query: any): Promise<[any]> +} diff --git a/packages/server/src/integrations/base/datasourcePlus.ts b/packages/server/src/integrations/base/datasourcePlus.ts index 371671afc0..6ea748c22b 100644 --- a/packages/server/src/integrations/base/datasourcePlus.ts +++ b/packages/server/src/integrations/base/datasourcePlus.ts @@ -1,6 +1,7 @@ import { Table } from "../../definitions/common" +import { IntegrationBase } from "./IntegrationBase" -export interface DatasourcePlus { +export interface DatasourcePlus extends IntegrationBase { tables: Record schemaErrors: Record diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 983e6cdac2..0405a319ea 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module CouchDBModule { const PouchDB = require("pouchdb") @@ -50,7 +51,7 @@ module CouchDBModule { }, } - class CouchDBIntegration { + class CouchDBIntegration implements IntegrationBase { private config: CouchDBConfig private client: any diff --git a/packages/server/src/integrations/dynamodb.ts b/packages/server/src/integrations/dynamodb.ts index 6b99ba04cc..0bc3a1273a 100644 --- a/packages/server/src/integrations/dynamodb.ts +++ b/packages/server/src/integrations/dynamodb.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module DynamoModule { const AWS = require("aws-sdk") @@ -113,7 +114,7 @@ module DynamoModule { }, } - class DynamoDBIntegration { + class DynamoDBIntegration implements IntegrationBase { private config: DynamoDBConfig private client: any diff --git a/packages/server/src/integrations/elasticsearch.ts b/packages/server/src/integrations/elasticsearch.ts index 147858c8dd..723d68f578 100644 --- a/packages/server/src/integrations/elasticsearch.ts +++ b/packages/server/src/integrations/elasticsearch.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module ElasticsearchModule { const { Client } = require("@elastic/elasticsearch") @@ -74,7 +75,7 @@ module ElasticsearchModule { }, } - class ElasticSearchIntegration { + class ElasticSearchIntegration implements IntegrationBase { private config: ElasticsearchConfig private client: any diff --git a/packages/server/src/integrations/mongodb.ts b/packages/server/src/integrations/mongodb.ts index 71364eb783..c955b43a65 100644 --- a/packages/server/src/integrations/mongodb.ts +++ b/packages/server/src/integrations/mongodb.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module MongoDBModule { const { MongoClient } = require("mongodb") @@ -62,7 +63,7 @@ module MongoDBModule { }, } - class MongoIntegration { + class MongoIntegration implements IntegrationBase { private config: MongoDBConfig private client: any diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index f2ff7adaee..a9b2b0efee 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -184,7 +184,7 @@ module MySQLModule { return results.length ? results : [{ created: true }] } - read(query: SqlQuery | string) { + async read(query: SqlQuery | string) { return internalQuery(this.client, getSqlQuery(query)) } diff --git a/packages/server/src/integrations/rest.ts b/packages/server/src/integrations/rest.ts index cf234518d9..acc2f849fa 100644 --- a/packages/server/src/integrations/rest.ts +++ b/packages/server/src/integrations/rest.ts @@ -3,6 +3,7 @@ import { DatasourceFieldTypes, QueryTypes, } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module RestModule { const fetch = require("node-fetch") @@ -131,7 +132,7 @@ module RestModule { }, } - class RestIntegration { + class RestIntegration implements IntegrationBase { private config: RestConfig private headers: { [key: string]: string diff --git a/packages/server/src/integrations/s3.ts b/packages/server/src/integrations/s3.ts index 691f3a05c0..bcbcdf342a 100644 --- a/packages/server/src/integrations/s3.ts +++ b/packages/server/src/integrations/s3.ts @@ -1,4 +1,5 @@ import { Integration, QueryTypes } from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" module S3Module { const AWS = require("aws-sdk") @@ -42,7 +43,7 @@ module S3Module { }, } - class S3Integration { + class S3Integration implements IntegrationBase { private readonly config: S3Config private client: any private connectionPromise: Promise diff --git a/packages/server/src/utilities/queryRunner/index.js b/packages/server/src/utilities/queryRunner/index.js new file mode 100644 index 0000000000..0660037a84 --- /dev/null +++ b/packages/server/src/utilities/queryRunner/index.js @@ -0,0 +1,31 @@ +const workerFarm = require("worker-farm") +const MAX_WORKER_TIME_MS = 10000 +const workers = workerFarm( + { + autoStart: true, + maxConcurrentWorkers: 1, + maxCallTime: MAX_WORKER_TIME_MS, + }, + require.resolve("./runner") +) + +function runService(data) { + return new Promise((resolve, reject) => { + workers(data, (err, response) => { + if (err) { + reject(err) + } else { + resolve(response) + } + }) + }) +} + +module.exports = async (datasource, queryVerb, query, transformer) => { + return runService({ + datasource, + queryVerb, + query, + transformer, + }) +} diff --git a/packages/server/src/utilities/queryRunner/runner.js b/packages/server/src/utilities/queryRunner/runner.js new file mode 100644 index 0000000000..11ffbaa33d --- /dev/null +++ b/packages/server/src/utilities/queryRunner/runner.js @@ -0,0 +1,63 @@ +const ScriptRunner = require("../scriptRunner") +const { integrations } = require("../../integrations") + +function formatResponse(resp) { + if (typeof resp === "string") { + try { + resp = JSON.parse(resp) + } catch (err) { + resp = { response: resp } + } + } + return resp +} + +async function runAndTransform(datasource, queryVerb, query, transformer) { + const Integration = integrations[datasource.source] + if (!Integration) { + throw "Integration type does not exist." + } + const integration = new Integration(datasource.config) + + let rows = formatResponse(await integration[queryVerb](query)) + + // transform as required + if (transformer) { + const runner = new ScriptRunner(transformer, { data: rows }) + rows = runner.execute() + } + + // needs to an array for next step + if (!Array.isArray(rows)) { + rows = [rows] + } + + // map into JSON if just raw primitive here + if (rows.find(row => typeof row !== "object")) { + rows = rows.map(value => ({ value })) + } + + // get all the potential fields in the schema + let keys = rows.flatMap(Object.keys) + + if (integration.end) { + integration.end() + } + + return { rows, keys } +} + +module.exports = (input, callback) => { + runAndTransform( + input.datasource, + input.queryVerb, + input.query, + input.transformer + ) + .then(response => { + callback(null, response) + }) + .catch(err => { + callback(err) + }) +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 934d0bdd2b..6a5ba315a1 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es6", "module": "commonjs", - "lib": ["es6"], + "lib": ["es2019"], "allowJs": true, "outDir": "dist", "strict": true, diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index aa95579d95..7f37e70b9e 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -943,10 +943,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/auth@^0.9.169-alpha.1": - version "0.9.169" - resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.169.tgz#fd2a8fc271782ba857259ace15118a4d53b3d161" - integrity sha512-Q087k/54Nzx6Oeg5uL7YD/9BB+qkBWIv7h4ct+cNQJFNK/aKKN8JLQft+z3mBN5omHTkdJYFmbgXWFxtX+rR3Q== +"@budibase/auth@^0.9.180-alpha.1": + version "0.9.183" + resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.183.tgz#da5a7e8b8ba9909d33399bbcd1b7164690ada257" + integrity sha512-BNlD4f7YfQejaq1wgMiIPzkNB+fu0HFpg9lyPYaD/mDWpa0F3HdMK3LxYewda9uRy9LJf6LtR3NJxVFvo0zXHA== dependencies: "@techpass/passport-openidconnect" "^0.3.0" aws-sdk "^2.901.0" @@ -956,6 +956,7 @@ jsonwebtoken "^8.5.1" koa-passport "^4.1.4" lodash "^4.17.21" + lodash.isarguments "^3.1.0" node-fetch "^2.6.1" passport-google-auth "^1.0.2" passport-google-oauth "^2.0.0" @@ -1015,10 +1016,10 @@ svelte-flatpickr "^3.1.0" svelte-portal "^1.0.0" -"@budibase/bbui@^0.9.169": - version "0.9.169" - resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.169.tgz#e8dac59b9792a7edf03c4301a9069760e2ebd2f4" - integrity sha512-2hks6GEjcXbDUzC37WgJvgloiqTP5ZS7IuRjlHU9kStDr6dAnXuy8pO6JNJmKrTXt+rgtwhHHrVWzzcmNLIYxA== +"@budibase/bbui@^0.9.183": + version "0.9.183" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.183.tgz#7e2ad9a34ec5ae9f32bc9d263199217b324f1b8c" + integrity sha512-SFTb5rxfUB1rVYMASvtwVYb5XDhSdsQ1Fkr85Mn+ME284WQqBeJKRSz87jLVXJFQAnSpPEDUShOUTTFVByqpig== dependencies: "@adobe/spectrum-css-workflow-icons" "^1.2.1" "@spectrum-css/actionbutton" "^1.0.1" @@ -1064,14 +1065,14 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/client@^0.9.169-alpha.1": - version "0.9.169" - resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.169.tgz#bec370b8f069b42f62483b281d6b9e2c7c8625f3" - integrity sha512-/bDnwv2iRysZrcrBQJEKzuxdwkwoJ2FalmQFhsfj+V/MWBN/wpQSDbJZQwf/YcI5bQk8f7xIn95O+DMH/m5izg== +"@budibase/client@^0.9.180-alpha.1": + version "0.9.183" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.183.tgz#cf86a2e0382d7e4a0898630f10f17d7640ce256d" + integrity sha512-1gw8EVIwouNJtYPgByX97EyeegAm35+jSd6irjU0PQEKldtvw2vLI9hmatvUdkUqLFUCT5PeXq37xfkp2JCYLQ== dependencies: - "@budibase/bbui" "^0.9.169" + "@budibase/bbui" "^0.9.183" "@budibase/standard-components" "^0.9.139" - "@budibase/string-templates" "^0.9.169" + "@budibase/string-templates" "^0.9.183" regexparam "^1.3.0" shortid "^2.2.15" svelte-spa-router "^3.0.5" @@ -1121,16 +1122,17 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/string-templates@^0.9.169", "@budibase/string-templates@^0.9.169-alpha.1": - version "0.9.169" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.169.tgz#3c0be97718f39a92ff6b2dbb8b470aaa7851005e" - integrity sha512-JUyg6XuUgFqnfdDSCAplo4cTtrqdSZ9NPrU3iGudZEQjO/Wk5sezWPznl3Yw/kFHKmPLjFHIveEa2+lODEAxIA== +"@budibase/string-templates@^0.9.180-alpha.1", "@budibase/string-templates@^0.9.183": + version "0.9.183" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.183.tgz#c75dc298d8ec69e1717721b46c3c99448b5ee0a1" + integrity sha512-S3Z81c2YGtG0hUXvOrDKn8Gj4iu1adxIDeNgHJAsesID3/SrI9KBhExx1HzIP14SLZlFEao5A12cVtpFBHC7LQ== dependencies: "@budibase/handlebars-helpers" "^0.11.7" dayjs "^1.10.4" handlebars "^4.7.6" handlebars-utils "^1.0.6" lodash "^4.17.20" + vm2 "^3.9.4" "@cnakazawa/watch@^1.0.3": version "1.0.4" @@ -4476,7 +4478,7 @@ ent@^2.2.0: resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= -errno@~0.1.1: +errno@~0.1.1, errno@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== @@ -11785,6 +11787,11 @@ vm2@^3.9.3: resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.4.tgz#2e118290fefe7bd8ea09ebe2f5faf53730dbddaa" integrity sha512-sOdharrJ7KEePIpHekiWaY1DwgueuiBeX/ZBJUPgETsVlJsXuEx0K0/naATq2haFvJrvZnRiORQRubR0b7Ye6g== +vm2@^3.9.4: + version "3.9.5" + resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.5.tgz#5288044860b4bbace443101fcd3bddb2a0aa2496" + integrity sha512-LuCAHZN75H9tdrAiLFf030oW7nJV5xwNMuk1ymOZwopmuK3d2H4L1Kv4+GFHgarKiLfXXLFU+7LDABHnwOkWng== + vuvuzela@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b" @@ -11920,6 +11927,13 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" From 8592c489f900c979364b360b5b76fe1d38670577 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Nov 2021 08:28:42 +0000 Subject: [PATCH 013/111] move modals to top level --- .../FrontendNavigatePane.svelte | 27 +------- .../NavigationSelectionModal.svelte | 52 ++++++++------- .../design/[assetType]/_layout.svelte | 64 ++++++++++++++++++- 3 files changed, 93 insertions(+), 50 deletions(-) diff --git a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte index ad62a7d6f4..92a20a4acc 100644 --- a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte +++ b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte @@ -10,11 +10,8 @@ import { roles } from "stores/backend" import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte" import Layout from "components/design/NavigationPanel/Layout.svelte" - import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte" import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui" - import NavigationSelectionModal from "./NavigationSelectionModal.svelte" - import ScreenNameModal from "./ScreenNameModal.svelte" const tabs = [ { @@ -27,11 +24,7 @@ }, ] - let modal - let navigationSelectionModal - let screenNameModal - let selectedScreens = [] - let screenName + export let modal $: selected = tabs.find(t => t.key === $params.assetType)?.title || "Screens" const navigate = ({ detail }) => { @@ -91,24 +84,6 @@ - - - - - - - - - -
diff --git a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte index 22351d0a13..6e2a7b3d25 100644 --- a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte @@ -5,6 +5,7 @@ export let screenNameModal export let selectedScreens export let modal + let roleId = $selectedAccessRole || "BASIC" let routeError @@ -19,28 +20,33 @@ $: blankSelected = selectedScreens.find(x => x.id === "createFromScratch") const save = async draftScreen => { - if (!draftScreen.routing.route) { - routeError = "URL is required" - } else { - if (routeExists(draftScreen.routing.route, roleId)) { - routeError = "This URL is already taken for this access role" + if (draftScreen) { + console.log(draftScreen) + if (!draftScreen.routing.route) { + routeError = "URL is required" } else { - routeError = "" + if (routeExists(draftScreen.routing.route, roleId)) { + routeError = "This URL is already taken for this access role" + } else { + routeError = "" + } } - } - if (routeError) return false + console.log(routeError) + if (routeError) return false - await store.actions.screens.create(draftScreen) - await store.actions.routing.fetch() - - const routeExists = (route, roleId) => { - return $allScreens.some( - screen => - screen.routing.route.toLowerCase() === route.toLowerCase() && - screen.routing.roleId === roleId - ) + draftScreen.props.navigation = selectedNav + await store.actions.screens.create(draftScreen) + await store.actions.routing.fetch() } } + + const routeExists = (route, roleId) => { + return $allScreens.some( + screen => + screen.routing.route.toLowerCase() === route.toLowerCase() && + screen.routing.roleId === roleId + ) + }
(selectedNav = "sideNav")} - class:unselected={selectedNav && selectedNav !== "sideNav"} + on:click={() => (selectedNav = "side")} + class:unselected={selectedNav && selectedNav !== "side"} >
@@ -68,8 +74,8 @@
Side Nav
(selectedNav = "topNav")} - class:unselected={selectedNav && selectedNav !== "topNav"} + on:click={() => (selectedNav = "top")} + class:unselected={selectedNav && selectedNav !== "top"} >
@@ -77,8 +83,8 @@
Top Nav
(selectedNav = "noNav")} - class:unselected={selectedNav && selectedNav !== "noNav"} + on:click={() => (selectedNav = "none")} + class:unselected={selectedNav && selectedNav !== "none"} >
No Nav
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index d3e5d5d2d0..b787d9adec 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -5,6 +5,8 @@ selectedComponent, allScreens, } from "builderStore" + import { Detail, Layout, Button, Modal } from "@budibase/bbui" + import CurrentItemPreview from "components/design/AppPreview" import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte" import ComponentSelectionList from "components/design/AppPreview/ComponentSelectionList.svelte" @@ -16,6 +18,9 @@ import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte" import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte" import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte" + import NavigationSelectionModal from "components/design/NavigationPanel/NavigationSelectionModal.svelte" + import ScreenNameModal from "components/design/NavigationPanel/ScreenNameModal.svelte" + import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" // Cache previous values so we don't update the URL more than necessary let previousType @@ -23,6 +28,13 @@ let previousComponentId let hydrationComplete = false + // Manage the layout modal flow from here + let modal + let navigationSelectionModal + let screenNameModal + let screenName + let selectedScreens = [] + // Hydrate state from URL params $: hydrateStateFromURL($params, $leftover) @@ -145,7 +157,7 @@
- +
@@ -166,6 +178,25 @@ {/key}
+ {:else} +
+
+ + + + + Let's add some life to this screen + + +
+
{/if}
@@ -177,6 +208,20 @@
+ + + + + + + + + + From 8645299ab1bd2a9e10fb07088430ea836e533357 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Nov 2021 11:07:55 +0000 Subject: [PATCH 014/111] save nav selection --- .../NavigationSelectionModal.svelte | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte index 6e2a7b3d25..4c67aef8fb 100644 --- a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte @@ -1,6 +1,7 @@ modal.show()} onConfirm={() => navigationSelectionModal.show()} cancelText={"Back"} - disabled={!name.length} + disabled={!screenName} > - + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index b787d9adec..dbed0e4cfb 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -32,9 +32,9 @@ let modal let navigationSelectionModal let screenNameModal - let screenName + let screenName = "" let selectedScreens = [] - + $: console.log(screenName) // Hydrate state from URL params $: hydrateStateFromURL($params, $leftover) @@ -220,7 +220,12 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index dbed0e4cfb..5f020aed9a 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -5,7 +5,7 @@ selectedComponent, allScreens, } from "builderStore" - import { Detail, Layout, Button, Modal } from "@budibase/bbui" + import { Detail, Layout, Button, Modal, Icon } from "@budibase/bbui" import CurrentItemPreview from "components/design/AppPreview" import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte" @@ -21,6 +21,7 @@ import NavigationSelectionModal from "components/design/NavigationPanel/NavigationSelectionModal.svelte" import ScreenNameModal from "components/design/NavigationPanel/ScreenNameModal.svelte" import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" + import Logo from "assets/bb-space-man.svg" // Cache previous values so we don't update the URL more than necessary let previousType @@ -182,17 +183,17 @@
- - - - Let's add some life to this screen - +
+ Let's add some life to this screen +
+
@@ -236,7 +237,28 @@ flex: 1 1 auto; height: 0; } + .new-screen-text { + width: 160px; + text-align: center; + } + .new-screen-button { + margin-left: 5px; + height: 20px; + width: 100px; + display: flex; + align-items: center; + } + + .background-icon { + margin-top: 4px; + margin-right: 4px; + } + + .img-size { + width: 160px; + height: 160px; + } .ui-nav { grid-column: 1; background-color: var(--background); @@ -291,7 +313,7 @@ .centered { top: 0; bottom: 0; - left: 0; + left: 10%; right: 0; width: 100%; height: 100%; From 35f0384b171db1ebdf1c7277a33062b6fc642062 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Nov 2021 12:55:04 +0000 Subject: [PATCH 018/111] removing log --- .../design/NavigationPanel/NavigationSelectionModal.svelte | 4 ++-- .../components/design/NavigationPanel/ScreenNameModal.svelte | 3 +-- .../app/[application]/design/[assetType]/_layout.svelte | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte index bed7028f6f..695638f83e 100644 --- a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte @@ -39,7 +39,7 @@ let route = screenName ? sanitizeUrl(`/${screenName}`) : draftScreen.routing.route - console.log(sanitizeUrl(`/${screenName}`)) + if (draftScreen) { if (!route) { routeError = "URL is required" @@ -50,7 +50,7 @@ routeError = "" } } - console.log(routeError) + if (routeError) return false draftScreen.routing.route = route diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte index a5fe61dbc4..ff656001df 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte @@ -4,12 +4,11 @@ export let modal export let navigationSelectionModal export let screenName - $: console.log(name) modal.show()} onConfirm={() => navigationSelectionModal.show()} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index 5f020aed9a..c61e5e09c7 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -35,7 +35,7 @@ let screenNameModal let screenName = "" let selectedScreens = [] - $: console.log(screenName) + // Hydrate state from URL params $: hydrateStateFromURL($params, $leftover) From de8a91da8308d0b984d2d48978008f6acffa185e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 11 Nov 2021 15:36:21 +0000 Subject: [PATCH 019/111] Fixing an issue with filtering by dates in SQL, where the lucene dates provided don't convert cleanly to JS dates. --- packages/server/src/integrations/base/sql.ts | 46 ++++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 778488cf83..08bff54fae 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -13,22 +13,49 @@ import SqlTableQueryBuilder from "./sqlTable" const BASE_LIMIT = 5000 type KnexQuery = Knex.QueryBuilder | Knex +// these are invalid dates sent by the client, need to convert them to a real max date +const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z" +const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z" + +function parse(input: any) { + if (Array.isArray(input)) { + return JSON.stringify(input) + } + if (typeof input !== "string") { + return input + } + if (input === MAX_ISO_DATE) { + return new Date(8640000000000000) + } + if (input === MIN_ISO_DATE) { + return new Date(-8640000000000000) + } + if (isIsoDateString(input)) { + return new Date(input) + } +} function parseBody(body: any) { for (let [key, value] of Object.entries(body)) { - if (Array.isArray(value)) { - body[key] = JSON.stringify(value) - } - if (typeof value !== "string") { - continue - } - if (isIsoDateString(value)) { - body[key] = new Date(value) - } + body[key] = parse(value) } return body } +function parseFilters(filters: SearchFilters): SearchFilters { + for (let [key, value] of Object.entries(filters)) { + let parsed + if (typeof value === "object") { + parsed = parseFilters(value) + } else { + parsed = parse(value) + } + // @ts-ignore + filters[key] = parsed + } + return filters +} + class InternalBuilder { private readonly client: string @@ -53,6 +80,7 @@ class InternalBuilder { if (!filters) { return query } + filters = parseFilters(filters) // if all or specified in filters, then everything is an or const allOr = filters.allOr if (filters.oneOf) { From 9f4ff190c340fbd8eb28f6d5fefc5424479a71ca Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 11 Nov 2021 16:20:30 +0000 Subject: [PATCH 020/111] Fixing issues dsicovered by automation test cases, as well as disabling threading for test scenarios. --- packages/server/src/api/controllers/query.js | 40 +++++++++++-------- .../src/automations/tests/automation.spec.js | 6 +-- packages/server/src/integrations/base/sql.ts | 1 + packages/server/src/threads/index.js | 27 +++++++++---- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 7323355c64..502ef5e67b 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -106,16 +106,20 @@ exports.preview = async function (ctx) { const { fields, parameters, queryVerb, transformer } = ctx.request.body const enrichedQuery = await enrichQueryFields(fields, parameters) - const { rows, keys } = await Runner.run({ - datasource, - queryVerb, - query: enrichedQuery, - transformer, - }) + try { + const { rows, keys } = await Runner.run({ + datasource, + queryVerb, + query: enrichedQuery, + transformer, + }) - ctx.body = { - rows, - schemaFields: [...new Set(keys)], + ctx.body = { + rows, + schemaFields: [...new Set(keys)], + } + } catch (err) { + ctx.throw(400, err) } } @@ -131,13 +135,17 @@ exports.execute = async function (ctx) { ) // call the relevant CRUD method on the integration class - const { rows } = await Runner.run({ - datasource, - queryVerb: query.queryVerb, - query: enrichedQuery, - transformer: query.transformer, - }) - ctx.body = rows + try { + const { rows } = await Runner.run({ + datasource, + queryVerb: query.queryVerb, + query: enrichedQuery, + transformer: query.transformer, + }) + ctx.body = rows + } catch (err) { + ctx.throw(400, err) + } } exports.destroy = async function (ctx) { diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index 81d34cea54..50c099cfb6 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -1,5 +1,5 @@ jest.mock("../../utilities/usageQuota") -jest.mock("../thread") +jest.mock("../../threads/automation") jest.mock("../../utilities/redis", () => ({ init: jest.fn(), checkTestFlag: () => { @@ -53,7 +53,7 @@ describe("Run through some parts of the automations system", () => { } }) await wait(100) - expect().toHaveBeenCalledWith(makePartial({ + expect(thread).toHaveBeenCalledWith(makePartial({ data: { event: { fields: { @@ -61,7 +61,7 @@ describe("Run through some parts of the automations system", () => { } } } - })) + }), expect.any(Function)) }) it("should be able to clean inputs with the utilities", () => { diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 08bff54fae..64f03aea47 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -33,6 +33,7 @@ function parse(input: any) { if (isIsoDateString(input)) { return new Date(input) } + return input } function parseBody(body: any) { diff --git a/packages/server/src/threads/index.js b/packages/server/src/threads/index.js index 909f381fd8..e24b45bbfa 100644 --- a/packages/server/src/threads/index.js +++ b/packages/server/src/threads/index.js @@ -1,4 +1,5 @@ const workerFarm = require("worker-farm") +const env = require("../environment") const ThreadType = { QUERY: "query", @@ -22,19 +23,29 @@ function typeToFile(type) { class Thread { constructor(type, opts = { timeoutMs: null, count: 1 }) { - const workerOpts = { - autoStart: true, - maxConcurrentWorkers: opts.count ? opts.count : 1, + this.type = type + if (!env.isTest()) { + const workerOpts = { + autoStart: true, + maxConcurrentWorkers: opts.count ? opts.count : 1, + } + if (opts.timeoutMs) { + workerOpts.maxCallTime = opts.timeoutMs + } + this.workers = workerFarm(workerOpts, typeToFile(type)) } - if (opts.timeoutMs) { - workerOpts.maxCallTime = opts.timeoutMs - } - this.workers = workerFarm(workerOpts, typeToFile(type)) } run(data) { return new Promise((resolve, reject) => { - this.workers(data, (err, response) => { + let fncToCall + // if in test then don't use threading + if (env.isTest()) { + fncToCall = require(typeToFile(this.type)) + } else { + fncToCall = this.workers + } + fncToCall(data, (err, response) => { if (err) { reject(err) } else { From 599702bfe95015ee7af422f99246c8f22eeb84c5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 11 Nov 2021 17:13:35 +0000 Subject: [PATCH 021/111] Fixing issue where deleted datasources wouldn't clear out queries. --- packages/builder/src/stores/backend/datasources.js | 1 + packages/server/src/api/controllers/datasource.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/stores/backend/datasources.js b/packages/builder/src/stores/backend/datasources.js index 91719ae9b7..7810c3a950 100644 --- a/packages/builder/src/stores/backend/datasources.js +++ b/packages/builder/src/stores/backend/datasources.js @@ -95,6 +95,7 @@ export function createDatasourcesStore() { return { list: sources, selected: null } }) + await queries.fetch() return response }, removeSchemaError: () => { diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index 643e822a36..009baec9e0 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -119,8 +119,16 @@ exports.destroy = async function (ctx) { const db = new CouchDB(ctx.appId) // Delete all queries for the datasource - const rows = await db.allDocs(getQueryParams(ctx.params.datasourceId, null)) - await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true }))) + const queries = await db.allDocs( + getQueryParams(ctx.params.datasourceId, null) + ) + await db.bulkDocs( + queries.rows.map(row => ({ + _id: row.id, + _rev: row.value.rev, + _deleted: true, + })) + ) // delete the datasource await db.remove(ctx.params.datasourceId, ctx.params.revId) From 6af8ab2dc0522fd56b0015e9372cb26876d8e8f5 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 12 Nov 2021 13:31:55 +0000 Subject: [PATCH 022/111] Fixes for google sso, cloud email url and cloud logo updates --- packages/auth/src/db/utils.js | 41 ++++++++++++- packages/auth/src/environment.js | 1 + packages/auth/src/objectStore/utils.js | 1 + packages/bbui/src/Label/Label.svelte | 57 ++++++++++++++++++- packages/bbui/src/Tooltip/Tooltip.svelte | 20 +++++-- .../builder/portal/manage/auth/index.svelte | 39 +++++++------ .../portal/settings/organisation.svelte | 7 ++- .../builder/src/stores/portal/organisation.js | 11 +++- packages/server/src/integrations/postgres.ts | 10 ++-- .../worker/src/api/controllers/global/auth.js | 34 +++++++---- .../src/api/controllers/global/configs.js | 25 +++++++- .../worker/src/api/routes/tests/auth.spec.js | 2 +- packages/worker/src/utilities/templates.js | 4 -- 13 files changed, 197 insertions(+), 55 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index fa162603e6..4da5d2d410 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,6 +1,6 @@ const { newid } = require("../hashing") const Replication = require("./Replication") -const { DEFAULT_TENANT_ID } = require("../constants") +const { DEFAULT_TENANT_ID, Configs } = require("../constants") const env = require("../environment") const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants") const { getTenantId, getTenantIDFromAppID } = require("../tenancy") @@ -322,13 +322,50 @@ const getScopedFullConfig = async function (db, { type, user, workspace }) { } // Find the config with the most granular scope based on context - const scopedConfig = response.rows.sort( + let scopedConfig = response.rows.sort( (a, b) => determineScore(a) - determineScore(b) )[0] + // custom logic for settings doc + // always provide the platform URL + if (type === Configs.SETTINGS) { + if (scopedConfig && scopedConfig.doc) { + scopedConfig.doc.config.platformUrl = await getPlatformUrl( + scopedConfig.doc.config + ) + } else { + scopedConfig = { + doc: { + config: { + platformUrl: await getPlatformUrl(), + }, + }, + } + } + } + return scopedConfig && scopedConfig.doc } +const getPlatformUrl = async settings => { + let platformUrl = env.PLATFORM_URL + + if (!env.SELF_HOSTED && env.MULTI_TENANCY) { + // cloud and multi tenant - add the tenant to the default platform url + const tenantId = getTenantId() + if (!platformUrl.includes("localhost:")) { + platformUrl = platformUrl.replace("://", `://${tenantId}.`) + } + } else { + // self hosted - check for platform url override + if (settings && settings.platformUrl) { + platformUrl = settings.platformUrl + } + } + + return platformUrl ? platformUrl : "http://localhost:10000" +} + async function getScopedConfig(db, params) { const configDoc = await getScopedFullConfig(db, params) return configDoc && configDoc.config ? configDoc.config : configDoc diff --git a/packages/auth/src/environment.js b/packages/auth/src/environment.js index c36b469c4e..c26ad1c199 100644 --- a/packages/auth/src/environment.js +++ b/packages/auth/src/environment.js @@ -25,6 +25,7 @@ module.exports = { DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL, SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED), COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, + PLATFORM_URL: process.env.PLATFORM_URL, isTest, _set(key, value) { process.env[key] = value diff --git a/packages/auth/src/objectStore/utils.js b/packages/auth/src/objectStore/utils.js index 41fa18d99e..1634a24981 100644 --- a/packages/auth/src/objectStore/utils.js +++ b/packages/auth/src/objectStore/utils.js @@ -6,6 +6,7 @@ exports.ObjectStoreBuckets = { APPS: "prod-budi-app-assets", TEMPLATES: "templates", GLOBAL: "global", + GLOBAL_CLOUD: "prod-budi-tenant-uploads", } exports.budibaseTempDir = function () { diff --git a/packages/bbui/src/Label/Label.svelte b/packages/bbui/src/Label/Label.svelte index 3ed18dc6f5..a3a94b2836 100644 --- a/packages/bbui/src/Label/Label.svelte +++ b/packages/bbui/src/Label/Label.svelte @@ -1,16 +1,67 @@ - +{#if tooltip} +
+ +
+
(showTooltip = true)} + on:mouseleave={() => (showTooltip = false)} + > + +
+ {#if showTooltip} +
+ +
+ {/if} +
+
+{:else} + +{/if} diff --git a/packages/bbui/src/Tooltip/Tooltip.svelte b/packages/bbui/src/Tooltip/Tooltip.svelte index a4b1d4ff59..408a9ddf8f 100644 --- a/packages/bbui/src/Tooltip/Tooltip.svelte +++ b/packages/bbui/src/Tooltip/Tooltip.svelte @@ -3,12 +3,22 @@ export let direction = "top" export let text = "" + export let textWrapping = false - - -
+ +{#if textWrapping} + {text} -
-
+ +{:else} + + + +
+ {text} + +
+
+{/if} diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte index c2445e14ae..20d30fdfbb 100644 --- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte @@ -21,26 +21,25 @@ } from "@budibase/bbui" import { onMount } from "svelte" import api from "builderStore/api" - import { organisation, auth, admin } from "stores/portal" + import { organisation, admin } from "stores/portal" import { uuid } from "builderStore/uuid" import analytics, { Events } from "analytics" - $: tenantId = $auth.tenantId - $: multiTenancyEnabled = $admin.multiTenancy - const ConfigTypes = { Google: "google", OIDC: "oidc", } - function callbackUrl(tenantId, end) { - let url = `/api/global/auth` - if (multiTenancyEnabled && tenantId) { - url += `/${tenantId}` - } - url += end - return url - } + // Some older google configs contain a manually specified value - retain the functionality to edit the field + // When there is no value or we are in the cloud - prohibit editing the field, must use platform url to change + $: googleCallbackUrl = undefined + $: googleCallbackReadonly = $admin.cloud || !googleCallbackUrl + + // Indicate to user that callback is based on platform url + // If there is an existing value, indicate that it may be removed to return to default behaviour + $: googleCallbackTooltip = googleCallbackReadonly + ? "Vist the organisation page to update the platform URL" + : "Leave blank to use the default callback URL" $: GoogleConfigFields = { Google: [ @@ -49,8 +48,9 @@ { name: "callbackURL", label: "Callback URL", - readonly: true, - placeholder: callbackUrl(tenantId, "/google/callback"), + readonly: googleCallbackReadonly, + tooltip: googleCallbackTooltip, + placeholder: $organisation.googleCallbackUrl, }, ], } @@ -62,9 +62,10 @@ { name: "clientSecret", label: "Client Secret" }, { name: "callbackURL", - label: "Callback URL", readonly: true, - placeholder: callbackUrl(tenantId, "/oidc/callback"), + tooltip: "Vist the organisation page to update the platform URL", + label: "Callback URL", + placeholder: $organisation.oidcCallbackUrl, }, ], } @@ -241,6 +242,8 @@ providers.google = googleDoc } + googleCallbackUrl = providers?.google?.config?.callbackURL + //Get the list of user uploaded logos and push it to the dropdown options. //This needs to be done before the config call so they're available when the dropdown renders const res = await api.get(`/api/global/configs/logos_oidc`) @@ -308,7 +311,7 @@ {#each GoogleConfigFields.Google as field}
- + {#each OIDCConfigFields.Oidc as field}
- +
- +
@@ -135,6 +139,7 @@ .field { display: grid; grid-template-columns: 100px 1fr; + grid-gap: var(--spacing-l); align-items: center; } .file { diff --git a/packages/builder/src/stores/portal/organisation.js b/packages/builder/src/stores/portal/organisation.js index 03bfa6ca28..21a110c54a 100644 --- a/packages/builder/src/stores/portal/organisation.js +++ b/packages/builder/src/stores/portal/organisation.js @@ -3,12 +3,14 @@ import api from "builderStore/api" import { auth } from "stores/portal" const DEFAULT_CONFIG = { - platformUrl: "http://localhost:10000", + platformUrl: "", logoUrl: undefined, docsUrl: undefined, company: "Budibase", oidc: undefined, google: undefined, + oidcCallbackUrl: "", + googleCallbackUrl: "", } export function createOrganisationStore() { @@ -28,6 +30,13 @@ export function createOrganisationStore() { } async function save(config) { + // delete non-persisted fields + const storeConfig = get(store) + delete storeConfig.oidc + delete storeConfig.google + delete storeConfig.oidcCallbackUrl + delete storeConfig.googleCallbackUrl + const res = await api.post("/api/global/configs", { type: "settings", config: { ...get(store), ...config }, diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index e42681873d..b2e48ad540 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -130,7 +130,7 @@ module PostgresModule { public tables: Record = {} public schemaErrors: Record = {} - COLUMNS_SQL!: string + COLUMNS_SQL!: string PRIMARY_KEYS_SQL = ` select tc.table_schema, tc.table_name, kc.column_name as primary_key @@ -165,11 +165,11 @@ module PostgresModule { setSchema() { if (!this.config.schema) { - this.config.schema = 'public' + this.config.schema = "public" } - this.client.on('connect', (client: any) => { - client.query(`SET search_path TO ${this.config.schema}`); - }); + this.client.on("connect", (client: any) => { + client.query(`SET search_path TO ${this.config.schema}`) + }) this.COLUMNS_SQL = `select * from information_schema.columns where table_schema = '${this.config.schema}'` } diff --git a/packages/worker/src/api/controllers/global/auth.js b/packages/worker/src/api/controllers/global/auth.js index 36db346dbd..5f7e656f7d 100644 --- a/packages/worker/src/api/controllers/global/auth.js +++ b/packages/worker/src/api/controllers/global/auth.js @@ -1,4 +1,5 @@ const authPkg = require("@budibase/auth") +const { getScopedConfig } = require("@budibase/auth/db") const { google } = require("@budibase/auth/src/middleware") const { oidc } = require("@budibase/auth/src/middleware") const { Configs, EmailTemplatePurpose } = require("../../../constants") @@ -21,17 +22,32 @@ const { } = require("@budibase/auth/tenancy") const env = require("../../../environment") -function googleCallbackUrl(config) { +const ssoCallbackUrl = async (config, type) => { // incase there is a callback URL from before if (config && config.callbackURL) { return config.callbackURL } + + const db = getGlobalDB() + const publicConfig = await getScopedConfig(db, { + type: Configs.SETTINGS, + }) + let callbackUrl = `/api/global/auth` if (isMultiTenant()) { callbackUrl += `/${getTenantId()}` } - callbackUrl += `/google/callback` - return callbackUrl + callbackUrl += `/${type}/callback` + + return `${publicConfig.platformUrl}${callbackUrl}` +} + +exports.googleCallbackUrl = async config => { + return ssoCallbackUrl(config, "google") +} + +exports.oidcCallbackUrl = async config => { + return ssoCallbackUrl(config, "oidc") } async function authInternal(ctx, user, err = null, info = null) { @@ -152,7 +168,7 @@ exports.googlePreAuth = async (ctx, next) => { type: Configs.GOOGLE, workspace: ctx.query.workspace, }) - let callbackUrl = googleCallbackUrl(config) + let callbackUrl = await exports.googleCallbackUrl(config) const strategy = await google.strategyFactory(config, callbackUrl) return passport.authenticate(strategy, { @@ -167,7 +183,7 @@ exports.googleAuth = async (ctx, next) => { type: Configs.GOOGLE, workspace: ctx.query.workspace, }) - const callbackUrl = googleCallbackUrl(config) + const callbackUrl = await exports.googleCallbackUrl(config) const strategy = await google.strategyFactory(config, callbackUrl) return passport.authenticate( @@ -189,13 +205,7 @@ async function oidcStrategyFactory(ctx, configId) { }) const chosenConfig = config.configs.filter(c => c.uuid === configId)[0] - - const protocol = env.NODE_ENV === "production" ? "https" : "http" - let callbackUrl = `${protocol}://${ctx.host}/api/global/auth` - if (isMultiTenant()) { - callbackUrl += `/${getTenantId()}` - } - callbackUrl += `/oidc/callback` + let callbackUrl = await exports.oidcCallbackUrl(chosenConfig) return oidc.strategyFactory(chosenConfig, callbackUrl) } diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index c0c300e4db..b6fcb9ee7d 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -9,8 +9,11 @@ const { Configs } = require("../../../constants") const email = require("../../../utilities/email") const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore const CouchDB = require("../../../db") -const { getGlobalDB } = require("@budibase/auth/tenancy") +const { getGlobalDB, getTenantId } = require("@budibase/auth/tenancy") const env = require("../../../environment") +const { googleCallbackUrl, oidcCallbackUrl } = require("./auth") + +const BB_TENANT_CDN = "https://tenants.cdn.budi.live" exports.save = async function (ctx) { const db = getGlobalDB() @@ -155,6 +158,10 @@ exports.publicSettings = async function (ctx) { config.config.google = false } + // callback urls + config.config.oidcCallbackUrl = await oidcCallbackUrl() + config.config.googleCallbackUrl = await googleCallbackUrl() + // oidc button flag if (oidcConfig && oidcConfig.config) { config.config.oidc = oidcConfig.config.configs[0].activated @@ -182,7 +189,13 @@ exports.upload = async function (ctx) { bucket = ObjectStoreBuckets.GLOBAL_CLOUD } - const key = `${type}/${name}` + let key + if (env.MULTI_TENANCY) { + key = `${getTenantId()}/${type}/${name}` + } else { + key = `${type}/${name}` + } + await upload({ bucket, filename: key, @@ -200,7 +213,13 @@ exports.upload = async function (ctx) { config: {}, } } - const url = `/${bucket}/${key}` + let url + if (env.SELF_HOSTED) { + url = `/${bucket}/${key}` + } else { + url = `${BB_TENANT_CDN}/${key}` + } + cfgStructure.config[`${name}`] = url // write back to db with url updated await db.put(cfgStructure) diff --git a/packages/worker/src/api/routes/tests/auth.spec.js b/packages/worker/src/api/routes/tests/auth.spec.js index bb66c547c4..13a93f9cfb 100644 --- a/packages/worker/src/api/routes/tests/auth.spec.js +++ b/packages/worker/src/api/routes/tests/auth.spec.js @@ -76,7 +76,7 @@ describe("/api/global/auth", () => { afterEach(() => { expect(strategyFactory).toBeCalledWith( chosenConfig, - `http://127.0.0.1:4003/api/global/auth/${TENANT_ID}/oidc/callback` // calculated url + `http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback` ) }) diff --git a/packages/worker/src/utilities/templates.js b/packages/worker/src/utilities/templates.js index aa06ede3ba..f261d9ca33 100644 --- a/packages/worker/src/utilities/templates.js +++ b/packages/worker/src/utilities/templates.js @@ -6,7 +6,6 @@ const { EmailTemplatePurpose, } = require("../constants") const { checkSlashesInUrl } = require("./index") -const env = require("../environment") const { getGlobalDB, addTenantToUrl } = require("@budibase/auth/tenancy") const BASE_COMPANY = "Budibase" @@ -14,9 +13,6 @@ exports.getSettingsTemplateContext = async (purpose, code = null) => { const db = getGlobalDB() // TODO: use more granular settings in the future if required let settings = (await getScopedConfig(db, { type: Configs.SETTINGS })) || {} - if (!settings || !settings.platformUrl) { - settings.platformUrl = env.PLATFORM_URL - } const URL = settings.platformUrl const context = { [InternalTemplateBindings.LOGO_URL]: From 3db35d3af99d06358b44ca9f5ede68696d1ac7dd Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 13:42:55 +0000 Subject: [PATCH 023/111] Enable data providers to use array and attachment fields as their source --- .../builder/src/builderStore/dataBinding.js | 27 ++- .../DataProviderSelect.svelte | 5 +- .../PropertyControls/DataSourceSelect.svelte | 181 +++++++++++------- packages/client/src/api/datasources.js | 20 ++ .../src/components/app/DataProvider.svelte | 11 +- 5 files changed, 164 insertions(+), 80 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 9cf00be3d4..d4d535a532 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -333,8 +333,11 @@ const getUrlBindings = asset => { */ export const getSchemaForDatasource = (asset, datasource, isForm = false) => { let schema, table + if (datasource) { const { type } = datasource + + // Determine the source table from the datasource type if (type === "provider") { const component = findComponent(asset.props, datasource.providerId) const source = getDatasourceForProvider(asset, component) @@ -342,11 +345,31 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => { } else if (type === "query") { const queries = get(queriesStores).list table = queries.find(query => query._id === datasource._id) + } else if (type === "field") { + const { fieldType } = datasource + if (fieldType === "attachment") { + schema = { + url: { + type: "string", + }, + name: { + type: "string", + }, + } + } else if (fieldType === "array") { + schema = { + value: { + type: "string", + }, + } + } } else { const tables = get(tablesStore).list table = tables.find(table => table._id === datasource.tableId) } - if (table) { + + // Determine the schema from the table if not already determined + if (table && !schema) { if (type === "view") { schema = cloneDeep(table.views?.[datasource.name]?.schema) } else if (type === "query" && isForm) { @@ -525,7 +548,7 @@ function bindingReplacement(bindableProperties, textWithBindings, convertTo) { * {{ literal [componentId] }} */ function extractLiteralHandlebarsID(value) { - return value?.match(/{{\s*literal[\s[]+([a-fA-F0-9]+)[\s\]]*}}/)?.[1] + return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1] } /** diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte index d27a542f47..979443a403 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte @@ -11,10 +11,7 @@ const getValue = component => `{{ literal ${makePropSafe(component._id)} }}` $: path = findComponentPath($currentAsset.props, $store.selectedComponentId) - $: providers = path.filter( - component => - component._component === "@budibase/standard-components/dataprovider" - ) + $: providers = path.filter(c => c._component?.endsWith("/dataprovider")) // Set initial value to closest data provider onMount(() => { diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte index d86f13e100..f12710e70d 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataSourceSelect.svelte @@ -20,16 +20,18 @@ import { notifications } from "@budibase/bbui" import ParameterBuilder from "components/integration/QueryParameterBuilder.svelte" import IntegrationQueryEditor from "components/integration/index.svelte" - - const dispatch = createEventDispatcher() - let anchorRight, dropdownRight - let drawer + import { makePropSafe as safe } from "@budibase/string-templates" export let value = {} export let otherSources export let showAllQueries export let bindings = [] + const dispatch = createEventDispatcher() + const arrayTypes = ["attachment", "array"] + let anchorRight, dropdownRight + let drawer + $: text = value?.label ?? "Choose an option" $: tables = $tablesStore.list.map(m => ({ label: m.name, @@ -54,8 +56,6 @@ name: query.name, tableId: query._id, ...query, - schema: query.schema, - parameters: query.parameters, type: "query", })) $: dataProviders = getDataProviderComponents( @@ -65,29 +65,40 @@ label: provider._instanceName, name: provider._instanceName, providerId: provider._id, - value: `{{ literal [${provider._id}] }}`, + value: `{{ literal ${safe(provider._id)} }}`, type: "provider", - schema: provider.schema, - })) - $: queryBindableProperties = bindings.map(property => ({ - ...property, - category: property.type === "instance" ? "Component" : "Table", - label: property.readableBinding, - path: property.readableBinding, })) $: links = bindings .filter(x => x.fieldSchema?.type === "link") - .map(property => { + .map(binding => { + const { providerId, readableBinding, fieldSchema } = binding || {} + const { name, tableId } = fieldSchema || {} + const safeProviderId = safe(providerId) return { - providerId: property.providerId, - label: property.readableBinding, - fieldName: property.fieldSchema.name, - tableId: property.fieldSchema.tableId, + providerId, + label: readableBinding, + fieldName: name, + tableId, type: "link", // These properties will be enriched by the client library and provide // details of the parent row of the relationship field, from context - rowId: `{{ ${property.providerId}._id }}`, - rowTableId: `{{ ${property.providerId}.tableId }}`, + rowId: `{{ ${safeProviderId}.${safe("_id")} }}`, + rowTableId: `{{ ${safeProviderId}.${safe("tableId")} }}`, + } + }) + $: fields = bindings + .filter(x => arrayTypes.includes(x.fieldSchema?.type)) + .map(binding => { + const { providerId, readableBinding, fieldSchema } = binding || {} + const { name, type, tableId } = fieldSchema || {} + return { + providerId, + label: readableBinding, + fieldName: name, + fieldType: type, + tableId, + type: "field", + value: `{{ literal ${safe(providerId)}.${safe(name)} }}`, } }) @@ -102,6 +113,14 @@ ).source return $integrations[source].query[query.queryVerb] } + + const getQueryParams = query => { + return $queriesStore.list.find(q => q._id === query?._id)?.parameters || [] + } + + const getQueryDatasource = query => { + return $datasources.list.find(ds => ds._id === query?.datasourceId) + }
@@ -127,11 +146,10 @@ - {#if value.parameters.length > 0} + {#if getQueryParams(value._id).length > 0} query._id === value._id) - .parameters} + parameters={getQueryParams(value)} {bindings} /> {/if} @@ -139,9 +157,7 @@ height={200} query={value} schema={fetchQueryDefinition(value)} - datasource={$datasources.list.find( - ds => ds._id === value.datasourceId - )} + datasource={getQueryDatasource(value)} editable={false} /> @@ -159,52 +175,71 @@
  • handleSelected(table)}>{table.label}
  • {/each} - -
    - Views -
    -
      - {#each views as view} -
    • handleSelected(view)}>{view.label}
    • - {/each} -
    - -
    - Relationships -
    -
      - {#each links as link} -
    • handleSelected(link)}>{link.label}
    • - {/each} -
    - -
    - Queries -
    -
      - {#each queries as query} -
    • handleSelected(query)} - > - {query.label} -
    • - {/each} -
    - -
    - Data Providers -
    -
      - {#each dataProviders as provider} -
    • handleSelected(provider)} - > - {provider.label} -
    • - {/each} -
    + {#if views?.length} + +
    + Views +
    +
      + {#each views as view} +
    • handleSelected(view)}>{view.label}
    • + {/each} +
    + {/if} + {#if queries?.length} + +
    + Queries +
    +
      + {#each queries as query} +
    • handleSelected(query)} + > + {query.label} +
    • + {/each} +
    + {/if} + {#if links?.length} + +
    + Relationships +
    +
      + {#each links as link} +
    • handleSelected(link)}>{link.label}
    • + {/each} +
    + {/if} + {#if fields?.length} + +
    + Fields +
    +
      + {#each fields as field} +
    • handleSelected(field)}>{field.label}
    • + {/each} +
    + {/if} + {#if dataProviders?.length} + +
    + Data Providers +
    +
      + {#each dataProviders as provider} +
    • handleSelected(provider)} + > + {provider.label} +
    • + {/each} +
    + {/if} {#if otherSources?.length}
    diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js index d2b05899cb..cea4ae0f3b 100644 --- a/packages/client/src/api/datasources.js +++ b/packages/client/src/api/datasources.js @@ -55,6 +55,26 @@ export const fetchDatasourceSchema = async dataSource => { return dataSource.value?.schema } + // Field sources will have their schema statically defined by the builder + if (type === "field") { + if (dataSource.fieldType === "attachment") { + return { + url: { + type: "string", + }, + name: { + type: "string", + }, + } + } else if (dataSource.fieldType === "array") { + return { + value: { + type: "string", + }, + } + } + } + // Tables, views and links can be fetched by table ID if ( (type === "table" || type === "view" || type === "link") && diff --git a/packages/client/src/components/app/DataProvider.svelte b/packages/client/src/components/app/DataProvider.svelte index a8e53f4906..0b9cbac4dd 100644 --- a/packages/client/src/components/app/DataProvider.svelte +++ b/packages/client/src/components/app/DataProvider.svelte @@ -183,7 +183,16 @@ } else if (dataSource?.type === "provider") { // For providers referencing another provider, just use the rows it // provides - allRows = dataSource?.value?.rows ?? [] + allRows = dataSource?.value?.rows || [] + } else if (dataSource?.type === "field") { + // Field sources will be available from context. + // Enrich non object elements into object to ensure a valid schema. + const data = dataSource?.value || [] + if (data[0] && typeof data[0] !== "object") { + allRows = data.map(value => ({ value })) + } else { + allRows = data + } } else { // For other data sources like queries or views, fetch all rows from the // server From aaa448cca89ee17c0a0d9f33c2c638734b637f19 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 14:48:53 +0000 Subject: [PATCH 024/111] Update settings bar to account for new block settings structure --- .../src/components/app/blocks/DataBlock.svelte | 0 .../src/components/preview/SettingsBar.svelte | 14 +++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/components/app/blocks/DataBlock.svelte diff --git a/packages/client/src/components/app/blocks/DataBlock.svelte b/packages/client/src/components/app/blocks/DataBlock.svelte new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/client/src/components/preview/SettingsBar.svelte b/packages/client/src/components/preview/SettingsBar.svelte index 43c77ef1e6..e1d88b1367 100644 --- a/packages/client/src/components/preview/SettingsBar.svelte +++ b/packages/client/src/components/preview/SettingsBar.svelte @@ -17,7 +17,19 @@ $: definition = $builderStore.selectedComponentDefinition $: showBar = definition?.showSettingsBar && !$builderStore.isDragging - $: settings = definition?.settings?.filter(setting => setting.showInBar) ?? [] + $: settings = getBarSettings(definition) + + const getBarSettings = definition => { + let allSettings = [] + definition?.settings?.forEach(setting => { + if (setting.section) { + allSettings = allSettings.concat(setting.settings || []) + } else { + allSettings.push(setting) + } + }) + return allSettings.filter(setting => setting.showInBar) + } const updatePosition = () => { if (!showBar) { From c4bbaa661707b1830f939880a0a5f3896c861bbd Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 15:18:55 +0000 Subject: [PATCH 025/111] Allow blocks which take children to work with DND --- packages/client/src/components/preview/DNDHandler.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/preview/DNDHandler.svelte b/packages/client/src/components/preview/DNDHandler.svelte index 474df4a674..82828b1258 100644 --- a/packages/client/src/components/preview/DNDHandler.svelte +++ b/packages/client/src/components/preview/DNDHandler.svelte @@ -147,7 +147,7 @@ return } - const element = e.target.closest(".component") + const element = e.target.closest(".component:not(.block)") if ( element && element.classList.contains("droppable") && From df455c2719a1c88701e70a8e37ce0041a28cdf1b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 15:19:25 +0000 Subject: [PATCH 026/111] Add data block component --- .../builder/src/builderStore/dataBinding.js | 1 + .../design/AppPreview/componentStructure.json | 3 +- packages/client/manifest.json | 198 ++++++++++++++++++ .../src/components/BlockComponent.svelte | 2 +- .../client/src/components/Component.svelte | 7 +- .../components/app/blocks/DataBlock.svelte | 60 ++++++ .../client/src/components/app/blocks/index.js | 1 + 7 files changed, 267 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index d4d535a532..e63e922f4f 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -346,6 +346,7 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => { const queries = get(queriesStores).list table = queries.find(query => query._id === datasource._id) } else if (type === "field") { + table = { name: datasource.fieldName } const { fieldType } = datasource if (fieldType === "attachment") { schema = { diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index c4fd33b084..c602000a03 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -4,7 +4,8 @@ "icon": "Article", "children": [ "tableblock", - "cardsblock" + "cardsblock", + "datablock" ] }, "section", diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 4a50a11fce..eec7b82c5c 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2932,5 +2932,203 @@ "type": "schema", "suffix": "repeater" } + }, + "datablock": { + "name": "Data block", + "icon": "ViewList", + "illegalChildren": ["section"], + "hasChildren": true, + "showSettingsBar": true, + "settings": [ + { + "type": "dataSource", + "label": "Data", + "key": "dataSource" + }, + { + "type": "filter", + "label": "Filtering", + "key": "filter" + }, + { + "type": "field", + "label": "Sort Column", + "key": "sortColumn" + }, + { + "type": "select", + "label": "Sort Order", + "key": "sortOrder", + "options": ["Ascending", "Descending"], + "defaultValue": "Descending" + }, + { + "type": "number", + "label": "Limit", + "key": "limit", + "defaultValue": 10 + }, + { + "type": "boolean", + "label": "Paginate", + "key": "paginate" + }, + { + "section": true, + "name": "Layout settings", + "settings": [ + { + "type": "text", + "label": "Empty Text", + "key": "noRowsMessage", + "defaultValue": "No rows found" + }, + { + "type": "select", + "label": "Direction", + "key": "direction", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Column", + "value": "column", + "barIcon": "ViewRow", + "barTitle": "Column layout" + }, + { + "label": "Row", + "value": "row", + "barIcon": "ViewColumn", + "barTitle": "Row layout" + } + ], + "defaultValue": "column" + }, + { + "type": "select", + "label": "Horiz. Align", + "key": "hAlign", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Left", + "value": "left", + "barIcon": "AlignLeft", + "barTitle": "Align left" + }, + { + "label": "Center", + "value": "center", + "barIcon": "AlignCenter", + "barTitle": "Align center" + }, + { + "label": "Right", + "value": "right", + "barIcon": "AlignRight", + "barTitle": "Align right" + }, + { + "label": "Stretch", + "value": "stretch", + "barIcon": "MoveLeftRight", + "barTitle": "Align stretched horizontally" + } + ], + "defaultValue": "stretch" + }, + { + "type": "select", + "label": "Vert. Align", + "key": "vAlign", + "showInBar": true, + "barStyle": "buttons", + "options": [ + { + "label": "Top", + "value": "top", + "barIcon": "AlignTop", + "barTitle": "Align top" + }, + { + "label": "Middle", + "value": "middle", + "barIcon": "AlignMiddle", + "barTitle": "Align middle" + }, + { + "label": "Bottom", + "value": "bottom", + "barIcon": "AlignBottom", + "barTitle": "Align bottom" + }, + { + "label": "Stretch", + "value": "stretch", + "barIcon": "MoveUpDown", + "barTitle": "Align stretched vertically" + } + ], + "defaultValue": "top" + }, + { + "type": "select", + "label": "Gap", + "key": "gap", + "showInBar": true, + "barStyle": "picker", + "options": [ + { + "label": "None", + "value": "N" + }, + { + "label": "Small", + "value": "S" + }, + { + "label": "Medium", + "value": "M" + }, + { + "label": "Large", + "value": "L" + } + ], + "defaultValue": "M" + } + ] + } + ], + "context": [ + { + "type": "static", + "suffix": "provider", + "values": [ + { + "label": "Rows", + "key": "rows" + }, + { + "label": "Rows Length", + "key": "rowsLength" + }, + { + "label": "Schema", + "key": "schema" + }, + { + "label": "Page Number", + "key": "pageNumber" + } + ] + }, + { + "type": "schema", + "suffix": "repeater" + } + ] } } diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index 589998994d..c23f18f55c 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -30,6 +30,6 @@ } - + diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 346de98f2f..443691595e 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -17,6 +17,7 @@ export let instance = {} export let isLayout = false export let isScreen = false + export let isBlock = false // The enriched component settings let enrichedSettings @@ -44,7 +45,6 @@ // Get contexts const context = getContext("context") const insideScreenslot = !!getContext("screenslot") - const insideBlock = !!getContext("block") // Create component context const componentStore = writable({}) @@ -69,7 +69,7 @@ $: interactive = $builderStore.inBuilder && ($builderStore.previewType === "layout" || insideScreenslot) && - !insideBlock + !isBlock $: draggable = interactive && !isLayout && !isScreen $: droppable = interactive && !isLayout && !isScreen @@ -262,6 +262,7 @@ class:droppable class:empty class:interactive + class:block={isBlock} data-id={id} data-name={name} > @@ -272,7 +273,7 @@ {/each} {:else if emptyState} - {:else if insideBlock} + {:else if isBlock} {/if} diff --git a/packages/client/src/components/app/blocks/DataBlock.svelte b/packages/client/src/components/app/blocks/DataBlock.svelte index e69de29bb2..935de48cbc 100644 --- a/packages/client/src/components/app/blocks/DataBlock.svelte +++ b/packages/client/src/components/app/blocks/DataBlock.svelte @@ -0,0 +1,60 @@ + + + +
    + + {#if $component.empty} + + {:else} + + + + {/if} + +
    +
    diff --git a/packages/client/src/components/app/blocks/index.js b/packages/client/src/components/app/blocks/index.js index af238b901d..56d2fcbd01 100644 --- a/packages/client/src/components/app/blocks/index.js +++ b/packages/client/src/components/app/blocks/index.js @@ -1,2 +1,3 @@ export { default as tableblock } from "./TableBlock.svelte" export { default as cardsblock } from "./CardsBlock.svelte" +export { default as datablock } from "./DataBlock.svelte" From b7b8231c5147e5b829c1ddd36c5f60d147db8651 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 15:27:42 +0000 Subject: [PATCH 027/111] Clafify comments --- packages/client/src/api/datasources.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/api/datasources.js b/packages/client/src/api/datasources.js index cea4ae0f3b..981d8301ca 100644 --- a/packages/client/src/api/datasources.js +++ b/packages/client/src/api/datasources.js @@ -55,7 +55,7 @@ export const fetchDatasourceSchema = async dataSource => { return dataSource.value?.schema } - // Field sources will have their schema statically defined by the builder + // Field sources have their schema statically defined if (type === "field") { if (dataSource.fieldType === "attachment") { return { From c922f4a7b9343ed2108a13374991cd075943bcfa Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 12 Nov 2021 15:28:08 +0000 Subject: [PATCH 028/111] Replace manual usage of square brackets with string-templates makePropSafe util --- packages/client/src/components/app/blocks/CardsBlock.svelte | 5 +++-- packages/client/src/components/app/blocks/DataBlock.svelte | 3 ++- packages/client/src/components/app/blocks/TableBlock.svelte | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index 9eccb2b2d1..1a8df189af 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -3,6 +3,7 @@ import Block from "components/Block.svelte" import BlockComponent from "components/BlockComponent.svelte" import { Heading } from "@budibase/bbui" + import { makePropSafe as safe } from "@budibase/string-templates" export let title export let dataSource @@ -103,7 +104,7 @@ } const col = linkColumn || "_id" const split = url.split("/:") - return `${split[0]}/{{ [${repeaterId}].[${col}] }}` + return `${split[0]}/{{ ${safe(repeaterId)}.${safe(col)} }}` } // Load the datasource schema on mount so we can determine column types @@ -171,7 +172,7 @@ bind:id={repeaterId} context="repeater" props={{ - dataProvider: `{{ literal [${dataProviderId}] }}`, + dataProvider: `{{ literal ${safe(dataProviderId)} }}`, direction: "row", hAlign: "stretch", vAlign: "top", diff --git a/packages/client/src/components/app/blocks/DataBlock.svelte b/packages/client/src/components/app/blocks/DataBlock.svelte index 935de48cbc..413d1df65e 100644 --- a/packages/client/src/components/app/blocks/DataBlock.svelte +++ b/packages/client/src/components/app/blocks/DataBlock.svelte @@ -3,6 +3,7 @@ import Block from "components/Block.svelte" import Placeholder from "components/app/Placeholder.svelte" import { getContext } from "svelte" + import { makePropSafe as safe } from "@budibase/string-templates" export let dataSource export let filter @@ -44,7 +45,7 @@ type="repeater" context="repeater" props={{ - dataProvider: `{{ literal [${providerId}] }}`, + dataProvider: `{{ literal ${safe(providerId)} }}`, noRowsMessage, direction, hAlign, diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index ac2a36adc7..255cbf44cf 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -3,6 +3,7 @@ import Block from "components/Block.svelte" import BlockComponent from "components/BlockComponent.svelte" import { Heading } from "@budibase/bbui" + import { makePropSafe as safe } from "@budibase/string-templates" export let title export let dataSource @@ -61,7 +62,7 @@ operator: column.type === "string" ? "string" : "equal", type: "string", valueType: "Binding", - value: `{{ [${formId}].[${column.name}] }}`, + value: `{{ ${safe(formId)}.${safe(column.name)} }}`, }) }) return enrichedFilter @@ -147,7 +148,7 @@ Date: Fri, 12 Nov 2021 18:26:57 +0000 Subject: [PATCH 029/111] Adding all required controls for data import to internal tables, just need to implement external table data import. --- .../bbui/src/InlineAlert/InlineAlert.svelte | 1 + .../backend/DataTable/DataTable.svelte | 5 ++ .../DataTable/buttons/ExportButton.svelte | 2 +- .../DataTable/buttons/ImportButton.svelte | 15 +++++ .../DataTable/modals/ImportModal.svelte | 36 +++++++++++ .../TableNavigator/TableDataImport.svelte | 26 ++++++-- .../src/api/controllers/table/external.js | 5 ++ .../server/src/api/controllers/table/index.js | 22 ++++++- .../src/api/controllers/table/internal.js | 14 +++- .../server/src/api/controllers/table/utils.js | 64 ++++++++++--------- packages/server/src/api/routes/table.js | 11 ++++ packages/server/src/utilities/csvParser.js | 48 ++++++++++++-- 12 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 packages/builder/src/components/backend/DataTable/buttons/ImportButton.svelte create mode 100644 packages/builder/src/components/backend/DataTable/modals/ImportModal.svelte diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte index a7740a68a7..a3c400e148 100644 --- a/packages/bbui/src/InlineAlert/InlineAlert.svelte +++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte @@ -47,5 +47,6 @@ --spectrum-semantic-positive-border-color: #2d9d78; --spectrum-semantic-positive-icon-color: #2d9d78; --spectrum-semantic-negative-icon-color: #e34850; + min-width: 150px !important; } diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 6bebf2ca02..27b1b54373 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -6,6 +6,7 @@ import CreateViewButton from "./buttons/CreateViewButton.svelte" import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte" import ExportButton from "./buttons/ExportButton.svelte" + import ImportButton from "./buttons/ImportButton.svelte" import EditRolesButton from "./buttons/EditRolesButton.svelte" import ManageAccessButton from "./buttons/ManageAccessButton.svelte" import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte" @@ -124,6 +125,10 @@ + {#key id} {/key} diff --git a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte index 7a76ba0820..d8502c7d3e 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte @@ -7,7 +7,7 @@ let modal - + Export diff --git a/packages/builder/src/components/backend/DataTable/buttons/ImportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ImportButton.svelte new file mode 100644 index 0000000000..6b9c3dd6dd --- /dev/null +++ b/packages/builder/src/components/backend/DataTable/buttons/ImportButton.svelte @@ -0,0 +1,15 @@ + + + + Import + + + + diff --git a/packages/builder/src/components/backend/DataTable/modals/ImportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ImportModal.svelte new file mode 100644 index 0000000000..1274a783e2 --- /dev/null +++ b/packages/builder/src/components/backend/DataTable/modals/ImportModal.svelte @@ -0,0 +1,36 @@ + + + + + + diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 0a59988da6..d42f795635 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -1,6 +1,5 @@
    (selectedNav = "Left")} class:unselected={selectedNav && selectedNav !== "Left"} > diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte index a9c72aa1ce..979c0a1a6e 100644 --- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte @@ -50,7 +50,7 @@ toggleScreenSelection(templates.find(t => t.id === blankScreen))} class:disabled={autoSelected} > -
    +
    Blank
    diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte index ff656001df..646f3ad303 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenNameModal.svelte @@ -1,19 +1,52 @@ modal.show()} onConfirm={() => navigationSelectionModal.show()} cancelText={"Back"} - disabled={!screenName} + disabled={!screenName || !url || routeError} > + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index c61e5e09c7..7fbb3ed7f6 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -34,6 +34,7 @@ let navigationSelectionModal let screenNameModal let screenName = "" + let url = "" let selectedScreens = [] // Hydrate state from URL params @@ -185,7 +186,7 @@ logo
    - Let's add some life to this screen + Let's add some life to this screen
    -
    - +
    + {/if} {:else if hasValidated}
    Date: Mon, 15 Nov 2021 14:43:16 +0000 Subject: [PATCH 041/111] v0.9.185-alpha.1 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index 36a68baf23..ae06964aad 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index e7a3f42698..ea69ba95f4 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 8ae4d4285e..ffda937df8 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 1459347ae8..a41a299c03 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.0", - "@budibase/client": "^0.9.185-alpha.0", + "@budibase/bbui": "^0.9.185-alpha.1", + "@budibase/client": "^0.9.185-alpha.1", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.185-alpha.0", + "@budibase/string-templates": "^0.9.185-alpha.1", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index ec83c5ae30..60004a0f4a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index eb15cbf847..5b767cfbf4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.0", + "@budibase/bbui": "^0.9.185-alpha.1", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.185-alpha.0", + "@budibase/string-templates": "^0.9.185-alpha.1", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index 780947a27f..cf38247b8e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -68,9 +68,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.0", - "@budibase/client": "^0.9.185-alpha.0", - "@budibase/string-templates": "^0.9.185-alpha.0", + "@budibase/auth": "^0.9.185-alpha.1", + "@budibase/client": "^0.9.185-alpha.1", + "@budibase/string-templates": "^0.9.185-alpha.1", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 03dab1089d..9359295d21 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 27cff2bae0..b87f6b3b3b 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.185-alpha.0", + "version": "0.9.185-alpha.1", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -29,8 +29,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.0", - "@budibase/string-templates": "^0.9.185-alpha.0", + "@budibase/auth": "^0.9.185-alpha.1", + "@budibase/string-templates": "^0.9.185-alpha.1", "@koa/router": "^8.0.0", "@sentry/node": "^6.0.0", "@techpass/passport-openidconnect": "^0.3.0", From ce47eb6cffa3de4a549a75b2c04215fc7c11d58f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 15 Nov 2021 16:42:17 +0000 Subject: [PATCH 042/111] Fixing issue found by Mitch, with syncing not functioning for singular role updates. --- packages/server/src/api/controllers/user.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 908018fe51..4461cbfe24 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -115,17 +115,17 @@ exports.syncUser = async function (ctx) { tableId: InternalTables.USER_METADATA, } } - let combined - if (deleting) { - combined = { - ...metadata, - status: UserStatus.INACTIVE, - metadata: BUILTIN_ROLE_IDS.PUBLIC, - } - } else { - combined = combineMetadataAndUser(user, metadata) + let combined = !deleting + ? combineMetadataAndUser(user, metadata) + : { + ...metadata, + status: UserStatus.INACTIVE, + metadata: BUILTIN_ROLE_IDS.PUBLIC, + } + // if its null then there was no updates required + if (combined) { + await db.put(combined) } - await db.put(combined) } } ctx.body = { From 0f5478707233d820f361a8ea0a95c81717f67d73 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 15 Nov 2021 16:46:56 +0000 Subject: [PATCH 043/111] Adding role, as it wasn't being set in metadata. --- packages/server/src/api/controllers/user.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 4461cbfe24..6dfaf9847d 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -97,6 +97,7 @@ exports.syncUser = async function (ctx) { .map(([appId]) => appId) } for (let prodAppId of prodAppIds) { + const roleId = roles[prodAppId] const devAppId = getDevelopmentAppID(prodAppId) for (let appId of [prodAppId, devAppId]) { if (!(await doesDatabaseExist(appId))) { @@ -115,6 +116,10 @@ exports.syncUser = async function (ctx) { tableId: InternalTables.USER_METADATA, } } + // assign the roleId for the metadata doc + if (roleId) { + metadata.roleId = roleId + } let combined = !deleting ? combineMetadataAndUser(user, metadata) : { From f13257bebe5fbfc699e535f300a468d26b46366e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 15 Nov 2021 17:40:45 +0000 Subject: [PATCH 044/111] Updating the getAllApps function to use a cached version of the app metadata, rather than retrieving it individually everytime. Also invalidating the results everytime they are updated (at least in the important locations). --- packages/auth/cache.js | 1 + packages/auth/src/cache/appMetadata.js | 35 +++++++++++++++++++ packages/auth/src/db/index.js | 4 +-- packages/auth/src/db/utils.js | 3 +- packages/auth/src/redis/authRedis.js | 10 +++++- packages/auth/src/redis/utils.js | 1 + .../server/src/api/controllers/application.js | 7 +++- .../src/api/controllers/deploy/index.js | 2 ++ packages/server/src/api/controllers/dev.js | 2 ++ packages/server/src/middleware/builder.js | 2 ++ packages/server/src/utilities/index.js | 1 + 11 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 packages/auth/src/cache/appMetadata.js diff --git a/packages/auth/cache.js b/packages/auth/cache.js index 48563a16f3..02344586a9 100644 --- a/packages/auth/cache.js +++ b/packages/auth/cache.js @@ -1,3 +1,4 @@ module.exports = { user: require("./src/cache/user"), + app: require("./src/cache/appMetadata"), } diff --git a/packages/auth/src/cache/appMetadata.js b/packages/auth/src/cache/appMetadata.js new file mode 100644 index 0000000000..e30a58770f --- /dev/null +++ b/packages/auth/src/cache/appMetadata.js @@ -0,0 +1,35 @@ +const redis = require("../redis/authRedis") +const { getDB } = require("../db") +const { DocumentTypes } = require("../db/constants") + +const EXPIRY_SECONDS = 3600 + +/** + * The default populate app metadata function + */ +const populateFromDB = async appId => { + return getDB(appId, { skip_setup: true }).get(DocumentTypes.APP_METADATA) +} + +/** + * Get the requested app metadata by id. + * Use redis cache to first read the app metadata. + * If not present fallback to loading the app metadata directly and re-caching. + * @param {*} appId the id of the app to get metadata from. + * @returns {object} the app metadata. + */ +exports.getAppMetadata = async appId => { + const client = await redis.getAppClient() + // try cache + let metadata = await client.get(appId) + if (!metadata) { + metadata = await populateFromDB(appId) + client.store(appId, metadata, EXPIRY_SECONDS) + } + return metadata +} + +exports.invalidateAppMetadata = async appId => { + const client = await redis.getAppClient() + await client.delete(appId) +} diff --git a/packages/auth/src/db/index.js b/packages/auth/src/db/index.js index 163364dbf3..94f5513f19 100644 --- a/packages/auth/src/db/index.js +++ b/packages/auth/src/db/index.js @@ -4,8 +4,8 @@ module.exports.setDB = pouch => { Pouch = pouch } -module.exports.getDB = dbName => { - return new Pouch(dbName) +module.exports.getDB = (dbName, opts = {}) => { + return new Pouch(dbName, opts) } module.exports.getCouch = () => { diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index b956089660..f8f36b17b3 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -6,6 +6,7 @@ const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants") const { getTenantId, getTenantIDFromAppID } = require("../tenancy") const fetch = require("node-fetch") const { getCouch } = require("./index") +const { getAppMetadata } = require("../cache/appMetadata") const UNICODE_MAX = "\ufff0" @@ -234,7 +235,7 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { } const appPromises = appDbNames.map(db => // skip setup otherwise databases could be re-created - new CouchDB(db, { skip_setup: true }).get(DocumentTypes.APP_METADATA) + getAppMetadata(db) ) if (appPromises.length === 0) { return [] diff --git a/packages/auth/src/redis/authRedis.js b/packages/auth/src/redis/authRedis.js index decce6763b..ca5c9bae37 100644 --- a/packages/auth/src/redis/authRedis.js +++ b/packages/auth/src/redis/authRedis.js @@ -1,16 +1,18 @@ const Client = require("./index") const utils = require("./utils") -let userClient, sessionClient +let userClient, sessionClient, appClient async function init() { userClient = await new Client(utils.Databases.USER_CACHE).init() sessionClient = await new Client(utils.Databases.SESSIONS).init() + appClient = await new Client(utils.Databases.APP_METADATA).init() } process.on("exit", async () => { if (userClient) await userClient.finish() if (sessionClient) await sessionClient.finish() + if (appClient) await appClient.finish() }) module.exports = { @@ -26,4 +28,10 @@ module.exports = { } return sessionClient }, + getAppClient: async () => { + if (!appClient) { + await init() + } + return appClient + }, } diff --git a/packages/auth/src/redis/utils.js b/packages/auth/src/redis/utils.js index 6befecd9ba..466b117e96 100644 --- a/packages/auth/src/redis/utils.js +++ b/packages/auth/src/redis/utils.js @@ -15,6 +15,7 @@ exports.Databases = { SESSIONS: "session", USER_CACHE: "users", FLAGS: "flags", + APP_METADATA: "appMetadata", } exports.SEPARATOR = SEPARATOR diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 99c16a975c..d38312af2f 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -45,6 +45,7 @@ const { } = require("../../utilities/fileSystem/clientLibrary") const { getTenantId, isMultiTenant } = require("@budibase/auth/tenancy") const { syncGlobalUsers } = require("./user") +const { app: appCache } = require("@budibase/auth/cache") const URL_REGEX_SLASH = /\/|\\/g @@ -319,6 +320,7 @@ exports.delete = async ctx => { } // make sure the app/role doesn't stick around after the app has been deleted await removeAppFromUserRoles(ctx, ctx.params.appId) + await appCache.invalidateAppMetadata(ctx.params.appId) ctx.status = 200 ctx.body = result @@ -387,7 +389,10 @@ const updateAppPackage = async (ctx, appPackage, appId) => { // Redis, shouldn't ever store it delete newAppPackage.lockedBy - return await db.put(newAppPackage) + const response = await db.put(newAppPackage) + // remove any cached metadata, so that it will be updated + await appCache.invalidateAppMetadata(appId) + return response } const createEmptyAppPackage = async (ctx, app) => { diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index 13002476fc..c1138f4b03 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -6,6 +6,7 @@ const { disableAllCrons, enableCronTrigger, } = require("../../../automations/utils") +const { app: appCache } = require("@budibase/auth/cache") // the max time we can wait for an invalidation to complete before considering it failed const MAX_PENDING_TIME_MS = 30 * 60000 @@ -103,6 +104,7 @@ async function deployApp(deployment) { appDoc.appId = productionAppId appDoc.instance._id = productionAppId await db.put(appDoc) + await appCache.invalidateAppMetadata(productionAppId) console.log("New app doc written successfully.") await initDeployedApp(productionAppId) console.log("Deployed app initialised, setting deployment to successful") diff --git a/packages/server/src/api/controllers/dev.js b/packages/server/src/api/controllers/dev.js index ed58b8048b..dbea82b06b 100644 --- a/packages/server/src/api/controllers/dev.js +++ b/packages/server/src/api/controllers/dev.js @@ -6,6 +6,7 @@ const { request } = require("../../utilities/workerRequests") const { clearLock } = require("../../utilities/redis") const { Replication } = require("@budibase/auth").db const { DocumentTypes } = require("../../db/utils") +const { app: appCache } = require("@budibase/auth/cache") async function redirect(ctx, method, path = "global") { const { devPath } = ctx.params @@ -106,6 +107,7 @@ exports.revert = async ctx => { appDoc.appId = appId appDoc.instance._id = appId await db.put(appDoc) + await appCache.invalidateAppMetadata(appId) ctx.body = { message: "Reverted changes successfully.", } diff --git a/packages/server/src/middleware/builder.js b/packages/server/src/middleware/builder.js index 8ea49a3b48..427e54287a 100644 --- a/packages/server/src/middleware/builder.js +++ b/packages/server/src/middleware/builder.js @@ -8,6 +8,7 @@ const { const CouchDB = require("../db") const { DocumentTypes } = require("../db/utils") const { PermissionTypes } = require("@budibase/auth/permissions") +const { app: appCache } = require("@budibase/auth/cache") const DEBOUNCE_TIME_SEC = 30 @@ -51,6 +52,7 @@ async function updateAppUpdatedAt(ctx) { const metadata = await db.get(DocumentTypes.APP_METADATA) metadata.updatedAt = new Date().toISOString() await db.put(metadata) + await appCache.invalidateAppMetadata(appId) // set a new debounce record with a short TTL await setDebounce(appId, DEBOUNCE_TIME_SEC) } diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index c2ebbcd9f1..eacf9708e2 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -48,6 +48,7 @@ exports.objectStoreUrl = () => { * via a specific endpoint (under /api/assets/client). * @param {string} appId In production we need the appId to look up the correct bucket, as the * version of the client lib may differ between apps. + * @param {string} version The version to retrieve. * @return {string} The URL to be inserted into appPackage response or server rendered * app index file. */ From 3ffa27969d66e177986c9fb1f5566af51f30627e Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Mon, 15 Nov 2021 17:47:27 +0000 Subject: [PATCH 045/111] v0.9.185-alpha.2 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index ae06964aad..ec9b932806 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index ea69ba95f4..d65ae4e453 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index ffda937df8..fd3770c206 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index a41a299c03..1743c22f12 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.1", - "@budibase/client": "^0.9.185-alpha.1", + "@budibase/bbui": "^0.9.185-alpha.2", + "@budibase/client": "^0.9.185-alpha.2", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.185-alpha.1", + "@budibase/string-templates": "^0.9.185-alpha.2", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 60004a0f4a..4cece77d7f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 5b767cfbf4..3e533d1857 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.1", + "@budibase/bbui": "^0.9.185-alpha.2", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.185-alpha.1", + "@budibase/string-templates": "^0.9.185-alpha.2", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index cf38247b8e..4864820e7b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -68,9 +68,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.1", - "@budibase/client": "^0.9.185-alpha.1", - "@budibase/string-templates": "^0.9.185-alpha.1", + "@budibase/auth": "^0.9.185-alpha.2", + "@budibase/client": "^0.9.185-alpha.2", + "@budibase/string-templates": "^0.9.185-alpha.2", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 9359295d21..7df9a74bf6 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index b87f6b3b3b..e2f21fc65e 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.185-alpha.1", + "version": "0.9.185-alpha.2", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -29,8 +29,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.1", - "@budibase/string-templates": "^0.9.185-alpha.1", + "@budibase/auth": "^0.9.185-alpha.2", + "@budibase/string-templates": "^0.9.185-alpha.2", "@koa/router": "^8.0.0", "@sentry/node": "^6.0.0", "@techpass/passport-openidconnect": "^0.3.0", From cc4702909cd4f1dece7d5c4836a5b57d6a144291 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 15 Nov 2021 18:02:24 +0000 Subject: [PATCH 046/111] Remove unused prop --- .../PropertiesPanel/PropertyControls/PropertyControl.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte index 72b54f3b96..911688b30c 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/PropertyControl.svelte @@ -6,7 +6,6 @@ } from "builderStore/dataBinding" export let label = "" - export let bindable = true export let componentInstance = {} export let control = null export let key = "" From 7a1b22ef5f3485e9a4b33a33c7af165d60e00c1a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 15 Nov 2021 18:07:51 +0000 Subject: [PATCH 047/111] Rename data block to repeater block --- .../src/components/design/AppPreview/componentStructure.json | 2 +- packages/client/manifest.json | 4 ++-- .../app/blocks/{DataBlock.svelte => RepeaterBlock.svelte} | 0 packages/client/src/components/app/blocks/index.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/client/src/components/app/blocks/{DataBlock.svelte => RepeaterBlock.svelte} (100%) diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index c602000a03..357ea5a7be 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -5,7 +5,7 @@ "children": [ "tableblock", "cardsblock", - "datablock" + "repeaterblock" ] }, "section", diff --git a/packages/client/manifest.json b/packages/client/manifest.json index eec7b82c5c..125dbb8db3 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2933,8 +2933,8 @@ "suffix": "repeater" } }, - "datablock": { - "name": "Data block", + "repeaterblock": { + "name": "Repeater block", "icon": "ViewList", "illegalChildren": ["section"], "hasChildren": true, diff --git a/packages/client/src/components/app/blocks/DataBlock.svelte b/packages/client/src/components/app/blocks/RepeaterBlock.svelte similarity index 100% rename from packages/client/src/components/app/blocks/DataBlock.svelte rename to packages/client/src/components/app/blocks/RepeaterBlock.svelte diff --git a/packages/client/src/components/app/blocks/index.js b/packages/client/src/components/app/blocks/index.js index 56d2fcbd01..db4de8fc13 100644 --- a/packages/client/src/components/app/blocks/index.js +++ b/packages/client/src/components/app/blocks/index.js @@ -1,3 +1,3 @@ export { default as tableblock } from "./TableBlock.svelte" export { default as cardsblock } from "./CardsBlock.svelte" -export { default as datablock } from "./DataBlock.svelte" +export { default as repeaterblock } from "./RepeaterBlock.svelte" From 5470b77fb3ca42af756ae84e12c48bb27158829c Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Mon, 15 Nov 2021 19:34:08 +0000 Subject: [PATCH 048/111] Fixing issue presented by test, passing Couch instance around for when it is being used in memory. --- packages/auth/src/cache/appMetadata.js | 14 +++++++++----- packages/auth/src/db/index.js | 4 ++-- packages/auth/src/db/utils.js | 9 ++++++--- .../worker/src/api/controllers/global/users.js | 6 +----- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/auth/src/cache/appMetadata.js b/packages/auth/src/cache/appMetadata.js index e30a58770f..e3758b349a 100644 --- a/packages/auth/src/cache/appMetadata.js +++ b/packages/auth/src/cache/appMetadata.js @@ -1,5 +1,5 @@ const redis = require("../redis/authRedis") -const { getDB } = require("../db") +const { getCouch } = require("../db") const { DocumentTypes } = require("../db/constants") const EXPIRY_SECONDS = 3600 @@ -7,8 +7,12 @@ const EXPIRY_SECONDS = 3600 /** * The default populate app metadata function */ -const populateFromDB = async appId => { - return getDB(appId, { skip_setup: true }).get(DocumentTypes.APP_METADATA) +const populateFromDB = async (appId, CouchDB = null) => { + if (!CouchDB) { + CouchDB = getCouch() + } + const db = new CouchDB(appId, { skip_setup: true }) + return db.get(DocumentTypes.APP_METADATA) } /** @@ -18,12 +22,12 @@ const populateFromDB = async appId => { * @param {*} appId the id of the app to get metadata from. * @returns {object} the app metadata. */ -exports.getAppMetadata = async appId => { +exports.getAppMetadata = async (appId, CouchDB = null) => { const client = await redis.getAppClient() // try cache let metadata = await client.get(appId) if (!metadata) { - metadata = await populateFromDB(appId) + metadata = await populateFromDB(appId, CouchDB) client.store(appId, metadata, EXPIRY_SECONDS) } return metadata diff --git a/packages/auth/src/db/index.js b/packages/auth/src/db/index.js index 94f5513f19..163364dbf3 100644 --- a/packages/auth/src/db/index.js +++ b/packages/auth/src/db/index.js @@ -4,8 +4,8 @@ module.exports.setDB = pouch => { Pouch = pouch } -module.exports.getDB = (dbName, opts = {}) => { - return new Pouch(dbName, opts) +module.exports.getDB = dbName => { + return new Pouch(dbName) } module.exports.getCouch = () => { diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index f8f36b17b3..11ce51fae9 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -54,6 +54,9 @@ exports.isProdAppID = appId => { } function isDevApp(app) { + if (!app) { + return false + } return exports.isDevAppID(app.appId) } @@ -233,16 +236,16 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { if (idsOnly) { return appDbNames } - const appPromises = appDbNames.map(db => + const appPromises = appDbNames.map(app => // skip setup otherwise databases could be re-created - getAppMetadata(db) + getAppMetadata(app, CouchDB) ) if (appPromises.length === 0) { return [] } else { const response = await Promise.allSettled(appPromises) const apps = response - .filter(result => result.status === "fulfilled") + .filter(result => result.status === "fulfilled" && result.value != null) .map(({ value }) => value) if (!all) { return apps.filter(app => { diff --git a/packages/worker/src/api/controllers/global/users.js b/packages/worker/src/api/controllers/global/users.js index 42166faad7..87194a7ceb 100644 --- a/packages/worker/src/api/controllers/global/users.js +++ b/packages/worker/src/api/controllers/global/users.js @@ -43,11 +43,7 @@ exports.save = async ctx => { } const parseBooleanParam = param => { - if (param && param === "false") { - return false - } else { - return true - } + return !(param && param === "false") } exports.adminUser = async ctx => { From 289c1325f87d10564d662c0d181d045637337606 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 16 Nov 2021 10:30:37 +0000 Subject: [PATCH 049/111] Adding specific error cases to all app ID checking functions - three cases, is dev/prod, isn't and no app/ID provided. --- packages/auth/src/db/utils.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 11ce51fae9..14e80df735 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -8,6 +8,8 @@ const fetch = require("node-fetch") const { getCouch } = require("./index") const { getAppMetadata } = require("../cache/appMetadata") +const NO_APP_ERROR = "No app provided" + const UNICODE_MAX = "\ufff0" exports.ViewNames = { @@ -46,16 +48,22 @@ function getDocParams(docType, docId = null, otherProps = {}) { } exports.isDevAppID = appId => { + if (!appId) { + throw NO_APP_ERROR + } return appId.startsWith(exports.APP_DEV_PREFIX) } exports.isProdAppID = appId => { + if (!appId) { + throw NO_APP_ERROR + } return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId) } function isDevApp(app) { if (!app) { - return false + throw NO_APP_ERROR } return exports.isDevAppID(app.appId) } From bb02491f41ea8b8008b887a0123f06e038acf3e3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 10:52:43 +0000 Subject: [PATCH 050/111] Reset app name in deletion confirmation modal when closing modal --- packages/builder/src/pages/builder/portal/apps/index.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index 53ad661f72..c238356915 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -165,6 +165,7 @@ notifications.error(`Error deleting app: ${err}`) } selectedApp = null + appName = null } const updateApp = async app => { @@ -298,6 +299,7 @@ title="Confirm deletion" okText="Delete app" onOk={confirmDeleteApp} + onCancel={() => (appName = null)} disabled={appName !== selectedApp?.name} > Are you sure you want to delete the app {selectedApp?.name}? From 3d2447db344e2fd2235fdddf045e746c3b707a13 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 16 Nov 2021 11:12:03 +0000 Subject: [PATCH 051/111] v0.9.185-alpha.3 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index ec9b932806..0a85d4fca7 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index d65ae4e453..faf037f422 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index fd3770c206..fa1138d1f8 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 1743c22f12..5ee8d051c6 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.2", - "@budibase/client": "^0.9.185-alpha.2", + "@budibase/bbui": "^0.9.185-alpha.3", + "@budibase/client": "^0.9.185-alpha.3", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.185-alpha.2", + "@budibase/string-templates": "^0.9.185-alpha.3", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 4cece77d7f..867c1c7846 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 3e533d1857..11733c2d6e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.2", + "@budibase/bbui": "^0.9.185-alpha.3", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.185-alpha.2", + "@budibase/string-templates": "^0.9.185-alpha.3", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index 4864820e7b..c24a5ff29c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -68,9 +68,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.2", - "@budibase/client": "^0.9.185-alpha.2", - "@budibase/string-templates": "^0.9.185-alpha.2", + "@budibase/auth": "^0.9.185-alpha.3", + "@budibase/client": "^0.9.185-alpha.3", + "@budibase/string-templates": "^0.9.185-alpha.3", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 7df9a74bf6..128b98118b 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index e2f21fc65e..9a660e78c7 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.185-alpha.2", + "version": "0.9.185-alpha.3", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -29,8 +29,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.2", - "@budibase/string-templates": "^0.9.185-alpha.2", + "@budibase/auth": "^0.9.185-alpha.3", + "@budibase/string-templates": "^0.9.185-alpha.3", "@koa/router": "^8.0.0", "@sentry/node": "^6.0.0", "@techpass/passport-openidconnect": "^0.3.0", From 0bac3765b348c86c494eba26854eac81cb07a0a6 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 11:45:28 +0000 Subject: [PATCH 052/111] Remove deprecated keydown event being sent by client library --- .../src/components/design/AppPreview/iframeTemplate.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/builder/src/components/design/AppPreview/iframeTemplate.js b/packages/builder/src/components/design/AppPreview/iframeTemplate.js index 372eac24f7..05192fa4d3 100644 --- a/packages/builder/src/components/design/AppPreview/iframeTemplate.js +++ b/packages/builder/src/components/design/AppPreview/iframeTemplate.js @@ -94,10 +94,6 @@ export default ` } window.addEventListener("message", receiveMessage) - window.addEventListener("keydown", evt => { - window.parent.postMessage({ type: "keydown", key: event.key }) - }) - window.parent.postMessage({ type: "ready" }) From 07e455257c2772240767322373b6577ed85c0e03 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 11:46:46 +0000 Subject: [PATCH 053/111] Prevent dragging of a component while being edited --- packages/client/src/components/Component.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 5543747e77..5ea7a5ea84 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -70,10 +70,10 @@ $builderStore.inBuilder && ($builderStore.previewType === "layout" || insideScreenslot) && !isBlock - $: draggable = interactive && !isLayout && !isScreen - $: droppable = interactive && !isLayout && !isScreen $: editable = definition.editable $: editing = editable && selected && $builderStore.editMode + $: draggable = !editing && interactive && !isLayout && !isScreen + $: droppable = interactive && !isLayout && !isScreen // Empty components are those which accept children but do not have any. // Empty states can be shown for these components, but can be disabled From 96fdcb6d931fbee238760f1e196b73d2d50a51c2 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 16 Nov 2021 11:47:03 +0000 Subject: [PATCH 054/111] Remove focus style on contenteditable components in chrome --- packages/client/src/components/CustomThemeWrapper.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/client/src/components/CustomThemeWrapper.svelte b/packages/client/src/components/CustomThemeWrapper.svelte index 9b466bb7d7..c656b09306 100644 --- a/packages/client/src/components/CustomThemeWrapper.svelte +++ b/packages/client/src/components/CustomThemeWrapper.svelte @@ -79,4 +79,9 @@ scrollbar-color: var(--spectrum-global-color-gray-400) var(--spectrum-alias-background-color-default); } + + /* Remove border when editing contenteditable components */ + :global(*[contenteditable="true"]:focus) { + outline: none; + } From 9650cee4d4836c02a4d47c7b358e04cf6798d4fc Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 16 Nov 2021 11:48:38 +0000 Subject: [PATCH 055/111] v0.9.185-alpha.4 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 8 ++++---- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lerna.json b/lerna.json index 0a85d4fca7..798d1e9997 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index faf037f422..4190d3490e 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index fa1138d1f8..adbc89b1ea 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 5ee8d051c6..8374ffb64f 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.3", - "@budibase/client": "^0.9.185-alpha.3", + "@budibase/bbui": "^0.9.185-alpha.4", + "@budibase/client": "^0.9.185-alpha.4", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.185-alpha.3", + "@budibase/string-templates": "^0.9.185-alpha.4", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 867c1c7846..4b47d909e2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index 11733c2d6e..6998701221 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "^0.9.185-alpha.3", + "@budibase/bbui": "^0.9.185-alpha.4", "@budibase/standard-components": "^0.9.139", - "@budibase/string-templates": "^0.9.185-alpha.3", + "@budibase/string-templates": "^0.9.185-alpha.4", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" diff --git a/packages/server/package.json b/packages/server/package.json index c24a5ff29c..20ae310846 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "description": "Budibase Web Server", "main": "src/index.js", "repository": { @@ -68,9 +68,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.3", - "@budibase/client": "^0.9.185-alpha.3", - "@budibase/string-templates": "^0.9.185-alpha.3", + "@budibase/auth": "^0.9.185-alpha.4", + "@budibase/client": "^0.9.185-alpha.4", + "@budibase/string-templates": "^0.9.185-alpha.4", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 128b98118b..f0c6fffbcd 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 9a660e78c7..78acd5f05c 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.185-alpha.3", + "version": "0.9.185-alpha.4", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -29,8 +29,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.185-alpha.3", - "@budibase/string-templates": "^0.9.185-alpha.3", + "@budibase/auth": "^0.9.185-alpha.4", + "@budibase/string-templates": "^0.9.185-alpha.4", "@koa/router": "^8.0.0", "@sentry/node": "^6.0.0", "@techpass/passport-openidconnect": "^0.3.0", From f3cddaaf080583727d68ff450842faa3fdddc1d7 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Tue, 16 Nov 2021 12:18:13 +0000 Subject: [PATCH 056/111] refactor wizard into its own file --- .../FrontendNavigatePane.svelte | 6 ++- .../NavigationSelectionModal.svelte | 4 +- .../NavigationPanel/NewScreenModal.svelte | 7 ++- .../NavigationPanel/ScreenNameModal.svelte | 2 +- .../NavigationPanel/ScreenWizard.svelte | 43 +++++++++++++++++++ .../design/[assetType]/_layout.svelte | 42 +++--------------- 6 files changed, 59 insertions(+), 45 deletions(-) create mode 100644 packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte diff --git a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte index 92a20a4acc..51dd44026e 100644 --- a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte +++ b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte @@ -13,6 +13,8 @@ import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte" import { Icon, Modal, Select, Search, Tabs, Tab } from "@budibase/bbui" + export let showModal + const tabs = [ { title: "Screens", @@ -24,7 +26,7 @@ }, ] - export let modal + let modal $: selected = tabs.find(t => t.key === $params.assetType)?.title || "Screens" const navigate = ({ detail }) => { @@ -98,7 +100,7 @@
    - +
    diff --git a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte index d62363827b..ec3d11683e 100644 --- a/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NavigationSelectionModal.svelte @@ -17,12 +17,12 @@ let selectedNav let createdScreens = [] $: { - selectedScreens.forEach(screen => { + selectedScreens?.forEach(screen => { createdScreens = [...createdScreens, screen.create()] }) } - $: blankSelected = selectedScreens.find(x => x.id === "createFromScratch") + $: blankSelected = selectedScreens.length === 1 const save = async screens => { for (let screen of screens) { diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte index 979c0a1a6e..297f6c1d02 100644 --- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte @@ -9,7 +9,7 @@ export let selectedScreens = [] const blankScreen = "createFromScratch" - $: blankSelected = selectedScreens.find(x => x.id === blankScreen) + $: blankSelected = selectedScreens.length === 1 $: autoSelected = selectedScreens.length > 0 && !blankSelected $: templates = getTemplates($store, $tables.list) @@ -19,7 +19,7 @@ screen => !screen.name.includes(table.name) ) } else { - const templates = getTemplates($store, $tables.list).filter(template => + const templates = templates.filter(template => template.name.includes(table.name) ) selectedScreens = [...templates, ...selectedScreens] @@ -41,7 +41,7 @@ Autogenerated screens come with CRUD functionality. - + Blank screen
    modal.show()} onConfirm={() => navigationSelectionModal.show()} diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte new file mode 100644 index 0000000000..cfe908894b --- /dev/null +++ b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index 7fbb3ed7f6..7844357288 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -5,7 +5,7 @@ selectedComponent, allScreens, } from "builderStore" - import { Detail, Layout, Button, Modal, Icon } from "@budibase/bbui" + import { Detail, Layout, Button, Icon } from "@budibase/bbui" import CurrentItemPreview from "components/design/AppPreview" import PropertiesPanel from "components/design/PropertiesPanel/PropertiesPanel.svelte" @@ -18,10 +18,8 @@ import AppThemeSelect from "components/design/AppPreview/AppThemeSelect.svelte" import ThemeEditor from "components/design/AppPreview/ThemeEditor.svelte" import DevicePreviewSelect from "components/design/AppPreview/DevicePreviewSelect.svelte" - import NavigationSelectionModal from "components/design/NavigationPanel/NavigationSelectionModal.svelte" - import ScreenNameModal from "components/design/NavigationPanel/ScreenNameModal.svelte" - import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" import Logo from "assets/bb-space-man.svg" + import ScreenWizard from "components/design/NavigationPanel/ScreenWizard.svelte" // Cache previous values so we don't update the URL more than necessary let previousType @@ -30,12 +28,7 @@ let hydrationComplete = false // Manage the layout modal flow from here - let modal - let navigationSelectionModal - let screenNameModal - let screenName = "" - let url = "" - let selectedScreens = [] + let showModal // Hydrate state from URL params $: hydrateStateFromURL($params, $leftover) @@ -159,7 +152,7 @@
    - +
    @@ -188,7 +181,7 @@
    Let's add some life to this screen
    -