diff --git a/.github/workflows/README.md b/.github/workflows/README.md index f77323d85a..7e4172e602 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -115,77 +115,4 @@ This job is responsible for deploying to our production, cloud kubernetes enviro ### Rollback A Bad Cloud Deployment - Kick off cloud deploy job - Ensure you are running off master -- Enter the version number of the last known good version of budibase. For example `1.0.0` - -## Pro - -| **NOTE**: When developing for both pro / budibase repositories, your branch names need to match, or else the correct pro doesn't get run within your CI job. - -### Installing Pro - -The pro package is always installed from source in our CI jobs. - -This is done to prevent pro needing to be published prior to CI runs in budiabse. This is required for two reasons: -- To reduce developer need to manually bump versions, i.e: - - release pro, bump pro dep in budibase, now ci can run successfully -- The cyclic dependency on backend-core, i.e: - - pro depends on backend-core - - server depends on pro - - backend-core lives in the monorepo, so it can't be released independently to be used in pro - - therefore the only option is to pull pro from source and release it as a part of the monorepo release, as if it were a mono package - -The install is performed using the same steps as local development, via the `yarn bootstrap` command, see the [Contributing Guide#Pro](../../docs/CONTRIBUTING.md#pro) - -The branch to install pro from can vary depending on ref of the commit that triggered the budibase CI job. This is done to enable branches which have changes in both the monorepo and the pro repo to have their CI pass successfully. - -This is done using the [pro/install.sh](../../scripts/pro/install.sh) script. The script will: -- Clone pro to it's default branch (`develop`) -- Check if the clone worked, on forked versions of budibase this will fail due to no access - - This is fine as the `yarn` command will install the version from NPM - - Community PRs should never touch pro so this will always work -- Checkout the `BRANCH` argument, if this fails fallback to `BASE_BRANCH` - - This enables the more complex case of a feature branch being merged to another feature branch, e.g. - - I am working on a branch `epic/stonks` which exists on budibase and pro. - - I want to merge a change to this branch in budibase from `feature/stonks-ui`, which only exists in budibase - - The base branch ensures that `epic/stonks` in pro will still be checked out for the CI run, rather than falling back to `develop` -- Run `yarn setup` to build and install dependencies - - `yarn` - - `yarn bootstrap` - - `yarn build` - - The will build .ts files, and also update the `main` and `types` of `package.json` to point to `dist` rather than src - - The build command will only ever work in CI, it is prevented in local dev - -#### `BRANCH` and `BASE_BRANCH` arguments -These arguments are supplied by the various budibase build and release pipelines -- `budibase_ci` - - `BRANCH: ${{ github.event.pull_request.head.ref }}` -> The branch being merged - - `BASE_BRANCH: ${{ github.event.pull_request.base.ref}}` -> The base branch -- `release-develop` - - `BRANCH: develop` -> always use the `develop` branch in pro -- `release` - - `BRANCH: master` -> always use the `master` branch in pro - - -### Releasing Pro -After budibase dependencies have been released we will release the new version of pro to match the release version of budibase dependencies. This is to ensure that we are always keeping the version of `backend-core` in sync in the pro package and in budibase packages. Without this we could run into scenarios where different versions are being used when installed via `yarn` inside the docker images, creating very difficult to debug cases. - -Pro is released using the [pro/release.sh](../../scripts/pro/release.sh) script. The script will: -- Inspect the `VERSION` from the `lerna.json` file in budibase -- Determine whether to use the `latest` or `develop` tag based on the command argument -- Go to pro directory - - install npm creds - - update the version of `backend-core` to be `VERSION`, the version just released by lerna - - publish to npm. Uses a `lerna publish` command, pro itself is a mono repo. - - force the version to be the same as `VERSION` to keep pro and budibase in sync - - reverts the changes to `main` and `types` in `package.json` that were made by the build step, to point back to source - - commit & push: `Prep next development iteration` -- Go to budibase - - Update to the new version of pro in `server` and `worker` so the latest pro version is used in the docker builds - - commit & push: `Update pro version to $VERSION` - - -#### `COMMAND` argument -This argument is supplied by the existing `release` and `release:develop` budibase commands, which invoke the pro release -- `release` will supply no command and default to use `latest` -- `release:develop` will supply `develop` - +- Enter the version number of the last known good version of budibase. For example `1.0.0` \ No newline at end of file diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index ddfe202465..d2689a0ea0 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -67,7 +67,6 @@ jobs: - name: Bootstrap and build (CLI) run: | yarn - yarn bootstrap yarn build - name: Build OpenAPI spec diff --git a/charts/budibase/templates/secrets.yaml b/charts/budibase/templates/secrets.yaml index 1c0a914ed3..263934187e 100644 --- a/charts/budibase/templates/secrets.yaml +++ b/charts/budibase/templates/secrets.yaml @@ -1,4 +1,5 @@ -{{- if .Values.globals.createSecrets -}} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "budibase.fullname" .) }} +{{- if .Values.globals.createSecrets }} apiVersion: v1 kind: Secret metadata: @@ -10,8 +11,15 @@ metadata: heritage: "{{ .Release.Service }}" type: Opaque data: + {{- if $existingSecret }} + internalApiKey: {{ index $existingSecret.data "internalApiKey" }} + jwtSecret: {{ index $existingSecret.data "jwtSecret" }} + objectStoreAccess: {{ index $existingSecret.data "objectStoreAccess" }} + objectStoreSecret: {{ index $existingSecret.data "objectStoreSecret" }} + {{- else }} internalApiKey: {{ template "budibase.defaultsecret" .Values.globals.internalApiKey }} jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }} objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }} objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }} -{{- end -}} + {{- end }} +{{- end }} diff --git a/docs/DEV-SETUP-DEBIAN.md b/docs/DEV-SETUP-DEBIAN.md index a8b1e3dce4..e098862c64 100644 --- a/docs/DEV-SETUP-DEBIAN.md +++ b/docs/DEV-SETUP-DEBIAN.md @@ -55,7 +55,7 @@ yarn setup The yarn setup command runs several build steps i.e. ``` -node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev +node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev ``` So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. diff --git a/docs/DEV-SETUP-MACOSX.md b/docs/DEV-SETUP-MACOSX.md index 94ed3fc1ee..0e13d540b3 100644 --- a/docs/DEV-SETUP-MACOSX.md +++ b/docs/DEV-SETUP-MACOSX.md @@ -55,7 +55,7 @@ yarn setup The yarn setup command runs several build steps i.e. ``` -node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev +node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev ``` So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. diff --git a/docs/DEV-SETUP-WINDOWS.md b/docs/DEV-SETUP-WINDOWS.md index 176e0700d7..f26a5a0882 100644 --- a/docs/DEV-SETUP-WINDOWS.md +++ b/docs/DEV-SETUP-WINDOWS.md @@ -74,7 +74,7 @@ yarn setup The yarn setup command runs several build steps i.e. ``` -node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev +node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev ``` So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose. diff --git a/hosting/single/README.md b/hosting/single/README.md index 1147d55c89..09010f5075 100644 --- a/hosting/single/README.md +++ b/hosting/single/README.md @@ -58,7 +58,6 @@ Node setup: ``` node ./hosting/scripts/setup.js yarn -yarn bootstrap yarn build ``` #### Build Image diff --git a/hosting/tests/README.md b/hosting/tests/README.md index 8586b31948..19b9ed5037 100644 --- a/hosting/tests/README.md +++ b/hosting/tests/README.md @@ -47,7 +47,6 @@ Node setup: ``` node ./hosting/scripts/setup.js yarn -yarn bootstrap yarn build ``` #### Build Image diff --git a/lerna.json b/lerna.json index c379dfe567..7f0341ceb5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.9.33-alpha.0", + "version": "2.9.39-alpha.11", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 4e4befb5f2..b0c428fe70 100644 --- a/package.json +++ b/package.json @@ -33,21 +33,18 @@ "scripts": { "preinstall": "node scripts/syncProPackage.js", "setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev", - "bootstrap": "./scripts/link-dependencies.sh && echo '***BOOTSTRAP ONLY REQUIRED FOR USE WITH ACCOUNT PORTAL***'", "build": "lerna run build --stream", "build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput", "check:types": "lerna run check:types", - "backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap", - "backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'", "build:sdk": "lerna run --stream build:sdk", "deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular", "release": "lerna publish from-package --yes --force-publish --no-git-tag-version --no-push --no-git-reset", "release:develop": "yarn release --dist-tag develop", - "restore": "yarn run clean && yarn run bootstrap && yarn run build", + "restore": "yarn run clean && yarn && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", "nuke:docker": "lerna run --stream dev:stack:nuke", - "clean": "lerna clean", + "clean": "lerna clean -y", "kill-builder": "kill-port 3000", "kill-server": "kill-port 4001 4002", "kill-all": "yarn run kill-builder && yarn run kill-server", @@ -93,9 +90,8 @@ "mode:account": "yarn mode:cloud && yarn env:account:enable", "security:audit": "node scripts/audit.js", "postinstall": "husky install", - "dep:clean": "yarn clean -y && yarn bootstrap", - "submodules:load": "git submodule init && git submodule update && yarn && yarn bootstrap", - "submodules:unload": "git submodule deinit --all && yarn && yarn bootstrap" + "submodules:load": "git submodule init && git submodule update && yarn", + "submodules:unload": "git submodule deinit --all && yarn" }, "workspaces": { "packages": [ diff --git a/packages/backend-core/.npmignore b/packages/backend-core/.npmignore index fb547825eb..30bba85ce8 100644 --- a/packages/backend-core/.npmignore +++ b/packages/backend-core/.npmignore @@ -1,4 +1,6 @@ * !dist/**/* dist/tsconfig.build.tsbuildinfo -!package.json \ No newline at end of file +!package.json +!src/** +!tests/** \ No newline at end of file diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 1d6aa5b9fd..cf8d6fbe17 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -6,7 +6,7 @@ "types": "dist/src/index.d.ts", "exports": { ".": "./dist/index.js", - "./tests": "./dist/tests.js", + "./tests": "./dist/tests/index.js", "./*": "./dist/*.js" }, "author": "Budibase", @@ -14,7 +14,7 @@ "scripts": { "prebuild": "rimraf dist/", "prepack": "cp package.json dist", - "build": "node ./scripts/build.js && tsc -p tsconfig.build.json --emitDeclarationOnly --paths null", + "build": "tsc -p tsconfig.build.json --paths null && node ./scripts/build.js", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "check:types": "tsc -p tsconfig.json --noEmit --paths null", "test": "bash scripts/test.sh", diff --git a/packages/backend-core/scripts/build.js b/packages/backend-core/scripts/build.js index bd00cbc7ff..9cc33d7b75 100644 --- a/packages/backend-core/scripts/build.js +++ b/packages/backend-core/scripts/build.js @@ -1,6 +1,4 @@ #!/usr/bin/node const coreBuild = require("../../../scripts/build") -coreBuild("./src/plugin/index.ts", "./dist/plugins.js") coreBuild("./src/index.ts", "./dist/index.js") -coreBuild("./tests/index.ts", "./dist/tests.js") diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 89f76769b3..29ca4123f5 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -8,7 +8,6 @@ import { DatabasePutOpts, DatabaseCreateIndexOpts, DatabaseDeleteIndexOpts, - DocExistsResponse, Document, isDocument, } from "@budibase/types" @@ -121,19 +120,6 @@ export class DatabaseImpl implements Database { return this.updateOutput(() => db.get(id)) } - async docExists(docId: string): Promise { - const db = await this.checkSetup() - let _rev, exists - try { - const { etag } = await db.head(docId) - _rev = etag - exists = true - } catch (err) { - exists = false - } - return { _rev, exists } - } - async remove(idOrDoc: string | Document, rev?: string) { const db = await this.checkSetup() let _id: string diff --git a/packages/backend-core/src/db/db.ts b/packages/backend-core/src/db/db.ts index f13eb9a965..9aae64b892 100644 --- a/packages/backend-core/src/db/db.ts +++ b/packages/backend-core/src/db/db.ts @@ -11,7 +11,11 @@ export function getDB(dbName?: string, opts?: any): Database { // we have to use a callback for this so that we can close // the DB when we're done, without this manual requests would // need to close the database when done with it to avoid memory leaks -export async function doWithDB(dbName: string, cb: any, opts = {}) { +export async function doWithDB( + dbName: string, + cb: (db: Database) => Promise, + opts = {} +) { const db = getDB(dbName, opts) // need this to be async so that we can correctly close DB after all // async operations have been completed diff --git a/packages/backend-core/src/security/permissions.ts b/packages/backend-core/src/security/permissions.ts index aa0b20a30c..13083534b1 100644 --- a/packages/backend-core/src/security/permissions.ts +++ b/packages/backend-core/src/security/permissions.ts @@ -87,6 +87,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.QUERY, PermissionLevel.WRITE), new Permission(PermissionType.TABLE, PermissionLevel.WRITE), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, POWER: { @@ -97,6 +98,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.USER, PermissionLevel.READ), new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, ADMIN: { @@ -108,6 +110,7 @@ export const BUILTIN_PERMISSIONS = { new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN), new Permission(PermissionType.WEBHOOK, PermissionLevel.READ), new Permission(PermissionType.QUERY, PermissionLevel.ADMIN), + new Permission(PermissionType.LEGACY_VIEW, PermissionLevel.READ), ], }, } diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index 081193b433..e87df2e9c9 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -253,7 +253,7 @@ export function checkForRoleResourceArray( * Given an app ID this will retrieve all of the roles that are currently within that app. * @return {Promise} An array of the role objects that were found. */ -export async function getAllRoles(appId?: string) { +export async function getAllRoles(appId?: string): Promise { if (appId) { return doWithDB(appId, internal) } else { @@ -312,37 +312,6 @@ export async function getAllRoles(appId?: string) { } } -/** - * This retrieves the required role for a resource - * @param permLevel The level of request - * @param resourceId The resource being requested - * @param subResourceId The sub resource being requested - * @return {Promise<{permissions}|Object>} returns the permissions required to access. - */ -export async function getRequiredResourceRole( - permLevel: string, - { resourceId, subResourceId }: { resourceId?: string; subResourceId?: string } -) { - const roles = await getAllRoles() - let main = [], - sub = [] - for (let role of roles) { - // no permissions, ignore it - if (!role.permissions) { - continue - } - const mainRes = resourceId ? role.permissions[resourceId] : undefined - const subRes = subResourceId ? role.permissions[subResourceId] : undefined - if (mainRes && mainRes.indexOf(permLevel) !== -1) { - main.push(role._id) - } else if (subRes && subRes.indexOf(permLevel) !== -1) { - sub.push(role._id) - } - } - // for now just return the IDs - return main.concat(sub) -} - export class AccessController { userHierarchies: { [key: string]: string[] } constructor() { @@ -411,8 +380,8 @@ export function getDBRoleID(roleName: string) { export function getExternalRoleID(roleId: string, version?: string) { // for built-in roles we want to remove the DB role ID element (role_) if ( - (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) || - version === RoleIDVersion.NAME + roleId.startsWith(DocumentType.ROLE) && + (isBuiltin(roleId) || version === RoleIDVersion.NAME) ) { return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1] } diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 4a5ef890bf..f2018272f6 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -17,6 +17,8 @@ export default function positionDropdown(element, opts) { maxWidth, useAnchorWidth, offset = 5, + customUpdate, + offsetBelow, } = opts if (!anchor) { return @@ -32,33 +34,42 @@ export default function positionDropdown(element, opts) { left: null, top: null, } - // Determine vertical styles - if (align === "right-outside") { - styles.top = anchorBounds.top - } else if (window.innerHeight - anchorBounds.bottom < (maxHeight || 100)) { - styles.top = anchorBounds.top - elementBounds.height - offset - styles.maxHeight = maxHeight || 240 - } else { - styles.top = anchorBounds.bottom + offset - styles.maxHeight = - maxHeight || window.innerHeight - anchorBounds.bottom - 20 - } - // Determine horizontal styles - if (!maxWidth && useAnchorWidth) { - styles.maxWidth = anchorBounds.width - } - if (useAnchorWidth) { - styles.minWidth = anchorBounds.width - } - if (align === "right") { - styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width - } else if (align === "right-outside") { - styles.left = anchorBounds.right + offset - } else if (align === "left-outside") { - styles.left = anchorBounds.left - elementBounds.width - offset + if (typeof customUpdate === "function") { + styles = customUpdate(anchorBounds, elementBounds, styles) } else { - styles.left = anchorBounds.left + // Determine vertical styles + if (align === "right-outside") { + styles.top = anchorBounds.top + } else if ( + window.innerHeight - anchorBounds.bottom < + (maxHeight || 100) + ) { + styles.top = anchorBounds.top - elementBounds.height - offset + styles.maxHeight = maxHeight || 240 + } else { + styles.top = anchorBounds.bottom + (offsetBelow || offset) + styles.maxHeight = + maxHeight || window.innerHeight - anchorBounds.bottom - 20 + } + + // Determine horizontal styles + if (!maxWidth && useAnchorWidth) { + styles.maxWidth = anchorBounds.width + } + if (useAnchorWidth) { + styles.minWidth = anchorBounds.width + } + if (align === "right") { + styles.left = + anchorBounds.left + anchorBounds.width - elementBounds.width + } else if (align === "right-outside") { + styles.left = anchorBounds.right + offset + } else if (align === "left-outside") { + styles.left = anchorBounds.left - elementBounds.width - offset + } else { + styles.left = anchorBounds.left + } } // Apply styles diff --git a/packages/bbui/src/DetailSummary/DetailSummary.svelte b/packages/bbui/src/DetailSummary/DetailSummary.svelte index f7e2611792..daa9f3f5ca 100644 --- a/packages/bbui/src/DetailSummary/DetailSummary.svelte +++ b/packages/bbui/src/DetailSummary/DetailSummary.svelte @@ -44,7 +44,9 @@ align-items: stretch; border-bottom: var(--border-light); } - + .property-group-container:last-child { + border-bottom: 0px; + } .property-group-name { cursor: pointer; display: flex; diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 4ff4df854b..421d12615f 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -4,6 +4,8 @@ import Body from "../Typography/Body.svelte" import Heading from "../Typography/Heading.svelte" import { setContext } from "svelte" + import { createEventDispatcher } from "svelte" + import { generate } from "shortid" export let title export let fillWidth @@ -11,13 +13,17 @@ export let width = "calc(100% - 626px)" export let headless = false + const dispatch = createEventDispatcher() + let visible = false + let drawerId = generate() export function show() { if (visible) { return } visible = true + dispatch("drawerShow", drawerId) } export function hide() { @@ -25,6 +31,7 @@ return } visible = false + dispatch("drawerHide", drawerId) } setContext("drawer-actions", { diff --git a/packages/bbui/src/FancyForm/FancySelect.svelte b/packages/bbui/src/FancyForm/FancySelect.svelte index ee43ecc3ca..e015f51570 100644 --- a/packages/bbui/src/FancyForm/FancySelect.svelte +++ b/packages/bbui/src/FancyForm/FancySelect.svelte @@ -2,8 +2,9 @@ import { createEventDispatcher } from "svelte" import FancyField from "./FancyField.svelte" import Icon from "../Icon/Icon.svelte" - import Popover from "../Popover/Popover.svelte" import FancyFieldLabel from "./FancyFieldLabel.svelte" + import StatusLight from "../StatusLight/StatusLight.svelte" + import Picker from "../Form/Core/Picker.svelte" export let label export let value @@ -11,18 +12,30 @@ export let error = null export let validate = null export let options = [] + export let isOptionEnabled = () => true export let getOptionLabel = option => extractProperty(option, "label") export let getOptionValue = option => extractProperty(option, "value") - + export let getOptionSubtitle = option => extractProperty(option, "subtitle") + export let getOptionColour = () => null const dispatch = createEventDispatcher() let open = false - let popover let wrapper $: placeholder = !value $: selectedLabel = getSelectedLabel(value) + $: fieldColour = getFieldAttribute(getOptionColour, value, options) + const getFieldAttribute = (getAttribute, value, options) => { + // Wait for options to load if there is a value but no options + if (!options?.length) { + return "" + } + const index = options.findIndex( + (option, idx) => getOptionValue(option, idx) === value + ) + return index !== -1 ? getAttribute(options[index], index) : null + } const extractProperty = (value, property) => { if (value && typeof value === "object") { return value[property] @@ -64,46 +77,45 @@ {label} {/if} + {#if fieldColour} + + + + {/if} +
{selectedLabel || ""}
-
+
- (open = false)} - useAnchorWidth={true} - maxWidth={null} -> -
- {#if options.length} - {#each options as option, idx} -
onChange(getOptionValue(option, idx))} - > - - {getOptionLabel(option, idx)} - - {#if value === getOptionValue(option, idx)} - - {/if} -
- {/each} - {/if} -
-
+
+ option === value} + /> +
diff --git a/packages/bbui/src/Form/Core/Combobox.svelte b/packages/bbui/src/Form/Core/Combobox.svelte index b68a24d8db..b1b264a9b7 100644 --- a/packages/bbui/src/Form/Core/Combobox.svelte +++ b/packages/bbui/src/Form/Core/Combobox.svelte @@ -2,8 +2,8 @@ import "@spectrum-css/inputgroup/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/menu/dist/index-vars.css" - import { fly } from "svelte/transition" import { createEventDispatcher } from "svelte" + import clickOutside from "../../Actions/click_outside" export let value = null export let id = null @@ -80,10 +80,11 @@ {#if open} -
(open = false)} />
{ + open = false + }} >
    {#if options && Array.isArray(options)} @@ -125,14 +126,6 @@ .spectrum-Textfield-input { width: 0; } - .overlay { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: 999; - } .spectrum-Popover { max-height: 240px; width: 100%; diff --git a/packages/bbui/src/Form/Core/Multiselect.svelte b/packages/bbui/src/Form/Core/Multiselect.svelte index ea9b5858f5..8816da33c4 100644 --- a/packages/bbui/src/Form/Core/Multiselect.svelte +++ b/packages/bbui/src/Form/Core/Multiselect.svelte @@ -17,6 +17,9 @@ export let fetchTerm = null export let useFetch = false export let customPopoverHeight + export let customPopoverOffsetBelow + export let customPopoverMaxHeight + export let open = false const dispatch = createEventDispatcher() @@ -88,6 +91,7 @@ isPlaceholder={!arrayValue.length} {autocomplete} bind:fetchTerm + bind:open {useFetch} {isOptionSelected} {getOptionLabel} @@ -96,4 +100,6 @@ {sort} {autoWidth} {customPopoverHeight} + {customPopoverOffsetBelow} + {customPopoverMaxHeight} /> diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index aada17b318..9b90c1a865 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -8,6 +8,8 @@ import Icon from "../../Icon/Icon.svelte" import StatusLight from "../../StatusLight/StatusLight.svelte" import Popover from "../../Popover/Popover.svelte" + import Tags from "../../Tags/Tags.svelte" + import Tag from "../../Tags/Tag.svelte" export let id = null export let disabled = false @@ -26,6 +28,7 @@ export let getOptionIcon = () => null export let useOptionIconImage = false export let getOptionColour = () => null + export let getOptionSubtitle = () => null export let open = false export let readonly = false export let quiet = false @@ -35,9 +38,11 @@ export let fetchTerm = null export let useFetch = false export let customPopoverHeight + export let customPopoverOffsetBelow + export let customPopoverMaxHeight export let align = "left" export let footer = null - + export let customAnchor = null const dispatch = createEventDispatcher() let searchTerm = null @@ -139,16 +144,17 @@ - (open = false)} useAnchorWidth={!autoWidth} maxWidth={autoWidth ? 400 : null} + maxHeight={customPopoverMaxHeight} customHeight={customPopoverHeight} + offsetBelow={customPopoverOffsetBelow} >
    {/if} + {#if getOptionSubtitle(option, idx)} + {getOptionSubtitle(option, idx)} + {/if} + {getOptionLabel(option, idx)} + {#if option.tag} + + + {option.tag} + + + {/if} .spectrum-Icon) { + margin-top: 2px; + } diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index 2fad886910..f843e2ccbd 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -21,11 +21,13 @@ export let sort = false export let align export let footer = null + export let open = false + export let tag = null + export let customPopoverOffsetBelow + export let customPopoverMaxHeight const dispatch = createEventDispatcher() - let open = false - $: fieldText = getFieldText(value, options, placeholder) $: fieldIcon = getFieldAttribute(getOptionIcon, value, options) $: fieldColour = getFieldAttribute(getOptionColour, value, options) @@ -83,6 +85,9 @@ {isOptionEnabled} {autocomplete} {sort} + {tag} + {customPopoverOffsetBelow} + {customPopoverMaxHeight} isPlaceholder={value == null || value === ""} placeholderOption={placeholder === false ? null : placeholder} isOptionSelected={option => option === value} diff --git a/packages/bbui/src/Form/Select.svelte b/packages/bbui/src/Form/Select.svelte index e87496652d..a9214320f9 100644 --- a/packages/bbui/src/Form/Select.svelte +++ b/packages/bbui/src/Form/Select.svelte @@ -25,7 +25,7 @@ export let customPopoverHeight export let align export let footer = null - + export let tag = null const dispatch = createEventDispatcher() const onChange = e => { value = e.detail @@ -61,6 +61,7 @@ {isOptionEnabled} {autocomplete} {customPopoverHeight} + {tag} on:change={onChange} on:click /> diff --git a/packages/bbui/src/Modal/Modal.svelte b/packages/bbui/src/Modal/Modal.svelte index 384cfe6cac..da97bf332e 100644 --- a/packages/bbui/src/Modal/Modal.svelte +++ b/packages/bbui/src/Modal/Modal.svelte @@ -9,6 +9,7 @@ export let fixed = false export let inline = false export let disableCancel = false + export let autoFocus = true const dispatch = createEventDispatcher() let visible = fixed || inline @@ -53,6 +54,9 @@ } async function focusModal(node) { + if (!autoFocus) { + return + } await tick() // Try to focus first input diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 1259f2704f..4c4b818440 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -19,10 +19,15 @@ export let useAnchorWidth = false export let dismissible = true export let offset = 5 + export let offsetBelow export let customHeight export let animate = true export let customZindex + export let handlePostionUpdate + export let showPopover = true + export let clickOutsideOverride = false + $: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum" export const show = () => { @@ -44,6 +49,9 @@ } const handleOutsideClick = e => { + if (clickOutsideOverride) { + return + } if (open) { // Stop propagation if the source is the anchor let node = e.target @@ -62,6 +70,9 @@ } function handleEscape(e) { + if (!clickOutsideOverride) { + return + } if (open && e.key === "Escape") { hide() } @@ -79,6 +90,8 @@ maxWidth, useAnchorWidth, offset, + offsetBelow, + customUpdate: handlePostionUpdate, }} use:clickOutside={{ callback: dismissible ? handleOutsideClick : () => {}, @@ -87,6 +100,7 @@ on:keydown={handleEscape} class="spectrum-Popover is-open" class:customZindex + class:hide-popover={open && !showPopover} role="presentation" style="height: {customHeight}; --customZindex: {customZindex};" transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }} @@ -97,6 +111,10 @@ {/if}
    @@ -72,9 +74,7 @@ - {#if isInternal} - - {/if} + {#if relationshipsEnabled} diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte index d239cabd59..f6160e3caa 100644 --- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte @@ -10,6 +10,7 @@ import ManageAccessButton from "./buttons/ManageAccessButton.svelte" import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte" import { notifications } from "@budibase/bbui" + import { ROW_EXPORT_FORMATS } from "constants/backend" export let view = {} @@ -19,6 +20,14 @@ let type = "internal" $: name = view.name + $: calculation = view.calculation + + $: supportedFormats = Object.values(ROW_EXPORT_FORMATS).filter(key => { + if (calculation && key === ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA) { + return false + } + return true + }) // Fetch rows for specified view $: fetchViewData(name, view.field, view.groupBy, view.calculation) @@ -68,5 +77,5 @@ {/if} - + diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte new file mode 100644 index 0000000000..0c6a0cca9a --- /dev/null +++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte @@ -0,0 +1,49 @@ + + +
    + + + + + + + + + +
    + + diff --git a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte index fd0d64f6cd..4fa1d07abd 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ExportButton.svelte @@ -7,6 +7,7 @@ export let sorting export let disabled = false export let selectedRows + export let formats let modal @@ -15,5 +16,5 @@ Export - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index bc8e0c5318..5c0b7df742 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -9,19 +9,15 @@ let modal let resourcePermissions - async function openDropdown() { - resourcePermissions = await permissions.forResource(resourceId) + async function openModal() { + resourcePermissions = await permissions.forResourceDetailed(resourceId) modal.show() } - + Access - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte index b96738ab1a..23f6d1dea1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte @@ -15,6 +15,7 @@ $: tempValue = filters || [] $: schemaFields = Object.values(schema || {}) $: text = getText(filters) + $: selected = tempValue.filter(x => !x.onEmptyFilter)?.length > 0 const getText = filters => { const count = filters?.filter(filter => filter.field)?.length @@ -22,13 +23,7 @@ } - 0} -> + {text} diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte index 33c416d7ef..3441d8de17 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte @@ -1,18 +1,30 @@ - - Add view - + + + Create view + + - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte index b5fe202d11..f1bbc04328 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridExportButton.svelte @@ -2,7 +2,7 @@ import ExportButton from "../ExportButton.svelte" import { getContext } from "svelte" - const { rows, columns, tableId, sort, selectedRows, filter } = + const { rows, columns, datasource, sort, selectedRows, filter } = getContext("grid") $: disabled = !$rows.length || !$columns.length @@ -12,7 +12,7 @@ { filter.set(e.detail || []) } -{#key $tableId} +{#key $datasource} {/key} diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte index a0881163b4..71d971891c 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte @@ -4,12 +4,12 @@ export let disabled = false - const { rows, tableId, table } = getContext("grid") + const { rows, datasource, definition } = getContext("grid") diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte index 154007950a..0cd008bab1 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte @@ -2,7 +2,16 @@ import ManageAccessButton from "../ManageAccessButton.svelte" import { getContext } from "svelte" - const { tableId } = getContext("grid") + const { datasource } = getContext("grid") + + $: resourceId = getResourceID($datasource) + + const getResourceID = datasource => { + if (!datasource) { + return null + } + return datasource.type === "table" ? datasource.tableId : datasource.id + } - + diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte index 460391366f..baa7dbed14 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte @@ -2,12 +2,12 @@ import ExistingRelationshipButton from "../ExistingRelationshipButton.svelte" import { getContext } from "svelte" - const { table, rows } = getContext("grid") + const { definition, rows } = getContext("grid") -{#if $table} +{#if $definition} rows.actions.refreshData()} /> {/if} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte deleted file mode 100644 index 8f2679f874..0000000000 --- a/packages/builder/src/components/backend/DataTable/modals/CreateViewModal.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte index ac168698fc..09f76d3522 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ExportModal.svelte @@ -9,30 +9,43 @@ import download from "downloadjs" import { API } from "api" import { Constants, LuceneUtils } from "@budibase/frontend-core" - - const FORMATS = [ - { - name: "CSV", - key: "csv", - }, - { - name: "JSON", - key: "json", - }, - { - name: "JSON with Schema", - key: "jsonWithSchema", - }, - ] + import { ROW_EXPORT_FORMATS } from "constants/backend" export let view export let filters export let sorting export let selectedRows = [] + export let formats - let exportFormat = FORMATS[0].key + const FORMATS = [ + { + name: "CSV", + key: ROW_EXPORT_FORMATS.CSV, + }, + { + name: "JSON", + key: ROW_EXPORT_FORMATS.JSON, + }, + { + name: "JSON with Schema", + key: ROW_EXPORT_FORMATS.JSON_WITH_SCHEMA, + }, + ] + + $: options = FORMATS.filter(format => { + if (formats && !formats.includes(format.key)) { + return false + } + return true + }) + + let exportFormat let filterLookup + $: if (options && !exportFormat) { + exportFormat = Array.isArray(options) ? options[0]?.key : [] + } + $: luceneFilter = LuceneUtils.buildLuceneQuery(filters) $: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters) @@ -190,7 +203,7 @@ + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index f7b6f61a10..1c264a5aaf 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -1,7 +1,14 @@ diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index 36c6a32801..9c98bdc2e5 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -290,11 +290,11 @@ datasource.entities[getTable(toId).name].schema[toRelationship.name] = toRelationship - await save() + await save({ action: "saved" }) } async function deleteRelationship() { removeExistingRelationship() - await save() + await save({ action: "deleted" }) await tables.fetch() close() } diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationshipModal.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationshipModal.svelte index 03683bcfc9..0dfb4ca796 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationshipModal.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationshipModal.svelte @@ -33,7 +33,7 @@ } // action is one of 'created', 'updated' or 'deleted' - async function saveRelationship(action) { + async function saveRelationship({ action }) { try { await beforeSave({ action, datasource }) diff --git a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte index d9def682dc..056a36c4a7 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableNavigator.svelte @@ -1,5 +1,5 @@ {#if $database?._id} @@ -37,18 +45,23 @@ {/if} - {#each [...Object.keys(table.views || {})].sort() as viewName, idx (idx)} + {#each [...Object.entries(table.views || {})].sort() as [name, view], idx (idx)} $goto(`./view/${encodeURIComponent(viewName)}`)} - selectedBy={$userSelectedResourceMap[viewName]} + text={name} + selected={isViewActive(view, $isActive, $views, $viewsV2)} + on:click={() => { + if (view.version === 2) { + $goto(`./view/v2/${view.id}`) + } else { + $goto(`./view/v1/${encodeURIComponent(name)}`) + } + }} + selectedBy={$userSelectedResourceMap[name] || + $userSelectedResourceMap[view.id]} > - + {/each} {/each} diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index 11ef60480b..1760938c53 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -35,7 +35,7 @@ screen => screen.autoTableId === table._id ) willBeDeleted = ["All table data"].concat( - templateScreens.map(screen => `Screen ${screen.props._instanceName}`) + templateScreens.map(screen => `Screen ${screen.routing?.route || ""}`) ) confirmDeleteDialog.show() } @@ -44,7 +44,10 @@ const isSelected = $params.tableId === table._id try { await tables.delete(table) - await store.actions.screens.delete(templateScreens) + // Screens need deleted one at a time because of undo/redo + for (let screen of templateScreens) { + await store.actions.screens.delete(screen) + } if (table.type === "external") { await datasources.fetch() } diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte index 99f19935a1..5e2b0102f8 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditViewPopover.svelte @@ -1,6 +1,5 @@ - role.name} + getOptionValue={role => role._id} + getOptionColour={getColor} + getOptionIcon={getIcon} + isOptionEnabled={option => + option._id !== Constants.Roles.CREATOR || + $licensing.perAppBuildersEnabled} + {placeholder} + {error} + /> +{/if} diff --git a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte index dacb076bdb..5c4f90606d 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte @@ -74,6 +74,8 @@ {/if}
    {actionText}
    - + Define what actions to run. @@ -86,6 +87,7 @@ {bindings} {key} {nested} + {componentInstance} /> diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte index e4a5f171ff..1e79c51051 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte @@ -1,12 +1,20 @@
    @@ -15,9 +23,9 @@ table.name} - getOptionValue={table => table._id} + {options} + getOptionLabel={table => table.label} + getOptionValue={table => table.resourceId} /> diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte index d16a279c68..c1917ad90f 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/SaveRow.svelte @@ -1,10 +1,10 @@ + +
      + {#each draggableItems as draggable (draggable.id)} +
    • +
      + {#if showHandle} +
      + +
      + {/if} +
      +
      + +
      +
    • + {/each} +
    + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte new file mode 100644 index 0000000000..7d2eaae478 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/EditFieldPopover.svelte @@ -0,0 +1,160 @@ + + + { + if (!open) { + popover.show() + open = true + } + }} +/> + + { + drawers = [] + $draggable.actions.select(field._id) + }} + on:close={() => { + open = false + if ($draggable.selected == field._id) { + $draggable.actions.select() + } + }} + {anchor} + align="left-outside" + showPopover={drawers.length == 0} + clickOutsideOverride={drawers.length > 0} + maxHeight={600} + handlePostionUpdate={(anchorBounds, eleBounds, cfg) => { + let { left, top } = cfg + let percentageOffset = 30 + // left-outside + left = anchorBounds.left - eleBounds.width - 18 + + // shift up from the anchor, if space allows + let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset + let defaultTop = anchorBounds.top - offsetPos + + if (window.innerHeight - defaultTop < eleBounds.height) { + top = window.innerHeight - eleBounds.height - 5 + } else { + top = anchorBounds.top - offsetPos + } + + return { ...cfg, left, top } + }} +> + + +
    + + {field.field} +
    + { + drawers = [...drawers, e.detail] + }} + on:drawerHide={() => { + drawers = drawers.slice(0, -1) + }} + /> +
    +
    +
    + + diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index f9dccf586c..4c4fa0b7b7 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -1,45 +1,81 @@
    - {text} + {#if fieldList?.length} + + {/if}
    - - - Configure the fields in your form. - - - - - diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js new file mode 100644 index 0000000000..d4a8963dba --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/utils.js @@ -0,0 +1,46 @@ +export const convertOldFieldFormat = fields => { + if (!fields) { + return [] + } + const converted = fields.map(field => { + if (typeof field === "string") { + // existed but was a string + return { + field, + active: true, + } + } else if (typeof field?.active != "boolean") { + // existed but had no state + return { + field: field.name, + active: true, + } + } else { + return field + } + }) + return converted +} + +export const getComponentForField = (field, schema) => { + if (!field || !schema?.[field]) { + return null + } + const type = schema[field].type + return FieldTypeToComponentMap[type] +} + +export const FieldTypeToComponentMap = { + string: "stringfield", + number: "numberfield", + bigint: "bigintfield", + options: "optionsfield", + array: "multifieldselect", + boolean: "booleanfield", + longform: "longformfield", + datetime: "datetimefield", + attachment: "attachmentfield", + link: "relationshipfield", + json: "jsonfield", + barcodeqr: "codescanner", +} diff --git a/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte b/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte index 1201edd31e..c626081042 100644 --- a/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/OptionsEditor/OptionsEditor.svelte @@ -24,11 +24,22 @@ } -Define Options - +
    +
    +
    Define Options
    +
    + Define the options for this picker. + + diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index 5125c3bade..c8135b4f61 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -100,6 +100,8 @@ {key} {type} {...props} + on:drawerHide + on:drawerShow />
    {#if info} diff --git a/packages/builder/src/components/design/settings/controls/TableSelect.svelte b/packages/builder/src/components/design/settings/controls/TableSelect.svelte index 384bbe1e3a..da50ad8c2a 100644 --- a/packages/builder/src/components/design/settings/controls/TableSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/TableSelect.svelte @@ -1,28 +1,48 @@ + {#if searching} +
    + +
    + {/if} +
    + {/if} +
{/if} + + diff --git a/packages/client/src/components/preview/IndicatorSet.svelte b/packages/client/src/components/preview/IndicatorSet.svelte index 053523aa12..84966a561d 100644 --- a/packages/client/src/components/preview/IndicatorSet.svelte +++ b/packages/client/src/components/preview/IndicatorSet.svelte @@ -45,7 +45,7 @@ return } nextIndicators[idx].visible = - nextIndicators[idx].isSidePanel || entries[0].isIntersecting + nextIndicators[idx].insideSidePanel || entries[0].isIntersecting if (++callbackCount === observers.length) { indicators = nextIndicators updating = false @@ -125,7 +125,7 @@ width: elBounds.width + 4, height: elBounds.height + 4, visible: false, - isSidePanel: child.classList.contains("side-panel"), + insideSidePanel: !!child.closest(".side-panel"), }) }) } diff --git a/packages/client/src/stores/dataSource.js b/packages/client/src/stores/dataSource.js index 6288cbc072..2e99a55aa3 100644 --- a/packages/client/src/stores/dataSource.js +++ b/packages/client/src/stores/dataSource.js @@ -18,6 +18,8 @@ export const createDataSourceStore = () => { // Extract table ID if (dataSource.type === "table" || dataSource.type === "view") { dataSourceId = dataSource.tableId + } else if (dataSource.type === "viewV2") { + dataSourceId = dataSource.id } // Only one side of the relationship is required as a trigger, as it will @@ -79,7 +81,7 @@ export const createDataSourceStore = () => { // Fetch related table IDs from table schema let schema - if (options.invalidateRelationships) { + if (options.invalidateRelationships && !dataSourceId?.includes("view_")) { try { const definition = await API.fetchTableDefinition(dataSourceId) schema = definition?.schema diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 4fdda2a076..18d6b3de3c 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -42,7 +42,7 @@ const saveRowHandler = async (action, context) => { } // Refresh related datasources - await dataSourceStore.actions.invalidateDataSource(row.tableId, { + await dataSourceStore.actions.invalidateDataSource(tableId, { invalidateRelationships: true, }) @@ -75,7 +75,7 @@ const duplicateRowHandler = async (action, context) => { } // Refresh related datasources - await dataSourceStore.actions.invalidateDataSource(row.tableId, { + await dataSourceStore.actions.invalidateDataSource(tableId, { invalidateRelationships: true, }) diff --git a/packages/client/src/utils/schema.js b/packages/client/src/utils/schema.js index e450c516d7..f20e724a6e 100644 --- a/packages/client/src/utils/schema.js +++ b/packages/client/src/utils/schema.js @@ -6,6 +6,7 @@ import RelationshipFetch from "@budibase/frontend-core/src/fetch/RelationshipFet import NestedProviderFetch from "@budibase/frontend-core/src/fetch/NestedProviderFetch.js" import FieldFetch from "@budibase/frontend-core/src/fetch/FieldFetch.js" import JSONArrayFetch from "@budibase/frontend-core/src/fetch/JSONArrayFetch.js" +import ViewV2Fetch from "@budibase/frontend-core/src/fetch/ViewV2Fetch.js" /** * Fetches the schema of any kind of datasource. @@ -21,6 +22,7 @@ export const fetchDatasourceSchema = async ( const handler = { table: TableFetch, view: ViewFetch, + viewV2: ViewV2Fetch, query: QueryFetch, link: RelationshipFetch, provider: NestedProviderFetch, @@ -49,6 +51,15 @@ export const fetchDatasourceSchema = async ( return null } + // Strip hidden fields from views + if (datasource.type === "viewV2") { + Object.keys(schema).forEach(field => { + if (!schema[field].visible) { + delete schema[field] + } + }) + } + // Enrich schema with relationships if required if (definition?.sql && options?.enrichRelationships) { const relationshipAdditions = await getRelationshipSchemaAdditions(schema) diff --git a/packages/frontend-core/src/api/groups.js b/packages/frontend-core/src/api/groups.js index 72cbe30718..b1be230ac6 100644 --- a/packages/frontend-core/src/api/groups.js +++ b/packages/frontend-core/src/api/groups.js @@ -104,5 +104,27 @@ export const buildGroupsEndpoints = API => { removeAppsFromGroup: async (groupId, appArray) => { return updateGroupResource(groupId, "apps", "remove", appArray) }, + + /** + * Add app builder to group + * @param groupId The group to update + * @param appId The app id where the builder will be added + */ + addGroupAppBuilder: async ({ groupId, appId }) => { + return await API.post({ + url: `/api/global/groups/${groupId}/app/${appId}/builder`, + }) + }, + + /** + * Remove app builder from group + * @param groupId The group to update + * @param appId The app id where the builder will be removed + */ + removeGroupAppBuilder: async ({ groupId, appId }) => { + return await API.delete({ + url: `/api/global/groups/${groupId}/app/${appId}/builder`, + }) + }, } } diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js index 5bffb82f4d..aefc3522a7 100644 --- a/packages/frontend-core/src/api/index.js +++ b/packages/frontend-core/src/api/index.js @@ -23,6 +23,7 @@ import { buildTemplateEndpoints } from "./templates" import { buildUserEndpoints } from "./user" import { buildSelfEndpoints } from "./self" import { buildViewEndpoints } from "./views" +import { buildViewV2Endpoints } from "./viewsV2" import { buildLicensingEndpoints } from "./licensing" import { buildGroupsEndpoints } from "./groups" import { buildPluginEndpoints } from "./plugins" @@ -279,5 +280,6 @@ export const createAPIClient = config => { ...buildEventEndpoints(API), ...buildAuditLogsEndpoints(API), ...buildLogsEndpoints(API), + viewV2: buildViewV2Endpoints(API), } } diff --git a/packages/frontend-core/src/api/permissions.js b/packages/frontend-core/src/api/permissions.js index 5407cb3ce5..dd0005aaf2 100644 --- a/packages/frontend-core/src/api/permissions.js +++ b/packages/frontend-core/src/api/permissions.js @@ -21,4 +21,27 @@ export const buildPermissionsEndpoints = API => ({ url: `/api/permission/${roleId}/${resourceId}/${level}`, }) }, + + /** + * Remove the the permissions for a certain resource + * @param resourceId the ID of the resource to update + * @param roleId the ID of the role to update the permissions of + * @param level the level to remove the role for this resource + * @return {Promise<*>} + */ + removePermissionFromResource: async ({ resourceId, roleId, level }) => { + return await API.delete({ + url: `/api/permission/${roleId}/${resourceId}/${level}`, + }) + }, + + /** + * Gets info about the resources that depend on this resource permissions + * @param resourceId the resource ID to check + */ + getDependants: async resourceId => { + return await API.get({ + url: `/api/permission/${resourceId}/dependants`, + }) + }, }) diff --git a/packages/frontend-core/src/api/rows.js b/packages/frontend-core/src/api/rows.js index 8e8570ea2a..475ef0e2a4 100644 --- a/packages/frontend-core/src/api/rows.js +++ b/packages/frontend-core/src/api/rows.js @@ -23,7 +23,23 @@ export const buildRowEndpoints = API => ({ return } return await API.post({ - url: `/api/${row.tableId}/rows`, + url: `/api/${row._viewId || row.tableId}/rows`, + body: row, + suppressErrors, + }) + }, + + /** + * Patches a row in a table. + * @param row the row to patch + * @param suppressErrors whether or not to suppress error notifications + */ + patchRow: async (row, suppressErrors = false) => { + if (!row?.tableId && !row?._viewId) { + return + } + return await API.patch({ + url: `/api/${row._viewId || row.tableId}/rows`, body: row, suppressErrors, }) @@ -31,7 +47,7 @@ export const buildRowEndpoints = API => ({ /** * Deletes a row from a table. - * @param tableId the ID of the table to delete from + * @param tableId the ID of the table or view to delete from * @param rowId the ID of the row to delete * @param revId the rev of the row to delete */ @@ -50,10 +66,13 @@ export const buildRowEndpoints = API => ({ /** * Deletes multiple rows from a table. - * @param tableId the table ID to delete the rows from + * @param tableId the table or view ID to delete the rows from * @param rows the array of rows to delete */ deleteRows: async ({ tableId, rows }) => { + rows?.forEach(row => { + delete row?._viewId + }) return await API.delete({ url: `/api/${tableId}/rows`, body: { diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index d8723a649c..6c616d7baf 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -144,8 +144,8 @@ export const buildUserEndpoints = API => ({ body: { email, userInfo: { - admin: admin ? { global: true } : undefined, - builder: builder ? { global: true } : undefined, + admin: admin?.global ? { global: true } : undefined, + builder: builder?.global ? { global: true } : undefined, apps: apps ? apps : undefined, }, }, @@ -160,8 +160,8 @@ export const buildUserEndpoints = API => ({ return { email, userInfo: { - admin: admin ? { global: true } : undefined, - builder: builder ? { global: true } : undefined, + admin, + builder, apps: apps ? apps : undefined, }, } @@ -179,6 +179,7 @@ export const buildUserEndpoints = API => ({ url: `/api/global/users/invite/update/${invite.code}`, body: { apps: invite.apps, + builder: invite.builder, }, }) }, @@ -250,4 +251,26 @@ export const buildUserEndpoints = API => ({ url: `/api/global/users/count/${appId}`, }) }, + + /** + * Adds a per app builder to the selected app + * @param appId the applications id + * @param userId The id of the user to add as a builder + */ + addAppBuilder: async ({ userId, appId }) => { + return await API.post({ + url: `/api/global/users/${userId}/app/${appId}/builder`, + }) + }, + + /** + * Removes a per app builder to the selected app + * @param appId the applications id + * @param userId The id of the user to remove as a builder + */ + removeAppBuilder: async ({ userId, appId }) => { + return await API.delete({ + url: `/api/global/users/${userId}/app/${appId}/builder`, + }) + }, }) diff --git a/packages/frontend-core/src/api/viewsV2.js b/packages/frontend-core/src/api/viewsV2.js new file mode 100644 index 0000000000..3580d08229 --- /dev/null +++ b/packages/frontend-core/src/api/viewsV2.js @@ -0,0 +1,72 @@ +export const buildViewV2Endpoints = API => ({ + /** + * Fetches the definition of a view + * @param viewId the ID of the view to fetch + */ + fetchDefinition: async viewId => { + return await API.get({ + url: `/api/v2/views/${viewId}`, + }) + }, + /** + * Create a new view + * @param view the view object + */ + create: async view => { + return await API.post({ + url: `/api/v2/views`, + body: view, + }) + }, + /** + * Updates a view + * @param view the view object + */ + update: async view => { + return await API.put({ + url: `/api/v2/views/${view.id}`, + body: view, + }) + }, + /** + * Fetches all rows in a view + * @param viewId the id of the view + * @param query the search query + * @param paginate whether to paginate or not + * @param limit page size + * @param bookmark pagination cursor + * @param sort sort column + * @param sortOrder sort order + * @param sortType sort type (text or numeric) + */ + fetch: async ({ + viewId, + query, + paginate, + limit, + bookmark, + sort, + sortOrder, + sortType, + }) => { + return await API.post({ + url: `/api/v2/views/${viewId}/search`, + body: { + query, + paginate, + limit, + bookmark, + sort, + sortOrder, + sortType, + }, + }) + }, + /** + * Delete a view + * @param viewId the id of the view + */ + delete: async viewId => { + return await API.delete({ url: `/api/v2/views/${viewId}` }) + }, +}) diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index cb8616a735..19bdf1bd74 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -34,7 +34,7 @@ column.schema.autocolumn || column.schema.disabled || column.schema.type === "formula" || - (!$config.allowEditRows && row._id) + (!$config.canEditRows && row._id) // Register this cell API if the row is focused $: { @@ -58,9 +58,14 @@ isReadonly: () => readonly, getType: () => column.schema.type, getValue: () => row[column.name], - setValue: value => { + setValue: (value, options = { save: true }) => { validation.actions.setError(cellId, null) - updateValue(row._id, column.name, value) + updateValue({ + rowId: row._id, + column: column.name, + value, + save: options?.save, + }) }, } diff --git a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte index dd11066b98..5357d4b5cf 100644 --- a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte @@ -40,7 +40,7 @@
@@ -48,14 +48,14 @@ {#if !disableNumber}
{row.__idx + 1}
{/if} {/if} - {#if rowSelected && $config.allowDeleteRows} + {#if rowSelected && $config.canDeleteRows}
dispatch("request-bulk-delete")}>
{:else} -
+
{ if (e.button === 0 && orderable) { timeout = setTimeout(() => { @@ -116,6 +117,7 @@ columns.actions.saveChanges() open = false } + onMount(() => subscribe("close-edit-column", cancelEdit)) @@ -170,7 +172,6 @@ align="right" offset={0} popoverTarget={document.getElementById(`grid-${rand}`)} - animate={false} customZindex={100} > {#if editIsOpen} @@ -187,7 +188,7 @@ Edit column @@ -195,7 +196,6 @@ icon="Label" on:click={makeDisplayColumn} disabled={idx === "sticky" || - !$config.allowSchemaChanges || bannedDisplayColumnTypes.includes(column.schema.type)} > Use as display column diff --git a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte index b8728156db..01c9dc648b 100644 --- a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte @@ -3,7 +3,7 @@ import { ActionButton, Popover, Toggle, Icon } from "@budibase/bbui" import { getColumnIcon } from "../lib/utils" - const { columns, stickyColumn } = getContext("grid") + const { columns, stickyColumn, dispatch } = getContext("grid") let open = false let anchor @@ -11,33 +11,36 @@ $: anyHidden = $columns.some(col => !col.visible) $: text = getText($columns) - const toggleVisibility = (column, visible) => { + const toggleVisibility = async (column, visible) => { columns.update(state => { const index = state.findIndex(col => col.name === column.name) state[index].visible = visible return state.slice() }) - columns.actions.saveChanges() + await columns.actions.saveChanges() + dispatch(visible ? "show-column" : "hide-column") } - const showAll = () => { + const showAll = async () => { columns.update(state => { return state.map(col => ({ ...col, visible: true, })) }) - columns.actions.saveChanges() + await columns.actions.saveChanges() + dispatch("show-column") } - const hideAll = () => { + const hideAll = async () => { columns.update(state => { return state.map(col => ({ ...col, visible: false, })) }) - columns.actions.saveChanges() + await columns.actions.saveChanges() + dispatch("hide-column") } const getText = columns => { diff --git a/packages/frontend-core/src/components/grid/controls/SizeButton.svelte b/packages/frontend-core/src/components/grid/controls/SizeButton.svelte index 22e0c6c2e9..c2797ce537 100644 --- a/packages/frontend-core/src/components/grid/controls/SizeButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/SizeButton.svelte @@ -8,8 +8,14 @@ SmallRowHeight, } from "../lib/constants" - const { stickyColumn, columns, rowHeight, table, fixedRowHeight } = - getContext("grid") + const { + stickyColumn, + columns, + rowHeight, + definition, + fixedRowHeight, + datasource, + } = getContext("grid") // Some constants for column width options const smallColSize = 120 @@ -60,8 +66,8 @@ ] const changeRowHeight = height => { - columns.actions.saveTable({ - ...$table, + datasource.actions.saveDefinition({ + ...$definition, rowHeight: height, }) } diff --git a/packages/frontend-core/src/components/grid/controls/SortButton.svelte b/packages/frontend-core/src/components/grid/controls/SortButton.svelte index bd75249216..60e3c8e514 100644 --- a/packages/frontend-core/src/components/grid/controls/SortButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/SortButton.svelte @@ -8,7 +8,6 @@ let anchor $: columnOptions = getColumnOptions($stickyColumn, $columns) - $: checkValidSortColumn($sort.column, $stickyColumn, $columns) $: orderOptions = getOrderOptions($sort.column, columnOptions) const getColumnOptions = (stickyColumn, columns) => { @@ -46,8 +45,8 @@ const updateSortColumn = e => { sort.update(state => ({ - ...state, column: e.detail, + order: e.detail ? state.order : "ascending", })) } @@ -57,29 +56,6 @@ order: e.detail, })) } - - // Ensure we never have a sort column selected that is not visible - const checkValidSortColumn = (sortColumn, stickyColumn, columns) => { - if (!sortColumn) { - return - } - if ( - sortColumn !== stickyColumn?.name && - !columns.some(col => col.name === sortColumn) - ) { - if (stickyColumn) { - sort.update(state => ({ - ...state, - column: stickyColumn.name, - })) - } else { - sort.update(state => ({ - ...state, - column: columns[0]?.name, - })) - } - } - }
@@ -98,21 +74,23 @@
+ {#if $sort.column} +