diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 2a2c10cb7d..0000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,19 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security - - roadmap -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 659dbc4974..9509a22e99 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -12,9 +12,6 @@ on: - master - develop pull_request: - branches: - - master - - develop workflow_dispatch: env: @@ -162,7 +159,7 @@ jobs: run: | cd qa-core yarn setup - yarn test:ci + yarn serve:test:self:ci env: BB_ADMIN_USER_EMAIL: admin BB_ADMIN_USER_PASSWORD: admin diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index a7bf041eb5..61cb283e28 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -6,7 +6,7 @@ concurrency: on: push: tags: - - v*-alpha.* + - "*-alpha.*" workflow_dispatch: env: diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index 68625ad7af..7f8b8f1d55 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -6,9 +6,9 @@ concurrency: on: push: tags: - - "v[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+" # Exclude all pre-releases - - "!v*[0-9]+.[0-9]+.[0-9]+-*" + - "!*[0-9]+.[0-9]+.[0-9]+-*" env: # Posthog token used by ui at build time @@ -34,7 +34,6 @@ jobs: exit 1 fi - - uses: actions/setup-node@v1 with: node-version: 14.x @@ -58,9 +57,12 @@ jobs: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc yarn release - - name: "Get Previous tag" - id: previoustag - uses: "WyriHaximus/github-action-get-previous-tag@v1" + - name: "Get Current tag" + id: currenttag + run: | + version=v$(./scripts/getCurrentVersion.sh) + echo 'Using tag $version' + echo "::set-output name=tag::$resversionult" - name: Build/release Docker images run: | @@ -69,7 +71,7 @@ jobs: env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} - BUDIBASE_RELEASE_VERSION: ${{ steps.previoustag.outputs.tag }} + BUDIBASE_RELEASE_VERSION: ${{ steps.currenttag.outputs.tag }} release-helm-chart: needs: [release-images] @@ -96,7 +98,7 @@ jobs: git fetch mkdir sync echo "Packaging chart to sync dir" - helm package charts/budibase --version 0.0.0-master --app-version v"$RELEASE_VERSION" --destination sync + helm package charts/budibase --version 0.0.0-master --app-version "$RELEASE_VERSION" --destination sync echo "Packaging successful" git checkout gh-pages echo "Indexing helm repo" diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index f4524e99dc..39ee812726 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -43,7 +43,7 @@ jobs: run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - release_tag=v${{ env.RELEASE_VERSION }} + release_tag=${{ env.RELEASE_VERSION }} # Pull apps and worker images docker pull budibase/apps:$release_tag @@ -108,8 +108,8 @@ jobs: - name: Perform Github Release uses: softprops/action-gh-release@v1 with: - name: v${{ env.RELEASE_VERSION }} - tag_name: v${{ env.RELEASE_VERSION }} + name: ${{ env.RELEASE_VERSION }} + tag_name: ${{ env.RELEASE_VERSION }} generate_release_notes: true files: | packages/cli/build/cli-win.exe diff --git a/.github/workflows/release-singleimage.yml b/.github/workflows/release-singleimage.yml index 5408b48ef8..92f21bd649 100644 --- a/.github/workflows/release-singleimage.yml +++ b/.github/workflows/release-singleimage.yml @@ -71,7 +71,7 @@ jobs: context: . push: true platforms: linux/amd64,linux/arm64 - tags: budibase/budibase,budibase/budibase:v${{ env.RELEASE_VERSION }} + tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile - name: Tag and release Budibase Azure App Service docker image uses: docker/build-push-action@v2 @@ -80,5 +80,5 @@ jobs: push: true platforms: linux/amd64 build-args: TARGETBUILD=aas - tags: budibase/budibase-aas,budibase/budibase-aas:v${{ env.RELEASE_VERSION }} + tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml new file mode 100644 index 0000000000..8cda3a9342 --- /dev/null +++ b/.github/workflows/stale_bot.yml @@ -0,0 +1,29 @@ +name: Close stale issues and PRs # https://github.com/actions/stale +on: + workflow_dispatch: + schedule: + - cron: '30 1 * * *' # 1:30 every morning + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + # stale rules + days-before-stale: 60 + days-before-pr-stale: 7 + stale-issue-label: stale + stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for 60 days." + + # close rules + # days after being marked as stale to close + days-before-close: 30 + close-issue-label: closed-stale + close-issue-message: This issue has been automatically closed it has not had any activity in 90 days." + days-before-pr-close: 7 + + # exemptions + exempt-pr-labels: pinned,security,roadmap + + diff --git a/.tool-versions b/.tool-versions index 094292d096..da92e03885 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -nodejs 14.20.1 +nodejs 14.21.3 python 3.10.0 \ No newline at end of file diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index e4825ea5d0..c087627100 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -40,6 +40,24 @@ spec: - image: budibase/proxy:{{ .Values.globals.appVersion | default .Chart.AppVersion }} imagePullPolicy: Always name: proxy-service + livenessProbe: + httpGet: + path: /health + port: {{ .Values.services.proxy.port }} + initialDelaySeconds: 0 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 2 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /health + port: {{ .Values.services.proxy.port }} + initialDelaySeconds: 0 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 2 + timeoutSeconds: 3 ports: - containerPort: {{ .Values.services.proxy.port }} env: diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 2d89e81b7f..74e4c52654 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -209,7 +209,7 @@ services: # Override values in couchDB subchart couchdb: ## clusterSize is the initial size of the CouchDB cluster. - clusterSize: 3 + clusterSize: 1 allowAdminParty: false # Secret Management diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ac35929be1..2fb4c36fa8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -231,18 +231,33 @@ An overview of the CI pipelines can be found [here](../.github/workflows/README. ### Pro -@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g. +@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you need to make an update to pro and have access to the repo, then you can update your submodule within the mono-repo by running `git submodule update --init` - from here you can use normal submodule flow to develop a change within pro. + +Once you have updated to use the pro submodule, it will be linked into all of your local dependencies by NX as with all other monorepo packages. If you have been using the NPM version of `@budibase/pro` then you may need to run a `git reset --hard` to fix all of the pro versions back to `0.0.0` to be monorepo aware. + +From here - to develop a change in pro, you can follow the below flow: ``` -. -|_ budibase -|_ budibase-pro +# enter the pro submodule +cd packages/pro +# get the base branch you are working from (same as monorepo) +git fetch +git checkout +# create a branch, named the same as the branch in your monorepo +git checkout -b +... make changes +# commit the changes you've made, with a message for pro +git commit +# within the monorepo, add the pro reference to your branch, commit it with a message like "Update pro ref" +cd ../.. +git add packages/pro +git commit ``` +From here, you will have created a branch in the pro repository and commited the reference to your branch on the monorepo. When you eventually PR this work back into the mainline branch, you will need to first merge your pro PR to the pro mainline, then go into your PR in the monorepo and update the reference again to the new mainline. + Note that only budibase maintainers will be able to access the pro repo. -By default, NX will make sure that dependencies are replaced with local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev. - ### Troubleshooting Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation. diff --git a/hosting/.env b/hosting/.env index c2b6d55eef..8a0756c0e3 100644 --- a/hosting/.env +++ b/hosting/.env @@ -28,3 +28,4 @@ BB_ADMIN_USER_PASSWORD= # A path that is watched for plugin bundles. Any bundles found are imported automatically/ PLUGINS_DIR= +ROLLING_LOG_MAX_SIZE= \ No newline at end of file diff --git a/hosting/single/nginx/nginx-default-site.conf b/hosting/single/nginx/nginx-default-site.conf index 3903c0647d..d2f8e229c6 100644 --- a/hosting/single/nginx/nginx-default-site.conf +++ b/hosting/single/nginx/nginx-default-site.conf @@ -22,6 +22,16 @@ server { proxy_pass http://127.0.0.1:4001; } + location /embed { + rewrite /embed/(.*) /app/$1 break; + proxy_pass http://127.0.0.1:4001; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header x-budibase-embed "true"; + add_header x-budibase-embed "true"; + add_header Content-Security-Policy "frame-ancestors *"; + } + location = / { proxy_pass http://127.0.0.1:4001; } diff --git a/lerna.json b/lerna.json index d8c5b50815..17c08dcf7f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.2-alpha.2", + "version": "2.8.22-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/nx.json b/nx.json index fc0712eed4..3df61886c2 100644 --- a/nx.json +++ b/nx.json @@ -1,9 +1,13 @@ { "tasksRunnerOptions": { "default": { - "runner": "nx/tasks-runners/default", + "runner": "nx-cloud", "options": { - "cacheableOperations": ["build", "test"] + "cacheableOperations": [ + "build", + "test" + ], + "accessToken": "YWNiYzc5NTEtMzMzZC00NDhjLTgyNjktZTllMjI1MzM4OGQxfHJlYWQtd3JpdGU=" } } }, @@ -11,7 +15,9 @@ "dev:builder": { "dependsOn": [ { - "projects": ["@budibase/string-templates"], + "projects": [ + "@budibase/string-templates" + ], "target": "build" } ] diff --git a/package.json b/package.json index a4b830d40b..3afe279e00 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "devDependencies": { "@esbuild-plugins/tsconfig-paths": "^0.1.2", - "@nx/js": "16.2.1", + "@nx/js": "16.4.3", "@rollup/plugin-json": "^4.0.2", "@typescript-eslint/parser": "5.45.0", "esbuild": "^0.17.18", @@ -13,9 +13,11 @@ "husky": "^8.0.3", "js-yaml": "^4.1.0", "kill-port": "^1.6.1", - "lerna": "7.0.2", + "lerna": "7.1.1", "madge": "^6.0.0", "minimist": "^1.2.8", + "nx": "16.4.3", + "nx-cloud": "16.0.5", "prettier": "2.8.8", "prettier-plugin-svelte": "^2.3.0", "rimraf": "^3.0.2", @@ -44,15 +46,15 @@ "restore": "yarn run clean && yarn run bootstrap && yarn run build", "nuke": "yarn run nuke:packages && yarn run nuke:docker", "nuke:packages": "yarn run restore", - "nuke:docker": "lerna run --stream --parallel dev:stack:nuke", + "nuke:docker": "lerna run --stream dev:stack:nuke", "clean": "lerna clean", "kill-builder": "kill-port 3000", "kill-server": "kill-port 4001 4002", "kill-all": "yarn run kill-builder && yarn run kill-server", - "dev": "yarn run kill-all && lerna run --stream --parallel dev:builder --stream", - "dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", - "dev:server": "yarn run kill-server && yarn build --projects=@budibase/client && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server", - "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built", + "dev": "yarn run kill-all && lerna run --stream dev:builder --stream", + "dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", + "dev:server": "yarn run kill-server && lerna run --stream dev:builder --scope @budibase/worker --scope @budibase/server", + "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built", "dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0", "test": "lerna run --stream test --stream", "lint:eslint": "eslint packages qa-core --max-warnings=0", @@ -106,6 +108,5 @@ "@budibase/string-templates": "0.0.0", "@budibase/types": "0.0.0" }, - "dependencies": { - } + "dependencies": {} } diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 4a1ed5c373..7f3c064c92 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -51,6 +51,7 @@ "pouchdb": "7.3.0", "pouchdb-find": "7.2.2", "redlock": "4.2.0", + "rotating-file-stream": "3.1.0", "sanitize-s3-objectkey": "0.0.1", "semver": "7.3.7", "tar-fs": "2.1.1", diff --git a/packages/backend-core/src/auth/auth.ts b/packages/backend-core/src/auth/auth.ts index fb2fd2cf51..0100a2d0e2 100644 --- a/packages/backend-core/src/auth/auth.ts +++ b/packages/backend-core/src/auth/auth.ts @@ -159,7 +159,7 @@ export async function updateUserOAuth(userId: string, oAuthConfig: any) { try { const db = getGlobalDB() - const dbUser = await db.get(userId) + const dbUser = await db.get(userId) //Do not overwrite the refresh token if a valid one is not provided. if (typeof details.refreshToken !== "string") { diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index b514c3af9b..8281bfca62 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -12,7 +12,7 @@ const EXPIRY_SECONDS = 3600 */ async function populateFromDB(userId: string, tenantId: string) { const db = tenancy.getTenantDB(tenantId) - const user = await db.get(userId) + const user = await db.get(userId) user.budibaseAccess = true if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { const account = await accounts.getAccount(user.email) diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index ba2533cf4a..0c68798164 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -20,6 +20,8 @@ export enum Header { TYPE = "x-budibase-type", PREVIEW_ROLE = "x-budibase-role", TENANT_ID = "x-budibase-tenant-id", + VERIFICATION_CODE = "x-budibase-verification-code", + RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code", TOKEN = "x-budibase-token", CSRF_TOKEN = "x-csrf-token", CORRELATION_ID = "x-budibase-correlation-id", diff --git a/packages/backend-core/src/db/lucene.ts b/packages/backend-core/src/db/lucene.ts index d501bb2166..a491451a62 100644 --- a/packages/backend-core/src/db/lucene.ts +++ b/packages/backend-core/src/db/lucene.ts @@ -433,6 +433,9 @@ export class QueryBuilder { if (!value) { return null } + if (typeof value === "boolean") { + return `(*:* AND !${key}:${value})` + } return `!${key}:${builder.preprocess(value, allPreProcessingOpts)}` }) } diff --git a/packages/backend-core/src/db/searchIndexes/searchIndexes.ts b/packages/backend-core/src/db/searchIndexes/searchIndexes.ts index f03259b47f..b953e3516e 100644 --- a/packages/backend-core/src/db/searchIndexes/searchIndexes.ts +++ b/packages/backend-core/src/db/searchIndexes/searchIndexes.ts @@ -5,7 +5,7 @@ export async function createUserIndex() { const db = getGlobalDB() let designDoc try { - designDoc = await db.get("_design/database") + designDoc = await db.get("_design/database") } catch (err: any) { if (err.status === 404) { designDoc = { _id: "_design/database" } diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 6e3707718e..f29720e7f7 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -47,7 +47,10 @@ function httpLogging() { return process.env.HTTP_LOGGING } -function findVersion() { +function getPackageJsonFields(): { + VERSION: string + SERVICE_NAME: string +} { function findFileInAncestors( fileName: string, currentDir: string @@ -69,10 +72,14 @@ function findVersion() { try { const packageJsonFile = findFileInAncestors("package.json", process.cwd()) const content = readFileSync(packageJsonFile!, "utf-8") - return JSON.parse(content).version + const parsedContent = JSON.parse(content) + return { + VERSION: parsedContent.version, + SERVICE_NAME: parsedContent.name, + } } catch { // throwing an error here is confusing/causes backend-core to be hard to import - return undefined + return { VERSION: "", SERVICE_NAME: "" } } } @@ -155,13 +162,14 @@ const environment = { ENABLE_SSO_MAINTENANCE_MODE: selfHosted ? process.env.ENABLE_SSO_MAINTENANCE_MODE : false, - VERSION: findVersion(), + ...getPackageJsonFields(), DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER, _set(key: any, value: any) { process.env[key] = value // @ts-ignore environment[key] = value }, + ROLLING_LOG_MAX_SIZE: process.env.ROLLING_LOG_MAX_SIZE || "10M", } // clean up any environment variable edge cases diff --git a/packages/backend-core/src/logging/index.ts b/packages/backend-core/src/logging/index.ts index b87062c478..0824fa681b 100644 --- a/packages/backend-core/src/logging/index.ts +++ b/packages/backend-core/src/logging/index.ts @@ -1,6 +1,4 @@ export * as correlation from "./correlation/correlation" export { logger } from "./pino/logger" export * from "./alerts" - -// turn off or on context logging i.e. tenantId, appId etc -export let LOG_CONTEXT = true +export * as system from "./system" diff --git a/packages/backend-core/src/logging/pino/logger.ts b/packages/backend-core/src/logging/pino/logger.ts index c96bc83e04..7c444a3a59 100644 --- a/packages/backend-core/src/logging/pino/logger.ts +++ b/packages/backend-core/src/logging/pino/logger.ts @@ -1,37 +1,60 @@ -import env from "../../environment" import pino, { LoggerOptions } from "pino" +import pinoPretty from "pino-pretty" + +import { IdentityType } from "@budibase/types" +import env from "../../environment" import * as context from "../../context" import * as correlation from "../correlation" -import { IdentityType } from "@budibase/types" -import { LOG_CONTEXT } from "../index" + +import { localFileDestination } from "../system" // LOGGER let pinoInstance: pino.Logger | undefined if (!env.DISABLE_PINO_LOGGER) { + const level = env.LOG_LEVEL const pinoOptions: LoggerOptions = { - level: env.LOG_LEVEL, + level, formatters: { - level: label => { - return { level: label.toUpperCase() } + level: level => { + return { level: level.toUpperCase() } }, bindings: () => { - return {} + if (env.SELF_HOSTED) { + // "service" is being injected in datadog using the pod names, + // so we should leave it blank to allow the default behaviour if it's not running self-hosted + return { + service: env.SERVICE_NAME, + } + } else { + return {} + } }, }, timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`, } - if (env.isDev()) { - pinoOptions.transport = { - target: "pino-pretty", - options: { - singleLine: true, - }, - } + const destinations: pino.StreamEntry[] = [] + + destinations.push( + env.isDev() + ? { + stream: pinoPretty({ singleLine: true }), + level: level as pino.Level, + } + : { stream: process.stdout, level: level as pino.Level } + ) + + if (env.SELF_HOSTED) { + destinations.push({ + stream: localFileDestination(), + level: level as pino.Level, + }) } - pinoInstance = pino(pinoOptions) + pinoInstance = destinations.length + ? pino(pinoOptions, pino.multistream(destinations)) + : pino(pinoOptions) // CONSOLE OVERRIDES @@ -83,15 +106,13 @@ if (!env.DISABLE_PINO_LOGGER) { let contextObject = {} - if (LOG_CONTEXT) { - contextObject = { - tenantId: getTenantId(), - appId: getAppId(), - automationId: getAutomationId(), - identityId: identity?._id, - identityType: identity?.type, - correlationId: correlation.getId(), - } + contextObject = { + tenantId: getTenantId(), + appId: getAppId(), + automationId: getAutomationId(), + identityId: identity?._id, + identityType: identity?.type, + correlationId: correlation.getId(), } const mergingObject: any = { diff --git a/packages/backend-core/src/logging/system.ts b/packages/backend-core/src/logging/system.ts new file mode 100644 index 0000000000..d918c6efd6 --- /dev/null +++ b/packages/backend-core/src/logging/system.ts @@ -0,0 +1,81 @@ +import fs from "fs" +import path from "path" +import * as rfs from "rotating-file-stream" + +import env from "../environment" +import { budibaseTempDir } from "../objectStore" + +const logsFileName = `budibase.log` +const budibaseLogsHistoryFileName = "budibase-logs-history.txt" + +const logsPath = path.join(budibaseTempDir(), "systemlogs") + +function getFullPath(fileName: string) { + return path.join(logsPath, fileName) +} + +export function getSingleFileMaxSizeInfo(totalMaxSize: string) { + const regex = /(\d+)([A-Za-z])/ + const match = totalMaxSize?.match(regex) + if (!match) { + console.warn(`totalMaxSize does not have a valid value`, { + totalMaxSize, + }) + return undefined + } + + const size = +match[1] + const unit = match[2] + if (size === 1) { + switch (unit) { + case "B": + return { size: `${size}B`, totalHistoryFiles: 1 } + case "K": + return { size: `${(size * 1000) / 2}B`, totalHistoryFiles: 1 } + case "M": + return { size: `${(size * 1000) / 2}K`, totalHistoryFiles: 1 } + case "G": + return { size: `${(size * 1000) / 2}M`, totalHistoryFiles: 1 } + default: + return undefined + } + } + + if (size % 2 === 0) { + return { size: `${size / 2}${unit}`, totalHistoryFiles: 1 } + } + + return { size: `1${unit}`, totalHistoryFiles: size - 1 } +} + +export function localFileDestination() { + const fileInfo = getSingleFileMaxSizeInfo(env.ROLLING_LOG_MAX_SIZE) + const outFile = rfs.createStream(logsFileName, { + // As we have a rolling size, we want to half the max size + size: fileInfo?.size, + path: logsPath, + maxFiles: fileInfo?.totalHistoryFiles || 1, + immutable: true, + history: budibaseLogsHistoryFileName, + initialRotation: false, + }) + + return outFile +} + +export function getLogReadStream() { + const streams = [] + const historyFile = getFullPath(budibaseLogsHistoryFileName) + if (fs.existsSync(historyFile)) { + const fileContent = fs.readFileSync(historyFile, "utf-8") + const historyFiles = fileContent.split("\n") + for (const historyFile of historyFiles.filter(x => x)) { + streams.push(fs.readFileSync(historyFile)) + } + } + + streams.push(fs.readFileSync(getFullPath(logsFileName))) + + const combinedContent = Buffer.concat(streams) + return combinedContent +} diff --git a/packages/backend-core/src/logging/tests/system.spec.ts b/packages/backend-core/src/logging/tests/system.spec.ts new file mode 100644 index 0000000000..b84d8e8456 --- /dev/null +++ b/packages/backend-core/src/logging/tests/system.spec.ts @@ -0,0 +1,61 @@ +import { getSingleFileMaxSizeInfo } from "../system" + +describe("system", () => { + describe("getSingleFileMaxSizeInfo", () => { + it.each([ + ["100B", "50B"], + ["200K", "100K"], + ["20M", "10M"], + ["4G", "2G"], + ])( + "Halving even number (%s) returns halved size and 1 history file (%s)", + (totalValue, expectedMaxSize) => { + const result = getSingleFileMaxSizeInfo(totalValue) + expect(result).toEqual({ + size: expectedMaxSize, + totalHistoryFiles: 1, + }) + } + ) + + it.each([ + ["5B", "1B", 4], + ["17K", "1K", 16], + ["21M", "1M", 20], + ["3G", "1G", 2], + ])( + "Halving an odd number (%s) returns as many files as size (-1) (%s)", + (totalValue, expectedMaxSize, totalHistoryFiles) => { + const result = getSingleFileMaxSizeInfo(totalValue) + expect(result).toEqual({ + size: expectedMaxSize, + totalHistoryFiles, + }) + } + ) + + it.each([ + ["1B", "1B"], + ["1K", "500B"], + ["1M", "500K"], + ["1G", "500M"], + ])( + "Halving '%s' returns halved unit (%s)", + (totalValue, expectedMaxSize) => { + const result = getSingleFileMaxSizeInfo(totalValue) + expect(result).toEqual({ + size: expectedMaxSize, + totalHistoryFiles: 1, + }) + } + ) + + it.each([[undefined], [""], ["50"], ["wrongvalue"]])( + "Halving wrongly formatted value ('%s') returns undefined", + totalValue => { + const result = getSingleFileMaxSizeInfo(totalValue!) + expect(result).toBeUndefined() + } + ) + }) +}) diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 166136df3c..b49058f546 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -67,9 +67,9 @@ export const bulkUpdateGlobalUsers = async (users: User[]) => { export async function getById(id: string, opts?: GetOpts): Promise { const db = context.getGlobalDB() - let user = await db.get(id) + let user = await db.get(id) if (opts?.cleanup) { - user = removeUserPassword(user) + user = removeUserPassword(user) as User } return user } diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index 91affdb6c7..9e49d84d44 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -1,6 +1,7 @@ - + + + diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 782c3c6ed2..4ff4df854b 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -30,6 +30,7 @@ setContext("drawer-actions", { hide, show, + headless, }) const easeInOutQuad = x => { diff --git a/packages/bbui/src/Form/Core/CheckboxGroup.svelte b/packages/bbui/src/Form/Core/CheckboxGroup.svelte index 640d5d99cd..2b8a1e438a 100644 --- a/packages/bbui/src/Form/Core/CheckboxGroup.svelte +++ b/packages/bbui/src/Form/Core/CheckboxGroup.svelte @@ -12,23 +12,24 @@ export let getOptionValue = option => option const dispatch = createEventDispatcher() + const onChange = e => { - let tempValue = value - let isChecked = e.target.checked - if (!tempValue.includes(e.target.value) && isChecked) { - tempValue.push(e.target.value) + const optionValue = e.target.value + if (e.target.checked && !value.includes(optionValue)) { + dispatch("change", [...value, optionValue]) + } else { + dispatch( + "change", + value.filter(x => x !== optionValue) + ) } - value = tempValue - dispatch( - "change", - tempValue.filter(val => val !== e.target.value || isChecked) - ) }
{#if options && Array.isArray(options)} {#each options as option} + {@const optionValue = getOptionValue(option)}
(open = false)} />
    diff --git a/packages/bbui/src/Form/Core/TextField.svelte b/packages/bbui/src/Form/Core/TextField.svelte index acc2169a06..b3b0865c64 100644 --- a/packages/bbui/src/Form/Core/TextField.svelte +++ b/packages/bbui/src/Form/Core/TextField.svelte @@ -62,6 +62,13 @@ } } + const getInputMode = type => { + if (type === "bigint") { + return "numeric" + } + return type === "number" ? "decimal" : "text" + } + onMount(() => { focus = autofocus if (focus) field.focus() @@ -103,7 +110,7 @@ {type} class="spectrum-Textfield-input" style={align ? `text-align: ${align};` : ""} - inputmode={type === "number" ? "decimal" : "text"} + inputmode={getInputMode(type)} {autocomplete} />
diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index 452a8c74a1..11dc9963d5 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -47,7 +47,7 @@ {#if tooltip && showTooltip}
- +
{/if}
@@ -80,15 +80,14 @@ position: absolute; pointer-events: none; left: 50%; - top: calc(100% + 4px); - width: 100vw; - max-width: 150px; + bottom: calc(100% + 4px); transform: translateX(-50%); text-align: center; + z-index: 1; } .spectrum-Icon--sizeXS { - width: 10px; - height: 10px; + width: var(--spectrum-global-dimension-size-150); + height: var(--spectrum-global-dimension-size-150); } diff --git a/packages/bbui/src/Popover/Popover.svelte b/packages/bbui/src/Popover/Popover.svelte index 20207a85da..9f951a6a7e 100644 --- a/packages/bbui/src/Popover/Popover.svelte +++ b/packages/bbui/src/Popover/Popover.svelte @@ -90,6 +90,6 @@ .spectrum-Popover { min-width: var(--spectrum-global-dimension-size-2000); border-color: var(--spectrum-global-color-gray-300); - overflow: visible; + overflow: auto; } diff --git a/packages/bbui/src/Tooltip/AbsTooltip.svelte b/packages/bbui/src/Tooltip/AbsTooltip.svelte new file mode 100644 index 0000000000..9be7251445 --- /dev/null +++ b/packages/bbui/src/Tooltip/AbsTooltip.svelte @@ -0,0 +1,157 @@ + + + + +
(hovered = true)} + on:mouseleave={() => (hovered = false)} +> + +
+ +{#if visible && text && left != null && top != null} + + + {text} + + + +{/if} + + diff --git a/packages/bbui/src/Tooltip/TempTooltip.svelte b/packages/bbui/src/Tooltip/TempTooltip.svelte new file mode 100644 index 0000000000..0d590b1ec6 --- /dev/null +++ b/packages/bbui/src/Tooltip/TempTooltip.svelte @@ -0,0 +1,39 @@ + + + + + diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index a9d71723f8..97762d2b3a 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -36,6 +36,12 @@ export { default as Layout } from "./Layout/Layout.svelte" export { default as Page } from "./Layout/Page.svelte" export { default as Link } from "./Link/Link.svelte" export { default as Tooltip } from "./Tooltip/Tooltip.svelte" +export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte" +export { + default as AbsTooltip, + TooltipPosition, + TooltipType, +} from "./Tooltip/AbsTooltip.svelte" export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte" export { default as Menu } from "./Menu/Menu.svelte" export { default as MenuSection } from "./Menu/Section.svelte" diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index e8fba9a0ee..2ca8057b48 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -127,13 +127,17 @@ export const selectedAutomation = derived(automationStore, $automationStore => { export const userSelectedResourceMap = derived(userStore, $userStore => { let map = {} $userStore.forEach(user => { - if (user.builderMetadata?.selectedResourceId) { - map[user.builderMetadata?.selectedResourceId] = user + const resource = user.builderMetadata?.selectedResourceId + if (resource) { + if (!map[resource]) { + map[resource] = [] + } + map[resource].push(user) } }) return map }) export const isOnlyUser = derived(userStore, $userStore => { - return $userStore.length === 1 + return $userStore.length < 2 }) diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index 9e5516c512..4ebf0515d6 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -248,4 +248,36 @@ const automationActions = store => ({ } await store.actions.save(newAutomation) }, + replace: async (automationId, automation) => { + if (!automation) { + store.update(state => { + // Remove the automation + state.automations = state.automations.filter( + x => x._id !== automationId + ) + // Select a new automation if required + if (automationId === state.selectedAutomationId) { + store.actions.select(state.automations[0]?._id) + } + return state + }) + } else { + const index = get(store).automations.findIndex( + x => x._id === automation._id + ) + if (index === -1) { + // Automation addition + store.update(state => ({ + ...state, + automations: [...state.automations, automation], + })) + } else { + // Automation update + store.update(state => { + state.automations[index] = automation + return state + }) + } + } + }, }) diff --git a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js index 6fdedbe44c..ace5196bba 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js +++ b/packages/builder/src/builderStore/store/screenTemplates/utils/commonComponents.js @@ -4,6 +4,7 @@ import { getSchemaForDatasource } from "../../../dataBinding" const fieldTypeToComponentMap = { string: "stringfield", number: "numberfield", + bigint: "bigintfield", options: "optionsfield", array: "multifieldselect", boolean: "booleanfield", diff --git a/packages/builder/src/builderStore/websocket.js b/packages/builder/src/builderStore/websocket.js index 87195bed25..6121831c38 100644 --- a/packages/builder/src/builderStore/websocket.js +++ b/packages/builder/src/builderStore/websocket.js @@ -1,5 +1,10 @@ import { createWebsocket } from "@budibase/frontend-core" -import { userStore, store, deploymentStore } from "builderStore" +import { + userStore, + store, + deploymentStore, + automationStore, +} from "builderStore" import { datasources, tables } from "stores/backend" import { get } from "svelte/store" import { auth } from "stores/portal" @@ -67,5 +72,10 @@ export const createBuilderWebsocket = appId => { } ) + // Automations + socket.onOther(BuilderSocketEvent.AutomationChange, ({ id, automation }) => { + automationStore.actions.replace(id, automation) + }) + return socket } diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte index 80d65a5cb6..cce0f4eeab 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationList.svelte @@ -1,6 +1,10 @@
- {#each $automationStore.automations.sort(aut => aut.name) as automation, idx} + {#each $automationStore.automations.sort(aut => aut.name) as automation} 0} icon="ShareAndroid" text={automation.name} selected={automation._id === selectedAutomationId} on:click={() => selectAutomation(automation._id)} + selectedBy={$userSelectedResourceMap[automation._id]} > @@ -40,6 +44,5 @@ flex-direction: column; justify-content: flex-start; align-items: stretch; - margin: 0 calc(-1 * var(--spacing-xl)); } diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte index 85e6a5faa3..fc52b7323a 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte @@ -11,8 +11,8 @@ - + diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 823dcc432b..cece075860 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -15,6 +15,7 @@ Icon, Checkbox, DatePicker, + Detail, } from "@budibase/bbui" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import { automationStore, selectedAutomation } from "builderStore" @@ -32,7 +33,7 @@ import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import { bindingsToCompletions, - jsAutocomplete, + hbAutocomplete, EditorModes, } from "components/common/CodeEditor" import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte" @@ -55,6 +56,7 @@ let drawer let fillWidth = true let inputData + let codeBindingOpen = false $: filters = lookForFilters(schemaProperties) || [] $: tempFilters = filters @@ -70,6 +72,13 @@ $: queryLimit = tableId?.includes("datasource") ? "∞" : "1000" $: isTrigger = block?.type === "TRIGGER" $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW + $: codeMode = + stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS + + $: stepCompletions = + codeMode === EditorModes.Handlebars + ? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])] + : [] /** * TODO - Remove after November 2023 @@ -489,6 +498,18 @@ /> {:else if value.customType === "code"} + {#if codeMode == EditorModes.JS} + (codeBindingOpen = !codeBindingOpen)} + quiet + icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} + > + Bindings + + {#if codeBindingOpen} +
{JSON.stringify(bindings, null, 2)}
+ {/if} + {/if} { @@ -496,19 +517,22 @@ onChange({ detail: e.detail }, key) inputData[key] = e.detail }} - completions={[ - jsAutocomplete([ - ...bindingsToCompletions(bindings, EditorModes.JS), - ]), - ]} - mode={EditorModes.JS} + completions={stepCompletions} + mode={codeMode} + autocompleteEnabled={codeMode != EditorModes.JS} height={500} />
- -
-
Add available bindings by typing $
-
+ {#if codeMode == EditorModes.Handlebars} + +
+
+ Add available bindings by typing + }} + +
+
+ {/if}
{:else if value.customType === "loopOption"} diff --git a/packages/builder/src/components/backend/DataTable/Table.svelte b/packages/builder/src/components/backend/DataTable/Table.svelte index 3c5b93df89..4569586762 100644 --- a/packages/builder/src/components/backend/DataTable/Table.svelte +++ b/packages/builder/src/components/backend/DataTable/Table.svelte @@ -109,6 +109,7 @@ {disableSorting} {customPlaceholder} allowEditRows={allowEditing} + allowEditColumns={allowEditing} showAutoColumns={!hideAutocolumns} {allowClickRows} on:clickrelationship={e => selectRelationship(e.detail)} diff --git a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte index ce6a3f0c51..b96738ab1a 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/TableFilterButton.svelte @@ -17,7 +17,7 @@ $: text = getText(filters) const getText = filters => { - const count = filters?.length + const count = filters?.filter(filter => filter.field)?.length return count ? `Filter (${count})` : "Filter" } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 620783be34..4761ccee02 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -18,7 +18,7 @@ import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { FIELDS, - RelationshipTypes, + RelationshipType, ALLOWABLE_STRING_OPTIONS, ALLOWABLE_NUMBER_OPTIONS, ALLOWABLE_STRING_TYPES, @@ -33,6 +33,7 @@ import { getBindings } from "components/backend/DataTable/formula" import { getContext } from "svelte" import JSONSchemaModal from "./JSONSchemaModal.svelte" + import { ValidColumnNameRegex } from "@budibase/shared-core" const AUTO_TYPE = "auto" const FORMULA_TYPE = FIELDS.FORMULA.type @@ -183,7 +184,7 @@ dispatch("updatecolumns") if ( saveColumn.type === LINK_TYPE && - saveColumn.relationshipType === RelationshipTypes.MANY_TO_MANY + saveColumn.relationshipType === RelationshipType.MANY_TO_MANY ) { // Fetching the new tables tables.fetch() @@ -237,7 +238,7 @@ // Default relationships many to many if (editableColumn.type === LINK_TYPE) { - editableColumn.relationshipType = RelationshipTypes.MANY_TO_MANY + editableColumn.relationshipType = RelationshipType.MANY_TO_MANY } if (editableColumn.type === FORMULA_TYPE) { editableColumn.formulaType = "dynamic" @@ -285,17 +286,17 @@ { name: `Many ${thisName} rows → many ${linkName} rows`, alt: `Many ${table.name} rows → many ${linkTable.name} rows`, - value: RelationshipTypes.MANY_TO_MANY, + value: RelationshipType.MANY_TO_MANY, }, { name: `One ${linkName} row → many ${thisName} rows`, alt: `One ${linkTable.name} rows → many ${table.name} rows`, - value: RelationshipTypes.ONE_TO_MANY, + value: RelationshipType.ONE_TO_MANY, }, { name: `One ${thisName} row → many ${linkName} rows`, alt: `One ${table.name} rows → many ${linkTable.name} rows`, - value: RelationshipTypes.MANY_TO_ONE, + value: RelationshipType.MANY_TO_ONE, }, ] } @@ -326,6 +327,7 @@ FIELDS.NUMBER, FIELDS.BOOLEAN, FIELDS.FORMULA, + FIELDS.BIGINT, ] // no-sql or a spreadsheet if (!external || table.sql) { @@ -374,7 +376,7 @@ const newError = {} if (!external && fieldInfo.name?.startsWith("_")) { newError.name = `Column name cannot start with an underscore.` - } else if (fieldInfo.name && !fieldInfo.name.match(/^[_a-zA-Z0-9\s]*$/g)) { + } else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) { newError.name = `Illegal character; must be alpha-numeric.` } else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) { newError.name = `${PROHIBITED_COLUMN_NAMES.join( diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte index 01964aed75..c18ba313e0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte @@ -95,9 +95,9 @@ {#if !creating}
A user's email, role, first and last names cannot be changed from within - the app builder. Please go to the user portal to do this. + the app builder. Please go to the + user portal + to do this.
{/if} - import { RelationshipTypes } from "constants/backend" + import { RelationshipType } from "constants/backend" import { keepOpen, Button, @@ -25,11 +25,11 @@ const relationshipTypes = [ { label: "One to Many", - value: RelationshipTypes.MANY_TO_ONE, + value: RelationshipType.MANY_TO_ONE, }, { label: "Many to Many", - value: RelationshipTypes.MANY_TO_MANY, + value: RelationshipType.MANY_TO_MANY, }, ] @@ -58,8 +58,8 @@ value: table._id, })) $: valid = getErrorCount(errors) === 0 && allRequiredAttributesSet() - $: isManyToMany = relationshipType === RelationshipTypes.MANY_TO_MANY - $: isManyToOne = relationshipType === RelationshipTypes.MANY_TO_ONE + $: isManyToMany = relationshipType === RelationshipType.MANY_TO_MANY + $: isManyToOne = relationshipType === RelationshipType.MANY_TO_ONE function getTable(id) { return plusTables.find(table => table._id === id) @@ -116,7 +116,7 @@ function allRequiredAttributesSet() { const base = getTable(fromId) && getTable(toId) && fromColumn && toColumn - if (relationshipType === RelationshipTypes.MANY_TO_ONE) { + if (relationshipType === RelationshipType.MANY_TO_ONE) { return base && fromPrimary && fromForeign } else { return base && getTable(throughId) && throughFromKey && throughToKey @@ -181,12 +181,12 @@ } function otherRelationshipType(type) { - if (type === RelationshipTypes.MANY_TO_ONE) { - return RelationshipTypes.ONE_TO_MANY - } else if (type === RelationshipTypes.ONE_TO_MANY) { - return RelationshipTypes.MANY_TO_ONE - } else if (type === RelationshipTypes.MANY_TO_MANY) { - return RelationshipTypes.MANY_TO_MANY + if (type === RelationshipType.MANY_TO_ONE) { + return RelationshipType.ONE_TO_MANY + } else if (type === RelationshipType.ONE_TO_MANY) { + return RelationshipType.MANY_TO_ONE + } else if (type === RelationshipType.MANY_TO_MANY) { + return RelationshipType.MANY_TO_MANY } } @@ -218,7 +218,7 @@ // if any to many only need to check from const manyToMany = - relateFrom.relationshipType === RelationshipTypes.MANY_TO_MANY + relateFrom.relationshipType === RelationshipType.MANY_TO_MANY if (!manyToMany) { delete relateFrom.through @@ -253,7 +253,7 @@ } relateTo = { ...relateTo, - relationshipType: RelationshipTypes.ONE_TO_MANY, + relationshipType: RelationshipType.ONE_TO_MANY, foreignKey: relateFrom.fieldName, fieldName: fromPrimary, } @@ -321,7 +321,7 @@ fromColumn = toRelationship.name } relationshipType = - fromRelationship.relationshipType || RelationshipTypes.MANY_TO_ONE + fromRelationship.relationshipType || RelationshipType.MANY_TO_ONE if (selectedFromTable) { fromId = selectedFromTable._id fromColumn = selectedFromTable.name diff --git a/packages/builder/src/components/backend/Datasources/TableImportSelection/tableSelectionStore.js b/packages/builder/src/components/backend/Datasources/TableImportSelection/tableSelectionStore.js index 3cb3ef96a6..6235ea397a 100644 --- a/packages/builder/src/components/backend/Datasources/TableImportSelection/tableSelectionStore.js +++ b/packages/builder/src/components/backend/Datasources/TableImportSelection/tableSelectionStore.js @@ -10,9 +10,8 @@ export const createTableSelectionStore = (integration, datasource) => { datasources.getTableNames(datasource).then(tableNames => { tableNamesStore.set(tableNames) - selectedTableNamesStore.set( - tableNames.filter(tableName => datasource.entities[tableName]) + tableNames.filter(tableName => datasource.entities?.[tableName]) ) loadingStore.set(false) diff --git a/packages/builder/src/components/backend/Datasources/relationshipErrors.js b/packages/builder/src/components/backend/Datasources/relationshipErrors.js index 0dc9b264b9..259484e9a9 100644 --- a/packages/builder/src/components/backend/Datasources/relationshipErrors.js +++ b/packages/builder/src/components/backend/Datasources/relationshipErrors.js @@ -1,4 +1,4 @@ -import { RelationshipTypes } from "constants/backend" +import { RelationshipType } from "constants/backend" const typeMismatch = "Column type of the foreign key must match the primary key" const columnBeingUsed = "Column name cannot be an existing column" @@ -40,7 +40,7 @@ export class RelationshipErrorChecker { } isMany() { - return this.type === RelationshipTypes.MANY_TO_MANY + return this.type === RelationshipType.MANY_TO_MANY } relationshipTypeSet(type) { diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 40af470b4d..ca76037b9d 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -1,17 +1,9 @@
@@ -119,10 +128,8 @@ on:change={handleFile} />
{/each} @@ -167,7 +177,7 @@ {:else if filter.type === "array" || (filter.type === "options" && filter.operator === "oneOf")} filter.field)) async function saveFilter() { dispatch("change", tempValue) diff --git a/packages/builder/src/components/integration/RestQueryViewer.svelte b/packages/builder/src/components/integration/RestQueryViewer.svelte index 88c10422de..254f65fcaf 100644 --- a/packages/builder/src/components/integration/RestQueryViewer.svelte +++ b/packages/builder/src/components/integration/RestQueryViewer.svelte @@ -419,16 +419,22 @@ if (query && !query.fields.pagination) { query.fields.pagination = {} } - dynamicVariables = getDynamicVariables( - datasource, - query._id, - (variable, queryId) => variable.queryId === queryId - ) - globalDynamicBindings = getDynamicVariables( - datasource, - query._id, - (variable, queryId) => variable.queryId !== queryId - ) + // if query doesn't have ID then its new - don't try to copy existing dynamic variables + if (!queryId) { + dynamicVariables = [] + globalDynamicBindings = getDynamicVariables(datasource) + } else { + dynamicVariables = getDynamicVariables( + datasource, + query._id, + (variable, queryId) => variable.queryId === queryId + ) + globalDynamicBindings = getDynamicVariables( + datasource, + query._id, + (variable, queryId) => variable.queryId !== queryId + ) + } prettifyQueryRequestBody( query, diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index d4958b386e..d959a6ef95 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -1,5 +1,5 @@
@@ -18,11 +15,6 @@ {text || ""}
{/if} - {#if tooltip} -
- -
- {/if}
diff --git a/packages/builder/src/components/start/AppRow.svelte b/packages/builder/src/components/start/AppRow.svelte index 50e6b8466a..2e7719987d 100644 --- a/packages/builder/src/components/start/AppRow.svelte +++ b/packages/builder/src/components/start/AppRow.svelte @@ -2,12 +2,12 @@ import { Heading, Body, Button, Icon } from "@budibase/bbui" import { processStringSync } from "@budibase/string-templates" import { goto } from "@roxi/routify" - import { UserAvatar } from "@budibase/frontend-core" + import { UserAvatars } from "@budibase/frontend-core" export let app export let lockedAction - $: editing = app?.lockedBy != null + $: editing = app.sessions?.length const handleDefaultClick = () => { if (window.innerWidth < 640) { @@ -41,7 +41,7 @@
{#if editing} Currently editing - + {:else if app.updatedAt} {processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: new Date().getTime() - new Date(app.updatedAt).getTime(), diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index f29b7067c3..9e831f5bd9 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -15,8 +15,6 @@ import { createValidationStore } from "helpers/validation/yup" import * as appValidation from "helpers/validation/yup/app" import TemplateCard from "components/common/TemplateCard.svelte" - import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen" - import { Roles } from "constants/backend" import { lowercase } from "helpers" export let template @@ -142,21 +140,6 @@ // Create user await auth.setInitInfo({}) - // Create a default home screen if no template was selected - if (template == null) { - let defaultScreenTemplate = createFromScratchScreen.create() - defaultScreenTemplate.routing.route = "/home" - defaultScreenTemplate.routing.roldId = Roles.BASIC - try { - await store.actions.screens.save(defaultScreenTemplate) - } catch (err) { - console.error("Could not create a default application screen", err) - notifications.warning( - "Encountered an issue creating the default screen." - ) - } - } - $goto(`/builder/app/${createdApp.instance._id}`) } catch (error) { creating = false diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 40e6e753a8..d1dcad24c7 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -53,6 +53,10 @@ export const FIELDS = { numericality: { greaterThanOrEqualTo: "", lessThanOrEqualTo: "" }, }, }, + BIGINT: { + name: "BigInt", + type: "bigint", + }, BOOLEAN: { name: "Boolean", type: "boolean", @@ -147,7 +151,7 @@ export function isAutoColumnUserRelationship(subtype) { ) } -export const RelationshipTypes = { +export const RelationshipType = { MANY_TO_MANY: "many-to-many", ONE_TO_MANY: "one-to-many", MANY_TO_ONE: "many-to-one", diff --git a/packages/builder/src/helpers/helpers.js b/packages/builder/src/helpers/helpers.js index 72c034d9a6..bf5cdc003b 100644 --- a/packages/builder/src/helpers/helpers.js +++ b/packages/builder/src/helpers/helpers.js @@ -29,3 +29,15 @@ export const lowercase = s => s.substring(0, 1).toLowerCase() + s.substring(1) export const get_name = s => (!s ? "" : last(s.split("/"))) export const get_capitalised_name = name => pipe(name, [get_name, capitalise]) + +export const isBuilderInputFocused = e => { + const activeTag = document.activeElement?.tagName.toLowerCase() + const inCodeEditor = document.activeElement?.classList?.contains("cm-content") + if ( + (inCodeEditor || ["input", "textarea"].indexOf(activeTag) !== -1) && + e.key !== "Escape" + ) { + return true + } + return false +} diff --git a/packages/builder/src/helpers/index.js b/packages/builder/src/helpers/index.js index 2b272998c1..687723e361 100644 --- a/packages/builder/src/helpers/index.js +++ b/packages/builder/src/helpers/index.js @@ -7,4 +7,5 @@ export { get_name, get_capitalised_name, lowercase, + isBuilderInputFocused, } from "./helpers" diff --git a/packages/builder/src/pages/builder/app/[application]/_components/PreviewOverlay.svelte b/packages/builder/src/pages/builder/app/[application]/_components/PreviewOverlay.svelte index d069d1b4c7..eedff6c2a7 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/PreviewOverlay.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/PreviewOverlay.svelte @@ -15,6 +15,7 @@ } onMount(() => { + window.isBuilder = true window.closePreview = () => { store.update(state => ({ ...state, diff --git a/packages/builder/src/pages/builder/app/[application]/_components/UserAvatars.svelte b/packages/builder/src/pages/builder/app/[application]/_components/UserAvatars.svelte deleted file mode 100644 index 0cd8c5bf6b..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/_components/UserAvatars.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -
- {#each uniqueUsers as user} - - {/each} -
- - diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index e6c79335a9..872151b4a3 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -15,6 +15,7 @@ Heading, Modal, notifications, + TooltipPosition, } from "@budibase/bbui" import AppActions from "components/deploy/AppActions.svelte" import { API } from "api" @@ -25,8 +26,8 @@ import TourWrap from "components/portal/onboarding/TourWrap.svelte" import TourPopover from "components/portal/onboarding/TourPopover.svelte" import BuilderSidePanel from "./_components/BuilderSidePanel.svelte" - import UserAvatars from "./_components/UserAvatars.svelte" - import { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js" + import { UserAvatars } from "@budibase/frontend-core" + import { TOUR_KEYS } from "components/portal/onboarding/tours.js" import PreviewOverlay from "./_components/PreviewOverlay.svelte" export let application @@ -86,17 +87,10 @@ // Check if onboarding is enabled. if (isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)) { if (!$auth.user?.onboardedAt) { - // Determine the correct step - const activeNav = $layout.children.find(c => $isActive(c.path)) - const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING] - const targetStep = activeNav - ? onboardingTour.find(step => step.route === activeNav?.path) - : null await store.update(state => ({ ...state, onboarding: true, tourKey: TOUR_KEYS.TOUR_BUILDER_ONBOARDING, - tourStepKey: targetStep?.id, })) } else { // Feature tour date @@ -172,7 +166,11 @@
- +
@@ -228,7 +226,7 @@ .top-nav { flex: 0 0 60px; background: var(--background); - padding: 0 var(--spacing-xl); + padding-left: var(--spacing-xl); display: grid; grid-template-columns: 1fr auto 1fr; flex-direction: row; diff --git a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte index 74dfe671ab..0afe257e60 100644 --- a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte @@ -8,6 +8,10 @@ import { onDestroy, onMount } from "svelte" import { syncURLToState } from "helpers/urlStateSync" import * as routify from "@roxi/routify" + import { store } from "builderStore" + + $: automationId = $selectedAutomation?._id + $: store.actions.websocket.selectResource(automationId) // Keep URL and state in sync for selected screen ID const stopSyncing = syncURLToState({ diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte index 8ce30fddfd..9e50ab8da3 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte @@ -74,11 +74,12 @@ border: 1px solid var(--spectrum-global-color-gray-300); border-radius: 5px; width: 100%; - background-color: #00000047; + background: var(--spectrum-global-color-gray-50); color: white; overflow: hidden; padding: 12px 16px; box-sizing: border-box; + transition: background 130ms ease-out; } .left { flex: 1; @@ -94,7 +95,7 @@ } .button:hover { cursor: pointer; - filter: brightness(1.2); + background: var(--spectrum-global-color-gray-100); } .connected { display: flex; diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Queries/RestImportButton.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Queries/RestImportButton.svelte index 682284adca..7f179d18b4 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Queries/RestImportButton.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Queries/RestImportButton.svelte @@ -2,11 +2,12 @@ import { Button, Modal } from "@budibase/bbui" import ImportQueriesModal from "./RestImportQueriesModal.svelte" + export let datasourceId let importQueriesModal - +
diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Variables/ViewDynamicVariables.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Variables/ViewDynamicVariables.svelte index c5e3666cf8..dd5668d603 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Variables/ViewDynamicVariables.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/panels/Variables/ViewDynamicVariables.svelte @@ -18,7 +18,7 @@ const onClick = dynamicVariable => { const queryId = dynamicVariable.queryId queries.select({ _id: queryId }) - $goto(`./${queryId}`) + $goto(`../../query/${queryId}`) } /** diff --git a/packages/builder/src/pages/builder/app/[application]/data/new.svelte b/packages/builder/src/pages/builder/app/[application]/data/new.svelte index 4eb9ce16e1..b2aca33c6a 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/new.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/new.svelte @@ -7,14 +7,14 @@ } from "stores/backend" import { hasData } from "stores/selectors" - import { Icon, notifications, Heading, Body } from "@budibase/bbui" + import { notifications, Body, Icon, AbsTooltip } from "@budibase/bbui" import { params, goto } from "@roxi/routify" import CreateExternalDatasourceModal from "./_components/CreateExternalDatasourceModal/index.svelte" import CreateInternalTableModal from "./_components/CreateInternalTableModal.svelte" import DatasourceOption from "./_components/DatasourceOption.svelte" import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte" + import CreationPage from "components/common/CreationPage.svelte" import ICONS from "components/backend/DatasourceNavigator/icons/index.js" - import FontAwesomeIcon from "components/common/FontAwesomeIcon.svelte" let internalTableModal let externalDatasourceModal @@ -46,25 +46,16 @@ bind:this={externalDatasourceModal} /> -
-
- {#if hasData($datasources, $tables)} - - {/if} -
-
- Add new data source -
- + $goto("./table")} + heading="Add new data source" +>
Get started with our Budibase DB - + + +
@@ -113,37 +104,19 @@ {/each}
-
+ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte index ec965ed659..fd5ddd9459 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenDropdownMenu.svelte @@ -9,7 +9,7 @@ Helpers, notifications, } from "@budibase/bbui" - import ScreenDetailsModal from "./ScreenDetailsModal.svelte" + import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte" import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" import { makeComponentUnique } from "builderStore/componentUtils" diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte index 92a88abb13..6362af3073 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenListPanel.svelte @@ -1,17 +1,16 @@ - - - - newScreenModal.show()} initalScreens={!selectedTemplates ? [] : [...selectedTemplates]} /> @@ -198,7 +194,6 @@ newScreenModal.show()} initialUrl={blankScreenUrl} /> diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/DatasourceModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/DatasourceModal.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/DatasourceModal.svelte rename to packages/builder/src/pages/builder/app/[application]/design/_components/DatasourceModal.svelte diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenRoleModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/ScreenRoleModal.svelte similarity index 93% rename from packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenRoleModal.svelte rename to packages/builder/src/pages/builder/app/[application]/design/_components/ScreenRoleModal.svelte index cde8047b97..5d73b7961c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/screens/_components/ScreenRoleModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/ScreenRoleModal.svelte @@ -40,14 +40,14 @@ - Select which level of access you want your screens to have + Select the level of access required to see these screens