Implement many-to-many user column migrations.
This commit is contained in:
parent
2176fe75ff
commit
a3ad8780de
|
@ -8,6 +8,7 @@ import {
|
|||
AutoFieldSubTypes,
|
||||
InternalTable,
|
||||
FieldSubtype,
|
||||
Row,
|
||||
} from "@budibase/types"
|
||||
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||
import * as setup from "./utilities"
|
||||
|
@ -421,8 +422,13 @@ describe("/tables", () => {
|
|||
})
|
||||
|
||||
describe("migrate", () => {
|
||||
it("should successfully migrate a user relationship to a user column", async () => {
|
||||
const users = await config.api.row.fetch(InternalTable.USER_METADATA)
|
||||
it("should successfully migrate a one-to-many user relationship to a user column", async () => {
|
||||
const users = await Promise.all([
|
||||
config.createUser({ email: "1@example.com" }),
|
||||
config.createUser({ email: "2@example.com" }),
|
||||
config.createUser({ email: "3@example.com" }),
|
||||
])
|
||||
|
||||
const table = await config.api.table.create({
|
||||
name: "table",
|
||||
type: "table",
|
||||
|
@ -441,9 +447,11 @@ describe("/tables", () => {
|
|||
},
|
||||
})
|
||||
|
||||
await config.api.row.save(table._id!, {
|
||||
"user relationship": users,
|
||||
})
|
||||
const rows = await Promise.all(
|
||||
users.map(u =>
|
||||
config.api.row.save(table._id!, { "user relationship": [u] })
|
||||
)
|
||||
)
|
||||
|
||||
await config.api.table.migrate(table._id!, {
|
||||
oldColumn: table.schema["user relationship"],
|
||||
|
@ -458,9 +466,80 @@ describe("/tables", () => {
|
|||
expect(migratedTable.schema["user column"]).toBeDefined()
|
||||
expect(migratedTable.schema["user relationship"]).not.toBeDefined()
|
||||
|
||||
const rows = await config.api.row.fetch(table._id!)
|
||||
expect(rows[0]["user column"]).toBeDefined()
|
||||
expect(rows[0]["user relationship"]).not.toBeDefined()
|
||||
const migratedRows = await config.api.row.fetch(table._id!)
|
||||
|
||||
rows.sort((a, b) => a._id!.localeCompare(b._id!))
|
||||
migratedRows.sort((a, b) => a._id!.localeCompare(b._id!))
|
||||
|
||||
for (const [i, row] of rows.entries()) {
|
||||
const migratedRow = migratedRows[i]
|
||||
expect(migratedRow["user column"]).toBeDefined()
|
||||
expect(migratedRow["user relationship"]).not.toBeDefined()
|
||||
expect(row["user relationship"][0]._id).toEqual(
|
||||
migratedRow["user column"][0]._id
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should successfully migrate a many-to-many user relationship to a users column", async () => {
|
||||
const users = await Promise.all([
|
||||
config.createUser({ email: "1@example.com" }),
|
||||
config.createUser({ email: "2@example.com" }),
|
||||
config.createUser({ email: "3@example.com" }),
|
||||
])
|
||||
|
||||
const table = await config.api.table.create({
|
||||
name: "table",
|
||||
type: "table",
|
||||
schema: {
|
||||
"user relationship": {
|
||||
type: FieldType.LINK,
|
||||
fieldName: "test",
|
||||
name: "user relationship",
|
||||
constraints: {
|
||||
type: "array",
|
||||
presence: false,
|
||||
},
|
||||
relationshipType: RelationshipType.MANY_TO_MANY,
|
||||
tableId: InternalTable.USER_METADATA,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const row1 = await config.api.row.save(table._id!, {
|
||||
"user relationship": [users[0], users[1]],
|
||||
})
|
||||
|
||||
const row2 = await config.api.row.save(table._id!, {
|
||||
"user relationship": [users[2]],
|
||||
})
|
||||
|
||||
await config.api.table.migrate(table._id!, {
|
||||
oldColumn: table.schema["user relationship"],
|
||||
newColumn: {
|
||||
name: "user column",
|
||||
type: FieldType.BB_REFERENCE,
|
||||
subtype: FieldSubtype.USERS,
|
||||
},
|
||||
})
|
||||
|
||||
const migratedTable = await config.api.table.get(table._id!)
|
||||
expect(migratedTable.schema["user column"]).toBeDefined()
|
||||
expect(migratedTable.schema["user relationship"]).not.toBeDefined()
|
||||
|
||||
const row1Migrated = (await config.api.row.get(table._id!, row1._id!))
|
||||
.body as Row
|
||||
expect(row1Migrated["user relationship"]).not.toBeDefined()
|
||||
expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual(
|
||||
expect.arrayContaining([users[0]._id, users[1]._id])
|
||||
)
|
||||
|
||||
const row2Migrated = (await config.api.row.get(table._id!, row2._id!))
|
||||
.body as Row
|
||||
expect(row2Migrated["user relationship"]).not.toBeDefined()
|
||||
expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual([
|
||||
users[2]._id,
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -57,6 +57,6 @@ export async function fetchView(
|
|||
tableId: string,
|
||||
viewName: string,
|
||||
params: ViewParams
|
||||
) {
|
||||
): Promise<Row[]> {
|
||||
return pickApi(tableId).fetchView(viewName, params)
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@ import { BadRequestError, context } from "@budibase/backend-core"
|
|||
import {
|
||||
BBReferenceFieldMetadata,
|
||||
FieldSchema,
|
||||
FieldSubtype,
|
||||
InternalTable,
|
||||
ManyToManyRelationshipFieldMetadata,
|
||||
OneToManyRelationshipFieldMetadata,
|
||||
RelationshipFieldMetadata,
|
||||
RelationshipType,
|
||||
Row,
|
||||
Table,
|
||||
isBBReferenceField,
|
||||
|
@ -77,13 +81,30 @@ function getColumnMigrator(
|
|||
)
|
||||
}
|
||||
|
||||
return new UserColumnMigrator(table, oldColumn, newColumn)
|
||||
if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) {
|
||||
if (newColumn.subtype !== FieldSubtype.USER) {
|
||||
throw new BadRequestError(
|
||||
`Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column`
|
||||
)
|
||||
}
|
||||
return new OneToManyUserColumnMigrator(table, oldColumn, newColumn)
|
||||
}
|
||||
if (oldColumn.relationshipType === RelationshipType.MANY_TO_MANY) {
|
||||
if (newColumn.subtype !== FieldSubtype.USERS) {
|
||||
throw new BadRequestError(
|
||||
`Column "${oldColumn.name}" is a many-to-many column but "${newColumn.name}" is not a multi user column`
|
||||
)
|
||||
}
|
||||
return new ManyToManyUserColumnMigrator(table, oldColumn, newColumn)
|
||||
}
|
||||
|
||||
throw new BadRequestError(`Unknown migration type`)
|
||||
}
|
||||
|
||||
class UserColumnMigrator implements ColumnMigrator {
|
||||
class OneToManyUserColumnMigrator implements ColumnMigrator {
|
||||
constructor(
|
||||
private table: Table,
|
||||
private oldColumn: RelationshipFieldMetadata,
|
||||
private oldColumn: OneToManyRelationshipFieldMetadata,
|
||||
private newColumn: BBReferenceFieldMetadata
|
||||
) {}
|
||||
|
||||
|
@ -115,3 +136,42 @@ class UserColumnMigrator implements ColumnMigrator {
|
|||
await db.bulkDocs(rows)
|
||||
}
|
||||
}
|
||||
|
||||
class ManyToManyUserColumnMigrator implements ColumnMigrator {
|
||||
constructor(
|
||||
private table: Table,
|
||||
private oldColumn: ManyToManyRelationshipFieldMetadata,
|
||||
private newColumn: BBReferenceFieldMetadata
|
||||
) {}
|
||||
|
||||
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<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.newColumn.name]) {
|
||||
row[this.newColumn.name] = []
|
||||
}
|
||||
row[this.newColumn.name].push(userId)
|
||||
}
|
||||
|
||||
let db = context.getAppDB()
|
||||
await db.bulkDocs(rows)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import {
|
|||
RelationshipType,
|
||||
CreateViewRequest,
|
||||
RelationshipFieldMetadata,
|
||||
User,
|
||||
} from "@budibase/types"
|
||||
|
||||
import API from "./api"
|
||||
|
@ -254,7 +255,7 @@ class TestConfiguration {
|
|||
} catch (err) {
|
||||
existing = { email }
|
||||
}
|
||||
const user = {
|
||||
const user: User = {
|
||||
_id: id,
|
||||
...existing,
|
||||
roles: roles || {},
|
||||
|
|
|
@ -27,5 +27,6 @@ export default class API {
|
|||
this.datasource = new DatasourceAPI(config)
|
||||
this.screen = new ScreenAPI(config)
|
||||
this.application = new ApplicationAPI(config)
|
||||
this.global = new GlobalAPI(config)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue