diff --git a/packages/server/src/api/controllers/view/viewsV2.ts b/packages/server/src/api/controllers/view/viewsV2.ts index c872abba39..f403d14180 100644 --- a/packages/server/src/api/controllers/view/viewsV2.ts +++ b/packages/server/src/api/controllers/view/viewsV2.ts @@ -9,9 +9,40 @@ import { RequiredKeys, } from "@budibase/types" -export async function create(ctx: Ctx) { - const view = ctx.request.body - const { tableId } = view +async function parseSchemaUI(ctx: Ctx, view: CreateViewRequest) { + if (!view.schema) { + return + } + + function hasOverrides( + newObj: Record, + existingObj: Record + ) { + for (const [key, value] of Object.entries(newObj)) { + if (typeof value === "object") { + if (hasOverrides(value, existingObj[key] || {})) { + return true + } + } else if (value !== existingObj[key]) { + return true + } + } + return false + } + + const table = await sdk.tables.getTable(view.tableId) + for (const [ + fieldName, + { order, width, visible, icon, ...schemaNonUI }, + ] of Object.entries(view.schema)) { + const overrides = hasOverrides(schemaNonUI, table.schema[fieldName]) + if (overrides) { + ctx.throw( + 400, + "This endpoint does not support overriding non UI fields in the schema" + ) + } + } const schemaUI = view.schema && @@ -24,6 +55,14 @@ export async function create(ctx: Ctx) { } return p }, {} as Record>) + return schemaUI +} + +export async function create(ctx: Ctx) { + const view = ctx.request.body + const { tableId } = view + + const schemaUI = await parseSchemaUI(ctx, view) const parsedView: Omit = { name: view.name, diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index 14fe90b93e..e60e9a5126 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -95,15 +95,15 @@ describe("/v2/views", () => { name: generator.name(), tableId: config.table!._id!, schema: { - name: { - name: "name", - type: FieldType.STRING, + Price: { + name: "Price", + type: FieldType.NUMBER, visible: true, order: 1, width: 100, }, - lastname: { - name: "lastname", + Category: { + name: "Category", type: FieldType.STRING, visible: false, icon: "ic", @@ -116,14 +116,14 @@ describe("/v2/views", () => { expect(await config.api.viewV2.get(createdView.id)).toEqual({ ...newView, schema: undefined, - columns: ["name", "lastname"], + columns: ["Price", "Category"], schemaUI: { - name: { + Price: { visible: true, order: 1, width: 100, }, - lastname: { + Category: { visible: false, icon: "ic", }, @@ -132,6 +132,54 @@ describe("/v2/views", () => { version: 2, }) }) + + it("throw an exception if the schema overrides a non UI field", async () => { + const newView: CreateViewRequest = { + name: generator.name(), + tableId: config.table!._id!, + schema: { + Price: { + name: "Price", + type: FieldType.NUMBER, + visible: true, + }, + Category: { + name: "Category", + type: FieldType.STRING, + constraints: { + type: "string", + presence: true, + }, + }, + }, + } + + await config.api.viewV2.create(newView, { + expectStatus: 400, + }) + }) + + it("will not throw an exception if the schema is 'deleting' non UI fields", async () => { + const newView: CreateViewRequest = { + name: generator.name(), + tableId: config.table!._id!, + schema: { + Price: { + name: "Price", + type: FieldType.NUMBER, + visible: true, + }, + Category: { + name: "Category", + type: FieldType.STRING, + }, + }, + } + + await config.api.viewV2.create(newView, { + expectStatus: 201, + }) + }) }) describe("update", () => {