Emit table updates to the builder socket, and some minor refactoring.
This commit is contained in:
parent
0f5c2cff00
commit
78afba63de
|
@ -165,7 +165,11 @@ export async function migrate(ctx: UserCtx<MigrateRequest, MigrateResponse>) {
|
||||||
const { oldColumn, newColumn } = ctx.request.body
|
const { oldColumn, newColumn } = ctx.request.body
|
||||||
let tableId = ctx.params.tableId as string
|
let tableId = ctx.params.tableId as string
|
||||||
const table = await sdk.tables.getTable(tableId)
|
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.status = 200
|
||||||
ctx.body = { message: `Column ${oldColumn.name} migrated.` }
|
ctx.body = { message: `Column ${oldColumn.name} migrated.` }
|
||||||
|
|
|
@ -1,37 +1,43 @@
|
||||||
import { BadRequestError, context } from "@budibase/backend-core"
|
import { BadRequestError, context, db as dbCore } from "@budibase/backend-core"
|
||||||
import {
|
import {
|
||||||
BBReferenceFieldMetadata,
|
BBReferenceFieldMetadata,
|
||||||
FieldSchema,
|
FieldSchema,
|
||||||
FieldSubtype,
|
FieldSubtype,
|
||||||
InternalTable,
|
InternalTable,
|
||||||
ManyToManyRelationshipFieldMetadata,
|
isBBReferenceField,
|
||||||
ManyToOneRelationshipFieldMetadata,
|
isRelationshipField,
|
||||||
OneToManyRelationshipFieldMetadata,
|
LinkDocument,
|
||||||
|
RelationshipFieldMetadata,
|
||||||
RelationshipType,
|
RelationshipType,
|
||||||
Row,
|
Row,
|
||||||
Table,
|
Table,
|
||||||
isBBReferenceField,
|
|
||||||
isRelationshipField,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { isExternalTable } from "../../../../src/integrations/utils"
|
import { isExternalTable } from "../../../integrations/utils"
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { EventType, updateLinks } from "../../../db/linkedRows"
|
||||||
import { EventType, updateLinks } from "../../../../src/db/linkedRows"
|
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
|
|
||||||
|
export interface MigrationResult {
|
||||||
|
tablesUpdated: Table[]
|
||||||
|
}
|
||||||
|
|
||||||
export async function migrate(
|
export async function migrate(
|
||||||
table: Table,
|
table: Table,
|
||||||
oldColumn: FieldSchema,
|
oldColumn: FieldSchema,
|
||||||
newColumn: FieldSchema
|
newColumn: FieldSchema
|
||||||
) {
|
): Promise<MigrationResult> {
|
||||||
let migrator = getColumnMigrator(table, oldColumn, newColumn)
|
if (newColumn.name in table.schema) {
|
||||||
let oldTable = cloneDeep(table)
|
throw new BadRequestError(`Column "${newColumn.name}" already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
table.schema[newColumn.name] = newColumn
|
table.schema[newColumn.name] = newColumn
|
||||||
table = await sdk.tables.saveTable(table)
|
table = await sdk.tables.saveTable(table)
|
||||||
|
|
||||||
|
let migrator = getColumnMigrator(table, oldColumn, newColumn)
|
||||||
|
let result: MigrationResult
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await migrator.doMigration()
|
result = await migrator.doMigration()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If the migration fails then we need to roll back the table schema
|
// If the migration fails then we need to roll back the table schema
|
||||||
// change.
|
// change.
|
||||||
|
@ -40,13 +46,11 @@ export async function migrate(
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
delete table.schema[oldColumn.name]
|
return result
|
||||||
table = await sdk.tables.saveTable(table)
|
|
||||||
await updateLinks({ eventType: EventType.TABLE_UPDATED, table, oldTable })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColumnMigrator {
|
interface ColumnMigrator {
|
||||||
doMigration(): Promise<void>
|
doMigration(): Promise<MigrationResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColumnMigrator(
|
function getColumnMigrator(
|
||||||
|
@ -54,8 +58,8 @@ function getColumnMigrator(
|
||||||
oldColumn: FieldSchema,
|
oldColumn: FieldSchema,
|
||||||
newColumn: FieldSchema
|
newColumn: FieldSchema
|
||||||
): ColumnMigrator {
|
): ColumnMigrator {
|
||||||
// For now we're only supporting migrations of user relationships to user
|
// For now, we're only supporting migrations of user relationships to user
|
||||||
// columns in internal tables. In future we may want to support other
|
// 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
|
// migrations but for now return an error if we aren't migrating a user
|
||||||
// relationship.
|
// relationship.
|
||||||
if (isExternalTable(table._id!)) {
|
if (isExternalTable(table._id!)) {
|
||||||
|
@ -66,10 +70,6 @@ function getColumnMigrator(
|
||||||
throw new BadRequestError(`Column "${oldColumn.name}" does not exist`)
|
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)) {
|
if (!isBBReferenceField(newColumn)) {
|
||||||
throw new BadRequestError(`Column "${newColumn.name}" is not a user column`)
|
throw new BadRequestError(`Column "${newColumn.name}" is not a user column`)
|
||||||
}
|
}
|
||||||
|
@ -113,14 +113,17 @@ function getColumnMigrator(
|
||||||
throw new BadRequestError(`Unknown migration type`)
|
throw new BadRequestError(`Unknown migration type`)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleUserColumnMigrator implements ColumnMigrator {
|
abstract class UserColumnMigrator implements ColumnMigrator {
|
||||||
constructor(
|
constructor(
|
||||||
private table: Table,
|
protected table: Table,
|
||||||
private oldColumn: OneToManyRelationshipFieldMetadata,
|
protected oldColumn: RelationshipFieldMetadata,
|
||||||
private newColumn: BBReferenceFieldMetadata
|
protected newColumn: BBReferenceFieldMetadata
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async doMigration() {
|
abstract updateRow(row: Row, link: LinkDocument): void
|
||||||
|
|
||||||
|
async doMigration(): Promise<MigrationResult> {
|
||||||
|
let oldTable = cloneDeep(this.table)
|
||||||
let rows = await sdk.rows.fetchRaw(this.table._id!)
|
let rows = await sdk.rows.fetchRaw(this.table._id!)
|
||||||
let rowsById = rows.reduce((acc, row) => {
|
let rowsById = rows.reduce((acc, row) => {
|
||||||
acc[row._id!] = row
|
acc[row._id!] = row
|
||||||
|
@ -129,17 +132,14 @@ class SingleUserColumnMigrator implements ColumnMigrator {
|
||||||
|
|
||||||
let links = await sdk.links.fetchWithDocument(this.table._id!)
|
let links = await sdk.links.fetchWithDocument(this.table._id!)
|
||||||
for (let link of links) {
|
for (let link of links) {
|
||||||
if (link.doc1.tableId !== this.table._id) {
|
if (
|
||||||
continue
|
link.doc1.tableId !== this.table._id ||
|
||||||
}
|
link.doc1.fieldName !== this.oldColumn.name ||
|
||||||
if (link.doc1.fieldName !== this.oldColumn.name) {
|
link.doc2.tableId !== InternalTable.USER_METADATA
|
||||||
continue
|
) {
|
||||||
}
|
|
||||||
if (link.doc2.tableId !== InternalTable.USER_METADATA) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let userId = dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId)
|
|
||||||
let row = rowsById[link.doc1.rowId]
|
let row = rowsById[link.doc1.rowId]
|
||||||
if (!row) {
|
if (!row) {
|
||||||
// This can happen if the row has been deleted but the link hasn't,
|
// This can happen if the row has been deleted but the link hasn't,
|
||||||
|
@ -148,58 +148,42 @@ class SingleUserColumnMigrator implements ColumnMigrator {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
row[this.newColumn.name] = userId
|
this.updateRow(row, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = context.getAppDB()
|
let db = context.getAppDB()
|
||||||
await db.bulkDocs(rows)
|
await db.bulkDocs(rows)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MultiUserColumnMigrator implements ColumnMigrator {
|
delete this.table.schema[this.oldColumn.name]
|
||||||
constructor(
|
this.table = await sdk.tables.saveTable(this.table)
|
||||||
private table: Table,
|
await updateLinks({
|
||||||
private oldColumn:
|
eventType: EventType.TABLE_UPDATED,
|
||||||
| ManyToManyRelationshipFieldMetadata
|
table: this.table,
|
||||||
| ManyToOneRelationshipFieldMetadata,
|
oldTable,
|
||||||
private newColumn: BBReferenceFieldMetadata
|
})
|
||||||
) {}
|
|
||||||
|
|
||||||
async doMigration() {
|
let otherTable = await sdk.tables.getTable(this.oldColumn.tableId)
|
||||||
let rows = await sdk.rows.fetchRaw(this.table._id!)
|
return {
|
||||||
let rowsById = rows.reduce((acc, row) => {
|
tablesUpdated: [this.table, otherTable],
|
||||||
acc[row._id!] = row
|
|
||||||
return acc
|
|
||||||
}, {} as Record<string, Row>)
|
|
||||||
|
|
||||||
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 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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,13 @@ export class TableAPI extends TestAPI {
|
||||||
.post(`/api/tables/${tableId}/migrate`)
|
.post(`/api/tables/${tableId}/migrate`)
|
||||||
.send(data)
|
.send(data)
|
||||||
.set(this.config.defaultHeaders())
|
.set(this.config.defaultHeaders())
|
||||||
.expect("Content-Type", /json/)
|
if (res.status !== expectStatus) {
|
||||||
.expect(expectStatus)
|
throw new Error(
|
||||||
|
`Expected status ${expectStatus} but got ${
|
||||||
|
res.status
|
||||||
|
} with body ${JSON.stringify(res.body)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue