From 863587ef300cf138faffa863696434038ebfc273 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 12:25:25 +0100
Subject: [PATCH 001/357] Fix scripts
---
scripts/add-app-migration.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/add-app-migration.js b/scripts/add-app-migration.js
index a58d3a4fbe..a4e01be635 100644
--- a/scripts/add-app-migration.js
+++ b/scripts/add-app-migration.js
@@ -21,7 +21,9 @@ const generateTimestamp = () => {
}
const createMigrationFile = () => {
- const migrationFilename = `${generateTimestamp()}_${title}`
+ const migrationFilename = `${generateTimestamp()}_${title
+ .replace(/-/g, "_")
+ .replace(/ /g, "_")}`
const migrationsDir = "../packages/server/src/appMigrations"
const template = `const migration = async () => {
From 90db9efb7045d0dfbf7db6e294442d64e150e816 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 12:43:39 +0100
Subject: [PATCH 002/357] Allow skipping migrations
---
packages/backend-core/src/environment.ts | 1 +
packages/server/src/middleware/appMigrations.ts | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 138dbbd9e0..66c91b19fb 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -183,6 +183,7 @@ const environment = {
environment[key] = value
},
ROLLING_LOG_MAX_SIZE: process.env.ROLLING_LOG_MAX_SIZE || "10M",
+ SKIP_APP_MIGRATIONS: process.env.SKIP_APP_MIGRATIONS || false,
}
// clean up any environment variable edge cases
diff --git a/packages/server/src/middleware/appMigrations.ts b/packages/server/src/middleware/appMigrations.ts
index 36e021c7ed..1fb13094c6 100644
--- a/packages/server/src/middleware/appMigrations.ts
+++ b/packages/server/src/middleware/appMigrations.ts
@@ -1,9 +1,14 @@
import { UserCtx } from "@budibase/types"
import { checkMissingMigrations } from "../appMigrations"
+import { env } from "@budibase/backend-core"
export default async (ctx: UserCtx, next: any) => {
const { appId } = ctx
+ if (env.SKIP_APP_MIGRATIONS) {
+ return next()
+ }
+
if (!appId) {
return next()
}
From f484c6dabd9b135128115707d1627d625c92f95d Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 12:43:53 +0100
Subject: [PATCH 003/357] Fix initial run
---
packages/server/src/appMigrations/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/server/src/appMigrations/index.ts b/packages/server/src/appMigrations/index.ts
index b382d8b533..0758b9f324 100644
--- a/packages/server/src/appMigrations/index.ts
+++ b/packages/server/src/appMigrations/index.ts
@@ -17,7 +17,7 @@ export const getLatestMigrationId = () =>
.sort()
.reverse()[0]
-const getTimestamp = (versionId: string) => versionId?.split("_")[0]
+const getTimestamp = (versionId: string) => versionId?.split("_")[0] || ""
export async function checkMissingMigrations(
ctx: UserCtx,
From 61e16b63a510ec19dcc47a03f9bd5ceb0a95a743 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 12:55:14 +0100
Subject: [PATCH 004/357] Fix initial run
---
packages/server/src/appMigrations/appMigrationMetadata.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/server/src/appMigrations/appMigrationMetadata.ts b/packages/server/src/appMigrations/appMigrationMetadata.ts
index 202e78d964..d87ddff3ef 100644
--- a/packages/server/src/appMigrations/appMigrationMetadata.ts
+++ b/packages/server/src/appMigrations/appMigrationMetadata.ts
@@ -33,7 +33,7 @@ export async function getAppMigrationVersion(appId: string): Promise {
let version
try {
metadata = await getFromDB(appId)
- version = metadata.version
+ version = metadata.version || ""
} catch (err: any) {
if (err.status !== 404) {
throw err
From 4a481877def0483bf3864af5e0f11467835356d9 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 12:55:52 +0100
Subject: [PATCH 005/357] Add empty migration
---
packages/server/src/appMigrations/migrations.ts | 6 ++++++
.../migrations/20231229122514_update_link_documents.ts | 5 +++++
2 files changed, 11 insertions(+)
create mode 100644 packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
diff --git a/packages/server/src/appMigrations/migrations.ts b/packages/server/src/appMigrations/migrations.ts
index d66e2e8895..544e702867 100644
--- a/packages/server/src/appMigrations/migrations.ts
+++ b/packages/server/src/appMigrations/migrations.ts
@@ -2,6 +2,12 @@
import { AppMigration } from "."
+import m20231229122514_update_link_documents from "./migrations/20231229122514_update_link_documents"
+
export const MIGRATIONS: AppMigration[] = [
// Migrations will be executed sorted by id
+ {
+ id: "20231229122514_update_link_documents",
+ func: m20231229122514_update_link_documents
+ },
]
diff --git a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
new file mode 100644
index 0000000000..f23f72e070
--- /dev/null
+++ b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
@@ -0,0 +1,5 @@
+const migration = async () => {
+ // Add your migration logic here
+}
+
+export default migration
From e1c37e75a41a6613080c638e088d4a5dfe7c1753 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 13:03:59 +0100
Subject: [PATCH 006/357] Add allLinkDocs utils
---
packages/server/src/db/utils.ts | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts
index 1712563085..a487c562b6 100644
--- a/packages/server/src/db/utils.ts
+++ b/packages/server/src/db/utils.ts
@@ -1,5 +1,5 @@
import newid from "./newid"
-import { db as dbCore } from "@budibase/backend-core"
+import { context, db as dbCore } from "@budibase/backend-core"
import {
DocumentType,
FieldSchema,
@@ -7,6 +7,7 @@ import {
VirtualDocumentType,
INTERNAL_TABLE_SOURCE_ID,
DatabaseQueryOpts,
+ LinkDocument,
} from "@budibase/types"
import { FieldTypes } from "../constants"
@@ -127,10 +128,24 @@ export function generateLinkID(
/**
* Gets parameters for retrieving link docs, this is a utility function for the getDocParams function.
*/
-export function getLinkParams(otherProps: any = {}) {
+function getLinkParams(otherProps: Partial = {}) {
return getDocParams(DocumentType.LINK, null, otherProps)
}
+/**
+ * Gets all the link docs document from the current app db.
+ */
+export async function allLinkDocs() {
+ const db = context.getAppDB()
+
+ const response = await db.allDocs(
+ getLinkParams({
+ include_docs: true,
+ })
+ )
+ return response.rows.map(row => row.doc!)
+}
+
/**
* Generates a new layout ID.
* @returns The new layout ID which the layout doc can be stored under.
From 4a1b99a9dc5f1b650a4ffe3cfa7602b51f6eedb3 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 13:32:45 +0100
Subject: [PATCH 007/357] Add migration
---
.../20231229122514_update_link_documents.ts | 23 ++++++++++++++++++-
packages/types/src/documents/app/links.ts | 1 +
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
index f23f72e070..e1e1d4efc6 100644
--- a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
+++ b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
@@ -1,5 +1,26 @@
+import { SEPARATOR, context } from "@budibase/backend-core"
+import { allLinkDocs } from "../../db/utils"
+
const migration = async () => {
- // Add your migration logic here
+ const linkDocs = await allLinkDocs()
+
+ const docsToUpdate = []
+ for (const linkDoc of linkDocs) {
+ if (linkDoc.tableId) {
+ // It already had the required data
+ continue
+ }
+
+ linkDoc.tableId = [linkDoc.doc1.tableId, linkDoc.doc2.tableId]
+ .sort()
+ .join(SEPARATOR)
+ docsToUpdate.push(linkDoc)
+ }
+
+ if (docsToUpdate.length) {
+ const db = context.getAppDB()
+ await db.bulkDocs(docsToUpdate)
+ }
}
export default migration
diff --git a/packages/types/src/documents/app/links.ts b/packages/types/src/documents/app/links.ts
index ae7e4de78e..2a9595d99f 100644
--- a/packages/types/src/documents/app/links.ts
+++ b/packages/types/src/documents/app/links.ts
@@ -8,6 +8,7 @@ export interface LinkInfo {
export interface LinkDocument extends Document {
type: string
+ tableId: string
doc1: LinkInfo
doc2: LinkInfo
}
From e265cc635c3b1f0ad66773078b1b657cbd4c0589 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 29 Dec 2023 13:41:46 +0100
Subject: [PATCH 008/357] Create link docs to new docs
---
packages/backend-core/src/users/db.ts | 2 +-
.../20231229122514_update_link_documents.ts | 14 ++++++++++----
packages/server/src/db/linkedRows/LinkDocument.ts | 7 ++++++-
packages/server/src/db/linkedRows/index.ts | 1 -
4 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts
index 326bed3cc5..01fa4899d1 100644
--- a/packages/backend-core/src/users/db.ts
+++ b/packages/backend-core/src/users/db.ts
@@ -2,7 +2,7 @@ import env from "../environment"
import * as eventHelpers from "./events"
import * as accountSdk from "../accounts"
import * as cache from "../cache"
-import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
+import { getGlobalDB, getIdentity, getTenantId } from "../context"
import * as dbUtils from "../db"
import { EmailUnavailableError, HTTPError } from "../errors"
import * as platform from "../platform"
diff --git a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
index e1e1d4efc6..adaa0653c4 100644
--- a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
+++ b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
@@ -1,5 +1,6 @@
-import { SEPARATOR, context } from "@budibase/backend-core"
+import { context } from "@budibase/backend-core"
import { allLinkDocs } from "../../db/utils"
+import LinkDocumentImpl from "../../db/linkedRows/LinkDocument"
const migration = async () => {
const linkDocs = await allLinkDocs()
@@ -11,9 +12,14 @@ const migration = async () => {
continue
}
- linkDoc.tableId = [linkDoc.doc1.tableId, linkDoc.doc2.tableId]
- .sort()
- .join(SEPARATOR)
+ linkDoc.tableId = new LinkDocumentImpl(
+ linkDoc.doc1.tableId,
+ linkDoc.doc1.fieldName,
+ linkDoc.doc1.rowId,
+ linkDoc.doc2.tableId,
+ linkDoc.doc2.fieldName,
+ linkDoc.doc2.rowId
+ ).tableId
docsToUpdate.push(linkDoc)
}
diff --git a/packages/server/src/db/linkedRows/LinkDocument.ts b/packages/server/src/db/linkedRows/LinkDocument.ts
index 234f43cb48..05097ed84f 100644
--- a/packages/server/src/db/linkedRows/LinkDocument.ts
+++ b/packages/server/src/db/linkedRows/LinkDocument.ts
@@ -1,6 +1,6 @@
import { generateLinkID } from "../utils"
import { FieldTypes } from "../../constants"
-import { LinkDocument } from "@budibase/types"
+import { LinkDocument, SEPARATOR } from "@budibase/types"
/**
* Creates a new link document structure which can be put to the database. It is important to
@@ -17,6 +17,7 @@ import { LinkDocument } from "@budibase/types"
class LinkDocumentImpl implements LinkDocument {
_id: string
type: string
+ tableId: string
doc1: {
rowId: string
fieldName: string
@@ -54,7 +55,11 @@ class LinkDocumentImpl implements LinkDocument {
fieldName: fieldName2,
rowId: rowId2,
}
+ this.tableId = [this.doc1.tableId, this.doc2.tableId].sort().join(SEPARATOR)
}
+ _rev?: string | undefined
+ createdAt?: string | number | undefined
+ updatedAt?: string | undefined
}
export default LinkDocumentImpl
diff --git a/packages/server/src/db/linkedRows/index.ts b/packages/server/src/db/linkedRows/index.ts
index 7af3f9392f..934b9d4a75 100644
--- a/packages/server/src/db/linkedRows/index.ts
+++ b/packages/server/src/db/linkedRows/index.ts
@@ -18,7 +18,6 @@ import {
Row,
LinkDocumentValue,
FieldType,
- LinkDocument,
ContextUser,
} from "@budibase/types"
import sdk from "../../sdk"
From 896c262c943ae0874fba548f398ffe6aa930f47b Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 10:52:17 +0200
Subject: [PATCH 009/357] Add readonly option in view columns
---
.../backend/DataTable/ViewV2DataTable.svelte | 1 +
.../grid/controls/ColumnsSettingButton.svelte | 41 ++++++++++++++-----
.../src/components/grid/layout/Grid.svelte | 3 +-
3 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
index 3b628c7b53..9b0e45a1b2 100644
--- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
@@ -28,6 +28,7 @@
showAvatars={false}
on:updatedatasource={handleGridViewUpdate}
isCloud={$admin.cloud}
+ allowReadonlyColumns
>
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 0a85e41966..70655bd5a3 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -1,9 +1,11 @@
@@ -15,7 +14,7 @@
dispatch("click", option.value)}
- {disabled}
+ disabled={option.disabled}
size="S"
icon={option.icon}
quiet
From 8e72f1f0facd831a62c385fb88754d0a36972710 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 11:07:30 +0200
Subject: [PATCH 011/357] Lock readonly
---
packages/builder/src/stores/portal/licensing.js | 6 ++++++
.../grid/controls/ColumnsSettingButton.svelte | 10 ++++++++--
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/packages/builder/src/stores/portal/licensing.js b/packages/builder/src/stores/portal/licensing.js
index 179d0918bb..1378a734e8 100644
--- a/packages/builder/src/stores/portal/licensing.js
+++ b/packages/builder/src/stores/portal/licensing.js
@@ -138,6 +138,11 @@ export const createLicensingStore = () => {
const isViewPermissionsEnabled = license.features.includes(
Constants.Features.VIEW_PERMISSIONS
)
+
+ const isViewReadonlyColumnsEnabled = license.features.includes(
+ Constants.Features.VIEW_READONLY_COLUMNS
+ )
+
store.update(state => {
return {
...state,
@@ -157,6 +162,7 @@ export const createLicensingStore = () => {
triggerAutomationRunEnabled,
isViewPermissionsEnabled,
perAppBuildersEnabled,
+ isViewReadonlyColumnsEnabled,
}
})
},
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 7e330b77e4..8faf603b80 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -1,6 +1,7 @@
diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js
index a3281be936..b76dcbfe0e 100644
--- a/packages/frontend-core/src/components/grid/stores/columns.js
+++ b/packages/frontend-core/src/components/grid/stores/columns.js
@@ -146,6 +146,7 @@ export const initialise = context => {
schema: fieldSchema,
width: fieldSchema.width || oldColumn?.width || DefaultColumnWidth,
visible: fieldSchema.visible ?? true,
+ readonly: fieldSchema.readonly,
order: fieldSchema.order ?? oldColumn?.order,
primaryDisplay: field === primaryDisplay,
}
From fbfe85c903c5d0b95419e6f5071d594204e566a7 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 11:43:28 +0200
Subject: [PATCH 013/357] Mark readonly as restricted
---
.../components/grid/controls/ColumnsSettingButton.svelte | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index c07adf9fcb..fd0afd48f2 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -29,8 +29,10 @@
}
const getText = columns => {
- const hidden = columns.filter(col => !col.visible).length
- return hidden ? `Columns (${hidden} restricted)` : "Columns"
+ const restricted = columns.filter(
+ col => !col.visible || col.readonly
+ ).length
+ return restricted ? `Columns (${restricted} restricted)` : "Columns"
}
$: isViewReadonlyColumnsEnabled = $licensing.isViewReadonlyColumnsEnabled
From 6ce0b3c368356459ad4d683e2f28f7acec549d66 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 11:46:57 +0200
Subject: [PATCH 014/357] Copy change
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 4 ++--
packages/server/src/sdk/app/views/index.ts | 5 +----
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 8069fadf10..962d6e82a3 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -424,7 +424,7 @@ describe.each([
await config.api.viewV2.create(newView, {
status: 400,
body: {
- message: "Readonly fields are not enabled for your tenant",
+ message: "Readonly fields are not enabled",
status: 400,
},
})
@@ -690,7 +690,7 @@ describe.each([
await config.api.viewV2.update(view, {
status: 400,
body: {
- message: "Readonly fields are not enabled for your tenant",
+ message: "Readonly fields are not enabled",
},
})
})
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index ea05ecf512..18ab94be21 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -55,10 +55,7 @@ async function guardViewSchema(
if (viewSchema[field].readonly) {
if (!(await features.isViewReadonlyColumnsEnabled())) {
- throw new HTTPError(
- `Readonly fields are not enabled for your tenant`,
- 400
- )
+ throw new HTTPError(`Readonly fields are not enabled`, 400)
}
if (isRequired(tableSchemaField.constraints)) {
From dee797656a10f6be8678c196d793b417b841ed4c Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 30 May 2024 14:31:45 +0100
Subject: [PATCH 015/357] Update account-portal submodule to latest master.
---
packages/account-portal | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/account-portal b/packages/account-portal
index c167c331ff..a03225549e 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit c167c331ff9b8161fc18e2ecbaaf1ea5815ba964
+Subproject commit a03225549e3ce61f43d0da878da162e08941b939
From 6f02185abe5947dd3e62e2e17b9489c77db31242 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Thu, 30 May 2024 14:33:10 +0100
Subject: [PATCH 016/357] Put pro back in line with master.
---
packages/pro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/pro b/packages/pro
index 1879d8686b..5189b83bea 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit 1879d8686b1d9392707595a02cdd4981923e7f99
+Subproject commit 5189b83bea1868574ff7f4c51fe5db38a11badb8
From 4dbfa28febd21889e271af769a1619cd5d2c44db Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 17:12:46 +0200
Subject: [PATCH 017/357] Move licence check out of frontend-core
---
.../backend/DataTable/ViewV2DataTable.svelte | 5 +++--
.../grid/controls/ColumnsSettingButton.svelte | 12 +++++-------
.../src/components/grid/layout/Grid.svelte | 8 ++++++--
3 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
index 9b0e45a1b2..45cdeeee07 100644
--- a/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
+++ b/packages/builder/src/components/backend/DataTable/ViewV2DataTable.svelte
@@ -1,6 +1,6 @@
@@ -57,6 +58,7 @@
on:blur={$component.editing ? updateText : null}
bind:this={node}
class:active
+ on:input={() => (touched = true)}
>
{#if icon}
diff --git a/packages/client/src/components/app/Heading.svelte b/packages/client/src/components/app/Heading.svelte
index 4adec59e2b..103ac31a93 100644
--- a/packages/client/src/components/app/Heading.svelte
+++ b/packages/client/src/components/app/Heading.svelte
@@ -14,6 +14,7 @@
export let size
let node
+ let touched = false
$: $component.editing && node?.focus()
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
@@ -47,7 +48,10 @@
// Convert contenteditable HTML to text and save
const updateText = e => {
- builderStore.actions.updateProp("text", e.target.textContent)
+ if (touched) {
+ builderStore.actions.updateProp("text", e.target.textContent)
+ }
+ touched = false
}
@@ -62,6 +66,7 @@
class:underline
class="spectrum-Heading {sizeClass} {alignClass}"
on:blur={$component.editing ? updateText : null}
+ on:input={() => (touched = true)}
>
{componentText}
diff --git a/packages/client/src/components/app/Link.svelte b/packages/client/src/components/app/Link.svelte
index 6cabcec7df..7eddcc6fe5 100644
--- a/packages/client/src/components/app/Link.svelte
+++ b/packages/client/src/components/app/Link.svelte
@@ -16,6 +16,7 @@
export let size
let node
+ let touched = false
$: $component.editing && node?.focus()
$: externalLink = url && typeof url === "string" && !url.startsWith("/")
@@ -62,7 +63,10 @@
}
const updateText = e => {
- builderStore.actions.updateProp("text", e.target.textContent)
+ if (touched) {
+ builderStore.actions.updateProp("text", e.target.textContent)
+ }
+ touched = false
}
@@ -76,6 +80,7 @@
class:underline
class="align--{align || 'left'} size--{size || 'M'}"
on:blur={$component.editing ? updateText : null}
+ on:input={() => (touched = true)}
>
{componentText}
diff --git a/packages/client/src/components/app/Text.svelte b/packages/client/src/components/app/Text.svelte
index 1037725ff8..fa15868d0f 100644
--- a/packages/client/src/components/app/Text.svelte
+++ b/packages/client/src/components/app/Text.svelte
@@ -13,6 +13,7 @@
export let size
let node
+ let touched = false
$: $component.editing && node?.focus()
$: placeholder = $builderStore.inBuilder && !text && !$component.editing
@@ -46,7 +47,10 @@
// Convert contenteditable HTML to text and save
const updateText = e => {
- builderStore.actions.updateProp("text", e.target.textContent)
+ if (touched) {
+ builderStore.actions.updateProp("text", e.target.textContent)
+ }
+ touched = false
}
@@ -61,6 +65,7 @@
class:underline
class="spectrum-Body {sizeClass} {alignClass}"
on:blur={$component.editing ? updateText : null}
+ on:input={() => (touched = true)}
>
{componentText}
diff --git a/packages/client/src/components/app/forms/Field.svelte b/packages/client/src/components/app/forms/Field.svelte
index 74ff5442a9..9210b6ea8f 100644
--- a/packages/client/src/components/app/forms/Field.svelte
+++ b/packages/client/src/components/app/forms/Field.svelte
@@ -26,6 +26,10 @@
// Register field with form
const formApi = formContext?.formApi
const labelPos = fieldGroupContext?.labelPosition || "above"
+
+ let touched = false
+ let labelNode
+
$: formStep = formStepContext ? $formStepContext || 1 : 1
$: formField = formApi?.registerField(
field,
@@ -36,14 +40,12 @@
validation,
formStep
)
-
$: schemaType =
fieldSchema?.type !== "formula" && fieldSchema?.type !== "bigint"
? fieldSchema?.type
: "string"
// Focus label when editing
- let labelNode
$: $component.editing && labelNode?.focus()
// Update form properties in parent component on every store change
@@ -57,7 +59,10 @@
$: labelClass = labelPos === "above" ? "" : `spectrum-FieldLabel--${labelPos}`
const updateLabel = e => {
- builderStore.actions.updateProp("label", e.target.textContent)
+ if (touched) {
+ builderStore.actions.updateProp("label", e.target.textContent)
+ }
+ touched = false
}
onDestroy(() => {
@@ -79,6 +84,7 @@
bind:this={labelNode}
contenteditable={$component.editing}
on:blur={$component.editing ? updateLabel : null}
+ on:input={() => (touched = true)}
class:hidden={!label}
class:readonly
for={fieldState?.fieldId}
From 75501c225198a43ffceae4f8eb8df44faf9c2532 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Fri, 31 May 2024 17:57:31 +0100
Subject: [PATCH 030/357] Updating object store stream upload to make sure the
stream has finished being processed before trying to upload to AWS (and only
uploading a partial stream).
---
.../src/objectStore/objectStore.ts | 35 +++++++++++++++----
.../types/src/documents/app/automation.ts | 2 +-
2 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts
index 0ac2c35179..35748d8f76 100644
--- a/packages/backend-core/src/objectStore/objectStore.ts
+++ b/packages/backend-core/src/objectStore/objectStore.ts
@@ -41,10 +41,7 @@ type UploadParams = BaseUploadParams & {
path?: string | PathLike
}
-export type StreamTypes =
- | ReadStream
- | NodeJS.ReadableStream
- | ReadableStream
+export type StreamTypes = ReadStream | NodeJS.ReadableStream
export type StreamUploadParams = BaseUploadParams & {
stream?: StreamTypes
@@ -222,6 +219,9 @@ export async function streamUpload({
extra,
ttl,
}: StreamUploadParams) {
+ if (!stream) {
+ throw new Error("Stream to upload is invalid/undefined")
+ }
const extension = filename.split(".").pop()
const objectStore = ObjectStore(bucketName)
const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
@@ -251,14 +251,35 @@ export async function streamUpload({
: CONTENT_TYPE_MAP.txt
}
+ const bucket = sanitizeBucket(bucketName),
+ objKey = sanitizeKey(filename)
const params = {
- Bucket: sanitizeBucket(bucketName),
- Key: sanitizeKey(filename),
+ Bucket: bucket,
+ Key: objKey,
Body: stream,
ContentType: contentType,
...extra,
}
- return objectStore.upload(params).promise()
+
+ // make sure we have the stream before we try to push it to object store
+ if (stream.on) {
+ await new Promise((resolve, reject) => {
+ stream.on("finish", resolve)
+ stream.on("error", reject)
+ })
+ }
+
+ const details = await objectStore.upload(params).promise()
+ const headDetails = await objectStore
+ .headObject({
+ Bucket: bucket,
+ Key: objKey,
+ })
+ .promise()
+ return {
+ ...details,
+ ContentLength: headDetails.ContentLength,
+ }
}
/**
diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts
index 6d1753dc28..63291fa3bb 100644
--- a/packages/types/src/documents/app/automation.ts
+++ b/packages/types/src/documents/app/automation.ts
@@ -245,7 +245,7 @@ export type AutomationAttachment = {
export type AutomationAttachmentContent = {
filename: string
- content: ReadStream | NodeJS.ReadableStream | ReadableStream
+ content: ReadStream | NodeJS.ReadableStream
}
export type BucketedContent = AutomationAttachmentContent & {
From d90763dd3c88008454a0cbe0a51175cfa454af0d Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Fri, 31 May 2024 17:59:16 +0100
Subject: [PATCH 031/357] Getting size parameter right for streams.
---
packages/server/src/integrations/utils/utils.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/integrations/utils/utils.ts b/packages/server/src/integrations/utils/utils.ts
index 9f04457d7a..be689fbfd3 100644
--- a/packages/server/src/integrations/utils/utils.ts
+++ b/packages/server/src/integrations/utils/utils.ts
@@ -368,13 +368,16 @@ export async function handleFileResponse(
size = parseInt(contentLength, 10)
}
- await objectStore.streamUpload({
+ const details = await objectStore.streamUpload({
bucket,
filename: key,
stream,
ttl: 1,
type: response.headers["content-type"],
})
+ if (!size && details.ContentLength) {
+ size = details.ContentLength
+ }
}
presignedUrl = objectStore.getPresignedUrl(bucket, key)
return {
From 26a0801b755ff704c62d6a7ac1755e8bd8b8ad5a Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Mon, 3 Jun 2024 10:15:16 +0100
Subject: [PATCH 032/357] Linting.
---
packages/server/src/threads/query.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/server/src/threads/query.ts b/packages/server/src/threads/query.ts
index 1210461bba..d76714661d 100644
--- a/packages/server/src/threads/query.ts
+++ b/packages/server/src/threads/query.ts
@@ -19,7 +19,6 @@ import {
Query,
SourceName,
Row,
- QueryParameter,
} from "@budibase/types"
import { isSQL } from "../integrations/utils"
From 5912c2b129f9888268a95afa23514668dcb97d43 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 31 May 2024 13:30:05 +0200
Subject: [PATCH 033/357] Copy change
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 3 +--
packages/server/src/sdk/app/views/index.ts | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 962d6e82a3..7e02966b44 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -349,8 +349,7 @@ describe.each([
await config.api.viewV2.create(newView, {
status: 400,
body: {
- message:
- 'Field "name" cannot be readonly as it is a required field',
+ message: 'You can\'t make the required field "name" read only',
status: 400,
},
})
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 18ab94be21..150fe2c678 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -60,7 +60,7 @@ async function guardViewSchema(
if (isRequired(tableSchemaField.constraints)) {
throw new HTTPError(
- `Field "${field}" cannot be readonly as it is a required field`,
+ `You can't make the required field "${field}" read only`,
400
)
}
From efc9d3399e11c20daf744e30194bcc7aac79e195 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Fri, 31 May 2024 17:08:50 +0200
Subject: [PATCH 034/357] Validate schema
---
.../src/api/routes/tests/viewV2.spec.ts | 126 +++++++++++++++++-
.../server/src/api/routes/utils/validators.ts | 36 ++++-
2 files changed, 153 insertions(+), 9 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 7e02966b44..af5cbfc063 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -23,9 +23,6 @@ import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
import merge from "lodash/merge"
import { quotas } from "@budibase/pro"
import { roles } from "@budibase/backend-core"
-import * as schemaUtils from "../../../utilities/schema"
-
-jest.mock("../../../utilities/schema")
describe.each([
["internal", undefined],
@@ -318,15 +315,13 @@ describe.each([
})
it("required fields cannot be marked as readonly", async () => {
- const isRequiredSpy = jest.spyOn(schemaUtils, "isRequired")
- isRequiredSpy.mockReturnValueOnce(true)
-
const table = await config.api.table.save(
saveTableRequest({
schema: {
name: {
name: "name",
type: FieldType.STRING,
+ constraints: { presence: true },
},
description: {
name: "description",
@@ -1347,4 +1342,123 @@ describe.each([
})
})
})
+
+ describe("updating table schema", () => {
+ describe("existing columns changed to required", () => {
+ beforeEach(async () => {
+ table = await config.api.table.save(
+ saveTableRequest({
+ schema: {
+ id: {
+ name: "id",
+ type: FieldType.AUTO,
+ autocolumn: true,
+ },
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ },
+ })
+ )
+ })
+
+ it("allows updating when no views constrains the field", async () => {
+ await config.api.viewV2.create({
+ name: "view a",
+ tableId: table._id!,
+ schema: {
+ id: { visible: true },
+ name: { visible: true },
+ },
+ })
+
+ table = await config.api.table.get(table._id!)
+ await config.api.table.save(
+ {
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: { presence: { allowEmpty: false } },
+ },
+ },
+ },
+ { status: 200 }
+ )
+ })
+
+ it("rejects if field is readonly in any view", async () => {
+ mocks.licenses.useViewReadonlyColumns()
+
+ await config.api.viewV2.create({
+ name: "view a",
+ tableId: table._id!,
+ schema: {
+ id: { visible: true },
+ name: {
+ visible: true,
+ readonly: true,
+ },
+ },
+ })
+
+ table = await config.api.table.get(table._id!)
+ await config.api.table.save(
+ {
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: { presence: true },
+ },
+ },
+ },
+ {
+ status: 400,
+ body: {
+ status: 400,
+ message:
+ 'Invalid body - Required field "name" is missing in view "view a"',
+ },
+ }
+ )
+ })
+
+ it("rejects if field is hidden in any view", async () => {
+ await config.api.viewV2.create({
+ name: "view a",
+ tableId: table._id!,
+ schema: { id: { visible: true } },
+ })
+
+ table = await config.api.table.get(table._id!)
+ await config.api.table.save(
+ {
+ ...table,
+ schema: {
+ ...table.schema,
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ constraints: { presence: true },
+ },
+ },
+ },
+ {
+ status: 400,
+ body: {
+ status: 400,
+ message:
+ 'Invalid body - Required field "name" is missing in view "view a"',
+ },
+ }
+ )
+ })
+ })
+ })
})
diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts
index 424d0d6c79..0a4eff4247 100644
--- a/packages/server/src/api/routes/utils/validators.ts
+++ b/packages/server/src/api/routes/utils/validators.ts
@@ -1,14 +1,44 @@
import { auth, permissions } from "@budibase/backend-core"
import { DataSourceOperation } from "../../../constants"
-import { WebhookActionType } from "@budibase/types"
-import Joi from "joi"
+import { Table, WebhookActionType } from "@budibase/types"
+import Joi, { CustomValidator } from "joi"
import { ValidSnippetNameRegex } from "@budibase/shared-core"
+import { isRequired } from "../../../utilities/schema"
+import sdk from "../../../sdk"
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
const APP_NAME_REGEX = /^[\w\s]+$/
+const validateViewSchemas: CustomValidator = (table, helpers) => {
+ if (table.views && Object.entries(table.views).length) {
+ const requiredFields = Object.entries(table.schema)
+ .filter(([_, v]) => isRequired(v.constraints))
+ .map(([key]) => key)
+ if (requiredFields.length) {
+ for (const view of Object.values(table.views)) {
+ if (!sdk.views.isV2(view)) {
+ continue
+ }
+
+ const editableViewFields = Object.entries(view.schema || {})
+ .filter(([_, f]) => f.visible && !f.readonly)
+ .map(([key]) => key)
+ const missingField = requiredFields.find(
+ f => !editableViewFields.includes(f)
+ )
+ if (missingField) {
+ return helpers.message({
+ custom: `Required field "${missingField}" is missing in view "${view.name}"`,
+ })
+ }
+ }
+ }
+ }
+ return table
+}
+
export function tableValidator() {
// prettier-ignore
return auth.joiValidator.body(Joi.object({
@@ -20,7 +50,7 @@ export function tableValidator() {
name: Joi.string().required(),
views: Joi.object(),
rows: Joi.array(),
- }).unknown(true))
+ }).custom(validateViewSchemas).unknown(true))
}
export function nameValidator() {
From 326a90a41e550b9859933fc7531b547efd06c622 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 14:37:19 +0200
Subject: [PATCH 035/357] Allow modifying views with readonly configs (other
fields)
---
packages/server/src/sdk/app/views/index.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 150fe2c678..292d643648 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -54,7 +54,10 @@ async function guardViewSchema(
}
if (viewSchema[field].readonly) {
- if (!(await features.isViewReadonlyColumnsEnabled())) {
+ if (
+ !(await features.isViewReadonlyColumnsEnabled()) &&
+ !(tableSchemaField as ViewUIFieldMetadata).readonly
+ ) {
throw new HTTPError(`Readonly fields are not enabled`, 400)
}
From dad689c78700c1344abffbf01276889d6a6deb46 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Thu, 30 May 2024 14:37:35 +0200
Subject: [PATCH 036/357] Reset schema mutations on erroring
---
.../src/components/grid/controls/ColumnsSettingButton.svelte | 2 ++
.../frontend-core/src/components/grid/stores/datasource.js | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 3f0e2341be..aa27871f92 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -24,6 +24,8 @@
await datasource.actions.saveSchemaMutations()
} catch (e) {
notifications.error(e.message)
+ } finally {
+ datasource.actions.resetSchemaMutations()
}
dispatch(visible ? "show-column" : "hide-column")
}
diff --git a/packages/frontend-core/src/components/grid/stores/datasource.js b/packages/frontend-core/src/components/grid/stores/datasource.js
index 1fc973f171..09b8be4868 100644
--- a/packages/frontend-core/src/components/grid/stores/datasource.js
+++ b/packages/frontend-core/src/components/grid/stores/datasource.js
@@ -204,6 +204,10 @@ export const createActions = context => {
...$definition,
schema: newSchema,
})
+ resetSchemaMutations()
+ }
+
+ const resetSchemaMutations = () => {
schemaMutations.set({})
}
@@ -253,6 +257,7 @@ export const createActions = context => {
addSchemaMutation,
addSchemaMutations,
saveSchemaMutations,
+ resetSchemaMutations,
},
},
}
From d73d7113aedd31a4674ca4560080f34b98d67f5f Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 10:07:42 +0200
Subject: [PATCH 037/357] Refresh on error
---
.../components/grid/controls/ColumnsSettingButton.svelte | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index aa27871f92..228cf69e34 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -19,13 +19,17 @@
const visible = permission !== PERMISSION_OPTIONS.HIDDEN
const readonly = permission === PERMISSION_OPTIONS.READONLY
- datasource.actions.addSchemaMutation(column.name, { visible, readonly })
+ await datasource.actions.addSchemaMutation(column.name, {
+ visible,
+ readonly,
+ })
try {
await datasource.actions.saveSchemaMutations()
} catch (e) {
notifications.error(e.message)
} finally {
- datasource.actions.resetSchemaMutations()
+ await datasource.actions.resetSchemaMutations()
+ await datasource.actions.refreshDefinition()
}
dispatch(visible ? "show-column" : "hide-column")
}
From 91c20213dc20341d1cee5dacc27b7a957ffadc37 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 10:29:26 +0200
Subject: [PATCH 038/357] Validate readonly
---
.../src/api/routes/tests/viewV2.spec.ts | 3 ++-
packages/server/src/sdk/app/views/index.ts | 25 +++++++++++--------
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index af5cbfc063..4e98ffe3cc 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -336,6 +336,7 @@ describe.each([
tableId: table._id!,
schema: {
name: {
+ visible: true,
readonly: true,
},
},
@@ -344,7 +345,7 @@ describe.each([
await config.api.viewV2.create(newView, {
status: 400,
body: {
- message: 'You can\'t make the required field "name" read only',
+ message: 'You can\'t make read only the required field "name"',
status: 400,
},
})
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 292d643648..ff51a73e99 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -39,9 +39,7 @@ async function guardViewSchema(
tableId: string,
viewSchema?: Record
) {
- if (!viewSchema || !Object.keys(viewSchema).length) {
- return
- }
+ viewSchema ??= {}
const table = await sdk.tables.getTable(tableId)
for (const field of Object.keys(viewSchema)) {
@@ -61,13 +59,6 @@ async function guardViewSchema(
throw new HTTPError(`Readonly fields are not enabled`, 400)
}
- if (isRequired(tableSchemaField.constraints)) {
- throw new HTTPError(
- `You can't make the required field "${field}" read only`,
- 400
- )
- }
-
if (!viewSchema[field].visible) {
throw new HTTPError(
`Field "${field}" must be visible if you want to make it readonly`,
@@ -76,6 +67,20 @@ async function guardViewSchema(
}
}
}
+
+ for (const field of Object.values(table.schema)) {
+ if (!isRequired(field.constraints)) {
+ continue
+ }
+
+ const viewSchemaField = viewSchema[field.name]
+ if (viewSchemaField?.readonly) {
+ throw new HTTPError(
+ `You can't make read only the required field "${field.name}"`,
+ 400
+ )
+ }
+ }
}
export async function create(
From c1b760ca9e1c977396fa0fd201c7b09ab0b0ba39 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 12:43:51 +0200
Subject: [PATCH 039/357] Validate that required fields can't be hidden in
views
---
packages/server/src/sdk/app/views/index.ts | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index ff51a73e99..98871539c2 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -74,7 +74,15 @@ async function guardViewSchema(
}
const viewSchemaField = viewSchema[field.name]
- if (viewSchemaField?.readonly) {
+
+ if (!viewSchemaField?.visible) {
+ throw new HTTPError(
+ `You can't hide the required field "${field.name}"`,
+ 400
+ )
+ }
+
+ if (viewSchemaField.readonly) {
throw new HTTPError(
`You can't make read only the required field "${field.name}"`,
400
From 155de99b68c8fc481cc4d0ac37eba751595cb22f Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Mon, 3 Jun 2024 11:46:20 +0100
Subject: [PATCH 040/357] Streaming to disk before passing onto S3.
---
.../src/objectStore/objectStore.ts | 9 +--
packages/server/package.json | 5 +-
.../src/integrations/tests/rest.spec.ts | 2 +
.../server/src/integrations/utils/utils.ts | 70 +++++++++++--------
yarn.lock | 12 +++-
5 files changed, 56 insertions(+), 42 deletions(-)
diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts
index 35748d8f76..de94e3968b 100644
--- a/packages/backend-core/src/objectStore/objectStore.ts
+++ b/packages/backend-core/src/objectStore/objectStore.ts
@@ -14,6 +14,7 @@ import { v4 } from "uuid"
import { APP_PREFIX, APP_DEV_PREFIX } from "../db"
import fsp from "fs/promises"
import { HeadObjectOutput } from "aws-sdk/clients/s3"
+import { ReadableStream } from "stream/web"
const streamPipeline = promisify(stream.pipeline)
// use this as a temporary store of buckets that are being created
@@ -261,14 +262,6 @@ export async function streamUpload({
...extra,
}
- // make sure we have the stream before we try to push it to object store
- if (stream.on) {
- await new Promise((resolve, reject) => {
- stream.on("finish", resolve)
- stream.on("error", reject)
- })
- }
-
const details = await objectStore.upload(params).promise()
const headDetails = await objectStore
.headObject({
diff --git a/packages/server/package.json b/packages/server/package.json
index bd5a82cb29..b3beac7ffb 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -68,7 +68,6 @@
"aws-sdk": "2.1030.0",
"bcrypt": "5.1.0",
"bcryptjs": "2.4.3",
- "bl": "^6.0.12",
"bull": "4.10.1",
"chokidar": "3.5.3",
"content-disposition": "^0.5.4",
@@ -116,7 +115,8 @@
"uuid": "^8.3.2",
"validate.js": "0.13.1",
"worker-farm": "1.7.0",
- "xml2js": "0.5.0"
+ "xml2js": "0.5.0",
+ "tmp": "0.2.3"
},
"devDependencies": {
"@babel/preset-env": "7.16.11",
@@ -137,6 +137,7 @@
"@types/supertest": "2.0.14",
"@types/tar": "6.1.5",
"@types/uuid": "8.3.4",
+ "@types/tmp": "0.2.6",
"copyfiles": "2.4.1",
"docker-compose": "0.23.17",
"jest": "29.7.0",
diff --git a/packages/server/src/integrations/tests/rest.spec.ts b/packages/server/src/integrations/tests/rest.spec.ts
index f20f369c25..dee17a5497 100644
--- a/packages/server/src/integrations/tests/rest.spec.ts
+++ b/packages/server/src/integrations/tests/rest.spec.ts
@@ -657,6 +657,7 @@ describe("REST Integration", () => {
mockReadable.push(null)
;(fetch as unknown as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
+ status: 200,
headers: {
raw: () => ({
"content-type": [contentType],
@@ -700,6 +701,7 @@ describe("REST Integration", () => {
mockReadable.push(null)
;(fetch as unknown as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
+ status: 200,
headers: {
raw: () => ({
"content-type": [contentType],
diff --git a/packages/server/src/integrations/utils/utils.ts b/packages/server/src/integrations/utils/utils.ts
index be689fbfd3..157bdba3bd 100644
--- a/packages/server/src/integrations/utils/utils.ts
+++ b/packages/server/src/integrations/utils/utils.ts
@@ -9,10 +9,12 @@ import { context, objectStore, sql } from "@budibase/backend-core"
import { v4 } from "uuid"
import { parseStringPromise as xmlParser } from "xml2js"
import { formatBytes } from "../../utilities"
-import bl from "bl"
import env from "../../environment"
import { InvalidColumns } from "../../constants"
import { helpers, utils } from "@budibase/shared-core"
+import { pipeline } from "stream/promises"
+import tmp from "tmp"
+import fs from "fs"
type PrimitiveTypes =
| FieldType.STRING
@@ -360,38 +362,44 @@ export async function handleFileResponse(
const key = `${context.getProdAppId()}/${processedFileName}`
const bucket = objectStore.ObjectStoreBuckets.TEMP
- const stream = response.body.pipe(bl((error, data) => data))
+ // put the response stream to disk temporarily as a buffer
+ const tmpObj = tmp.fileSync()
+ try {
+ await pipeline(response.body, fs.createWriteStream(tmpObj.name))
+ if (response.body) {
+ const contentLength = response.headers.get("content-length")
+ if (contentLength) {
+ size = parseInt(contentLength, 10)
+ }
- if (response.body) {
- const contentLength = response.headers.get("content-length")
- if (contentLength) {
- size = parseInt(contentLength, 10)
+ const details = await objectStore.streamUpload({
+ bucket,
+ filename: key,
+ stream: fs.createReadStream(tmpObj.name),
+ ttl: 1,
+ type: response.headers["content-type"],
+ })
+ if (!size && details.ContentLength) {
+ size = details.ContentLength
+ }
}
-
- const details = await objectStore.streamUpload({
- bucket,
- filename: key,
- stream,
- ttl: 1,
- type: response.headers["content-type"],
- })
- if (!size && details.ContentLength) {
- size = details.ContentLength
+ presignedUrl = objectStore.getPresignedUrl(bucket, key)
+ return {
+ data: {
+ size,
+ name: processedFileName,
+ url: presignedUrl,
+ extension: fileExtension,
+ key: key,
+ },
+ info: {
+ code: response.status,
+ size: formatBytes(size.toString()),
+ time: `${Math.round(performance.now() - startTime)}ms`,
+ },
}
- }
- presignedUrl = objectStore.getPresignedUrl(bucket, key)
- return {
- data: {
- size,
- name: processedFileName,
- url: presignedUrl,
- extension: fileExtension,
- key: key,
- },
- info: {
- code: response.status,
- size: formatBytes(size.toString()),
- time: `${Math.round(performance.now() - startTime)}ms`,
- },
+ } finally {
+ // cleanup tmp
+ tmpObj.removeCallback()
}
}
diff --git a/yarn.lock b/yarn.lock
index d85a50e938..5297fe0cad 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6348,6 +6348,11 @@
dependencies:
"@types/estree" "*"
+"@types/tmp@0.2.6":
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.6.tgz#d785ee90c52d7cc020e249c948c36f7b32d1e217"
+ integrity sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==
+
"@types/tough-cookie@*", "@types/tough-cookie@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
@@ -7700,7 +7705,7 @@ bl@^4.0.3, bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
-bl@^6.0.12, bl@^6.0.3:
+bl@^6.0.3:
version "6.0.12"
resolved "https://registry.yarnpkg.com/bl/-/bl-6.0.12.tgz#77c35b96e13aeff028496c798b75389ddee9c7f8"
integrity sha512-EnEYHilP93oaOa2MnmNEjAcovPS3JlQZOyzGXi3EyEpPhm9qWvdDp7BmAVEVusGzp8LlwQK56Av+OkDoRjzE0w==
@@ -21283,6 +21288,11 @@ tlhunter-sorted-set@^0.1.0:
resolved "https://registry.yarnpkg.com/tlhunter-sorted-set/-/tlhunter-sorted-set-0.1.0.tgz#1c3eae28c0fa4dff97e9501d2e3c204b86406f4b"
integrity sha512-eGYW4bjf1DtrHzUYxYfAcSytpOkA44zsr7G2n3PV7yOUR23vmkGe3LL4R+1jL9OsXtbsFOwe8XtbCrabeaEFnw==
+tmp@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
+ integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
From 38ff7debb46dc32f8d7f1b8d3ce073d5ad893594 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Mon, 3 Jun 2024 12:08:54 +0100
Subject: [PATCH 041/357] Linting.
---
packages/server/src/threads/query.ts | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/packages/server/src/threads/query.ts b/packages/server/src/threads/query.ts
index d76714661d..ba451a3325 100644
--- a/packages/server/src/threads/query.ts
+++ b/packages/server/src/threads/query.ts
@@ -14,12 +14,7 @@ import { context, cache, auth } from "@budibase/backend-core"
import { getGlobalIDFromUserMetadataID } from "../db/utils"
import sdk from "../sdk"
import { cloneDeep } from "lodash/fp"
-import {
- Datasource,
- Query,
- SourceName,
- Row,
-} from "@budibase/types"
+import { Datasource, Query, SourceName, Row } from "@budibase/types"
import { isSQL } from "../integrations/utils"
import { interpolateSQL } from "../integrations/queries/sql"
From cc3808997cb3938df99e793c13aad2935ef4e446 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 13:26:49 +0200
Subject: [PATCH 042/357] Fix viewV2 tests
---
.../src/api/routes/tests/viewV2.spec.ts | 59 ++++++++++++++++++-
1 file changed, 57 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 4e98ffe3cc..57a26dd6c7 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -117,6 +117,9 @@ describe.each([
const newView: CreateViewRequest = {
name: generator.name(),
tableId: table._id!,
+ schema: {
+ id: { visible: true },
+ },
}
const res = await config.api.viewV2.create(newView)
@@ -145,6 +148,7 @@ describe.each([
type: SortType.STRING,
},
schema: {
+ id: { visible: true },
Price: {
visible: true,
},
@@ -155,6 +159,7 @@ describe.each([
expect(res).toEqual({
...newView,
schema: {
+ id: { visible: true },
Price: {
visible: true,
},
@@ -169,6 +174,11 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: {
+ name: "id",
+ type: FieldType.NUMBER,
+ visible: true,
+ },
Price: {
name: "Price",
type: FieldType.NUMBER,
@@ -190,6 +200,7 @@ describe.each([
expect(createdView).toEqual({
...newView,
schema: {
+ id: { visible: true },
Price: {
visible: true,
order: 1,
@@ -206,6 +217,12 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: {
+ name: "id",
+ type: FieldType.AUTO,
+ autocolumn: true,
+ visible: true,
+ },
Price: {
name: "Price",
type: FieldType.NUMBER,
@@ -229,6 +246,7 @@ describe.each([
tableId: table._id!,
primaryDisplay: generator.word(),
schema: {
+ id: { visible: true },
Price: { visible: true },
Category: { visible: false },
},
@@ -238,6 +256,7 @@ describe.each([
expect(res).toEqual({
...newView,
schema: {
+ id: { visible: true },
Price: {
visible: true,
},
@@ -252,6 +271,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
nonExisting: {
visible: true,
},
@@ -290,6 +310,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
name: {
visible: true,
readonly: true,
@@ -303,6 +324,7 @@ describe.each([
const res = await config.api.viewV2.create(newView)
expect(res.schema).toEqual({
+ id: { visible: true },
name: {
visible: true,
readonly: true,
@@ -335,6 +357,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
name: {
visible: true,
readonly: true,
@@ -371,6 +394,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
name: {
visible: false,
readonly: true,
@@ -409,6 +433,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
name: {
visible: true,
readonly: true,
@@ -436,6 +461,9 @@ describe.each([
view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ },
})
})
@@ -484,6 +512,7 @@ describe.each([
type: SortType.STRING,
},
schema: {
+ id: { visible: true },
Category: {
visible: false,
},
@@ -501,7 +530,7 @@ describe.each([
schema: {
...table.schema,
id: expect.objectContaining({
- visible: false,
+ visible: true,
}),
Category: expect.objectContaining({
visible: false,
@@ -598,6 +627,9 @@ describe.each([
const anotherView = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ },
})
const result = await config
.request!.put(`/api/v2/views/${anotherView.id}`)
@@ -616,6 +648,7 @@ describe.each([
const updatedView = await config.api.viewV2.update({
...view,
schema: {
+ ...view.schema,
Price: {
name: "Price",
type: FieldType.NUMBER,
@@ -635,6 +668,7 @@ describe.each([
expect(updatedView).toEqual({
...view,
schema: {
+ id: { visible: true },
Price: {
visible: true,
order: 1,
@@ -651,6 +685,7 @@ describe.each([
{
...view,
schema: {
+ ...view.schema,
Price: {
name: "Price",
type: FieldType.NUMBER,
@@ -674,6 +709,7 @@ describe.each([
view = await config.api.viewV2.update({
...view,
schema: {
+ id: { visible: true },
Price: {
visible: true,
readonly: true,
@@ -696,6 +732,7 @@ describe.each([
view = await config.api.viewV2.update({
...view,
schema: {
+ id: { visible: true },
Price: {
visible: true,
readonly: true,
@@ -710,6 +747,7 @@ describe.each([
const res = await config.api.viewV2.update({
...view,
schema: {
+ id: { visible: true },
Price: {
visible: true,
readonly: false,
@@ -720,6 +758,7 @@ describe.each([
expect.objectContaining({
...view,
schema: {
+ id: { visible: true },
Price: {
visible: true,
readonly: false,
@@ -737,6 +776,9 @@ describe.each([
view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ },
})
})
@@ -759,6 +801,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
Price: { visible: false },
Category: { visible: true },
},
@@ -781,6 +824,7 @@ describe.each([
name: generator.name(),
tableId: table._id!,
schema: {
+ id: { visible: true },
Price: { visible: true, readonly: true },
},
})
@@ -816,6 +860,7 @@ describe.each([
tableId: table._id!,
name: generator.guid(),
schema: {
+ id: { visible: true },
Country: {
visible: true,
},
@@ -850,6 +895,7 @@ describe.each([
tableId: table._id!,
name: generator.guid(),
schema: {
+ id: { visible: true },
two: { visible: true },
},
})
@@ -875,6 +921,7 @@ describe.each([
tableId: table._id!,
name: generator.guid(),
schema: {
+ id: { visible: true },
one: { visible: true, readonly: true },
two: { visible: true },
},
@@ -916,6 +963,7 @@ describe.each([
tableId: table._id!,
name: generator.guid(),
schema: {
+ id: { visible: true },
one: { visible: true, readonly: true },
two: { visible: true },
},
@@ -983,6 +1031,7 @@ describe.each([
rows.map(r => ({
_viewId: view.id,
tableId: table._id,
+ id: r.id,
_id: r._id,
_rev: r._rev,
...(isInternal
@@ -1023,6 +1072,7 @@ describe.each([
},
],
schema: {
+ id: { visible: true },
two: { visible: true },
},
})
@@ -1034,6 +1084,7 @@ describe.each([
{
_viewId: view.id,
tableId: table._id,
+ id: two.id,
two: two.two,
_id: two._id,
_rev: two._rev,
@@ -1187,7 +1238,11 @@ describe.each([
describe("sorting", () => {
let table: Table
- const viewSchema = { age: { visible: true }, name: { visible: true } }
+ const viewSchema = {
+ id: { visible: true },
+ age: { visible: true },
+ name: { visible: true },
+ }
beforeAll(async () => {
table = await config.api.table.save(
From 64a5accffd13661464197e07bb234eb1631326f7 Mon Sep 17 00:00:00 2001
From: Budibase Staging Release Bot <>
Date: Mon, 3 Jun 2024 13:02:24 +0000
Subject: [PATCH 043/357] Bump version to 2.27.6
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index 335df975af..d90f7732a2 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.27.5",
+ "version": "2.27.6",
"npmClient": "yarn",
"packages": [
"packages/*",
From 3909bbcfc00cda2173ef10eff463f7e78a67ad83 Mon Sep 17 00:00:00 2001
From: Martin McKeaveney
Date: Mon, 3 Jun 2024 15:05:18 +0100
Subject: [PATCH 044/357] NGINX headers for security audit
---
hosting/proxy/nginx.prod.conf | 1 +
packages/account-portal | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/hosting/proxy/nginx.prod.conf b/hosting/proxy/nginx.prod.conf
index 79007da311..217106b1bf 100644
--- a/hosting/proxy/nginx.prod.conf
+++ b/hosting/proxy/nginx.prod.conf
@@ -74,6 +74,7 @@ http {
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "${csp_default}; ${csp_script}; ${csp_style}; ${csp_object}; ${csp_base_uri}; ${csp_connect}; ${csp_font}; ${csp_frame}; ${csp_img}; ${csp_manifest}; ${csp_media}; ${csp_worker};" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# upstreams
set $apps ${APPS_UPSTREAM_URL};
diff --git a/packages/account-portal b/packages/account-portal
index c167c331ff..2a5022fb94 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit c167c331ff9b8161fc18e2ecbaaf1ea5815ba964
+Subproject commit 2a5022fb946481c9f7a9c38d1413922729972be0
From 020477f1f69abf9e0c265e4f452cfbf382b35e5f Mon Sep 17 00:00:00 2001
From: Christos Alexiou
Date: Mon, 3 Jun 2024 17:15:51 +0300
Subject: [PATCH 045/357] qa-arc-runner-set -> ubuntu-latest
---
.github/workflows/force-release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/force-release.yml b/.github/workflows/force-release.yml
index 8a9d444f51..3d96d51484 100644
--- a/.github/workflows/force-release.yml
+++ b/.github/workflows/force-release.yml
@@ -9,7 +9,7 @@ on:
jobs:
ensure-is-master-tag:
name: Ensure is a master tag
- runs-on: qa-arc-runner-set
+ runs-on: ubuntu-latest
steps:
- name: Checkout monorepo
uses: actions/checkout@v4
From b937d95de2a4556fa10deb11a7311b7bd44f0318 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 16:55:02 +0200
Subject: [PATCH 046/357] Move isRequired to shared-core
---
packages/server/src/sdk/app/views/index.ts | 4 ++--
packages/server/src/utilities/schema.ts | 10 ----------
packages/shared-core/src/helpers/schema.ts | 10 ++++++++++
.../src/helpers}/tests/schema.spec.ts | 0
4 files changed, 12 insertions(+), 12 deletions(-)
rename packages/{server/src/utilities => shared-core/src/helpers}/tests/schema.spec.ts (100%)
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 18ab94be21..127c955dc8 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -8,6 +8,7 @@ import {
} from "@budibase/types"
import { HTTPError, db as dbCore } from "@budibase/backend-core"
import { features } from "@budibase/pro"
+import { helpers } from "@budibase/shared-core"
import { cloneDeep } from "lodash"
import * as utils from "../../../db/utils"
@@ -16,7 +17,6 @@ import { isExternalTableID } from "../../../integrations/utils"
import * as internal from "./internal"
import * as external from "./external"
import sdk from "../../../sdk"
-import { isRequired } from "../../../utilities/schema"
function pickApi(tableId: any) {
if (isExternalTableID(tableId)) {
@@ -58,7 +58,7 @@ async function guardViewSchema(
throw new HTTPError(`Readonly fields are not enabled`, 400)
}
- if (isRequired(tableSchemaField.constraints)) {
+ if (helpers.schema.isRequired(tableSchemaField.constraints)) {
throw new HTTPError(
`Field "${field}" cannot be readonly as it is a required field`,
400
diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts
index 7fea417d5a..b4ea1277d6 100644
--- a/packages/server/src/utilities/schema.ts
+++ b/packages/server/src/utilities/schema.ts
@@ -4,7 +4,6 @@ import {
TableSchema,
FieldSchema,
Row,
- FieldConstraints,
} from "@budibase/types"
import { ValidColumnNameRegex, utils } from "@budibase/shared-core"
import { db } from "@budibase/backend-core"
@@ -41,15 +40,6 @@ export function isRows(rows: any): rows is Rows {
return Array.isArray(rows) && rows.every(row => typeof row === "object")
}
-export function isRequired(constraints: FieldConstraints | undefined) {
- const isRequired =
- !!constraints &&
- ((typeof constraints.presence !== "boolean" &&
- constraints.presence?.allowEmpty === false) ||
- constraints.presence === true)
- return isRequired
-}
-
export function validate(rows: Rows, schema: TableSchema): ValidationResults {
const results: ValidationResults = {
schemaValidation: {},
diff --git a/packages/shared-core/src/helpers/schema.ts b/packages/shared-core/src/helpers/schema.ts
index ad4c237247..caf562a8cb 100644
--- a/packages/shared-core/src/helpers/schema.ts
+++ b/packages/shared-core/src/helpers/schema.ts
@@ -1,5 +1,6 @@
import {
BBReferenceFieldSubType,
+ FieldConstraints,
FieldSchema,
FieldType,
} from "@budibase/types"
@@ -16,3 +17,12 @@ export function isDeprecatedSingleUserColumn(
schema.constraints?.type !== "array"
return result
}
+
+export function isRequired(constraints: FieldConstraints | undefined) {
+ const isRequired =
+ !!constraints &&
+ ((typeof constraints.presence !== "boolean" &&
+ constraints.presence?.allowEmpty === false) ||
+ constraints.presence === true)
+ return isRequired
+}
diff --git a/packages/server/src/utilities/tests/schema.spec.ts b/packages/shared-core/src/helpers/tests/schema.spec.ts
similarity index 100%
rename from packages/server/src/utilities/tests/schema.spec.ts
rename to packages/shared-core/src/helpers/tests/schema.spec.ts
From 42d60ad95b549e6fe9e005dbd584425079da5ef2 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 16:56:12 +0200
Subject: [PATCH 047/357] Fix
---
packages/server/src/utilities/schema.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts
index b4ea1277d6..8e6cd34c7c 100644
--- a/packages/server/src/utilities/schema.ts
+++ b/packages/server/src/utilities/schema.ts
@@ -5,7 +5,7 @@ import {
FieldSchema,
Row,
} from "@budibase/types"
-import { ValidColumnNameRegex, utils } from "@budibase/shared-core"
+import { ValidColumnNameRegex, helpers, utils } from "@budibase/shared-core"
import { db } from "@budibase/backend-core"
import { parseCsvExport } from "../api/controllers/view/exporters"
@@ -99,7 +99,7 @@ export function validate(rows: Rows, schema: TableSchema): ValidationResults {
columnData,
columnType,
columnSubtype,
- isRequired(constraints)
+ helpers.schema.isRequired(constraints)
)
) {
results.schemaValidation[columnName] = false
From cb2349fdeff75aec3bc7ef71ed1e5d4cf2c442b3 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 17:04:35 +0200
Subject: [PATCH 048/357] Allow edition display
---
.../grid/controls/ColumnsSettingButton.svelte | 76 +++++++++----------
1 file changed, 37 insertions(+), 39 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 3f0e2341be..43efda80fa 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -11,7 +11,9 @@
let open = false
let anchor
- $: restrictedColumns = $columns.filter(col => !col.visible || col.readonly)
+ $: allColumns = $stickyColumn ? [$stickyColumn, ...$columns] : $columns
+
+ $: restrictedColumns = allColumns.filter(col => !col.visible || col.readonly)
$: anyRestricted = restrictedColumns.length
$: text = anyRestricted ? `Columns (${anyRestricted} restricted)` : "Columns"
@@ -34,36 +36,44 @@
HIDDEN: "hidden",
}
- const EDIT_OPTION = {
- icon: "Edit",
- value: PERMISSION_OPTIONS.WRITABLE,
- tooltip: "Writable",
- }
- $: READONLY_OPTION = {
- icon: "Visibility",
- value: PERMISSION_OPTIONS.READONLY,
- tooltip: allowViewReadonlyColumns
- ? "Read only"
- : "Read only (premium feature)",
- disabled: !allowViewReadonlyColumns,
- }
- const HIDDEN_OPTION = {
- icon: "VisibilityOff",
- value: PERMISSION_OPTIONS.HIDDEN,
- tooltip: "Hidden",
- }
+ $: displayColumns = allColumns.map(c => {
+ const isDisplayColumn = $stickyColumn === c
- $: options =
- $datasource.type === "viewV2"
- ? [EDIT_OPTION, READONLY_OPTION, HIDDEN_OPTION]
- : [EDIT_OPTION, HIDDEN_OPTION]
+ const options = [
+ {
+ icon: "Edit",
+ value: PERMISSION_OPTIONS.WRITABLE,
+ tooltip: "Writable",
+ },
+ ]
+ if ($datasource.type === "viewV2") {
+ options.push({
+ icon: "Visibility",
+ value: PERMISSION_OPTIONS.READONLY,
+ tooltip: allowViewReadonlyColumns
+ ? "Read only"
+ : "Read only (premium feature)",
+ disabled: !allowViewReadonlyColumns,
+ })
+ }
+
+ options.push({
+ icon: "VisibilityOff",
+ value: PERMISSION_OPTIONS.HIDDEN,
+ tooltip: "Hidden",
+ disabled: isDisplayColumn,
+ tooltip: isDisplayColumn && "Display column cannot be hidden",
+ })
+
+ return { ...c, options }
+ })
function columnToPermissionOptions(column) {
- if (!column.visible) {
+ if (!column.schema.visible) {
return PERMISSION_OPTIONS.HIDDEN
}
- if (column.readonly) {
+ if (column.schema.readonly) {
return PERMISSION_OPTIONS.READONLY
}
@@ -87,19 +97,7 @@
- {#if $stickyColumn}
-
-
- {$stickyColumn.label}
-
-
-
({ ...o, disabled: true }))}
- />
- {/if}
- {#each $columns as column}
+ {#each displayColumns as column}
{column.label}
@@ -107,7 +105,7 @@
toggleColumn(column, e.detail)}
value={columnToPermissionOptions(column)}
- {options}
+ options={column.options}
/>
{/each}
From 10f77c83b6ef08522da14181f1aabda05f213cca Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Mon, 3 Jun 2024 17:24:30 +0200
Subject: [PATCH 049/357] Don't allow selecting required columns
---
.../grid/controls/ColumnsSettingButton.svelte | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 43efda80fa..d4ae71280b 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -3,6 +3,7 @@
import { ActionButton, Popover, Icon, notifications } from "@budibase/bbui"
import { getColumnIcon } from "../lib/utils"
import ToggleActionButtonGroup from "./ToggleActionButtonGroup.svelte"
+ import { helpers } from "@budibase/shared-core"
export let allowViewReadonlyColumns = false
@@ -37,13 +38,17 @@
}
$: displayColumns = allColumns.map(c => {
+ const isRequired = helpers.schema.isRequired(c.schema.constraints)
const isDisplayColumn = $stickyColumn === c
+ const requiredTooltip = isRequired && "Required columns must be writable"
+
const options = [
{
icon: "Edit",
value: PERMISSION_OPTIONS.WRITABLE,
- tooltip: "Writable",
+ tooltip: requiredTooltip || "Writable",
+ disabled: isRequired,
},
]
if ($datasource.type === "viewV2") {
@@ -51,17 +56,17 @@
icon: "Visibility",
value: PERMISSION_OPTIONS.READONLY,
tooltip: allowViewReadonlyColumns
- ? "Read only"
+ ? requiredTooltip || "Read only"
: "Read only (premium feature)",
- disabled: !allowViewReadonlyColumns,
+ disabled: !allowViewReadonlyColumns || isRequired,
})
}
options.push({
icon: "VisibilityOff",
value: PERMISSION_OPTIONS.HIDDEN,
- tooltip: "Hidden",
- disabled: isDisplayColumn,
+ tooltip: requiredTooltip || "Hidden",
+ disabled: isDisplayColumn || isRequired,
tooltip: isDisplayColumn && "Display column cannot be hidden",
})
From 20c18259a95cf9303ece127c76456aef7011c10f Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Mon, 3 Jun 2024 17:38:22 +0100
Subject: [PATCH 050/357] Update CouchDB chart from 4.3.0 to 4.5.3.
---
charts/budibase/Chart.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml
index e2c9378f2c..83a72d203f 100644
--- a/charts/budibase/Chart.yaml
+++ b/charts/budibase/Chart.yaml
@@ -17,6 +17,6 @@ version: 0.0.0
appVersion: 0.0.0
dependencies:
- name: couchdb
- version: 4.3.0
+ version: 4.5.3
repository: https://apache.github.io/couchdb-helm
condition: services.couchdb.enabled
From b3d7e69046a7bc0cdaef26485ca8f21afdb554f1 Mon Sep 17 00:00:00 2001
From: Hector Valcarcel
Date: Tue, 4 Jun 2024 10:24:52 +0200
Subject: [PATCH 051/357] feat: add values definition for extra env var using
secrets ref
---
charts/budibase/values.yaml | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml
index 27037cdaa8..0b86218d2e 100644
--- a/charts/budibase/values.yaml
+++ b/charts/budibase/values.yaml
@@ -240,6 +240,13 @@ services:
# -- Extra environment variables to set for apps pods. Takes a list of
# name=value pairs.
extraEnv: []
+ # -- Name of the K8s Secret in the same namespace which contains the extra environment variables.
+ # This can be used to avoid storing sensitive information in the values.yaml file.
+ extraEnvFromSecret: []
+ # - name: MY_SECRET_KEY
+ # secretName : my-secret
+ # secretKey: my-secret-key
+
# -- Startup probe configuration for apps pods. You shouldn't need to
# change this, but if you want to you can find more information here:
#
@@ -323,6 +330,13 @@ services:
# -- Extra environment variables to set for automation worker pods. Takes a list of
# name=value pairs.
extraEnv: []
+ # -- Name of the K8s Secret in the same namespace which contains the extra environment variables.
+ # This can be used to avoid storing sensitive information in the values.yaml file.
+ extraEnvFromSecret: []
+ # - name: MY_SECRET_KEY
+ # secretName : my-secret
+ # secretKey: my-secret-key
+
# -- Startup probe configuration for automation worker pods. You shouldn't
# need to change this, but if you want to you can find more information
# here:
@@ -408,6 +422,13 @@ services:
# -- Extra environment variables to set for worker pods. Takes a list of
# name=value pairs.
extraEnv: []
+ # -- Name of the K8s Secret in the same namespace which contains the extra environment variables.
+ # This can be used to avoid storing sensitive information in the values.yaml file.
+ extraEnvFromSecret: []
+ # - name: MY_SECRET_KEY
+ # secretName : my-secret
+ # secretKey: my-secret-key
+
# -- Startup probe configuration for worker pods. You shouldn't need to
# change this, but if you want to you can find more information here:
#
From d554a8287b74c030ee0254872b869e514ce2da79 Mon Sep 17 00:00:00 2001
From: Hector Valcarcel
Date: Tue, 4 Jun 2024 10:26:44 +0200
Subject: [PATCH 052/357] feat: attach env vars from 'extraEnvFromSecret'
---
charts/budibase/templates/app-service-deployment.yaml | 7 +++++++
.../templates/automation-worker-service-deployment.yaml | 9 ++++++++-
charts/budibase/templates/worker-service-deployment.yaml | 7 +++++++
3 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml
index ed7166ec5d..b764065bfc 100644
--- a/charts/budibase/templates/app-service-deployment.yaml
+++ b/charts/budibase/templates/app-service-deployment.yaml
@@ -202,6 +202,13 @@ spec:
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
+ {{- range .Values.services.apps.extraEnvFromSecret}}
+ - name: {{ .name }}
+ valueFrom:
+ secretKeyRef:
+ name: {{ .secretName }}
+ key: {{ .secretKey | quote }}
+ {{- end}}
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
imagePullPolicy: Always
{{- if .Values.services.apps.startupProbe }}
diff --git a/charts/budibase/templates/automation-worker-service-deployment.yaml b/charts/budibase/templates/automation-worker-service-deployment.yaml
index 3c6f94ae9e..38a384626e 100644
--- a/charts/budibase/templates/automation-worker-service-deployment.yaml
+++ b/charts/budibase/templates/automation-worker-service-deployment.yaml
@@ -201,6 +201,13 @@ spec:
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
+ {{- range .Values.services.automationWorkers.extraEnvFromSecret}}
+ - name: {{ .name }}
+ valueFrom:
+ secretKeyRef:
+ name: {{ .secretName }}
+ key: {{ .secretKey | quote }}
+ {{- end}}
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
imagePullPolicy: Always
@@ -272,4 +279,4 @@ spec:
{{- toYaml .Values.services.automationWorkers.extraVolumes | nindent 8 }}
{{ end }}
status: {}
-{{- end }}
\ No newline at end of file
+{{- end }}
diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml
index 66a9bb6c14..1f9d1a8ed7 100644
--- a/charts/budibase/templates/worker-service-deployment.yaml
+++ b/charts/budibase/templates/worker-service-deployment.yaml
@@ -188,6 +188,13 @@ spec:
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
+ {{- range .Values.services.worker.extraEnvFromSecret}}
+ - name: {{ .name }}
+ valueFrom:
+ secretKeyRef:
+ name: {{ .secretName }}
+ key: {{ .secretKey | quote }}
+ {{- end}}
image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
imagePullPolicy: Always
{{- if .Values.services.worker.startupProbe }}
From 9b82116c61f7ea9854bea5cefe7dfd1be9c85c19 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 10:39:56 +0200
Subject: [PATCH 053/357] Copy changes
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 3 ++-
packages/server/src/sdk/app/views/index.ts | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 4e98ffe3cc..663cf5c864 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -345,7 +345,8 @@ describe.each([
await config.api.viewV2.create(newView, {
status: 400,
body: {
- message: 'You can\'t make read only the required field "name"',
+ message:
+ 'You can\'t make field "name" readonly because it is a required field.',
status: 400,
},
})
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index ff51a73e99..6cdd38bf43 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -76,7 +76,7 @@ async function guardViewSchema(
const viewSchemaField = viewSchema[field.name]
if (viewSchemaField?.readonly) {
throw new HTTPError(
- `You can't make read only the required field "${field.name}"`,
+ `You can't make field "${field.name}" readonly because it is a required field.`,
400
)
}
From 2d953f19cc37a47756a0655cacf7273b19bf0333 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 11:11:50 +0200
Subject: [PATCH 054/357] Clean validation message
---
.../src/middleware/joi-validator.ts | 23 +-
.../server/src/api/routes/utils/validators.ts | 276 ++++++++++--------
2 files changed, 172 insertions(+), 127 deletions(-)
diff --git a/packages/backend-core/src/middleware/joi-validator.ts b/packages/backend-core/src/middleware/joi-validator.ts
index ac8064a512..5047cdbbc1 100644
--- a/packages/backend-core/src/middleware/joi-validator.ts
+++ b/packages/backend-core/src/middleware/joi-validator.ts
@@ -3,7 +3,8 @@ import { Ctx } from "@budibase/types"
function validate(
schema: Joi.ObjectSchema | Joi.ArraySchema,
- property: string
+ property: string,
+ opts: { errorPrefix: string } = { errorPrefix: `Invalid ${property}` }
) {
// Return a Koa middleware function
return (ctx: Ctx, next: any) => {
@@ -29,16 +30,26 @@ function validate(
const { error } = schema.validate(params)
if (error) {
- ctx.throw(400, `Invalid ${property} - ${error.message}`)
+ let message = error.message
+ if (opts.errorPrefix) {
+ message = `Invalid ${property} - ${message}`
+ }
+ ctx.throw(400, message)
}
return next()
}
}
-export function body(schema: Joi.ObjectSchema | Joi.ArraySchema) {
- return validate(schema, "body")
+export function body(
+ schema: Joi.ObjectSchema | Joi.ArraySchema,
+ opts?: { errorPrefix: string }
+) {
+ return validate(schema, "body", opts)
}
-export function params(schema: Joi.ObjectSchema | Joi.ArraySchema) {
- return validate(schema, "params")
+export function params(
+ schema: Joi.ObjectSchema | Joi.ArraySchema,
+ opts?: { errorPrefix: string }
+) {
+ return validate(schema, "params", opts)
}
diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts
index 0a4eff4247..9a726dc757 100644
--- a/packages/server/src/api/routes/utils/validators.ts
+++ b/packages/server/src/api/routes/utils/validators.ts
@@ -40,42 +40,49 @@ const validateViewSchemas: CustomValidator = (table, helpers) => {
}
export function tableValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- _id: OPTIONAL_STRING,
- _rev: OPTIONAL_STRING,
- type: OPTIONAL_STRING.valid("table", "internal", "external"),
- primaryDisplay: OPTIONAL_STRING,
- schema: Joi.object().required(),
- name: Joi.string().required(),
- views: Joi.object(),
- rows: Joi.array(),
- }).custom(validateViewSchemas).unknown(true))
+ return auth.joiValidator.body(
+ Joi.object({
+ _id: OPTIONAL_STRING,
+ _rev: OPTIONAL_STRING,
+ type: OPTIONAL_STRING.valid("table", "internal", "external"),
+ primaryDisplay: OPTIONAL_STRING,
+ schema: Joi.object().required(),
+ name: Joi.string().required(),
+ views: Joi.object(),
+ rows: Joi.array(),
+ })
+ .custom(validateViewSchemas)
+ .unknown(true),
+ { errorPrefix: "" }
+ )
}
export function nameValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- name: OPTIONAL_STRING,
- }))
+ return auth.joiValidator.body(
+ Joi.object({
+ name: OPTIONAL_STRING,
+ })
+ )
}
export function datasourceValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- _id: Joi.string(),
- _rev: Joi.string(),
- type: OPTIONAL_STRING.allow("datasource_plus"),
- relationships: Joi.array().items(Joi.object({
- from: Joi.string().required(),
- to: Joi.string().required(),
- cardinality: Joi.valid("1:N", "1:1", "N:N").required()
- })),
- }).unknown(true))
+ return auth.joiValidator.body(
+ Joi.object({
+ _id: Joi.string(),
+ _rev: Joi.string(),
+ type: OPTIONAL_STRING.allow("datasource_plus"),
+ relationships: Joi.array().items(
+ Joi.object({
+ from: Joi.string().required(),
+ to: Joi.string().required(),
+ cardinality: Joi.valid("1:N", "1:1", "N:N").required(),
+ })
+ ),
+ }).unknown(true)
+ )
}
function filterObject() {
- // prettier-ignore
return Joi.object({
string: Joi.object().optional(),
fuzzy: Joi.object().optional(),
@@ -92,17 +99,20 @@ function filterObject() {
}
export function internalSearchValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- tableId: OPTIONAL_STRING,
- query: filterObject(),
- limit: OPTIONAL_NUMBER,
- sort: OPTIONAL_STRING,
- sortOrder: OPTIONAL_STRING,
- sortType: OPTIONAL_STRING,
- paginate: Joi.boolean(),
- bookmark: Joi.alternatives().try(OPTIONAL_STRING, OPTIONAL_NUMBER).optional(),
- }))
+ return auth.joiValidator.body(
+ Joi.object({
+ tableId: OPTIONAL_STRING,
+ query: filterObject(),
+ limit: OPTIONAL_NUMBER,
+ sort: OPTIONAL_STRING,
+ sortOrder: OPTIONAL_STRING,
+ sortType: OPTIONAL_STRING,
+ paginate: Joi.boolean(),
+ bookmark: Joi.alternatives()
+ .try(OPTIONAL_STRING, OPTIONAL_NUMBER)
+ .optional(),
+ })
+ )
}
export function externalSearchValidator() {
@@ -124,92 +134,110 @@ export function externalSearchValidator() {
}
export function datasourceQueryValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- endpoint: Joi.object({
- datasourceId: Joi.string().required(),
- operation: Joi.string().required().valid(...Object.values(DataSourceOperation)),
- entityId: Joi.string().required(),
- }).required(),
- resource: Joi.object({
- fields: Joi.array().items(Joi.string()).optional(),
- }).optional(),
- body: Joi.object().optional(),
- sort: Joi.object().optional(),
- filters: filterObject().optional(),
- paginate: Joi.object({
- page: Joi.string().alphanum().optional(),
- limit: Joi.number().optional(),
- }).optional(),
- }))
+ return auth.joiValidator.body(
+ Joi.object({
+ endpoint: Joi.object({
+ datasourceId: Joi.string().required(),
+ operation: Joi.string()
+ .required()
+ .valid(...Object.values(DataSourceOperation)),
+ entityId: Joi.string().required(),
+ }).required(),
+ resource: Joi.object({
+ fields: Joi.array().items(Joi.string()).optional(),
+ }).optional(),
+ body: Joi.object().optional(),
+ sort: Joi.object().optional(),
+ filters: filterObject().optional(),
+ paginate: Joi.object({
+ page: Joi.string().alphanum().optional(),
+ limit: Joi.number().optional(),
+ }).optional(),
+ })
+ )
}
export function webhookValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- live: Joi.bool(),
- _id: OPTIONAL_STRING,
- _rev: OPTIONAL_STRING,
- name: Joi.string().required(),
- bodySchema: Joi.object().optional(),
- action: Joi.object({
- type: Joi.string().required().valid(WebhookActionType.AUTOMATION),
- target: Joi.string().required(),
- }).required(),
- }).unknown(true))
+ return auth.joiValidator.body(
+ Joi.object({
+ live: Joi.bool(),
+ _id: OPTIONAL_STRING,
+ _rev: OPTIONAL_STRING,
+ name: Joi.string().required(),
+ bodySchema: Joi.object().optional(),
+ action: Joi.object({
+ type: Joi.string().required().valid(WebhookActionType.AUTOMATION),
+ target: Joi.string().required(),
+ }).required(),
+ }).unknown(true)
+ )
}
export function roleValidator() {
const permLevelArray = Object.values(permissions.PermissionLevel)
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- _id: OPTIONAL_STRING,
- _rev: OPTIONAL_STRING,
- name: Joi.string().regex(/^[a-zA-Z0-9_]*$/).required(),
- // this is the base permission ID (for now a built in)
- permissionId: Joi.string().valid(...Object.values(permissions.BuiltinPermissionID)).required(),
- permissions: Joi.object()
- .pattern(/.*/, [Joi.string().valid(...permLevelArray)])
- .optional(),
- inherits: OPTIONAL_STRING,
- }).unknown(true))
+
+ return auth.joiValidator.body(
+ Joi.object({
+ _id: OPTIONAL_STRING,
+ _rev: OPTIONAL_STRING,
+ name: Joi.string()
+ .regex(/^[a-zA-Z0-9_]*$/)
+ .required(),
+ // this is the base permission ID (for now a built in)
+ permissionId: Joi.string()
+ .valid(...Object.values(permissions.BuiltinPermissionID))
+ .required(),
+ permissions: Joi.object()
+ .pattern(/.*/, [Joi.string().valid(...permLevelArray)])
+ .optional(),
+ inherits: OPTIONAL_STRING,
+ }).unknown(true)
+ )
}
export function permissionValidator() {
const permLevelArray = Object.values(permissions.PermissionLevel)
- // prettier-ignore
- return auth.joiValidator.params(Joi.object({
- level: Joi.string().valid(...permLevelArray).required(),
- resourceId: Joi.string(),
- roleId: Joi.string(),
- }).unknown(true))
+
+ return auth.joiValidator.params(
+ Joi.object({
+ level: Joi.string()
+ .valid(...permLevelArray)
+ .required(),
+ resourceId: Joi.string(),
+ roleId: Joi.string(),
+ }).unknown(true)
+ )
}
export function screenValidator() {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- name: Joi.string().required(),
- showNavigation: OPTIONAL_BOOLEAN,
- width: OPTIONAL_STRING,
- routing: Joi.object({
- route: Joi.string().required(),
- roleId: Joi.string().required().allow(""),
- homeScreen: OPTIONAL_BOOLEAN,
- }).required().unknown(true),
- props: Joi.object({
- _id: Joi.string().required(),
- _component: Joi.string().required(),
- _children: Joi.array().required(),
- _styles: Joi.object().required(),
- type: OPTIONAL_STRING,
- table: OPTIONAL_STRING,
- layoutId: OPTIONAL_STRING,
- }).required().unknown(true),
- }).unknown(true))
+ return auth.joiValidator.body(
+ Joi.object({
+ name: Joi.string().required(),
+ showNavigation: OPTIONAL_BOOLEAN,
+ width: OPTIONAL_STRING,
+ routing: Joi.object({
+ route: Joi.string().required(),
+ roleId: Joi.string().required().allow(""),
+ homeScreen: OPTIONAL_BOOLEAN,
+ })
+ .required()
+ .unknown(true),
+ props: Joi.object({
+ _id: Joi.string().required(),
+ _component: Joi.string().required(),
+ _children: Joi.array().required(),
+ _styles: Joi.object().required(),
+ type: OPTIONAL_STRING,
+ table: OPTIONAL_STRING,
+ layoutId: OPTIONAL_STRING,
+ })
+ .required()
+ .unknown(true),
+ }).unknown(true)
+ )
}
function generateStepSchema(allowStepTypes: string[]) {
- // prettier-ignore
return Joi.object({
stepId: Joi.string().required(),
id: Joi.string().required(),
@@ -219,33 +247,39 @@ function generateStepSchema(allowStepTypes: string[]) {
icon: Joi.string().required(),
params: Joi.object(),
args: Joi.object(),
- type: Joi.string().required().valid(...allowStepTypes),
+ type: Joi.string()
+ .required()
+ .valid(...allowStepTypes),
}).unknown(true)
}
export function automationValidator(existing = false) {
- // prettier-ignore
- return auth.joiValidator.body(Joi.object({
- _id: existing ? Joi.string().required() : OPTIONAL_STRING,
- _rev: existing ? Joi.string().required() : OPTIONAL_STRING,
- name: Joi.string().required(),
- type: Joi.string().valid("automation").required(),
- definition: Joi.object({
- steps: Joi.array().required().items(generateStepSchema(["ACTION", "LOGIC"])),
- trigger: generateStepSchema(["TRIGGER"]).allow(null),
- }).required().unknown(true),
- }).unknown(true))
+ return auth.joiValidator.body(
+ Joi.object({
+ _id: existing ? Joi.string().required() : OPTIONAL_STRING,
+ _rev: existing ? Joi.string().required() : OPTIONAL_STRING,
+ name: Joi.string().required(),
+ type: Joi.string().valid("automation").required(),
+ definition: Joi.object({
+ steps: Joi.array()
+ .required()
+ .items(generateStepSchema(["ACTION", "LOGIC"])),
+ trigger: generateStepSchema(["TRIGGER"]).allow(null),
+ })
+ .required()
+ .unknown(true),
+ }).unknown(true)
+ )
}
export function applicationValidator(opts = { isCreate: true }) {
- // prettier-ignore
const base: any = {
_id: OPTIONAL_STRING,
_rev: OPTIONAL_STRING,
url: OPTIONAL_STRING,
template: Joi.object({
templateString: OPTIONAL_STRING,
- })
+ }),
}
const appNameValidator = Joi.string()
From 819cc6bebb8c2db492c2bbfe8641200bf8dc5b47 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 11:18:33 +0200
Subject: [PATCH 055/357] Fix tests
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 663cf5c864..3376cb86fa 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -1424,8 +1424,7 @@ describe.each([
status: 400,
body: {
status: 400,
- message:
- 'Invalid body - Required field "name" is missing in view "view a"',
+ message: 'Required field "name" is missing in view "view a"',
},
}
)
@@ -1455,8 +1454,7 @@ describe.each([
status: 400,
body: {
status: 400,
- message:
- 'Invalid body - Required field "name" is missing in view "view a"',
+ message: 'Required field "name" is missing in view "view a"',
},
}
)
From aefedce568168d116e9046b12278cfca1a4f69fa Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 11:35:09 +0200
Subject: [PATCH 056/357] Renames
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 6 ++++--
packages/server/src/api/routes/utils/validators.ts | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 3376cb86fa..cf9db761ce 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -1424,7 +1424,8 @@ describe.each([
status: 400,
body: {
status: 400,
- message: 'Required field "name" is missing in view "view a"',
+ message:
+ 'To make field "name" required, this field must be present and writable in views: view a.',
},
}
)
@@ -1454,7 +1455,8 @@ describe.each([
status: 400,
body: {
status: 400,
- message: 'Required field "name" is missing in view "view a"',
+ message:
+ 'To make field "name" required, this field must be present and writable in views: view a.',
},
}
)
diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts
index 9a726dc757..e2cc463f38 100644
--- a/packages/server/src/api/routes/utils/validators.ts
+++ b/packages/server/src/api/routes/utils/validators.ts
@@ -30,7 +30,7 @@ const validateViewSchemas: CustomValidator = (table, helpers) => {
)
if (missingField) {
return helpers.message({
- custom: `Required field "${missingField}" is missing in view "${view.name}"`,
+ custom: `To make field "${missingField}" required, this field must be present and writable in views: ${view.name}.`,
})
}
}
From 1c8feaedb1da457c5593732025467604de810367 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 12:03:42 +0200
Subject: [PATCH 057/357] Copy change
---
packages/server/src/api/routes/tests/viewV2.spec.ts | 2 +-
packages/server/src/sdk/app/views/index.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index b0ffd1e85c..650c36794b 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -369,7 +369,7 @@ describe.each([
status: 400,
body: {
message:
- 'You can\'t make field "name" readonly because it is a required field.',
+ 'You can\'t make "name" readonly because it is a required field.',
status: 400,
},
})
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 52ab323faa..f82ef133d7 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -77,14 +77,14 @@ async function guardViewSchema(
if (!viewSchemaField?.visible) {
throw new HTTPError(
- `You can't hide the required field "${field.name}"`,
+ `You can't hide "${field.name} because it is a required field."`,
400
)
}
if (viewSchemaField.readonly) {
throw new HTTPError(
- `You can't make field "${field.name}" readonly because it is a required field.`,
+ `You can't make "${field.name}" readonly because it is a required field.`,
400
)
}
From b65e9cfc80c08deea471b3cad8e33a1d8508f9d6 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 12:20:19 +0200
Subject: [PATCH 058/357] Lint
---
.../components/grid/controls/ColumnsSettingButton.svelte | 6 ++++--
packages/server/src/api/routes/utils/validators.ts | 5 +++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 7f54dbf582..9f7ced013d 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -71,9 +71,11 @@
options.push({
icon: "VisibilityOff",
value: PERMISSION_OPTIONS.HIDDEN,
- tooltip: requiredTooltip || "Hidden",
disabled: isDisplayColumn || isRequired,
- tooltip: isDisplayColumn && "Display column cannot be hidden",
+ tooltip:
+ (isDisplayColumn && "Display column cannot be hidden") ||
+ requiredTooltip ||
+ "Hidden",
})
return { ...c, options }
diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts
index e2cc463f38..a63b29fe5a 100644
--- a/packages/server/src/api/routes/utils/validators.ts
+++ b/packages/server/src/api/routes/utils/validators.ts
@@ -2,10 +2,11 @@ import { auth, permissions } from "@budibase/backend-core"
import { DataSourceOperation } from "../../../constants"
import { Table, WebhookActionType } from "@budibase/types"
import Joi, { CustomValidator } from "joi"
-import { ValidSnippetNameRegex } from "@budibase/shared-core"
-import { isRequired } from "../../../utilities/schema"
+import { ValidSnippetNameRegex, helpers } from "@budibase/shared-core"
import sdk from "../../../sdk"
+const { isRequired } = helpers.schema
+
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
From e6e67af2c4f3e151cbe2f1dfd1afd5a7e0acc008 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 12:32:33 +0200
Subject: [PATCH 059/357] Guard display name column
---
packages/server/src/sdk/app/views/index.ts | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 07c207e334..b088051773 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -37,9 +37,9 @@ export async function getEnriched(viewId: string): Promise {
async function guardViewSchema(
tableId: string,
- viewSchema?: Record
+ view: Omit
) {
- viewSchema ??= {}
+ const viewSchema = view.schema || {}
const table = await sdk.tables.getTable(tableId)
for (const field of Object.keys(viewSchema)) {
@@ -89,19 +89,30 @@ async function guardViewSchema(
)
}
}
+
+ if (view.primaryDisplay) {
+ const viewSchemaField = viewSchema[view.primaryDisplay]
+
+ if (!viewSchemaField?.visible) {
+ throw new HTTPError(
+ `You can't hide "${view.primaryDisplay}" because it is the display column.`,
+ 400
+ )
+ }
+ }
}
export async function create(
tableId: string,
viewRequest: Omit
): Promise {
- await guardViewSchema(tableId, viewRequest.schema)
+ await guardViewSchema(tableId, viewRequest)
return pickApi(tableId).create(tableId, viewRequest)
}
export async function update(tableId: string, view: ViewV2): Promise {
- await guardViewSchema(tableId, view.schema)
+ await guardViewSchema(tableId, view)
return pickApi(tableId).update(tableId, view)
}
From edd9ebc38923d40149eb6554360c60893e0e51d9 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 12:33:48 +0200
Subject: [PATCH 060/357] Tests
---
.../src/api/routes/tests/viewV2.spec.ts | 78 ++++++++++++++++++-
1 file changed, 75 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index 650c36794b..c8035bd578 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -134,7 +134,7 @@ describe.each([
const newView: Required = {
name: generator.name(),
tableId: table._id!,
- primaryDisplay: generator.word(),
+ primaryDisplay: "id",
query: [
{
operator: SearchFilterOperator.EQUAL,
@@ -244,7 +244,7 @@ describe.each([
const newView: CreateViewRequest = {
name: generator.name(),
tableId: table._id!,
- primaryDisplay: generator.word(),
+ primaryDisplay: "id",
schema: {
id: { visible: true },
Price: { visible: true },
@@ -451,6 +451,78 @@ describe.each([
})
})
})
+
+ it("display fields must be visible", async () => {
+ const table = await config.api.table.save(
+ saveTableRequest({
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ description: {
+ name: "description",
+ type: FieldType.STRING,
+ },
+ },
+ })
+ )
+
+ const newView: CreateViewRequest = {
+ name: generator.name(),
+ tableId: table._id!,
+ primaryDisplay: "name",
+ schema: {
+ id: { visible: true },
+ name: {
+ visible: false,
+ },
+ },
+ }
+
+ await config.api.viewV2.create(newView, {
+ status: 400,
+ body: {
+ message: 'You can\'t hide "name" because it is the display column.',
+ status: 400,
+ },
+ })
+ })
+
+ it("display fields can be readonly", async () => {
+ mocks.licenses.useViewReadonlyColumns()
+ const table = await config.api.table.save(
+ saveTableRequest({
+ schema: {
+ name: {
+ name: "name",
+ type: FieldType.STRING,
+ },
+ description: {
+ name: "description",
+ type: FieldType.STRING,
+ },
+ },
+ })
+ )
+
+ const newView: CreateViewRequest = {
+ name: generator.name(),
+ tableId: table._id!,
+ primaryDisplay: "name",
+ schema: {
+ id: { visible: true },
+ name: {
+ visible: true,
+ readonly: true,
+ },
+ },
+ }
+
+ await config.api.viewV2.create(newView, {
+ status: 201,
+ })
+ })
})
describe("update", () => {
@@ -499,7 +571,7 @@ describe.each([
id: view.id,
tableId,
name: view.name,
- primaryDisplay: generator.word(),
+ primaryDisplay: "Price",
query: [
{
operator: SearchFilterOperator.EQUAL,
From 8018d957aedcbdff91dd60353da60fbefd424c98 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 12:58:43 +0200
Subject: [PATCH 061/357] Add PR size labeler
---
.github/workflows/pr-labeler.yml | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 .github/workflows/pr-labeler.yml
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
new file mode 100644
index 0000000000..1bfa79dad3
--- /dev/null
+++ b/.github/workflows/pr-labeler.yml
@@ -0,0 +1,25 @@
+name: PR labeler
+
+on:
+ pull_request:
+ types: [opened, synchronize]
+
+jobs:
+ size-labeler:
+ runs-on: ubuntu-latest
+ name: Label the PR size
+ steps:
+ - uses: codelytv/pr-size-labeler@v1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ xs_label: "xs"
+ xs_max_size: "10"
+ s_label: "s"
+ s_max_size: "100"
+ m_label: "m"
+ m_max_size: "500"
+ l_label: "l"
+ l_max_size: "1000"
+ xl_label: "xl"
+ fail_if_xl: "false"
+ files_to_ignore: "yarn.lock"
From fb7411dee3fca07e30d44363bca44cfeb2d4faef Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 13:03:30 +0200
Subject: [PATCH 062/357] Team label
---
.github/workflows/pr-labeler.yml | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 1bfa79dad3..d10d67c4b7 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -23,3 +23,18 @@ jobs:
xl_label: "xl"
fail_if_xl: "false"
files_to_ignore: "yarn.lock"
+
+ team-labeler:
+ runs-on: ubuntu-latest
+ name: Label the PR size
+ steps:
+ - uses: rodrigoarias/auto-label-per-user@v1.0.0
+ with:
+ git-token: ${{ secrets.GITHUB_TOKEN }}
+ user-team-map: |
+ {
+ "adrinr": "firestorm"
+ "samwho": "firestorm"
+ "pclmnt": "firestorm"
+ "mike12345567": "firestorm"
+ }
From 63e7421dd56cbfe18ba304628af4394e899328e8 Mon Sep 17 00:00:00 2001
From: Martin McKeaveney
Date: Tue, 4 Jun 2024 12:41:07 +0100
Subject: [PATCH 063/357] acct portal
---
packages/account-portal | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/account-portal b/packages/account-portal
index 2a5022fb94..e8136bd1ea 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit 2a5022fb946481c9f7a9c38d1413922729972be0
+Subproject commit e8136bd1ea9fa4c61a4bcbeda482abea0b6c3d9f
From 9717c2bd17b89c2c435d76f0f6ff3e4d17029356 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 14:16:29 +0200
Subject: [PATCH 064/357] Fix json
---
.github/workflows/pr-labeler.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index d10d67c4b7..85f664ca30 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -33,8 +33,8 @@ jobs:
git-token: ${{ secrets.GITHUB_TOKEN }}
user-team-map: |
{
- "adrinr": "firestorm"
- "samwho": "firestorm"
- "pclmnt": "firestorm"
+ "adrinr": "firestorm",
+ "samwho": "firestorm",
+ "pclmnt": "firestorm",
"mike12345567": "firestorm"
}
From cbcba76309a2c418d61a1cd401a016457a94f19b Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 14:18:47 +0200
Subject: [PATCH 065/357] Use default labels
---
.github/workflows/pr-labeler.yml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 85f664ca30..8a9e20c2ac 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -12,15 +12,10 @@ jobs:
- uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- xs_label: "xs"
xs_max_size: "10"
- s_label: "s"
s_max_size: "100"
- m_label: "m"
m_max_size: "500"
- l_label: "l"
l_max_size: "1000"
- xl_label: "xl"
fail_if_xl: "false"
files_to_ignore: "yarn.lock"
From c17b12d4669ac3615381ddc8cad5b05a3c8d4133 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 14:19:54 +0200
Subject: [PATCH 066/357] Add concurrency
---
.github/workflows/pr-labeler.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 8a9e20c2ac..c63e7029ba 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -1,5 +1,9 @@
name: PR labeler
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
on:
pull_request:
types: [opened, synchronize]
From c8e68978d8eafa2308d342b05ceb51d5b2216ad6 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 14:22:30 +0200
Subject: [PATCH 067/357] Label team only on opened
---
.github/workflows/pr-labeler.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index c63e7029ba..553b87d3cf 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -26,6 +26,7 @@ jobs:
team-labeler:
runs-on: ubuntu-latest
name: Label the PR size
+ if: ${{ github.event.action == 'opened' }}
steps:
- uses: rodrigoarias/auto-label-per-user@v1.0.0
with:
From e131835852088c92e86515b5a4bde7fb6f4582d2 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 14:24:38 +0200
Subject: [PATCH 068/357] Use default names
---
.github/workflows/pr-labeler.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 553b87d3cf..998c95be27 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -11,7 +11,6 @@ on:
jobs:
size-labeler:
runs-on: ubuntu-latest
- name: Label the PR size
steps:
- uses: codelytv/pr-size-labeler@v1
with:
@@ -25,7 +24,6 @@ jobs:
team-labeler:
runs-on: ubuntu-latest
- name: Label the PR size
if: ${{ github.event.action == 'opened' }}
steps:
- uses: rodrigoarias/auto-label-per-user@v1.0.0
From 4221a13fd1cdda5ef9097ae0b06483fbf4e82a61 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 4 Jun 2024 14:03:52 +0100
Subject: [PATCH 069/357] Updating pro back to correct reference.
---
packages/pro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/pro b/packages/pro
index d3c3077011..5189b83bea 160000
--- a/packages/pro
+++ b/packages/pro
@@ -1 +1 @@
-Subproject commit d3c3077011a8e20ed3c48dcd6301caca4120b6ac
+Subproject commit 5189b83bea1868574ff7f4c51fe5db38a11badb8
From 595e518df0aceab80d394090fea6a0ccd5acdac9 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Tue, 4 Jun 2024 14:32:34 +0100
Subject: [PATCH 070/357] Update CouchDB chart from 4.3.0 to 4.5.5.
---
charts/budibase/Chart.lock | 6 +++---
charts/budibase/Chart.yaml | 2 +-
charts/budibase/charts/couchdb-4.3.0.tgz | Bin 14629 -> 0 bytes
charts/budibase/charts/couchdb-4.5.5.tgz | Bin 0 -> 15743 bytes
charts/budibase/values.yaml | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
delete mode 100644 charts/budibase/charts/couchdb-4.3.0.tgz
create mode 100644 charts/budibase/charts/couchdb-4.5.5.tgz
diff --git a/charts/budibase/Chart.lock b/charts/budibase/Chart.lock
index 3ee752a362..2a59452971 100644
--- a/charts/budibase/Chart.lock
+++ b/charts/budibase/Chart.lock
@@ -1,6 +1,6 @@
dependencies:
- name: couchdb
repository: https://apache.github.io/couchdb-helm
- version: 4.3.0
-digest: sha256:94449a7f195b186f5af33ec5aa66d58b36bede240fae710f021ca87837b30606
-generated: "2023-11-20T17:43:02.777596Z"
+ version: 4.5.5
+digest: sha256:bbbf022c77105c43b735539de05fe69e4c9de51581a0e245b3553030d5e44fa9
+generated: "2024-06-04T10:42:00.640809+01:00"
diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml
index 83a72d203f..e0403f90d6 100644
--- a/charts/budibase/Chart.yaml
+++ b/charts/budibase/Chart.yaml
@@ -17,6 +17,6 @@ version: 0.0.0
appVersion: 0.0.0
dependencies:
- name: couchdb
- version: 4.5.3
+ version: 4.5.5
repository: https://apache.github.io/couchdb-helm
condition: services.couchdb.enabled
diff --git a/charts/budibase/charts/couchdb-4.3.0.tgz b/charts/budibase/charts/couchdb-4.3.0.tgz
deleted file mode 100644
index d3cce28ee630a20d8bdc0051746b6e2dac95f7fd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 14629
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
Date: Tue, 4 Jun 2024 16:17:41 +0200
Subject: [PATCH 071/357] Copy fix
---
packages/server/src/sdk/app/views/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index b088051773..2d34a3f5a5 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -77,7 +77,7 @@ async function guardViewSchema(
if (!viewSchemaField?.visible) {
throw new HTTPError(
- `You can't hide "${field.name} because it is a required field."`,
+ `You can't hide "${field.name}" because it is a required field.`,
400
)
}
From 7aa6af6e134a80e8ef863244ca581352a0243b61 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 16:40:09 +0200
Subject: [PATCH 072/357] Add tests to support existing configs
---
.../src/api/routes/tests/viewV2.spec.ts | 48 ++++++++++++++++++-
packages/server/src/sdk/app/views/index.ts | 11 ++++-
2 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index c8035bd578..bc48b54b51 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -22,7 +22,7 @@ import { generator, mocks } from "@budibase/backend-core/tests"
import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
import merge from "lodash/merge"
import { quotas } from "@budibase/pro"
-import { roles } from "@budibase/backend-core"
+import { db, roles } from "@budibase/backend-core"
describe.each([
["internal", undefined],
@@ -840,6 +840,52 @@ describe.each([
})
)
})
+
+ it("updating schema will only validate modified field", async () => {
+ let view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
+ schema: {
+ id: { visible: true },
+ Price: {
+ visible: true,
+ },
+ Category: { visible: true },
+ },
+ })
+
+ // Update the view to an invalid state
+ const tableToUpdate = await config.api.table.get(table._id!)
+ ;(tableToUpdate.views![view.name] as ViewV2).schema!.id.visible = false
+ await db.getDB(config.appId!).put(tableToUpdate)
+
+ view = await config.api.viewV2.get(view.id)
+ await config.api.viewV2.update({
+ ...view,
+ schema: {
+ ...view.schema,
+ Price: {
+ visible: false,
+ },
+ },
+ })
+
+ expect(await config.api.viewV2.get(view.id)).toEqual(
+ expect.objectContaining({
+ schema: {
+ id: expect.objectContaining({
+ visible: false,
+ }),
+ Price: expect.objectContaining({
+ visible: false,
+ }),
+ Category: expect.objectContaining({
+ visible: true,
+ }),
+ },
+ })
+ )
+ })
})
describe("delete", () => {
diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts
index 2d34a3f5a5..b6ac7b6f6b 100644
--- a/packages/server/src/sdk/app/views/index.ts
+++ b/packages/server/src/sdk/app/views/index.ts
@@ -9,7 +9,7 @@ import {
import { HTTPError, db as dbCore } from "@budibase/backend-core"
import { features } from "@budibase/pro"
import { helpers } from "@budibase/shared-core"
-import { cloneDeep } from "lodash"
+import { cloneDeep } from "lodash/fp"
import * as utils from "../../../db/utils"
import { isExternalTableID } from "../../../integrations/utils"
@@ -68,12 +68,21 @@ async function guardViewSchema(
}
}
+ const existingView =
+ table?.views && (table.views[view.name] as ViewV2 | undefined)
+
for (const field of Object.values(table.schema)) {
if (!helpers.schema.isRequired(field.constraints)) {
continue
}
const viewSchemaField = viewSchema[field.name]
+ const existingViewSchema =
+ existingView?.schema && existingView.schema[field.name]
+ if (!viewSchemaField && !existingViewSchema?.visible) {
+ // Supporting existing configs with required columns but hidden in views
+ continue
+ }
if (!viewSchemaField?.visible) {
throw new HTTPError(
From 47b77d6744f9f7272196201c4a1f6a6f75dc2f7b Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 16:46:50 +0200
Subject: [PATCH 073/357] Run test only with internal tables
---
.../src/api/routes/tests/viewV2.spec.ts | 83 ++++++++++---------
1 file changed, 42 insertions(+), 41 deletions(-)
diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts
index bc48b54b51..375c4c7c77 100644
--- a/packages/server/src/api/routes/tests/viewV2.spec.ts
+++ b/packages/server/src/api/routes/tests/viewV2.spec.ts
@@ -841,51 +841,52 @@ describe.each([
)
})
- it("updating schema will only validate modified field", async () => {
- let view = await config.api.viewV2.create({
- tableId: table._id!,
- name: generator.guid(),
- schema: {
- id: { visible: true },
- Price: {
- visible: true,
- },
- Category: { visible: true },
- },
- })
-
- // Update the view to an invalid state
- const tableToUpdate = await config.api.table.get(table._id!)
- ;(tableToUpdate.views![view.name] as ViewV2).schema!.id.visible = false
- await db.getDB(config.appId!).put(tableToUpdate)
-
- view = await config.api.viewV2.get(view.id)
- await config.api.viewV2.update({
- ...view,
- schema: {
- ...view.schema,
- Price: {
- visible: false,
- },
- },
- })
-
- expect(await config.api.viewV2.get(view.id)).toEqual(
- expect.objectContaining({
+ isInternal &&
+ it("updating schema will only validate modified field", async () => {
+ let view = await config.api.viewV2.create({
+ tableId: table._id!,
+ name: generator.guid(),
schema: {
- id: expect.objectContaining({
- visible: false,
- }),
- Price: expect.objectContaining({
- visible: false,
- }),
- Category: expect.objectContaining({
+ id: { visible: true },
+ Price: {
visible: true,
- }),
+ },
+ Category: { visible: true },
},
})
- )
- })
+
+ // Update the view to an invalid state
+ const tableToUpdate = await config.api.table.get(table._id!)
+ ;(tableToUpdate.views![view.name] as ViewV2).schema!.id.visible = false
+ await db.getDB(config.appId!).put(tableToUpdate)
+
+ view = await config.api.viewV2.get(view.id)
+ await config.api.viewV2.update({
+ ...view,
+ schema: {
+ ...view.schema,
+ Price: {
+ visible: false,
+ },
+ },
+ })
+
+ expect(await config.api.viewV2.get(view.id)).toEqual(
+ expect.objectContaining({
+ schema: {
+ id: expect.objectContaining({
+ visible: false,
+ }),
+ Price: expect.objectContaining({
+ visible: false,
+ }),
+ Category: expect.objectContaining({
+ visible: true,
+ }),
+ },
+ })
+ )
+ })
})
describe("delete", () => {
From ac9f5d5d1ee27367366a9bfbf62e256b45646e17 Mon Sep 17 00:00:00 2001
From: Adria Navarro
Date: Tue, 4 Jun 2024 16:50:12 +0200
Subject: [PATCH 074/357] Allow editing old configs
---
.../components/grid/controls/ColumnsSettingButton.svelte | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
index 9f7ced013d..ed94a01e56 100644
--- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte
@@ -49,12 +49,15 @@
const requiredTooltip = isRequired && "Required columns must be writable"
+ const editEnabled =
+ !isRequired ||
+ columnToPermissionOptions(c) !== PERMISSION_OPTIONS.WRITABLE
const options = [
{
icon: "Edit",
value: PERMISSION_OPTIONS.WRITABLE,
- tooltip: requiredTooltip || "Writable",
- disabled: isRequired,
+ tooltip: (!editEnabled && requiredTooltip) || "Writable",
+ disabled: !editEnabled,
},
]
if ($datasource.type === "viewV2") {
From cf6f86cb2f4cbf346e5919c63c69cf37c1df256a Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 4 Jun 2024 17:24:11 +0100
Subject: [PATCH 075/357] Updating migrations to correctly cover all the
required elements.
---
.../backend-core/src/db/couch/DatabaseImpl.ts | 3 +-
.../20231229122514_update_link_documents.ts | 32 -----------
.../20240604153647_update_link_documents.ts | 57 +++++++++++++++++++
.../server/src/sdk/app/tables/internal/sqs.ts | 13 ++++-
packages/types/src/documents/app/sqlite.ts | 5 +-
packages/types/src/sdk/db.ts | 10 ++++
6 files changed, 84 insertions(+), 36 deletions(-)
delete mode 100644 packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
create mode 100644 packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts
index 8194d1aabf..1fa1e50e7e 100644
--- a/packages/backend-core/src/db/couch/DatabaseImpl.ts
+++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts
@@ -8,6 +8,7 @@ import {
DatabaseOpts,
DatabasePutOpts,
DatabaseQueryOpts,
+ DBError,
Document,
isDocument,
RowResponse,
@@ -41,7 +42,7 @@ function buildNano(couchInfo: { url: string; cookie: string }) {
type DBCall = () => Promise
-class CouchDBError extends Error {
+class CouchDBError extends Error implements DBError {
status: number
statusCode: number
reason: string
diff --git a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
deleted file mode 100644
index adaa0653c4..0000000000
--- a/packages/server/src/appMigrations/migrations/20231229122514_update_link_documents.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { context } from "@budibase/backend-core"
-import { allLinkDocs } from "../../db/utils"
-import LinkDocumentImpl from "../../db/linkedRows/LinkDocument"
-
-const migration = async () => {
- const linkDocs = await allLinkDocs()
-
- const docsToUpdate = []
- for (const linkDoc of linkDocs) {
- if (linkDoc.tableId) {
- // It already had the required data
- continue
- }
-
- linkDoc.tableId = new LinkDocumentImpl(
- linkDoc.doc1.tableId,
- linkDoc.doc1.fieldName,
- linkDoc.doc1.rowId,
- linkDoc.doc2.tableId,
- linkDoc.doc2.fieldName,
- linkDoc.doc2.rowId
- ).tableId
- docsToUpdate.push(linkDoc)
- }
-
- if (docsToUpdate.length) {
- const db = context.getAppDB()
- await db.bulkDocs(docsToUpdate)
- }
-}
-
-export default migration
diff --git a/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
new file mode 100644
index 0000000000..475e7c721c
--- /dev/null
+++ b/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
@@ -0,0 +1,57 @@
+import { context } from "@budibase/backend-core"
+import { allLinkDocs } from "../../db/utils"
+import LinkDocumentImpl from "../../db/linkedRows/LinkDocument"
+import sdk from "../../sdk"
+import env from "../../environment"
+import { DBError } from "@budibase/types"
+
+const migration = async () => {
+ const linkDocs = await allLinkDocs()
+
+ const docsToUpdate = []
+ for (const linkDoc of linkDocs) {
+ if (linkDoc.tableId) {
+ // It already had the required data
+ continue
+ }
+
+ // it already has the junction table ID - no need to migrate
+ if (!linkDoc.tableId) {
+ linkDoc.tableId = new LinkDocumentImpl(
+ linkDoc.doc1.tableId,
+ linkDoc.doc1.fieldName,
+ linkDoc.doc1.rowId,
+ linkDoc.doc2.tableId,
+ linkDoc.doc2.fieldName,
+ linkDoc.doc2.rowId
+ ).tableId
+ docsToUpdate.push(linkDoc)
+ }
+ }
+
+ const db = context.getAppDB()
+ if (docsToUpdate.length) {
+ await db.bulkDocs(docsToUpdate)
+ }
+
+ // at the end make sure design doc is ready
+ await sdk.tables.sqs.syncDefinition()
+ // only do initial search if environment is using SQS already
+ if (env.SQS_SEARCH_ENABLE) {
+ const tables = await sdk.tables.getAllInternalTables()
+ // do these one by one - running in parallel could cause problems
+ for (let table of tables) {
+ try {
+ await db.sql(`select * from ${table._id} limit 1`)
+ } catch (err) {
+ if (typeof err === "object") {
+ const dbErr = err as DBError
+ throw new Error(`Failed to run initial SQS search - ${dbErr.message}`)
+ }
+ throw err
+ }
+ }
+ }
+}
+
+export default migration
diff --git a/packages/server/src/sdk/app/tables/internal/sqs.ts b/packages/server/src/sdk/app/tables/internal/sqs.ts
index eb57d1f3b8..b0c6f28be7 100644
--- a/packages/server/src/sdk/app/tables/internal/sqs.ts
+++ b/packages/server/src/sdk/app/tables/internal/sqs.ts
@@ -107,8 +107,17 @@ async function buildBaseDefinition(): Promise {
export async function syncDefinition(): Promise {
const db = context.getAppDB()
- const definition = await buildBaseDefinition()
- await db.put(definition)
+ let rev: string | undefined
+ try {
+ const existing = await db.get(SQLITE_DESIGN_DOC_ID)
+ rev = existing._rev
+ } finally {
+ const definition = await buildBaseDefinition()
+ if (rev) {
+ definition._rev = rev
+ }
+ await db.put(definition)
+ }
}
export async function addTable(table: Table) {
diff --git a/packages/types/src/documents/app/sqlite.ts b/packages/types/src/documents/app/sqlite.ts
index 5636fef15b..516669bd59 100644
--- a/packages/types/src/documents/app/sqlite.ts
+++ b/packages/types/src/documents/app/sqlite.ts
@@ -30,4 +30,7 @@ export interface SQLiteDefinition {
}
}
-export type PreSaveSQLiteDefinition = Omit
+export interface PreSaveSQLiteDefinition
+ extends Omit {
+ _rev?: string
+}
diff --git a/packages/types/src/sdk/db.ts b/packages/types/src/sdk/db.ts
index 7ad740ad05..63c37195b7 100644
--- a/packages/types/src/sdk/db.ts
+++ b/packages/types/src/sdk/db.ts
@@ -165,3 +165,13 @@ export interface Database {
deleteIndex(...args: any[]): Promise
getIndexes(...args: any[]): Promise
}
+
+export interface DBError extends Error {
+ status: number
+ statusCode: number
+ reason: string
+ name: string
+ errid: string
+ error: string
+ description: string
+}
From f735f8c6f50c9da63710e3b635df5288244e4bed Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 4 Jun 2024 17:24:56 +0100
Subject: [PATCH 076/357] Comment update.
---
.../migrations/20240604153647_update_link_documents.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
index 475e7c721c..d092f416ff 100644
--- a/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
+++ b/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
@@ -37,6 +37,8 @@ const migration = async () => {
// at the end make sure design doc is ready
await sdk.tables.sqs.syncDefinition()
// only do initial search if environment is using SQS already
+ // initial search makes sure that all the indexes have been created
+ // and are ready to use, avoiding any initial waits for large tables
if (env.SQS_SEARCH_ENABLE) {
const tables = await sdk.tables.getAllInternalTables()
// do these one by one - running in parallel could cause problems
From dbda7b5ee4ee2117adc88540c677a4f595ba0e8f Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 4 Jun 2024 17:25:44 +0100
Subject: [PATCH 077/357] Updating migration name.
---
packages/server/src/appMigrations/migrations.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/appMigrations/migrations.ts b/packages/server/src/appMigrations/migrations.ts
index 544e702867..3d14bd5fa6 100644
--- a/packages/server/src/appMigrations/migrations.ts
+++ b/packages/server/src/appMigrations/migrations.ts
@@ -2,12 +2,12 @@
import { AppMigration } from "."
-import m20231229122514_update_link_documents from "./migrations/20231229122514_update_link_documents"
+import m20240604153647_update_link_documents from "./migrations/20240604153647_update_link_documents"
export const MIGRATIONS: AppMigration[] = [
// Migrations will be executed sorted by id
{
- id: "20231229122514_update_link_documents",
- func: m20231229122514_update_link_documents
+ id: "20240604153647_update_link_documents",
+ func: m20240604153647_update_link_documents,
},
]
From f062b73852590fbe5392d71f00ecb62a86262c7e Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Tue, 4 Jun 2024 17:39:53 +0100
Subject: [PATCH 078/357] Only run migration if SQS is enabled.
---
.../server/src/appMigrations/migrations.ts | 20 +++++++++++--------
...ments.ts => 20240604153647_initial_sqs.ts} | 0
2 files changed, 12 insertions(+), 8 deletions(-)
rename packages/server/src/appMigrations/migrations/{20240604153647_update_link_documents.ts => 20240604153647_initial_sqs.ts} (100%)
diff --git a/packages/server/src/appMigrations/migrations.ts b/packages/server/src/appMigrations/migrations.ts
index 3d14bd5fa6..48b7f8f0b2 100644
--- a/packages/server/src/appMigrations/migrations.ts
+++ b/packages/server/src/appMigrations/migrations.ts
@@ -1,13 +1,17 @@
// This file should never be manually modified, use `yarn add-app-migration` in order to add a new one
+import env from "../environment"
import { AppMigration } from "."
-import m20240604153647_update_link_documents from "./migrations/20240604153647_update_link_documents"
+import m20240604153647_initial_sqs from "./migrations/20240604153647_initial_sqs"
-export const MIGRATIONS: AppMigration[] = [
- // Migrations will be executed sorted by id
- {
- id: "20240604153647_update_link_documents",
- func: m20240604153647_update_link_documents,
- },
-]
+// Migrations will be executed sorted by ID
+export const MIGRATIONS: AppMigration[] = []
+
+// only run the SQS migration if SQS is enabled
+if (env.SQS_SEARCH_ENABLE) {
+ MIGRATIONS.push({
+ id: "20240604153647_initial_sqs",
+ func: m20240604153647_initial_sqs,
+ })
+}
diff --git a/packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts b/packages/server/src/appMigrations/migrations/20240604153647_initial_sqs.ts
similarity index 100%
rename from packages/server/src/appMigrations/migrations/20240604153647_update_link_documents.ts
rename to packages/server/src/appMigrations/migrations/20240604153647_initial_sqs.ts
From 32e4493a96973d857a93f60447e6076a2744afb8 Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Wed, 5 Jun 2024 11:58:40 +0100
Subject: [PATCH 079/357] Quick fix for using the roles option within the
builder - this was broken in JS which was a bit of a pain - this works
properly now and allows more compat between HBS and JS.
---
packages/builder/src/dataBinding.js | 2 +-
packages/string-templates/src/helpers/javascript.ts | 5 +++++
packages/string-templates/test/javascript.spec.ts | 7 +++++++
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js
index 4e48c237ca..64257e7a7b 100644
--- a/packages/builder/src/dataBinding.js
+++ b/packages/builder/src/dataBinding.js
@@ -728,7 +728,7 @@ const getRoleBindings = () => {
return (get(rolesStore) || []).map(role => {
return {
type: "context",
- runtimeBinding: `trim "${role._id}"`,
+ runtimeBinding: `'${role._id}'`,
readableBinding: `Role.${role.name}`,
category: "Role",
icon: "UserGroup",
diff --git a/packages/string-templates/src/helpers/javascript.ts b/packages/string-templates/src/helpers/javascript.ts
index 931cc46dc7..3e16d8a07b 100644
--- a/packages/string-templates/src/helpers/javascript.ts
+++ b/packages/string-templates/src/helpers/javascript.ts
@@ -33,7 +33,12 @@ const removeSquareBrackets = (value: string) => {
// Our context getter function provided to JS code as $.
// Extracts a value from context.
const getContextValue = (path: string, context: any) => {
+ const literalStringRegex = /^(["'`]).*\1$/
let data = context
+ // check if it's a literal string - just return path if its quoted
+ if (literalStringRegex.test(path)) {
+ return path.substring(1, path.length - 1)
+ }
path.split(".").forEach(key => {
if (data == null || typeof data !== "object") {
return null
diff --git a/packages/string-templates/test/javascript.spec.ts b/packages/string-templates/test/javascript.spec.ts
index cb2f765007..99e6ee122a 100644
--- a/packages/string-templates/test/javascript.spec.ts
+++ b/packages/string-templates/test/javascript.spec.ts
@@ -149,4 +149,11 @@ describe("Javascript", () => {
expect(output).toMatch(UUID_REGEX)
})
})
+
+ describe("JS literal strings", () => {
+ it("should be able to handle a literal string that is quoted (like role IDs)", () => {
+ const output = processJS(`return $("'Custom'")`)
+ expect(output).toBe("Custom")
+ })
+ })
})
From c9fb6e35c82d76412c472617b558f46f8b50f958 Mon Sep 17 00:00:00 2001
From: Conor Webb <126772285+ConorWebb96@users.noreply.github.com>
Date: Wed, 5 Jun 2024 12:29:07 +0100
Subject: [PATCH 080/357] Enhancement: add ability to set custom auto-dismissal
duration for notifications (#13829)
* Enhancement: add ability to set custom auto-dismissal duration for notifications
* Updated based on feedback.
* Enforce max duration for auto-dismiss to 2 minutes
---
.../actions/ShowNotification.svelte | 20 ++++++++++++++++
packages/client/src/stores/notification.js | 24 ++++++++++---------
packages/client/src/utils/buttonActions.js | 4 ++--
3 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ShowNotification.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ShowNotification.svelte
index d95e13cb5f..ef6232b382 100644
--- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ShowNotification.svelte
+++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ShowNotification.svelte
@@ -25,6 +25,8 @@
},
]
+ const MAX_DURATION = 120000 // Maximum duration in milliseconds (2 minutes)
+
onMount(() => {
if (!parameters.type) {
parameters.type = "success"
@@ -33,6 +35,14 @@
parameters.autoDismiss = true
}
})
+
+ function handleDurationChange(event) {
+ let newDuration = event.detail
+ if (newDuration > MAX_DURATION) {
+ newDuration = MAX_DURATION
+ }
+ parameters.duration = newDuration
+ }
@@ -47,6 +57,16 @@
/>
+ {#if parameters.autoDismiss}
+
+
+ {/if}
diff --git a/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte b/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte
index 2e62c593d1..497e77c2c9 100644
--- a/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte
+++ b/packages/frontend-core/src/components/grid/controls/ToggleActionButtonGroup.svelte
@@ -29,7 +29,6 @@
.permissionPicker {
display: flex;
gap: var(--spacing-xs);
- padding-left: calc(var(--spacing-xl) * 2);
}
.permissionPicker :global(.spectrum-Icon) {
From d579972e5f96749653c446e8d6567143147e0598 Mon Sep 17 00:00:00 2001
From: Sam Rose
Date: Mon, 10 Jun 2024 10:59:33 +0100
Subject: [PATCH 138/357] Slim down postgres.spec.ts, most of its content is
tested elsewhere now across all datasources.
---
.../src/api/routes/tests/datasource.spec.ts | 13 +-
.../server/src/api/routes/tests/row.spec.ts | 9 +-
.../src/integration-test/postgres.spec.ts | 1001 +----------------
3 files changed, 19 insertions(+), 1004 deletions(-)
diff --git a/packages/server/src/api/routes/tests/datasource.spec.ts b/packages/server/src/api/routes/tests/datasource.spec.ts
index 4cb93160f7..6f249f5016 100644
--- a/packages/server/src/api/routes/tests/datasource.spec.ts
+++ b/packages/server/src/api/routes/tests/datasource.spec.ts
@@ -165,8 +165,17 @@ describe("/datasources", () => {
describe("get", () => {
it("should be able to get a datasource", async () => {
const ds = await config.api.datasource.get(datasource._id!)
- expect(ds._id).toEqual(datasource._id)
- expect(ds._rev).toBeDefined()
+ expect(ds).toEqual({
+ config: expect.any(Object),
+ plus: datasource.plus,
+ source: datasource.source,
+ isSQL: true,
+ type: "datasource_plus",
+ _id: datasource._id,
+ _rev: expect.any(String),
+ createdAt: expect.any(String),
+ updatedAt: expect.any(String),
+ })
})
it("should not return database password", async () => {
diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts
index ccfd3891c5..3ded61207e 100644
--- a/packages/server/src/api/routes/tests/row.spec.ts
+++ b/packages/server/src/api/routes/tests/row.spec.ts
@@ -756,13 +756,16 @@ describe.each([
it("should be able to bulk delete rows, including a row that doesn't exist", async () => {
const createdRow = await config.api.row.save(table._id!, {})
+ const createdRow2 = await config.api.row.save(table._id!, {})
const res = await config.api.row.bulkDelete(table._id!, {
- rows: [createdRow, { _id: "9999999" }],
+ rows: [createdRow, createdRow2, { _id: "9999999" }],
})
- expect(res[0]._id).toEqual(createdRow._id)
- expect(res.length).toEqual(1)
+ expect(res.map(r => r._id)).toEqual(
+ expect.arrayContaining([createdRow._id, createdRow2._id])
+ )
+ expect(res.length).toEqual(2)
})
})
diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts
index 3e2b133b1e..cf4752978c 100644
--- a/packages/server/src/integration-test/postgres.spec.ts
+++ b/packages/server/src/integration-test/postgres.spec.ts
@@ -5,17 +5,9 @@ import {
} from "../api/routes/public/tests/utils"
import * as setup from "../api/routes/tests/utilities"
-import {
- Datasource,
- FieldType,
- RelationshipType,
- Row,
- Table,
- TableSourceType,
-} from "@budibase/types"
+import { Datasource, FieldType } from "@budibase/types"
import _ from "lodash"
import { generator } from "@budibase/backend-core/tests"
-import { utils } from "@budibase/backend-core"
import {
DatabaseName,
getDatasource,
@@ -32,11 +24,7 @@ jest.mock("../websockets")
describe("postgres integrations", () => {
let makeRequest: MakeRequestResponse,
rawDatasource: Datasource,
- datasource: Datasource,
- primaryPostgresTable: Table,
- oneToManyRelationshipInfo: ForeignTableInfo,
- manyToOneRelationshipInfo: ForeignTableInfo,
- manyToManyRelationshipInfo: ForeignTableInfo
+ datasource: Datasource
beforeAll(async () => {
await config.init()
@@ -48,993 +36,8 @@ describe("postgres integrations", () => {
datasource = await config.api.datasource.create(rawDatasource)
})
- beforeEach(async () => {
- async function createAuxTable(prefix: string) {
- return await config.createTable({
- name: `${prefix}_${generator
- .guid()
- .replaceAll("-", "")
- .substring(0, 6)}`,
- type: "table",
- primary: ["id"],
- primaryDisplay: "title",
- schema: {
- id: {
- name: "id",
- type: FieldType.AUTO,
- autocolumn: true,
- },
- title: {
- name: "title",
- type: FieldType.STRING,
- },
- },
- sourceId: datasource._id,
- sourceType: TableSourceType.EXTERNAL,
- })
- }
-
- oneToManyRelationshipInfo = {
- table: await createAuxTable("o2m"),
- fieldName: "oneToManyRelation",
- relationshipType: RelationshipType.ONE_TO_MANY,
- }
- manyToOneRelationshipInfo = {
- table: await createAuxTable("m2o"),
- fieldName: "manyToOneRelation",
- relationshipType: RelationshipType.MANY_TO_ONE,
- }
- manyToManyRelationshipInfo = {
- table: await createAuxTable("m2m"),
- fieldName: "manyToManyRelation",
- relationshipType: RelationshipType.MANY_TO_MANY,
- }
-
- primaryPostgresTable = await config.createTable({
- name: `p_${generator.guid().replaceAll("-", "").substring(0, 6)}`,
- type: "table",
- primary: ["id"],
- schema: {
- id: {
- name: "id",
- type: FieldType.AUTO,
- autocolumn: true,
- },
- name: {
- name: "name",
- type: FieldType.STRING,
- },
- description: {
- name: "description",
- type: FieldType.STRING,
- },
- value: {
- name: "value",
- type: FieldType.NUMBER,
- },
- oneToManyRelation: {
- type: FieldType.LINK,
- constraints: {
- type: "array",
- },
- fieldName: oneToManyRelationshipInfo.fieldName,
- name: "oneToManyRelation",
- relationshipType: RelationshipType.ONE_TO_MANY,
- tableId: oneToManyRelationshipInfo.table._id!,
- main: true,
- },
- manyToOneRelation: {
- type: FieldType.LINK,
- constraints: {
- type: "array",
- },
- fieldName: manyToOneRelationshipInfo.fieldName,
- name: "manyToOneRelation",
- relationshipType: RelationshipType.MANY_TO_ONE,
- tableId: manyToOneRelationshipInfo.table._id!,
- main: true,
- },
- manyToManyRelation: {
- type: FieldType.LINK,
- constraints: {
- type: "array",
- },
- fieldName: manyToManyRelationshipInfo.fieldName,
- name: "manyToManyRelation",
- relationshipType: RelationshipType.MANY_TO_MANY,
- tableId: manyToManyRelationshipInfo.table._id!,
- main: true,
- },
- },
- sourceId: datasource._id,
- sourceType: TableSourceType.EXTERNAL,
- })
- })
-
afterAll(config.end)
- function generateRandomPrimaryRowData() {
- return {
- name: generator.name(),
- description: generator.paragraph(),
- value: generator.age(),
- }
- }
-
- type PrimaryRowData = {
- name: string
- description: string
- value: number
- }
-
- type ForeignTableInfo = {
- table: Table
- fieldName: string
- relationshipType: RelationshipType
- }
-
- type ForeignRowsInfo = {
- row: Row
- relationshipType: RelationshipType
- }
-
- async function createPrimaryRow(opts: {
- rowData: PrimaryRowData
- createForeignRows?: {
- createOneToMany?: boolean
- createManyToOne?: number
- createManyToMany?: number
- }
- }) {
- let { rowData } = opts as any
- let foreignRows: ForeignRowsInfo[] = []
-
- if (opts?.createForeignRows?.createOneToMany) {
- const foreignKey = `fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`
-
- const foreignRow = await config.createRow({
- tableId: oneToManyRelationshipInfo.table._id,
- title: generator.name(),
- })
-
- rowData = {
- ...rowData,
- [foreignKey]: foreignRow.id,
- }
- foreignRows.push({
- row: foreignRow,
- relationshipType: oneToManyRelationshipInfo.relationshipType,
- })
- }
-
- for (let i = 0; i < (opts?.createForeignRows?.createManyToOne || 0); i++) {
- const foreignRow = await config.createRow({
- tableId: manyToOneRelationshipInfo.table._id,
- title: generator.name(),
- })
-
- rowData = {
- ...rowData,
- [manyToOneRelationshipInfo.fieldName]:
- rowData[manyToOneRelationshipInfo.fieldName] || [],
- }
- rowData[manyToOneRelationshipInfo.fieldName].push(foreignRow._id)
- foreignRows.push({
- row: foreignRow,
- relationshipType: RelationshipType.MANY_TO_ONE,
- })
- }
-
- for (let i = 0; i < (opts?.createForeignRows?.createManyToMany || 0); i++) {
- const foreignRow = await config.createRow({
- tableId: manyToManyRelationshipInfo.table._id,
- title: generator.name(),
- })
-
- rowData = {
- ...rowData,
- [manyToManyRelationshipInfo.fieldName]:
- rowData[manyToManyRelationshipInfo.fieldName] || [],
- }
- rowData[manyToManyRelationshipInfo.fieldName].push(foreignRow._id)
- foreignRows.push({
- row: foreignRow,
- relationshipType: RelationshipType.MANY_TO_MANY,
- })
- }
-
- const row = await config.createRow({
- tableId: primaryPostgresTable._id,
- ...rowData,
- })
-
- return { row, foreignRows }
- }
-
- async function createDefaultPgTable() {
- return await config.createTable({
- name: generator.guid().replaceAll("-", "").substring(0, 10),
- type: "table",
- primary: ["id"],
- schema: {
- id: {
- name: "id",
- type: FieldType.AUTO,
- autocolumn: true,
- },
- },
- sourceId: datasource._id,
- sourceType: TableSourceType.EXTERNAL,
- })
- }
-
- const createRandomTableWithRows = async () => {
- const tableId = (await createDefaultPgTable())._id!
- return await config.api.row.save(tableId, {
- tableId,
- title: generator.name(),
- })
- }
-
- async function populatePrimaryRows(
- count: number,
- opts?: {
- createOneToMany?: boolean
- createManyToOne?: number
- createManyToMany?: number
- }
- ) {
- return await Promise.all(
- Array(count)
- .fill({})
- .map(async () => {
- const rowData = generateRandomPrimaryRowData()
- return {
- rowData,
- ...(await createPrimaryRow({
- rowData,
- createForeignRows: opts,
- })),
- }
- })
- )
- }
-
- it("validate table schema", async () => {
- const res = await makeRequest("get", `/api/datasources/${datasource._id}`)
-
- expect(res.status).toBe(200)
- expect(res.body).toEqual({
- config: {
- ca: false,
- database: expect.any(String),
- host: datasource.config!.host,
- password: "--secret-value--",
- port: datasource.config!.port,
- rejectUnauthorized: false,
- schema: "public",
- ssl: false,
- user: "postgres",
- },
- plus: true,
- source: "POSTGRES",
- isSQL: true,
- type: "datasource_plus",
- _id: expect.any(String),
- _rev: expect.any(String),
- createdAt: expect.any(String),
- updatedAt: expect.any(String),
- entities: expect.any(Object),
- })
- })
-
- describe("POST /api/:tableId/rows", () => {
- const createRow = (tableId: string | undefined, body: object) =>
- makeRequest("post", `/api/${tableId}/rows`, body)
-
- describe("given than no row exists", () => {
- it("adding a new one persists it", async () => {
- const newRow = generateRandomPrimaryRowData()
-
- const res = await createRow(primaryPostgresTable._id, newRow)
-
- expect(res.status).toBe(200)
-
- const persistedRows = await config.getRows(primaryPostgresTable._id!)
- expect(persistedRows).toHaveLength(1)
-
- const expected = {
- ...res.body,
- ...newRow,
- }
-
- expect(persistedRows).toEqual([expect.objectContaining(expected)])
- })
-
- it("multiple rows can be persisted", async () => {
- const numberOfRows = 10
- const newRows: Row[] = Array(numberOfRows).fill(
- generateRandomPrimaryRowData()
- )
-
- await Promise.all(
- newRows.map(async newRow => {
- const res = await createRow(primaryPostgresTable._id, newRow)
- expect(res.status).toBe(200)
- })
- )
-
- const persistedRows = await config.getRows(primaryPostgresTable._id!)
- expect(persistedRows).toHaveLength(numberOfRows)
- expect(persistedRows).toEqual(
- expect.arrayContaining(newRows.map(expect.objectContaining))
- )
- })
- })
- })
-
- describe("PATCH /api/:tableId/rows", () => {
- const updateRow = (tableId: string | undefined, body: Row) =>
- makeRequest("patch", `/api/${tableId}/rows`, body)
-
- describe("given than a row exists", () => {
- let row: Row
- beforeEach(async () => {
- let rowResponse = _.sample(await populatePrimaryRows(1))!
- row = rowResponse.row
- })
-
- it("updating it persists it", async () => {
- const newName = generator.name()
- const newValue = generator.age()
- const updatedRow = {
- ...row,
- name: newName,
- value: newValue,
- }
-
- const res = await updateRow(primaryPostgresTable._id, updatedRow)
-
- expect(res.status).toBe(200)
- expect(res.body).toEqual(updatedRow)
-
- const persistedRow = await config.api.row.get(
- primaryPostgresTable._id!,
- row.id
- )
-
- expect(persistedRow).toEqual(
- expect.objectContaining({
- id: row.id,
- name: newName,
- value: newValue,
- })
- )
- })
- })
- })
-
- describe("DELETE /api/:tableId/rows", () => {
- const deleteRow = (
- tableId: string | undefined,
- body: Row | { rows: Row[] }
- ) => makeRequest("delete", `/api/${tableId}/rows`, body)
-
- describe("given than multiple row exist", () => {
- const numberOfInitialRows = 5
- let rows: Row[]
- beforeEach(async () => {
- rows = (await populatePrimaryRows(numberOfInitialRows)).map(x => x.row)
- })
-
- it("delete request removes it", async () => {
- const row = _.sample(rows)!
- const res = await deleteRow(primaryPostgresTable._id, row)
-
- expect(res.status).toBe(200)
-
- const persistedRows = await config.getRows(primaryPostgresTable._id!)
- expect(persistedRows).toHaveLength(numberOfInitialRows - 1)
-
- expect(row.id).toBeDefined()
- expect(persistedRows).not.toContain(
- expect.objectContaining({ _id: row.id })
- )
- })
-
- it("multiple rows can be removed at once", async () => {
- let rowsToDelete = _.sampleSize(rows, 3)!
-
- const res = await deleteRow(primaryPostgresTable._id, {
- rows: rowsToDelete,
- })
-
- expect(res.status).toBe(200)
-
- const persistedRows = await config.getRows(primaryPostgresTable._id!)
- expect(persistedRows).toHaveLength(numberOfInitialRows - 3)
-
- for (const row of rowsToDelete) {
- expect(persistedRows).not.toContain(
- expect.objectContaining({ _id: row.id })
- )
- }
- })
- })
- })
-
- describe("GET /api/:tableId/rows/:rowId", () => {
- const getRow = (tableId: string | undefined, rowId?: string | undefined) =>
- makeRequest("get", `/api/${tableId}/rows/${rowId}`)
-
- describe("given than a table have a single row", () => {
- let rowData: PrimaryRowData, row: Row
- beforeEach(async () => {
- const [createdRow] = await populatePrimaryRows(1)
- rowData = createdRow.rowData
- row = createdRow.row
- })
-
- it("the row can be retrieved successfully", async () => {
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toEqual(expect.objectContaining(rowData))
- })
- })
-
- describe("given than a table have a multiple rows", () => {
- let rows: { row: Row; rowData: PrimaryRowData }[]
-
- beforeEach(async () => {
- rows = await populatePrimaryRows(5)
- })
-
- it("a single row can be retrieved successfully", async () => {
- const { rowData, row } = _.sample(rows)!
-
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toEqual(expect.objectContaining(rowData))
- })
- })
-
- describe("given a row with relation data", () => {
- let row: Row
- let rowData: {
- name: string
- description: string
- value: number
- }
- let foreignRows: ForeignRowsInfo[]
-
- describe("with all relationship types", () => {
- beforeEach(async () => {
- let [createdRow] = await populatePrimaryRows(1, {
- createOneToMany: true,
- createManyToOne: 3,
- createManyToMany: 2,
- })
- row = createdRow.row
- rowData = createdRow.rowData
- foreignRows = createdRow.foreignRows
- })
-
- it("only one to primary keys are retrieved", async () => {
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- const one2ManyForeignRows = foreignRows.filter(
- x => x.relationshipType === RelationshipType.ONE_TO_MANY
- )
- const many2OneForeignRows = foreignRows.filter(
- x => x.relationshipType === RelationshipType.MANY_TO_ONE
- )
- const many2ManyForeignRows = foreignRows.filter(
- x => x.relationshipType === RelationshipType.MANY_TO_MANY
- )
- expect(one2ManyForeignRows).toHaveLength(1)
-
- expect(res.body).toEqual({
- ...rowData,
- id: row.id,
- tableId: row.tableId,
- _id: expect.any(String),
- _rev: expect.any(String),
- [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
- one2ManyForeignRows[0].row.id,
- [oneToManyRelationshipInfo.fieldName]: expect.arrayContaining(
- one2ManyForeignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- [manyToOneRelationshipInfo.fieldName]: expect.arrayContaining(
- many2OneForeignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- [manyToManyRelationshipInfo.fieldName]: expect.arrayContaining(
- many2ManyForeignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- })
- })
- })
-
- describe("with only one to many", () => {
- beforeEach(async () => {
- let [createdRow] = await populatePrimaryRows(1, {
- createOneToMany: true,
- })
- row = createdRow.row
- rowData = createdRow.rowData
- foreignRows = createdRow.foreignRows
- })
-
- it("only one to many foreign keys are retrieved", async () => {
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- expect(foreignRows).toHaveLength(1)
-
- expect(res.body).toEqual({
- ...rowData,
- id: row.id,
- tableId: row.tableId,
- _id: expect.any(String),
- _rev: expect.any(String),
- [`fk_${oneToManyRelationshipInfo.table.name}_${oneToManyRelationshipInfo.fieldName}`]:
- foreignRows[0].row.id,
- [oneToManyRelationshipInfo.fieldName]: expect.arrayContaining(
- foreignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- })
- })
- })
-
- describe("with only many to one", () => {
- beforeEach(async () => {
- let [createdRow] = await populatePrimaryRows(1, {
- createManyToOne: 3,
- })
- row = createdRow.row
- rowData = createdRow.rowData
- foreignRows = createdRow.foreignRows
- })
-
- it("only one to many foreign keys are retrieved", async () => {
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- expect(foreignRows).toHaveLength(3)
-
- expect(res.body).toEqual({
- ...rowData,
- id: row.id,
- tableId: row.tableId,
- _id: expect.any(String),
- _rev: expect.any(String),
- [manyToOneRelationshipInfo.fieldName]: expect.arrayContaining(
- foreignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- })
- })
- })
-
- describe("with only many to many", () => {
- beforeEach(async () => {
- let [createdRow] = await populatePrimaryRows(1, {
- createManyToMany: 2,
- })
- row = createdRow.row
- rowData = createdRow.rowData
- foreignRows = createdRow.foreignRows
- })
-
- it("only one to many foreign keys are retrieved", async () => {
- const res = await getRow(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- expect(foreignRows).toHaveLength(2)
-
- expect(res.body).toEqual({
- ...rowData,
- id: row.id,
- tableId: row.tableId,
- _id: expect.any(String),
- _rev: expect.any(String),
- [manyToManyRelationshipInfo.fieldName]: expect.arrayContaining(
- foreignRows.map(r => ({
- _id: r.row._id,
- primaryDisplay: r.row.title,
- }))
- ),
- })
- })
- })
- })
- })
-
- describe("POST /api/:tableId/search", () => {
- const search = (tableId: string | undefined, body?: object) =>
- makeRequest("post", `/api/${tableId}/search`, body)
-
- describe("search without parameters", () => {
- describe("given than a table has no rows", () => {
- it("search without query returns empty", async () => {
- const res = await search(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toEqual({
- rows: [],
- hasNextPage: false,
- })
- })
- })
-
- describe("given than a table has multiple rows", () => {
- const rowsCount = 6
- let rows: {
- row: Row
- rowData: PrimaryRowData
- }[]
- beforeEach(async () => {
- rows = await populatePrimaryRows(rowsCount)
- })
-
- it("search without query returns all of them", async () => {
- const res = await search(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toEqual({
- rows: expect.arrayContaining(
- rows.map(r => expect.objectContaining(r.rowData))
- ),
- hasNextPage: false,
- })
- expect(res.body.rows).toHaveLength(rowsCount)
- })
- })
-
- describe("given than multiple tables have multiple rows", () => {
- const rowsCount = 6
- beforeEach(async () => {
- await createRandomTableWithRows()
- await createRandomTableWithRows()
-
- await populatePrimaryRows(rowsCount)
-
- await createRandomTableWithRows()
- })
- it("search only return the requested ones", async () => {
- const res = await search(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body.rows).toHaveLength(rowsCount)
- })
- })
- })
-
- it("Querying by a string field returns the rows with field containing or starting by that value", async () => {
- const name = generator.name()
- const rowsToFilter = [
- ...Array(2).fill({
- name,
- description: generator.paragraph(),
- value: generator.age(),
- }),
- ...Array(2).fill({
- name: `${name}${utils.newid()}`,
- description: generator.paragraph(),
- value: generator.age(),
- }),
- ]
-
- await populatePrimaryRows(3)
- for (const row of rowsToFilter) {
- await createPrimaryRow({
- rowData: row,
- })
- }
- await populatePrimaryRows(1)
-
- const res = await search(primaryPostgresTable._id, {
- query: {
- string: {
- name,
- },
- },
- })
-
- expect(res.status).toBe(200)
-
- expect(res.body).toEqual({
- rows: expect.arrayContaining(rowsToFilter.map(expect.objectContaining)),
- hasNextPage: false,
- })
- expect(res.body.rows).toHaveLength(4)
- })
-
- it("Querying respects the limit fields", async () => {
- await populatePrimaryRows(6)
-
- const res = await search(primaryPostgresTable._id, {
- limit: 2,
- })
-
- expect(res.status).toBe(200)
-
- expect(res.body.rows).toHaveLength(2)
- })
-
- describe("sort", () => {
- beforeEach(async () => {
- const defaultValue = generateRandomPrimaryRowData()
-
- await createPrimaryRow({
- rowData: {
- ...defaultValue,
- name: "d",
- value: 3,
- },
- })
- await createPrimaryRow({
- rowData: { ...defaultValue, name: "aaa", value: 40 },
- })
- await createPrimaryRow({
- rowData: { ...defaultValue, name: "ccccc", value: -5 },
- })
- await createPrimaryRow({
- rowData: { ...defaultValue, name: "bb", value: 0 },
- })
- })
-
- it("Querying respects the sort order when sorting ascending by a string value", async () => {
- const res = await search(primaryPostgresTable._id, {
- sort: "name",
- sortOrder: "ascending",
- sortType: "string",
- })
-
- expect(res.status).toBe(200)
- expect(res.body.rows).toEqual([
- expect.objectContaining({ name: "aaa" }),
- expect.objectContaining({ name: "bb" }),
- expect.objectContaining({ name: "ccccc" }),
- expect.objectContaining({ name: "d" }),
- ])
- })
-
- it("Querying respects the sort order when sorting descending by a string value", async () => {
- const res = await search(primaryPostgresTable._id, {
- sort: "name",
- sortOrder: "descending",
- sortType: "string",
- })
-
- expect(res.status).toBe(200)
- expect(res.body.rows).toEqual([
- expect.objectContaining({ name: "d" }),
- expect.objectContaining({ name: "ccccc" }),
- expect.objectContaining({ name: "bb" }),
- expect.objectContaining({ name: "aaa" }),
- ])
- })
-
- it("Querying respects the sort order when sorting ascending by a numeric value", async () => {
- const res = await search(primaryPostgresTable._id, {
- sort: "value",
- sortOrder: "ascending",
- sortType: "number",
- })
-
- expect(res.status).toBe(200)
- expect(res.body.rows).toEqual([
- expect.objectContaining({ value: -5 }),
- expect.objectContaining({ value: 0 }),
- expect.objectContaining({ value: 3 }),
- expect.objectContaining({ value: 40 }),
- ])
- })
-
- it("Querying respects the sort order when sorting descending by a numeric value", async () => {
- const res = await search(primaryPostgresTable._id, {
- sort: "value",
- sortOrder: "descending",
- sortType: "number",
- })
-
- expect(res.status).toBe(200)
- expect(res.body.rows).toEqual([
- expect.objectContaining({ value: 40 }),
- expect.objectContaining({ value: 3 }),
- expect.objectContaining({ value: 0 }),
- expect.objectContaining({ value: -5 }),
- ])
- })
- })
- })
-
- describe("GET /api/:tableId/:rowId/enrich", () => {
- const getAll = (tableId: string | undefined, rowId: string | undefined) =>
- makeRequest("get", `/api/${tableId}/${rowId}/enrich`)
- describe("given a row with relation data", () => {
- let row: Row, rowData: PrimaryRowData, foreignRows: ForeignRowsInfo[]
-
- describe("with all relationship types", () => {
- beforeEach(async () => {
- rowData = generateRandomPrimaryRowData()
- const rowsInfo = await createPrimaryRow({
- rowData,
- createForeignRows: {
- createOneToMany: true,
- createManyToOne: 3,
- createManyToMany: 2,
- },
- })
-
- row = rowsInfo.row
- foreignRows = rowsInfo.foreignRows
- })
-
- it("enrich populates the foreign fields", async () => {
- const res = await getAll(primaryPostgresTable._id, row.id)
-
- expect(res.status).toBe(200)
-
- const foreignRowsByType = _.groupBy(
- foreignRows,
- x => x.relationshipType
- )
- const m2mFieldName = manyToManyRelationshipInfo.fieldName,
- o2mFieldName = oneToManyRelationshipInfo.fieldName,
- m2oFieldName = manyToOneRelationshipInfo.fieldName
- const m2mRow1 = res.body[m2mFieldName].find(
- (row: Row) => row.id === 1
- )
- const m2mRow2 = res.body[m2mFieldName].find(
- (row: Row) => row.id === 2
- )
- expect(m2mRow1).toEqual({
- ...foreignRowsByType[RelationshipType.MANY_TO_MANY][0].row,
- [m2mFieldName]: [
- {
- _id: row._id,
- },
- ],
- })
- expect(m2mRow2).toEqual({
- ...foreignRowsByType[RelationshipType.MANY_TO_MANY][1].row,
- [m2mFieldName]: [
- {
- _id: row._id,
- },
- ],
- })
- const m2oRel = {
- [m2oFieldName]: [
- {
- _id: row._id,
- },
- ],
- }
- expect(res.body[m2oFieldName]).toEqual([
- {
- ...m2oRel,
- ...foreignRowsByType[RelationshipType.MANY_TO_ONE][0].row,
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
- row.id,
- },
- {
- ...m2oRel,
- ...foreignRowsByType[RelationshipType.MANY_TO_ONE][1].row,
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
- row.id,
- },
- {
- ...m2oRel,
- ...foreignRowsByType[RelationshipType.MANY_TO_ONE][2].row,
- [`fk_${manyToOneRelationshipInfo.table.name}_${manyToOneRelationshipInfo.fieldName}`]:
- row.id,
- },
- ])
- const o2mRel = {
- [o2mFieldName]: [
- {
- _id: row._id,
- },
- ],
- }
- expect(res.body[o2mFieldName]).toEqual([
- {
- ...o2mRel,
- ...foreignRowsByType[RelationshipType.ONE_TO_MANY][0].row,
- _id: expect.any(String),
- _rev: expect.any(String),
- },
- ])
- })
- })
- })
- })
-
- describe("GET /api/:tableId/rows", () => {
- const getAll = (tableId: string | undefined) =>
- makeRequest("get", `/api/${tableId}/rows`)
-
- describe("given a table with no rows", () => {
- it("get request returns empty", async () => {
- const res = await getAll(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toHaveLength(0)
- })
- })
- describe("given a table with multiple rows", () => {
- const rowsCount = 6
- let rows: {
- row: Row
- foreignRows: ForeignRowsInfo[]
- rowData: PrimaryRowData
- }[]
- beforeEach(async () => {
- rows = await populatePrimaryRows(rowsCount)
- })
-
- it("get request returns all of them", async () => {
- const res = await getAll(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toHaveLength(rowsCount)
- expect(res.body).toEqual(
- expect.arrayContaining(
- rows.map(r => expect.objectContaining(r.rowData))
- )
- )
- })
- })
-
- describe("given multiple tables with multiple rows", () => {
- const rowsCount = 6
-
- beforeEach(async () => {
- await createRandomTableWithRows()
- await populatePrimaryRows(rowsCount)
- await createRandomTableWithRows()
- })
-
- it("get returns the requested ones", async () => {
- const res = await getAll(primaryPostgresTable._id)
-
- expect(res.status).toBe(200)
-
- expect(res.body).toHaveLength(rowsCount)
- })
- })
- })
-
describe("POST /api/datasources/:datasourceId/schema", () => {
let tableName: string
From 65c2039d0c63ccc7a98cdfd6c9ff18c542e1035e Mon Sep 17 00:00:00 2001
From: Hector Valcarcel
Date: Fri, 7 Jun 2024 17:57:54 +0200
Subject: [PATCH 139/357] Chore: Allow using an AWS_SESSION_TOKEN for object
storage with AWS S3
---
packages/backend-core/src/environment.ts | 1 +
packages/backend-core/src/objectStore/objectStore.ts | 5 +++++
packages/server/src/environment.ts | 1 +
packages/worker/src/environment.ts | 1 +
4 files changed, 8 insertions(+)
diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts
index 1e7da2f9a2..0c7f84220b 100644
--- a/packages/backend-core/src/environment.ts
+++ b/packages/backend-core/src/environment.ts
@@ -120,6 +120,7 @@ const environment = {
REDIS_CLUSTERED: process.env.REDIS_CLUSTERED,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
+ AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,
AWS_REGION: process.env.AWS_REGION,
MINIO_URL: process.env.MINIO_URL,
MINIO_ENABLED: process.env.MINIO_ENABLED || 1,
diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts
index de94e3968b..68b1b10ec2 100644
--- a/packages/backend-core/src/objectStore/objectStore.ts
+++ b/packages/backend-core/src/objectStore/objectStore.ts
@@ -101,6 +101,11 @@ export function ObjectStore(
}
}
+ // for AWS Credentials using temporary session token
+ if (!env.MINIO_ENABLED && env.AWS_SESSION_TOKEN) {
+ config.sessionToken = env.AWS_SESSION_TOKEN
+ }
+
// custom S3 is in use i.e. minio
if (env.MINIO_URL) {
if (opts.presigning && env.MINIO_ENABLED) {
diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts
index b44d7547a2..341483d861 100644
--- a/packages/server/src/environment.ts
+++ b/packages/server/src/environment.ts
@@ -48,6 +48,7 @@ const environment = {
MINIO_URL: process.env.MINIO_URL,
WORKER_URL: process.env.WORKER_URL,
AWS_REGION: process.env.AWS_REGION,
+ AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
REDIS_URL: process.env.REDIS_URL,
diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts
index 70fb911ee1..f6b3701be1 100644
--- a/packages/worker/src/environment.ts
+++ b/packages/worker/src/environment.ts
@@ -24,6 +24,7 @@ const environment = {
// auth
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
+ AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,
SALT_ROUNDS: process.env.SALT_ROUNDS,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
From 6a859e568b17d83d8f98ca75ea1dca117b6f1439 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Mon, 10 Jun 2024 13:28:04 +0100
Subject: [PATCH 140/357] Fix missing on:change event proxy from builder
dropzone component
---
packages/builder/src/components/common/Dropzone.svelte | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte
index 37569df0d5..8cd85e2530 100644
--- a/packages/builder/src/components/common/Dropzone.svelte
+++ b/packages/builder/src/components/common/Dropzone.svelte
@@ -38,4 +38,5 @@
{processFiles}
handleFileTooLarge={$admin.cloud ? handleFileTooLarge : null}
{fileSizeLimit}
+ on:change
/>
From aadf8ff3b3ebf6005fbd8b01941e77a952f396fe Mon Sep 17 00:00:00 2001
From: mike12345567
Date: Mon, 10 Jun 2024 13:55:26 +0100
Subject: [PATCH 141/357] Updating app migration test to not create the app
context, relying on the app migration processor to do this for us instead
(including tenancy context).
---
.../tests/20240604153647_initial_sqs.spec.ts | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/packages/server/src/appMigrations/migrations/tests/20240604153647_initial_sqs.spec.ts b/packages/server/src/appMigrations/migrations/tests/20240604153647_initial_sqs.spec.ts
index 64420d239f..49aef4c310 100644
--- a/packages/server/src/appMigrations/migrations/tests/20240604153647_initial_sqs.spec.ts
+++ b/packages/server/src/appMigrations/migrations/tests/20240604153647_initial_sqs.spec.ts
@@ -16,7 +16,17 @@ import {
generateLinkID,
generateRowID,
} from "../../../db/utils"
+import { processMigrations } from "../../migrationsProcessor"
import migration from "../20240604153647_initial_sqs"
+import { AppMigration } from "src/appMigrations"
+
+const MIGRATIONS: AppMigration[] = [
+ {
+ id: "20240604153647_initial_sqs",
+ func: migration,
+ disabled: false,
+ },
+]
const config = setup.getConfig()
let tableId: string
@@ -91,9 +101,7 @@ describe("SQS migration", () => {
expect(error.status).toBe(404)
})
await sqsEnabled(async () => {
- await context.doInAppContext(config.appId!, async () => {
- await migration()
- })
+ await processMigrations(config.appId!, MIGRATIONS)
const designDoc = await db.get(SQLITE_DESIGN_DOC_ID)
expect(designDoc.sql.tables).toBeDefined()
const mainTableDef = designDoc.sql.tables[tableId]
From 4cfa02f5d2f44d8dcdd6b65bce84ec3f44b7b7f3 Mon Sep 17 00:00:00 2001
From: Andrew Kingston
Date: Mon, 10 Jun 2024 13:59:35 +0100
Subject: [PATCH 142/357] Fix options not clearing when adding select-type
columns
---
.../components/backend/DataTable/modals/CreateEditColumn.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
index 730e51bf48..17ecd8f844 100644
--- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
+++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
@@ -334,7 +334,7 @@
// Add in defaults and initial definition
const definition = fieldDefinitions[type?.toUpperCase()]
if (definition?.constraints) {
- editableColumn.constraints = definition.constraints
+ editableColumn.constraints = cloneDeep(definition.constraints)
}
editableColumn.type = definition.type
From f1ea6a52aef7d79d0dbdc45990ecc2dda42c8ffb Mon Sep 17 00:00:00 2001
From: mike12345567