From 15f2aaf950b15e15dcbae5fed8f04e3306018554 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 16 Dec 2024 16:55:38 +0100 Subject: [PATCH 1/7] Fix encodings --- packages/backend-core/src/sql/utils.ts | 4 ++++ packages/server/src/api/controllers/row/utils/utils.ts | 2 +- packages/server/src/db/utils.ts | 10 ++-------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/backend-core/src/sql/utils.ts b/packages/backend-core/src/sql/utils.ts index 14127a189f..16b352995b 100644 --- a/packages/backend-core/src/sql/utils.ts +++ b/packages/backend-core/src/sql/utils.ts @@ -70,6 +70,10 @@ export function encodeTableId(tableId: string) { } } +export function encodeViewId(viewId: string) { + return encodeURIComponent(viewId) +} + export function breakExternalTableId(tableId: string) { const parts = tableId.split(DOUBLE_SEPARATOR) let datasourceId = parts.shift() diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts index baa811fe90..86986b64e8 100644 --- a/packages/server/src/api/controllers/row/utils/utils.ts +++ b/packages/server/src/api/controllers/row/utils/utils.ts @@ -66,7 +66,7 @@ export function getSourceId(ctx: Ctx): { tableId: string; viewId?: string } { if (docIds.isViewId(sourceId)) { return { tableId: utils.extractViewInfoFromID(sourceId).tableId, - viewId: sourceId, + viewId: sql.utils.encodeViewId(sourceId), } } return { tableId: sql.utils.encodeTableId(ctx.params.sourceId) } diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 70c69b3c60..6c1065e847 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -1,10 +1,4 @@ -import { - context, - db as dbCore, - docIds, - utils, - sql, -} from "@budibase/backend-core" +import { context, db as dbCore, docIds, utils } from "@budibase/backend-core" import { DatabaseQueryOpts, Datasource, @@ -334,7 +328,7 @@ export function extractViewInfoFromID(viewId: string) { const regex = new RegExp(`^(?.+)${SEPARATOR}([^${SEPARATOR}]+)$`) const res = regex.exec(viewId) return { - tableId: sql.utils.encodeTableId(res!.groups!["tableId"]), + tableId: res!.groups!["tableId"], } } From 1349634d9d3261965e8110bf434ecccf120585e6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 18 Dec 2024 10:22:17 +0100 Subject: [PATCH 2/7] Build builder in dev mode while on dev --- packages/builder/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index 485cced7ab..f2a829d5a9 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -7,7 +7,7 @@ "build": "routify -b && NODE_OPTIONS=\"--max_old_space_size=4096\" vite build --emptyOutDir", "start": "routify -c rollup", "dev": "routify -c dev:vite", - "dev:vite": "vite --host 0.0.0.0", + "dev:vite": "vite --host 0.0.0.0 --mode=dev", "rollup": "rollup -c -w", "test": "vitest run", "test:watch": "vitest", From b538125d1faf0af2ecf67e6600e825ba29eb9a18 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 18 Dec 2024 10:40:12 +0100 Subject: [PATCH 3/7] Add failing test --- .../src/api/routes/tests/viewV2.spec.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 244a0a23eb..632dbfec3a 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -55,7 +55,7 @@ if (descriptions.length) { let datasource: Datasource | undefined function saveTableRequest( - ...overrides: Partial>[] + ...overrides: Partial[] ): SaveTableRequest { const req: SaveTableRequest = { name: generator.guid().replaceAll("-", "").substring(0, 16), @@ -1898,6 +1898,36 @@ if (descriptions.length) { } expect(view.queryUI).toEqual(expected) }) + + it("tables and views can contain whitespaces", async () => { + const table = await config.api.table.save( + saveTableRequest({ + name: "table with spaces", + schema: { + name: { + type: FieldType.STRING, + name: "name", + }, + }, + }) + ) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: `view name with spaces`, + schema: { + name: { visible: true }, + }, + }) + + expect(await getDelegate(view)).toEqual({ + ...view, + schema: { + id: { ...table.schema["id"], visible: false }, + name: { ...table.schema["name"], visible: true }, + }, + }) + }) }) describe("updating table schema", () => { From b81e43ed59855c15abce129894eeabf1637e7ba8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 18 Dec 2024 10:55:32 +0100 Subject: [PATCH 4/7] Fix tests --- packages/server/src/api/routes/tests/viewV2.spec.ts | 2 +- packages/server/src/tests/utilities/api/viewV2.ts | 7 +++++-- 2 files changed, 6 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 632dbfec3a..ba2ad422eb 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -1902,7 +1902,7 @@ if (descriptions.length) { it("tables and views can contain whitespaces", async () => { const table = await config.api.table.save( saveTableRequest({ - name: "table with spaces", + name: `table with spaces ${generator.hash()}`, schema: { name: { type: FieldType.STRING, diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 7cc57673a0..d0350a5521 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -46,8 +46,11 @@ export class ViewV2API extends TestAPI { } get = async (viewId: string) => { - return (await this._get(`/api/v2/views/${viewId}`)) - .data + return ( + await this._get( + `/api/v2/views/${encodeURIComponent(viewId)}` + ) + ).data } fetch = async (expectations?: Expectations) => { From 6586b47c48ba6a96e9f865f2a25acdce2c3d0933 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 18 Dec 2024 10:32:18 +0000 Subject: [PATCH 5/7] Fix searching relationsgips --- .../src/components/grid/cells/RelationshipCell.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index 92186f19b1..bda0f5fd5d 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -102,9 +102,8 @@ lastSearchId = Math.random() searching = true const thisSearchId = lastSearchId - const results = await searchFunction({ + const results = await searchFunction(schema.tableId, { paginate: false, - tableId: schema.tableId, limit: 20, query: { string: { From 84a67bb3460a04999e62cdba9b5431eb0c9720c9 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 17 Dec 2024 18:42:19 +0100 Subject: [PATCH 6/7] Add test capturing --- .../server/src/api/routes/tests/row.spec.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index fb728a3fea..a3012c3760 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1333,6 +1333,62 @@ if (descriptions.length) { expect(resp.relationship.length).toBe(1) }) + it("should be able to keep linked data when updating from views that trims links from the main table", async () => { + let row = await config.api.row.save(table._id!, { + name: "main", + description: "main description", + }) + const row2 = await config.api.row.save(otherTable._id!, { + name: "link", + description: "link description", + relationship: [row._id], + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: "view", + schema: { + name: { visible: true }, + }, + }) + const resp = await config.api.row.patch(view.id, { + _id: row._id!, + _rev: row._rev!, + tableId: row.tableId!, + name: "test2", + relationship: [row2._id], + }) + expect(resp.relationship).toBeUndefined() + + const updatedRow = await config.api.row.get(table._id!, row._id!) + expect(updatedRow.relationship.length).toBe(1) + }) + + it("should be able to keep linked data when updating from views that trims links from the foreign table", async () => { + let row = await config.api.row.save(table._id!, { + name: "main", + description: "main description", + }) + const row2 = await config.api.row.save(otherTable._id!, { + name: "link", + description: "link description", + relationship: [row._id], + }) + + const view = await config.api.viewV2.create({ + tableId: otherTable._id!, + name: "view", + }) + await config.api.row.patch(view.id, { + _id: row2._id!, + _rev: row2._rev!, + tableId: row2.tableId!, + }) + + const updatedRow = await config.api.row.get(table._id!, row._id!) + expect(updatedRow.relationship.length).toBe(1) + }) + !isInternal && // MSSQL needs a setting called IDENTITY_INSERT to be set to ON to allow writing // to identity columns. This is not something Budibase does currently. From d061f44eda07dd1e08129da983edd5a5218299a7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 17 Dec 2024 18:50:17 +0100 Subject: [PATCH 7/7] Fix external patches --- .../src/api/controllers/row/external.ts | 20 +++++++++++++------ .../src/api/controllers/row/utils/utils.ts | 15 ++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/packages/server/src/api/controllers/row/external.ts b/packages/server/src/api/controllers/row/external.ts index 082d07283b..d56fb1a344 100644 --- a/packages/server/src/api/controllers/row/external.ts +++ b/packages/server/src/api/controllers/row/external.ts @@ -52,10 +52,22 @@ export async function patch(ctx: UserCtx) { const table = await utils.getTableFromSource(source) const { _id, ...rowData } = ctx.request.body - const dataToUpdate = await inputProcessing( + const beforeRow = await sdk.rows.external.getRow(table._id!, _id, { + relationships: true, + }) + + let dataToUpdate = cloneDeep(beforeRow) + const allowedField = utils.getSourceFields(source) + for (const key of Object.keys(rowData)) { + if (!allowedField.includes(key)) continue + + dataToUpdate[key] = rowData[key] + } + + dataToUpdate = await inputProcessing( ctx.user?._id, cloneDeep(source), - rowData + dataToUpdate ) const validateResult = await sdk.rows.utils.validate({ @@ -66,10 +78,6 @@ export async function patch(ctx: UserCtx) { throw { validation: validateResult.errors } } - const beforeRow = await sdk.rows.external.getRow(table._id!, _id, { - relationships: true, - }) - const response = await handleRequest(Operation.UPDATE, source, { id: breakRowIdField(_id), row: dataToUpdate, diff --git a/packages/server/src/api/controllers/row/utils/utils.ts b/packages/server/src/api/controllers/row/utils/utils.ts index baa811fe90..9d1711d000 100644 --- a/packages/server/src/api/controllers/row/utils/utils.ts +++ b/packages/server/src/api/controllers/row/utils/utils.ts @@ -110,6 +110,21 @@ function fixBooleanFields(row: Row, table: Table) { return row } +export function getSourceFields(source: Table | ViewV2): string[] { + const isView = sdk.views.isView(source) + if (isView) { + const fields = Object.keys( + helpers.views.basicFields(source, { visible: true }) + ) + return fields + } + + const fields = Object.entries(source.schema) + .filter(([_, field]) => field.visible !== false) + .map(([columnName]) => columnName) + return fields +} + export async function sqlOutputProcessing( rows: DatasourcePlusQueryResponse, source: Table | ViewV2,