diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index 69305c461e..f799113333 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -84,8 +84,8 @@ export async function save(ctx: UserCtx) { } let savedTable = await api.save(ctx, renaming) if (!table._id) { - await events.table.created(savedTable) savedTable = sdk.tables.enrichViewSchemas(savedTable) + await events.table.created(savedTable) } else { await events.table.updated(savedTable) } diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index f9e05c5bd8..d9895466a5 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -722,6 +722,39 @@ describe.each([ }) }) + describe("bulkImport", () => { + isInternal && + it("should update Auto ID field after bulk import", async () => { + const table = await config.api.table.save( + saveTableRequest({ + primary: ["autoId"], + schema: { + autoId: { + name: "autoId", + type: FieldType.NUMBER, + subtype: AutoFieldSubType.AUTO_ID, + autocolumn: true, + constraints: { + type: "number", + presence: false, + }, + }, + }, + }) + ) + + let row = await config.api.row.save(table._id!, {}) + expect(row.autoId).toEqual(1) + + await config.api.row.bulkImport(table._id!, { + rows: [{ autoId: 2 }], + }) + + row = await config.api.row.save(table._id!, {}) + expect(row.autoId).toEqual(3) + }) + }) + describe("enrich", () => { beforeAll(async () => { table = await config.api.table.save(defaultTable()) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 400ff858a7..6d2dd791b1 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -6,7 +6,6 @@ import { FieldType, INTERNAL_TABLE_SOURCE_ID, InternalTable, - NumberFieldMetadata, RelationshipType, Row, SaveTableRequest, @@ -14,17 +13,16 @@ import { TableSourceType, User, ViewCalculation, + ViewV2, } from "@budibase/types" import { checkBuilderEndpoint } from "./utilities/TestFunctions" import * as setup from "./utilities" import * as uuid from "uuid" -import tk from "timekeeper" -import { generator, mocks } from "@budibase/backend-core/tests" +import { generator } from "@budibase/backend-core/tests" import { DatabaseName, getDatasource } from "../../../integrations/tests/utils" import { tableForDatasource } from "../../../tests/utilities/structures" - -tk.freeze(mocks.date.MOCK_DATE) +import timekeeper from "timekeeper" const { basicTable } = setup.structures const ISO_REGEX_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ @@ -57,7 +55,7 @@ describe.each([ jest.clearAllMocks() }) - it("returns a success message when the table is successfully created", async () => { + it("creates a table successfully", async () => { const name = generator.guid() const table = await config.api.table.save( tableForDatasource(datasource, { name }) @@ -67,64 +65,6 @@ describe.each([ expect(events.table.created).toHaveBeenCalledWith(table) }) - it("creates all the passed fields", async () => { - const tableData = tableForDatasource(datasource, { - schema: { - autoId: { - name: "id", - type: FieldType.NUMBER, - subtype: AutoFieldSubType.AUTO_ID, - autocolumn: true, - constraints: { - type: "number", - presence: false, - }, - }, - }, - views: { - "table view": { - id: "viewId", - version: 2, - name: "table view", - tableId: "tableId", - }, - }, - }) - - const testTable = await config.api.table.save(tableData) - - const expected: Table = { - ...tableData, - type: "table", - views: { - "table view": { - ...tableData.views!["table view"], - schema: { - autoId: { - autocolumn: true, - constraints: { - presence: false, - type: "number", - }, - name: "id", - type: FieldType.NUMBER, - subtype: AutoFieldSubType.AUTO_ID, - visible: false, - } as NumberFieldMetadata, - }, - }, - }, - _rev: expect.stringMatching(/^1-.+/), - _id: expect.any(String), - createdAt: mocks.date.MOCK_DATE.toISOString(), - updatedAt: mocks.date.MOCK_DATE.toISOString(), - } - expect(testTable).toEqual(expected) - - const persistedTable = await config.api.table.get(testTable._id!) - expect(persistedTable).toEqual(expected) - }) - it("creates a table via data import", async () => { const table: SaveTableRequest = basicTable() table.rows = [{ name: "test-name", description: "test-desc" }] @@ -165,17 +105,17 @@ describe.each([ }) ) - await config.api.table.save({ + const updatedTable = await config.api.table.save({ ...table, name: generator.guid(), }) expect(events.table.updated).toHaveBeenCalledTimes(1) - expect(events.table.updated).toHaveBeenCalledWith(table) + expect(events.table.updated).toHaveBeenCalledWith(updatedTable) }) it("updates all the row fields for a table when a schema key is renamed", async () => { - const testTable = await config.createTable() + const testTable = await config.api.table.save(basicTable(datasource)) await config.createLegacyView({ name: "TestView", field: "Price", @@ -215,64 +155,68 @@ describe.each([ }) it("updates only the passed fields", async () => { - const table = await config.api.table.save( - tableForDatasource(datasource, { - name: "TestTable", - schema: { - autoId: { - name: "id", - type: FieldType.NUMBER, - subtype: AutoFieldSubType.AUTO_ID, - autocolumn: true, - constraints: { - type: "number", - presence: false, + await timekeeper.withFreeze(new Date(2021, 1, 1), async () => { + const table = await config.api.table.save( + tableForDatasource(datasource, { + schema: { + autoId: { + name: "id", + type: FieldType.NUMBER, + subtype: AutoFieldSubType.AUTO_ID, + autocolumn: true, + constraints: { + type: "number", + presence: false, + }, }, }, - }, + }) + ) + + const newName = generator.guid() + + const updatedTable = await config.api.table.save({ + ...table, + name: newName, }) - ) - const updatedTable = await config.api.table.save({ - ...table, - name: "UpdatedName", + let expected: Table = { + ...table, + name: newName, + _id: expect.any(String), + } + if (isInternal) { + expected._rev = expect.stringMatching(/^2-.+/) + } + + expect(updatedTable).toEqual(expected) + + const persistedTable = await config.api.table.get(updatedTable._id!) + expected = { + ...table, + name: newName, + _id: updatedTable._id, + } + if (datasource?.isSQL) { + expected.sql = true + } + if (isInternal) { + expected._rev = expect.stringMatching(/^2-.+/) + } + expect(persistedTable).toEqual(expected) }) - - let expected: Table = { - ...table, - name: "UpdatedName", - _id: expect.any(String), - } - if (isInternal) { - expected._rev = expect.stringMatching(/^2-.+/) - } - - expect(updatedTable).toEqual(expected) - - const persistedTable = await config.api.table.get(updatedTable._id!) - expected = { - ...table, - name: "UpdatedName", - _id: updatedTable._id, - } - if (datasource?.isSQL) { - expected.sql = true - } - if (isInternal) { - expected._rev = expect.stringMatching(/^2-.+/) - } - expect(persistedTable).toEqual(expected) }) describe("user table", () => { - it("should add roleId and email field when adjusting user table schema", async () => { - const table = await config.api.table.save({ - ...basicTable(datasource), - _id: "ta_users", + isInternal && + it("should add roleId and email field when adjusting user table schema", async () => { + const table = await config.api.table.save({ + ...basicTable(datasource), + _id: "ta_users", + }) + expect(table.schema.email).toBeDefined() + expect(table.schema.roleId).toBeDefined() }) - expect(table.schema.email).toBeDefined() - expect(table.schema.roleId).toBeDefined() - }) }) it("should add a new column for an internal DB table", async () => { @@ -300,7 +244,10 @@ describe.each([ describe("import", () => { it("imports rows successfully", async () => { - const table = await config.api.table.save(basicTable(datasource)) + const name = generator.guid() + const table = await config.api.table.save( + basicTable(datasource, { name }) + ) const importRequest = { schema: table.schema, rows: [{ name: "test-name", description: "test-desc" }], @@ -314,43 +261,12 @@ describe.each([ expect(events.rows.imported).toHaveBeenCalledTimes(1) expect(events.rows.imported).toHaveBeenCalledWith( expect.objectContaining({ - name: "TestTable", + name, _id: table._id, }), 1 ) }) - - it("should update Auto ID field after bulk import", async () => { - const table = await config.api.table.save( - tableForDatasource(datasource, { - name: "TestTable", - schema: { - autoId: { - name: "id", - type: FieldType.NUMBER, - subtype: AutoFieldSubType.AUTO_ID, - autocolumn: true, - constraints: { - type: "number", - presence: false, - }, - }, - }, - }) - ) - - let row = await config.api.row.save(table._id!, {}) - expect(row.autoId).toEqual(1) - - await config.api.row.bulkImport(table._id!, { - rows: [{ autoId: 2 }], - identifierFields: [], - }) - - row = await config.api.row.save(table._id!, {}) - expect(row.autoId).toEqual(3) - }) }) describe("fetch", () => { @@ -396,7 +312,8 @@ describe.each([ const table = res.find(t => t._id === testTable._id) expect(table).toBeDefined() expect(table!.views![viewV2.name]).toBeDefined() - expect(table!.views![viewV2.name!]).toEqual({ + + const expectedViewV2: ViewV2 = { ...viewV2, schema: { description: { @@ -404,7 +321,7 @@ describe.each([ type: "string", }, name: "description", - type: "string", + type: FieldType.STRING, visible: false, }, name: { @@ -412,11 +329,22 @@ describe.each([ type: "string", }, name: "name", - type: "string", + type: FieldType.STRING, visible: false, }, }, - }) + } + + if (!isInternal) { + expectedViewV2.schema!.id = { + name: "id", + type: FieldType.NUMBER, + visible: false, + autocolumn: true, + } + } + + expect(table!.views![viewV2.name!]).toEqual(expectedViewV2) if (isInternal) { expect(table!.views![legacyView.name!]).toBeDefined() @@ -440,23 +368,16 @@ describe.each([ }, }) } + }) + }) - // expect(res).toEqual( - // expect.arrayContaining([ - // expect.objectContaining({ - // _id: tableId, - // views: { - // view1: { - // version: 2, - // name: "view1", - // schema: {}, - // id: "new_view_id", - // tableId, - // }, - // }, - // }), - // ]) - // ) + describe("get", () => { + it("returns a table", async () => { + const table = await config.api.table.save( + basicTable(datasource, { name: generator.guid() }) + ) + const res = await config.api.table.get(table._id!) + expect(res).toEqual(table) }) }) @@ -781,33 +702,31 @@ describe.each([ describe("unhappy paths", () => { let table: Table beforeAll(async () => { - table = await config.api.table.save({ - name: "table", - type: "table", - sourceId: INTERNAL_TABLE_SOURCE_ID, - sourceType: TableSourceType.INTERNAL, - schema: { - "user relationship": { - type: FieldType.LINK, - fieldName: "test", - name: "user relationship", - constraints: { - type: "array", - presence: false, + table = await config.api.table.save( + tableForDatasource(datasource, { + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.MANY_TO_ONE, + tableId: InternalTable.USER_METADATA, }, - relationshipType: RelationshipType.MANY_TO_ONE, - tableId: InternalTable.USER_METADATA, - }, - num: { - type: FieldType.NUMBER, - name: "num", - constraints: { - type: "number", - presence: false, + num: { + type: FieldType.NUMBER, + name: "num", + constraints: { + type: "number", + presence: false, + }, }, }, - }, - }) + }) + ) }) it("should fail if the new column name is blank", async () => { diff --git a/packages/server/src/sdk/app/tables/external/index.ts b/packages/server/src/sdk/app/tables/external/index.ts index 0ace19d00e..65cd4a07c1 100644 --- a/packages/server/src/sdk/app/tables/external/index.ts +++ b/packages/server/src/sdk/app/tables/external/index.ts @@ -48,6 +48,18 @@ export async function save( oldTable = await getTable(tableId) } + if ( + !oldTable && + (tableToSave.primary == null || tableToSave.primary.length === 0) + ) { + tableToSave.primary = ["id"] + tableToSave.schema.id = { + type: FieldType.NUMBER, + autocolumn: true, + name: "id", + } + } + if (hasTypeChanged(tableToSave, oldTable)) { throw new Error("A column type has changed.") } @@ -183,6 +195,10 @@ export async function save( // that the datasource definition changed const updatedDatasource = await datasourceSdk.get(datasource._id!) + if (updatedDatasource.isSQL) { + tableToSave.sql = true + } + return { datasource: updatedDatasource, table: tableToSave } } diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 45e39268ac..42bac7d673 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -10,10 +10,14 @@ import { } from "./constants" export interface UIFieldMetadata { + constraints?: FieldConstraints + name?: string + type?: FieldType order?: number width?: number visible?: boolean icon?: string + autocolumn?: boolean } interface BaseRelationshipFieldMetadata