diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 02134f0317..06d44bc41c 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -37,6 +37,7 @@ import { jsonFromCsvString } from "../../../utilities/csv" import { builderSocket } from "../../../websockets" import { cloneDeep } from "lodash" import { + canBeDisplayColumn, helpers, PROTECTED_EXTERNAL_COLUMNS, PROTECTED_INTERNAL_COLUMNS, @@ -67,6 +68,27 @@ function checkDefaultFields(table: Table) { } } +async function guardTable(table: Table, isCreate: boolean) { + checkDefaultFields(table) + + if ( + table.primaryDisplay && + !canBeDisplayColumn(table.schema[table.primaryDisplay]?.type) + ) { + // Prevent throwing errors from existing badly configured tables. Only throw for new tables or if this setting is being updated + if ( + isCreate || + (await sdk.tables.getTable(table._id!)).primaryDisplay !== + table.primaryDisplay + ) { + throw new HTTPError( + `Column "${table.primaryDisplay}" cannot be used as a display type.`, + 400 + ) + } + } +} + // covers both internal and external export async function fetch(ctx: UserCtx) { const internal = await sdk.tables.getAllInternalTables() @@ -111,7 +133,7 @@ export async function save(ctx: UserCtx) { const isCreate = !table._id - checkDefaultFields(table) + await guardTable(table, isCreate) let savedTable: Table if (isCreate) { diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index e97f48afbe..e94e567b43 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -3399,7 +3399,7 @@ if (descriptions.length) { type: FieldType.LINK, relationshipType: RelationshipType.MANY_TO_ONE, tableId: toRelateTableId, - fieldName: "link", + fieldName: "main", }, }) @@ -3408,7 +3408,7 @@ if (descriptions.length) { ) await config.api.table.save({ ...toRelateTable, - primaryDisplay: "link", + primaryDisplay: "name", }) const relatedRows = await Promise.all([ config.api.row.save(toRelateTable._id!, { diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index e47181e21f..2a7f039ff5 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -185,6 +185,62 @@ if (descriptions.length) { ) } ) + + it("can set primary display", async () => { + const columnName = generator.word() + const table = await config.api.table.save( + tableForDatasource(datasource, { + primaryDisplay: columnName, + schema: { + [columnName]: { + name: columnName, + type: FieldType.STRING, + }, + }, + }) + ) + expect(table.primaryDisplay).toEqual(columnName) + + const res = await config.api.table.get(table._id!) + expect(res.primaryDisplay).toEqual(columnName) + }) + + it("cannot use unexisting columns as primary display", async () => { + const columnName = generator.word() + await config.api.table.save( + tableForDatasource(datasource, { + primaryDisplay: columnName, + }), + { + status: 400, + body: { + message: `Column "${columnName}" cannot be used as a display type.`, + }, + } + ) + }) + + it("cannot use invalid column types as display name", async () => { + const columnName = generator.word() + + await config.api.table.save( + tableForDatasource(datasource, { + primaryDisplay: columnName, + schema: { + [columnName]: { + name: columnName, + type: FieldType.BOOLEAN, + }, + }, + }), + { + status: 400, + body: { + message: `Column "${columnName}" cannot be used as a display type.`, + }, + } + ) + }) }) describe("permissions", () => { @@ -603,6 +659,49 @@ if (descriptions.length) { } expect(response).toEqual(expectedResponse) }) + + it("cannot use unexisting columns as primary display", async () => { + const table = await config.api.table.save( + tableForDatasource(datasource) + ) + + const columnName = generator.word() + const tableRequest = { + ...table, + primaryDisplay: columnName, + } + await config.api.table.save(tableRequest, { + status: 400, + body: { + message: `Column "${columnName}" cannot be used as a display type.`, + }, + }) + }) + + it("cannot use invalid column types as display name", async () => { + const table = await config.api.table.save( + tableForDatasource(datasource) + ) + const columnName = generator.word() + const tableRequest: SaveTableRequest = { + ...table, + primaryDisplay: columnName, + schema: { + ...table.schema, + [columnName]: { + name: columnName, + type: FieldType.BOOLEAN, + }, + }, + } + + await config.api.table.save(tableRequest, { + status: 400, + body: { + message: `Column "${columnName}" cannot be used as a display type.`, + }, + }) + }) }) describe("import", () => { diff --git a/packages/shared-core/src/table.ts b/packages/shared-core/src/table.ts index 5402439d9c..070ee6c760 100644 --- a/packages/shared-core/src/table.ts +++ b/packages/shared-core/src/table.ts @@ -12,8 +12,8 @@ const allowDisplayColumnByType: Record = { [FieldType.AUTO]: true, [FieldType.INTERNAL]: true, [FieldType.BARCODEQR]: true, - [FieldType.BIGINT]: true, + [FieldType.BOOLEAN]: false, [FieldType.ARRAY]: false, [FieldType.ATTACHMENTS]: false,