diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml
index 9ab8530341..df25182cd6 100644
--- a/.github/workflows/release-master.yml
+++ b/.github/workflows/release-master.yml
@@ -36,6 +36,7 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: 18.x
+ cache: yarn
- run: yarn install --frozen-lockfile
- name: Update versions
@@ -63,14 +64,64 @@ jobs:
echo "Using tag $version"
echo "version=$version" >> "$GITHUB_OUTPUT"
- - name: Build/release Docker images
+ - name: Setup Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Docker login
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
- yarn build:docker
env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
- BUDIBASE_RELEASE_VERSION: ${{ steps.currenttag.outputs.version }}
+
+ - name: Build worker docker
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ build-args: |
+ BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
+ tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
+ file: ./packages/worker/Dockerfile.v2
+ cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
+ cache-to: type=inline
+ env:
+ IMAGE_NAME: budibase/worker
+ IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
+ BUDIBASE_VERSION: ${{ steps.currenttag.outputs.version }}
+
+ - name: Build server docker
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ build-args: |
+ BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
+ tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
+ file: ./packages/server/Dockerfile.v2
+ cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
+ cache-to: type=inline
+ env:
+ IMAGE_NAME: budibase/apps
+ IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
+ BUDIBASE_VERSION: ${{ steps.currenttag.outputs.version }}
+
+ - name: Build proxy docker
+ uses: docker/build-push-action@v5
+ with:
+ context: ./hosting/proxy
+ push: true
+ platforms: linux/amd64,linux/arm64
+ tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
+ file: ./hosting/proxy/Dockerfile
+ cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
+ cache-to: type=inline
+ env:
+ IMAGE_NAME: budibase/proxy
+ IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
release-helm-chart:
needs: [release-images]
diff --git a/.github/workflows/release-singleimage.yml b/.github/workflows/release-singleimage.yml
index f7f87f6e4c..4d35916f4d 100644
--- a/.github/workflows/release-singleimage.yml
+++ b/.github/workflows/release-singleimage.yml
@@ -67,7 +67,7 @@ jobs:
push: true
platforms: linux/amd64,linux/arm64
tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
- file: ./hosting/single/Dockerfile
+ file: ./hosting/single/Dockerfile.v2
- name: Tag and release Budibase Azure App Service docker image
uses: docker/build-push-action@v2
with:
@@ -76,4 +76,4 @@ jobs:
platforms: linux/amd64
build-args: TARGETBUILD=aas
tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
- file: ./hosting/single/Dockerfile
+ file: ./hosting/single/Dockerfile.v2
diff --git a/README.md b/README.md
index 9deb16cd4f..7827d4e48a 100644
--- a/README.md
+++ b/README.md
@@ -126,13 +126,6 @@ You can learn more about the Budibase API at the following places:
- [Build an app with Budibase and Next.js](https://budibase.com/blog/building-a-crud-app-with-budibase-and-next.js/)
-
-
-
-
-
-
-
## 🏁 Get started
Deploy Budibase self-hosted in your existing infrastructure, using Docker, Kubernetes, and Digital Ocean.
diff --git a/hosting/scripts/linux/release-to-docker-hub.sh b/hosting/scripts/linux/release-to-docker-hub.sh
deleted file mode 100755
index 599a10f914..0000000000
--- a/hosting/scripts/linux/release-to-docker-hub.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-tag=$1
-
-if [[ ! "$tag" ]]; then
- echo "No tag present. You must pass a tag to this script"
- exit 1
-fi
-
-echo "Tagging images with tag: $tag"
-
-docker tag proxy-service budibase/proxy:$tag
-docker tag app-service budibase/apps:$tag
-docker tag worker-service budibase/worker:$tag
-
-docker push --all-tags budibase/apps
-docker push --all-tags budibase/worker
-docker push --all-tags budibase/proxy
diff --git a/lerna.json b/lerna.json
index 384473120b..6df4a4c4cd 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.11.45",
+ "version": "2.12.1",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/package.json b/package.json
index d3f4903e6c..417fb31e0e 100644
--- a/package.json
+++ b/package.json
@@ -54,10 +54,6 @@
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --write \"qa-core/**/*.{js,ts,svelte}\"",
"lint:fix": "yarn run lint:fix:prettier && yarn run lint:fix:eslint",
"build:specs": "lerna run --stream specs",
- "build:docker": "lerna run --stream build:docker && yarn build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -",
- "build:docker:proxy": "docker build hosting/proxy -t proxy-service",
- "build:docker:selfhost": "lerna run --stream build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -",
- "build:docker:develop": "node scripts/pinVersions && lerna run --stream build:docker && yarn build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
"build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild",
"build:docker:airgap:single": "SINGLE_IMAGE=1 node hosting/scripts/airgapped/airgappedDockerBuild",
"build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -",
diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json
index b23cd8e5b1..dc8d71b52c 100644
--- a/packages/backend-core/package.json
+++ b/packages/backend-core/package.json
@@ -21,7 +21,7 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
- "@budibase/nano": "10.1.2",
+ "@budibase/nano": "10.1.3",
"@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/shared-core": "0.0.0",
"@budibase/types": "0.0.0",
diff --git a/packages/backend-core/src/cache/writethrough.ts b/packages/backend-core/src/cache/writethrough.ts
index e64c116663..c331d791a6 100644
--- a/packages/backend-core/src/cache/writethrough.ts
+++ b/packages/backend-core/src/cache/writethrough.ts
@@ -119,8 +119,8 @@ export class Writethrough {
this.writeRateMs = writeRateMs
}
- async put(doc: any) {
- return put(this.db, doc, this.writeRateMs)
+ async put(doc: any, writeRateMs: number = this.writeRateMs) {
+ return put(this.db, doc, writeRateMs)
}
async get(id: string) {
diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts
index aea485e3e3..bfa7595d62 100644
--- a/packages/backend-core/src/db/constants.ts
+++ b/packages/backend-core/src/db/constants.ts
@@ -8,3 +8,7 @@ export const CONSTANT_INTERNAL_ROW_COLS = [
] as const
export const CONSTANT_EXTERNAL_ROW_COLS = ["_id", "_rev", "tableId"] as const
+
+export function isInternalColumnName(name: string): boolean {
+ return (CONSTANT_INTERNAL_ROW_COLS as readonly string[]).includes(name)
+}
diff --git a/packages/backend-core/src/docIds/params.ts b/packages/backend-core/src/docIds/params.ts
index 36fd75622b..d9baee3dc6 100644
--- a/packages/backend-core/src/docIds/params.ts
+++ b/packages/backend-core/src/docIds/params.ts
@@ -6,6 +6,7 @@ import {
ViewName,
} from "../constants"
import { getProdAppID } from "./conversions"
+import { DatabaseQueryOpts } from "@budibase/types"
/**
* If creating DB allDocs/query params with only a single top level ID this can be used, this
@@ -22,8 +23,8 @@ import { getProdAppID } from "./conversions"
export function getDocParams(
docType: string,
docId?: string | null,
- otherProps: any = {}
-) {
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
if (docId == null) {
docId = ""
}
@@ -45,8 +46,8 @@ export function getDocParams(
export function getRowParams(
tableId?: string | null,
rowId?: string | null,
- otherProps = {}
-) {
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
if (tableId == null) {
return getDocParams(DocumentType.ROW, null, otherProps)
}
@@ -88,7 +89,10 @@ export const isDatasourceId = (id: string) => {
/**
* Gets parameters for retrieving workspaces.
*/
-export function getWorkspaceParams(id = "", otherProps = {}) {
+export function getWorkspaceParams(
+ id = "",
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
return {
...otherProps,
startkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}`,
@@ -99,7 +103,10 @@ export function getWorkspaceParams(id = "", otherProps = {}) {
/**
* Gets parameters for retrieving users.
*/
-export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
+export function getGlobalUserParams(
+ globalId: any,
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
if (!globalId) {
globalId = ""
}
@@ -117,11 +124,17 @@ export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
/**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/
-export function getUserMetadataParams(userId?: string | null, otherProps = {}) {
+export function getUserMetadataParams(
+ userId?: string | null,
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
return getRowParams(InternalTable.USER_METADATA, userId, otherProps)
}
-export function getUsersByAppParams(appId: any, otherProps: any = {}) {
+export function getUsersByAppParams(
+ appId: any,
+ otherProps: Partial = {}
+): DatabaseQueryOpts {
const prodAppId = getProdAppID(appId)
return {
...otherProps,
diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index b05cf79c8c..0d33031de5 100644
--- a/packages/backend-core/src/security/roles.ts
+++ b/packages/backend-core/src/security/roles.ts
@@ -122,7 +122,9 @@ export async function roleToNumber(id?: string) {
if (isBuiltin(id)) {
return builtinRoleToNumber(id)
}
- const hierarchy = (await getUserRoleHierarchy(id)) as RoleDoc[]
+ const hierarchy = (await getUserRoleHierarchy(id, {
+ defaultPublic: true,
+ })) as RoleDoc[]
for (let role of hierarchy) {
if (isBuiltin(role?.inherits)) {
return builtinRoleToNumber(role.inherits) + 1
@@ -192,12 +194,15 @@ export async function getRole(
/**
* Simple function to get all the roles based on the top level user role ID.
*/
-async function getAllUserRoles(userRoleId?: string): Promise {
+async function getAllUserRoles(
+ userRoleId?: string,
+ opts?: { defaultPublic?: boolean }
+): Promise {
// admins have access to all roles
if (userRoleId === BUILTIN_IDS.ADMIN) {
return getAllRoles()
}
- let currentRole = await getRole(userRoleId)
+ let currentRole = await getRole(userRoleId, opts)
let roles = currentRole ? [currentRole] : []
let roleIds = [userRoleId]
// get all the inherited roles
@@ -226,12 +231,16 @@ export async function getUserRoleIdHierarchy(
* Returns an ordered array of the user's inherited role IDs, this can be used
* to determine if a user can access something that requires a specific role.
* @param userRoleId The user's role ID, this can be found in their access token.
+ * @param opts optional - if want to default to public use this.
* @returns returns an ordered array of the roles, with the first being their
* highest level of access and the last being the lowest level.
*/
-export async function getUserRoleHierarchy(userRoleId?: string) {
+export async function getUserRoleHierarchy(
+ userRoleId?: string,
+ opts?: { defaultPublic?: boolean }
+) {
// special case, if they don't have a role then they are a public user
- return getAllUserRoles(userRoleId)
+ return getAllUserRoles(userRoleId, opts)
}
// this function checks that the provided permissions are in an array format
diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts
index a2539e836e..c071064713 100644
--- a/packages/backend-core/src/users/db.ts
+++ b/packages/backend-core/src/users/db.ts
@@ -25,12 +25,17 @@ import {
import {
getAccountHolderFromUserIds,
isAdmin,
+ isCreator,
validateUniqueUser,
} from "./utils"
import { searchExistingEmails } from "./lookup"
import { hash } from "../utils"
-type QuotaUpdateFn = (change: number, cb?: () => Promise) => Promise
+type QuotaUpdateFn = (
+ change: number,
+ creatorsChange: number,
+ cb?: () => Promise
+) => Promise
type GroupUpdateFn = (groupId: string, userIds: string[]) => Promise
type FeatureFn = () => Promise
type GroupGetFn = (ids: string[]) => Promise
@@ -160,13 +165,9 @@ export class UserDB {
}
static async getUsersByAppAccess(opts: { appId?: string; limit?: number }) {
- const params: any = {
- include_docs: true,
- limit: opts.limit || 50,
- }
let response: User[] = await usersCore.searchGlobalUsersByAppAccess(
opts.appId,
- params
+ { limit: opts.limit || 50 }
)
return response
}
@@ -245,7 +246,8 @@ export class UserDB {
}
const change = dbUser ? 0 : 1 // no change if there is existing user
- return UserDB.quotas.addUsers(change, async () => {
+ const creatorsChange = isCreator(dbUser) !== isCreator(user) ? 1 : 0
+ return UserDB.quotas.addUsers(change, creatorsChange, async () => {
await validateUniqueUser(email, tenantId)
let builtUser = await UserDB.buildUser(user, opts, tenantId, dbUser)
@@ -307,6 +309,7 @@ export class UserDB {
let usersToSave: any[] = []
let newUsers: any[] = []
+ let newCreators: any[] = []
const emails = newUsersRequested.map((user: User) => user.email)
const existingEmails = await searchExistingEmails(emails)
@@ -327,59 +330,66 @@ export class UserDB {
}
newUser.userGroups = groups
newUsers.push(newUser)
+ if (isCreator(newUser)) {
+ newCreators.push(newUser)
+ }
}
const account = await accountSdk.getAccountByTenantId(tenantId)
- return UserDB.quotas.addUsers(newUsers.length, async () => {
- // create the promises array that will be called by bulkDocs
- newUsers.forEach((user: any) => {
- usersToSave.push(
- UserDB.buildUser(
- user,
- {
- hashPassword: true,
- requirePassword: user.requirePassword,
- },
- tenantId,
- undefined, // no dbUser
- account
+ return UserDB.quotas.addUsers(
+ newUsers.length,
+ newCreators.length,
+ async () => {
+ // create the promises array that will be called by bulkDocs
+ newUsers.forEach((user: any) => {
+ usersToSave.push(
+ UserDB.buildUser(
+ user,
+ {
+ hashPassword: true,
+ requirePassword: user.requirePassword,
+ },
+ tenantId,
+ undefined, // no dbUser
+ account
+ )
)
- )
- })
+ })
- const usersToBulkSave = await Promise.all(usersToSave)
- await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
+ const usersToBulkSave = await Promise.all(usersToSave)
+ await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
- // Post-processing of bulk added users, e.g. events and cache operations
- for (const user of usersToBulkSave) {
- // TODO: Refactor to bulk insert users into the info db
- // instead of relying on looping tenant creation
- await platform.users.addUser(tenantId, user._id, user.email)
- await eventHelpers.handleSaveEvents(user, undefined)
- }
+ // Post-processing of bulk added users, e.g. events and cache operations
+ for (const user of usersToBulkSave) {
+ // TODO: Refactor to bulk insert users into the info db
+ // instead of relying on looping tenant creation
+ await platform.users.addUser(tenantId, user._id, user.email)
+ await eventHelpers.handleSaveEvents(user, undefined)
+ }
+
+ const saved = usersToBulkSave.map(user => {
+ return {
+ _id: user._id,
+ email: user.email,
+ }
+ })
+
+ // now update the groups
+ if (Array.isArray(saved) && groups) {
+ const groupPromises = []
+ const createdUserIds = saved.map(user => user._id)
+ for (let groupId of groups) {
+ groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds))
+ }
+ await Promise.all(groupPromises)
+ }
- const saved = usersToBulkSave.map(user => {
return {
- _id: user._id,
- email: user.email,
+ successful: saved,
+ unsuccessful,
}
- })
-
- // now update the groups
- if (Array.isArray(saved) && groups) {
- const groupPromises = []
- const createdUserIds = saved.map(user => user._id)
- for (let groupId of groups) {
- groupPromises.push(UserDB.groups.addUsers(groupId, createdUserIds))
- }
- await Promise.all(groupPromises)
}
-
- return {
- successful: saved,
- unsuccessful,
- }
- })
+ )
}
static async bulkDelete(userIds: string[]): Promise {
@@ -419,11 +429,12 @@ export class UserDB {
_deleted: true,
}))
const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
+ const creatorsToDelete = usersToDelete.filter(isCreator)
- await UserDB.quotas.removeUsers(toDelete.length)
for (let user of usersToDelete) {
await bulkDeleteProcessing(user)
}
+ await UserDB.quotas.removeUsers(toDelete.length, creatorsToDelete.length)
// Build Response
// index users by id
@@ -472,7 +483,8 @@ export class UserDB {
await db.remove(userId, dbUser._rev)
- await UserDB.quotas.removeUsers(1)
+ const creatorsToDelete = isCreator(dbUser) ? 1 : 0
+ await UserDB.quotas.removeUsers(1, creatorsToDelete)
await eventHelpers.handleDeleteEvents(dbUser)
await cache.user.invalidateUser(userId)
await sessions.invalidateSessions(userId, { reason: "deletion" })
diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts
index 6237c23972..ea4a17214d 100644
--- a/packages/backend-core/src/users/users.ts
+++ b/packages/backend-core/src/users/users.ts
@@ -14,12 +14,13 @@ import {
} from "../db"
import {
BulkDocsResponse,
- ContextUser,
SearchQuery,
SearchQueryOperators,
SearchUsersRequest,
User,
+ ContextUser,
DatabaseQueryOpts,
+ CouchFindOptions,
} from "@budibase/types"
import { getGlobalDB } from "../context"
import * as context from "../context"
@@ -140,7 +141,7 @@ export const getGlobalUserByEmail = async (
export const searchGlobalUsersByApp = async (
appId: any,
- opts: any,
+ opts: DatabaseQueryOpts,
getOpts?: GetOpts
) => {
if (typeof appId !== "string") {
@@ -166,7 +167,10 @@ export const searchGlobalUsersByApp = async (
Return any user who potentially has access to the application
Admins, developers and app users with the explicitly role.
*/
-export const searchGlobalUsersByAppAccess = async (appId: any, opts: any) => {
+export const searchGlobalUsersByAppAccess = async (
+ appId: any,
+ opts?: { limit?: number }
+) => {
const roleSelector = `roles.${appId}`
let orQuery: any[] = [
@@ -187,7 +191,7 @@ export const searchGlobalUsersByAppAccess = async (appId: any, opts: any) => {
orQuery.push(roleCheck)
}
- let searchOptions = {
+ let searchOptions: CouchFindOptions = {
selector: {
$or: orQuery,
_id: {
@@ -198,7 +202,7 @@ export const searchGlobalUsersByAppAccess = async (appId: any, opts: any) => {
}
const resp = await directCouchFind(context.getGlobalDBName(), searchOptions)
- return resp?.rows
+ return resp.rows
}
export const getGlobalUserByAppPage = (appId: string, user: User) => {
diff --git a/packages/backend-core/tests/core/users/users.spec.js b/packages/backend-core/tests/core/users/users.spec.js
new file mode 100644
index 0000000000..ae7109344a
--- /dev/null
+++ b/packages/backend-core/tests/core/users/users.spec.js
@@ -0,0 +1,54 @@
+const _ = require('lodash/fp')
+const {structures} = require("../../../tests")
+
+jest.mock("../../../src/context")
+jest.mock("../../../src/db")
+
+const context = require("../../../src/context")
+const db = require("../../../src/db")
+
+const {getCreatorCount} = require('../../../src/users/users')
+
+describe("Users", () => {
+
+ let getGlobalDBMock
+ let getGlobalUserParamsMock
+ let paginationMock
+
+ beforeEach(() => {
+ jest.resetAllMocks()
+
+ getGlobalDBMock = jest.spyOn(context, "getGlobalDB")
+ getGlobalUserParamsMock = jest.spyOn(db, "getGlobalUserParams")
+ paginationMock = jest.spyOn(db, "pagination")
+ })
+
+ it("Retrieves the number of creators", async () => {
+ const getUsers = (offset, limit, creators = false) => {
+ const range = _.range(offset, limit)
+ const opts = creators ? {builder: {global: true}} : undefined
+ return range.map(() => structures.users.user(opts))
+ }
+ const page1Data = getUsers(0, 8)
+ const page2Data = getUsers(8, 12, true)
+ getGlobalDBMock.mockImplementation(() => ({
+ name : "fake-db",
+ allDocs: () => ({
+ rows: [...page1Data, ...page2Data]
+ })
+ }))
+ paginationMock.mockImplementationOnce(() => ({
+ data: page1Data,
+ hasNextPage: true,
+ nextPage: "1"
+ }))
+ paginationMock.mockImplementation(() => ({
+ data: page2Data,
+ hasNextPage: false,
+ nextPage: undefined
+ }))
+ const creatorsCount = await getCreatorCount()
+ expect(creatorsCount).toBe(4)
+ expect(paginationMock).toHaveBeenCalledTimes(2)
+ })
+})
diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts
index 0e34f2e9bb..bb452f9ad5 100644
--- a/packages/backend-core/tests/core/utilities/structures/licenses.ts
+++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts
@@ -123,6 +123,10 @@ export function customer(): Customer {
export function subscription(): Subscription {
return {
amount: 10000,
+ amounts: {
+ user: 10000,
+ creator: 0,
+ },
cancelAt: undefined,
currency: "usd",
currentPeriodEnd: 0,
@@ -131,6 +135,10 @@ export function subscription(): Subscription {
duration: PriceDuration.MONTHLY,
pastDueAt: undefined,
quantity: 0,
+ quantities: {
+ user: 0,
+ creator: 0,
+ },
status: "active",
}
}
diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte
index e2e25b4a04..bfa0285250 100644
--- a/packages/bbui/src/Form/Core/Dropzone.svelte
+++ b/packages/bbui/src/Form/Core/Dropzone.svelte
@@ -159,8 +159,10 @@
{#if selectedImage.size}
{#if selectedImage.size <= BYTES_IN_MB}
- {`${selectedImage.size / BYTES_IN_KB} KB`}
- {:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if}
+ {`${(selectedImage.size / BYTES_IN_KB).toFixed(1)} KB`}
+ {:else}{`${(selectedImage.size / BYTES_IN_MB).toFixed(
+ 1
+ )} MB`}{/if}
{/if}
{#if !disabled}
@@ -203,8 +205,8 @@
{#if file.size}
{#if file.size <= BYTES_IN_MB}
- {`${file.size / BYTES_IN_KB} KB`}
- {:else}{`${file.size / BYTES_IN_MB} MB`}{/if}
+ {`${(file.size / BYTES_IN_KB).toFixed(1)} KB`}
+ {:else}{`${(file.size / BYTES_IN_MB).toFixed(1)} MB`}{/if}
{/if}
{#if !disabled}
diff --git a/packages/builder/.gitignore b/packages/builder/.gitignore
index e5c961d509..acd1a70579 100644
--- a/packages/builder/.gitignore
+++ b/packages/builder/.gitignore
@@ -5,4 +5,4 @@ package-lock.json
release/
dist/
routify
-.routify/
\ No newline at end of file
+.routify/
diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js
index a567caf87f..a4729b4a8a 100644
--- a/packages/builder/src/builderStore/store/frontend.js
+++ b/packages/builder/src/builderStore/store/frontend.js
@@ -580,7 +580,7 @@ export const getFrontendStore = () => {
let table = validTables.find(table => {
return (
table.sourceId !== BUDIBASE_INTERNAL_DB_ID &&
- table.type === DB_TYPE_INTERNAL
+ table.sourceType === DB_TYPE_INTERNAL
)
})
if (table) {
@@ -591,7 +591,7 @@ export const getFrontendStore = () => {
table = validTables.find(table => {
return (
table.sourceId === BUDIBASE_INTERNAL_DB_ID &&
- table.type === DB_TYPE_INTERNAL
+ table.sourceType === DB_TYPE_INTERNAL
)
})
if (table) {
@@ -599,7 +599,7 @@ export const getFrontendStore = () => {
}
// Finally try an external table
- return validTables.find(table => table.type === DB_TYPE_EXTERNAL)
+ return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
},
enrichEmptySettings: (component, opts) => {
if (!component?._component) {
diff --git a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
index 8ef870caca..4e67a92443 100644
--- a/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/RelationshipDataTable.svelte
@@ -16,7 +16,6 @@
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)
$: schema = linkedTable?.schema
$: table = $tables.list.find(table => table._id === tableId)
- $: type = table?.type
$: fetchData(tableId, rowId)
$: {
let rowLabel = row?.[table?.primaryDisplay]
@@ -41,5 +40,5 @@
{#if row && row._id === rowId}
-
+
{/if}
diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
index 5fee849afb..8dd685e766 100644
--- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte
@@ -16,6 +16,7 @@
import GridRelationshipButton from "components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte"
import GridEditColumnModal from "components/backend/DataTable/modals/grid/GridEditColumnModal.svelte"
import GridUsersTableButton from "components/backend/DataTable/modals/grid/GridUsersTableButton.svelte"
+ import { DB_TYPE_EXTERNAL } from "constants/backend"
const userSchemaOverrides = {
firstName: { displayName: "First name", disabled: true },
@@ -27,7 +28,7 @@
$: id = $tables.selected?._id
$: isUsersTable = id === TableNames.USERS
- $: isInternal = $tables.selected?.type !== "external"
+ $: isInternal = $tables.selected?.sourceType !== DB_TYPE_EXTERNAL
$: gridDatasource = {
type: "table",
tableId: id,
@@ -46,10 +47,7 @@
tables.replaceTable(id, e.detail)
// We need to refresh datasources when an external table changes.
- // Type "external" may exist - sometimes type is "table" and sometimes it
- // is "external" - it has different meanings in different endpoints.
- // If we check both these then we hopefully catch all external tables.
- if (e.detail?.type === "external" || e.detail?.sql) {
+ if (e.detail?.sourceType === DB_TYPE_EXTERNAL) {
await datasources.fetch()
}
}
diff --git a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
index 6068439dbd..6fcba8d418 100644
--- a/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewDataTable.svelte
@@ -17,7 +17,6 @@
let hideAutocolumns = true
let data = []
let loading = false
- let type = "internal"
$: name = view.name
$: schema = view.schema
@@ -66,7 +65,6 @@
tableId={view.tableId}
{data}
{loading}
- {type}
rowCount={10}
allowEditing={false}
bind:hideAutocolumns
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 71d971891c..74e255cf7e 100644
--- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte
+++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridImportButton.svelte
@@ -10,6 +10,6 @@
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 467ae413c3..d5a9aba488 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -26,6 +26,7 @@
ALLOWABLE_NUMBER_TYPES,
SWITCHABLE_TYPES,
PrettyRelationshipDefinitions,
+ DB_TYPE_EXTERNAL,
} from "constants/backend"
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@@ -254,10 +255,11 @@
!uneditable &&
editableColumn?.type !== AUTO_TYPE &&
!editableColumn.autocolumn
- $: external = table.type === "external"
+ $: externalTable = table.sourceType === DB_TYPE_EXTERNAL
// in the case of internal tables the sourceId will just be undefined
$: tableOptions = $tables.list.filter(
- opt => opt.type === table.type && table.sourceId === opt.sourceId
+ opt =>
+ opt.sourceType === table.sourceType && table.sourceId === opt.sourceId
)
$: typeEnabled =
!originalName ||
@@ -409,7 +411,7 @@
editableColumn.type === FieldType.BB_REFERENCE &&
editableColumn.subtype === FieldSubtype.USERS
- if (!external) {
+ if (!externalTable) {
return [
FIELDS.STRING,
FIELDS.BARCODEQR,
@@ -441,7 +443,7 @@
isUsers ? FIELDS.USERS : FIELDS.USER,
]
// no-sql or a spreadsheet
- if (!external || table.sql) {
+ if (!externalTable || table.sql) {
fields = [...fields, FIELDS.LINK, FIELDS.ARRAY]
}
return fields
@@ -486,7 +488,7 @@
})
}
const newError = {}
- if (!external && fieldInfo.name?.startsWith("_")) {
+ if (!externalTable && fieldInfo.name?.startsWith("_")) {
newError.name = `Column name cannot start with an underscore.`
} else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) {
newError.name = `Illegal character; must be alpha-numeric.`
@@ -498,7 +500,7 @@
newError.name = `Column name already in use.`
}
- if (fieldInfo.type == "auto" && !fieldInfo.subtype) {
+ if (fieldInfo.type === "auto" && !fieldInfo.subtype) {
newError.subtype = `Auto Column requires a type`
}
diff --git a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
index 43751ad944..eb1e7bc7ff 100644
--- a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
+++ b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte
@@ -1,6 +1,6 @@