From 78afba63dec506e823589858af382b65804f61d1 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 25 Oct 2023 11:03:35 +0100 Subject: [PATCH] Emit table updates to the builder socket, and some minor refactoring. --- .../server/src/api/controllers/table/index.ts | 6 +- .../server/src/sdk/app/tables/migration.ts | 148 ++++++++---------- .../server/src/tests/utilities/api/table.ts | 9 +- 3 files changed, 78 insertions(+), 85 deletions(-) diff --git a/packages/server/src/api/controllers/table/index.ts b/packages/server/src/api/controllers/table/index.ts index fb7f8d2a5a..1ad8542afd 100644 --- a/packages/server/src/api/controllers/table/index.ts +++ b/packages/server/src/api/controllers/table/index.ts @@ -165,7 +165,11 @@ export async function migrate(ctx: UserCtx) { const { oldColumn, newColumn } = ctx.request.body let tableId = ctx.params.tableId as string const table = await sdk.tables.getTable(tableId) - await sdk.tables.migrate(table, oldColumn, newColumn) + let result = await sdk.tables.migrate(table, oldColumn, newColumn) + + for (let table of result.tablesUpdated) { + builderSocket?.emitTableUpdate(ctx, table) + } ctx.status = 200 ctx.body = { message: `Column ${oldColumn.name} migrated.` } diff --git a/packages/server/src/sdk/app/tables/migration.ts b/packages/server/src/sdk/app/tables/migration.ts index 407302cf1a..b678b64318 100644 --- a/packages/server/src/sdk/app/tables/migration.ts +++ b/packages/server/src/sdk/app/tables/migration.ts @@ -1,37 +1,43 @@ -import { BadRequestError, context } from "@budibase/backend-core" +import { BadRequestError, context, db as dbCore } from "@budibase/backend-core" import { BBReferenceFieldMetadata, FieldSchema, FieldSubtype, InternalTable, - ManyToManyRelationshipFieldMetadata, - ManyToOneRelationshipFieldMetadata, - OneToManyRelationshipFieldMetadata, + isBBReferenceField, + isRelationshipField, + LinkDocument, + RelationshipFieldMetadata, RelationshipType, Row, Table, - isBBReferenceField, - isRelationshipField, } from "@budibase/types" import sdk from "../../../sdk" -import { isExternalTable } from "../../../../src/integrations/utils" -import { db as dbCore } from "@budibase/backend-core" -import { EventType, updateLinks } from "../../../../src/db/linkedRows" +import { isExternalTable } from "../../../integrations/utils" +import { EventType, updateLinks } from "../../../db/linkedRows" import { cloneDeep } from "lodash" +export interface MigrationResult { + tablesUpdated: Table[] +} + export async function migrate( table: Table, oldColumn: FieldSchema, newColumn: FieldSchema -) { - let migrator = getColumnMigrator(table, oldColumn, newColumn) - let oldTable = cloneDeep(table) +): Promise { + if (newColumn.name in table.schema) { + throw new BadRequestError(`Column "${newColumn.name}" already exists`) + } table.schema[newColumn.name] = newColumn table = await sdk.tables.saveTable(table) + let migrator = getColumnMigrator(table, oldColumn, newColumn) + let result: MigrationResult + try { - await migrator.doMigration() + result = await migrator.doMigration() } catch (e) { // If the migration fails then we need to roll back the table schema // change. @@ -40,13 +46,11 @@ export async function migrate( throw e } - delete table.schema[oldColumn.name] - table = await sdk.tables.saveTable(table) - await updateLinks({ eventType: EventType.TABLE_UPDATED, table, oldTable }) + return result } interface ColumnMigrator { - doMigration(): Promise + doMigration(): Promise } function getColumnMigrator( @@ -54,8 +58,8 @@ function getColumnMigrator( oldColumn: FieldSchema, newColumn: FieldSchema ): ColumnMigrator { - // For now we're only supporting migrations of user relationships to user - // columns in internal tables. In future we may want to support other + // For now, we're only supporting migrations of user relationships to user + // columns in internal tables. In the future, we may want to support other // migrations but for now return an error if we aren't migrating a user // relationship. if (isExternalTable(table._id!)) { @@ -66,10 +70,6 @@ function getColumnMigrator( throw new BadRequestError(`Column "${oldColumn.name}" does not exist`) } - if (newColumn.name in table.schema) { - throw new BadRequestError(`Column "${newColumn.name}" already exists`) - } - if (!isBBReferenceField(newColumn)) { throw new BadRequestError(`Column "${newColumn.name}" is not a user column`) } @@ -113,14 +113,17 @@ function getColumnMigrator( throw new BadRequestError(`Unknown migration type`) } -class SingleUserColumnMigrator implements ColumnMigrator { +abstract class UserColumnMigrator implements ColumnMigrator { constructor( - private table: Table, - private oldColumn: OneToManyRelationshipFieldMetadata, - private newColumn: BBReferenceFieldMetadata + protected table: Table, + protected oldColumn: RelationshipFieldMetadata, + protected newColumn: BBReferenceFieldMetadata ) {} - async doMigration() { + abstract updateRow(row: Row, link: LinkDocument): void + + async doMigration(): Promise { + let oldTable = cloneDeep(this.table) let rows = await sdk.rows.fetchRaw(this.table._id!) let rowsById = rows.reduce((acc, row) => { acc[row._id!] = row @@ -129,17 +132,14 @@ class SingleUserColumnMigrator implements ColumnMigrator { let links = await sdk.links.fetchWithDocument(this.table._id!) for (let link of links) { - if (link.doc1.tableId !== this.table._id) { - continue - } - if (link.doc1.fieldName !== this.oldColumn.name) { - continue - } - if (link.doc2.tableId !== InternalTable.USER_METADATA) { + if ( + link.doc1.tableId !== this.table._id || + link.doc1.fieldName !== this.oldColumn.name || + link.doc2.tableId !== InternalTable.USER_METADATA + ) { continue } - let userId = dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId) let row = rowsById[link.doc1.rowId] if (!row) { // This can happen if the row has been deleted but the link hasn't, @@ -148,58 +148,42 @@ class SingleUserColumnMigrator implements ColumnMigrator { continue } - row[this.newColumn.name] = userId + this.updateRow(row, link) } let db = context.getAppDB() await db.bulkDocs(rows) - } -} -class MultiUserColumnMigrator implements ColumnMigrator { - constructor( - private table: Table, - private oldColumn: - | ManyToManyRelationshipFieldMetadata - | ManyToOneRelationshipFieldMetadata, - private newColumn: BBReferenceFieldMetadata - ) {} + delete this.table.schema[this.oldColumn.name] + this.table = await sdk.tables.saveTable(this.table) + await updateLinks({ + eventType: EventType.TABLE_UPDATED, + table: this.table, + oldTable, + }) - async doMigration() { - let rows = await sdk.rows.fetchRaw(this.table._id!) - let rowsById = rows.reduce((acc, row) => { - acc[row._id!] = row - return acc - }, {} as Record) - - let links = await sdk.links.fetchWithDocument(this.table._id!) - for (let link of links) { - if (link.doc1.tableId !== this.table._id) { - continue - } - if (link.doc1.fieldName !== this.oldColumn.name) { - continue - } - if (link.doc2.tableId !== InternalTable.USER_METADATA) { - continue - } - - let userId = dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId) - let row = rowsById[link.doc1.rowId] - if (!row) { - // This can happen if the row has been deleted but the link hasn't, - // which was a state that was found during the initial testing of this - // feature. Not sure exactly what can cause it, but best to be safe. - continue - } - - if (!row[this.newColumn.name]) { - row[this.newColumn.name] = [] - } - row[this.newColumn.name].push(userId) + let otherTable = await sdk.tables.getTable(this.oldColumn.tableId) + return { + tablesUpdated: [this.table, otherTable], } - - let db = context.getAppDB() - await db.bulkDocs(rows) + } +} + +class SingleUserColumnMigrator extends UserColumnMigrator { + updateRow(row: Row, link: LinkDocument): void { + row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID( + link.doc2.rowId + ) + } +} + +class MultiUserColumnMigrator extends UserColumnMigrator { + updateRow(row: Row, link: LinkDocument): void { + if (!row[this.newColumn.name]) { + row[this.newColumn.name] = [] + } + row[this.newColumn.name].push( + dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId) + ) } } diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts index 501841f6e7..b80c940697 100644 --- a/packages/server/src/tests/utilities/api/table.ts +++ b/packages/server/src/tests/utilities/api/table.ts @@ -58,8 +58,13 @@ export class TableAPI extends TestAPI { .post(`/api/tables/${tableId}/migrate`) .send(data) .set(this.config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(expectStatus) + if (res.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + res.status + } with body ${JSON.stringify(res.body)}` + ) + } return res.body } }