Merge pull request #13594 from Budibase/budi-8123/usercolumn-migration

Fix user relationship to user column migration
This commit is contained in:
Adria Navarro 2024-05-09 18:04:37 +02:00 committed by GitHub
commit 7bcdcda10d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 191 additions and 188 deletions

View File

@ -13,7 +13,11 @@
Layout, Layout,
AbsTooltip, AbsTooltip,
} from "@budibase/bbui" } from "@budibase/bbui"
import { SWITCHABLE_TYPES, ValidColumnNameRegex } from "@budibase/shared-core" import {
SWITCHABLE_TYPES,
ValidColumnNameRegex,
helpers,
} from "@budibase/shared-core"
import { createEventDispatcher, getContext, onMount } from "svelte" import { createEventDispatcher, getContext, onMount } from "svelte"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/builder" import { tables, datasources } from "stores/builder"
@ -29,7 +33,11 @@
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import { getBindings } from "components/backend/DataTable/formula" import { getBindings } from "components/backend/DataTable/formula"
import JSONSchemaModal from "./JSONSchemaModal.svelte" import JSONSchemaModal from "./JSONSchemaModal.svelte"
import { FieldType, SourceName } from "@budibase/types" import {
BBReferenceFieldSubType,
FieldType,
SourceName,
} from "@budibase/types"
import RelationshipSelector from "components/common/RelationshipSelector.svelte" import RelationshipSelector from "components/common/RelationshipSelector.svelte"
import { RowUtils } from "@budibase/frontend-core" import { RowUtils } from "@budibase/frontend-core"
import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte" import ServerBindingPanel from "components/common/bindings/ServerBindingPanel.svelte"
@ -356,9 +364,29 @@
function getAllowedTypes(datasource) { function getAllowedTypes(datasource) {
if (originalName) { if (originalName) {
const possibleTypes = SWITCHABLE_TYPES[field.type] || [ let possibleTypes = SWITCHABLE_TYPES[field.type] || [editableColumn.type]
editableColumn.type, if (helpers.schema.isDeprecatedSingleUserColumn(editableColumn)) {
] // This will handle old single users columns
return [
{
...FIELDS.USER,
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
},
]
} else if (
editableColumn.type === FieldType.BB_REFERENCE &&
editableColumn.subtype === BBReferenceFieldSubType.USERS
) {
// This will handle old multi users columns
return [
{
...FIELDS.USERS,
subtype: BBReferenceFieldSubType.USERS,
},
]
}
return Object.entries(FIELDS) return Object.entries(FIELDS)
.filter(([_, field]) => possibleTypes.includes(field.type)) .filter(([_, field]) => possibleTypes.includes(field.type))
.map(([_, fieldDefinition]) => fieldDefinition) .map(([_, fieldDefinition]) => fieldDefinition)

View File

@ -59,7 +59,7 @@
value: FieldType.ATTACHMENTS, value: FieldType.ATTACHMENTS,
}, },
{ {
label: "User", label: "Users",
value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USER}`, value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USER}`,
}, },
{ {

View File

@ -165,20 +165,11 @@ export const FIELDS = {
BBReferenceFieldSubType.USER BBReferenceFieldSubType.USER
], ],
}, },
// Used for display of editing existing columns
DEPRECATED_USER: {
name: "User",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][
BBReferenceFieldSubType.USER
],
},
USERS: { USERS: {
name: "User List", name: "User List",
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS, subtype: BBReferenceFieldSubType.USER,
icon: TypeIconMap[FieldType.BB_REFERENCE][BBReferenceFieldSubType.USERS], icon: TypeIconMap[FieldType.BB_REFERENCE][BBReferenceFieldSubType.USER],
constraints: { constraints: {
type: "array", type: "array",
}, },

View File

@ -60,7 +60,7 @@
// Persist the initial values as options, allowing them to be present in the dropdown, // Persist the initial values as options, allowing them to be present in the dropdown,
// even if they are not in the inital fetch results // even if they are not in the inital fetch results
let valueAsSafeArray = fieldState.value || [] let valueAsSafeArray = fieldState.value || []
if (!Array.isArray(fieldState.value)) { if (!Array.isArray(valueAsSafeArray)) {
valueAsSafeArray = [fieldState.value] valueAsSafeArray = [fieldState.value]
} }
optionsObj = valueAsSafeArray.reduce((accumulator, value) => { optionsObj = valueAsSafeArray.reduce((accumulator, value) => {

View File

@ -1,20 +1,27 @@
<script> <script>
import { getContext } from "svelte" import { getContext } from "svelte"
import { helpers } from "@budibase/shared-core"
import RelationshipCell from "./RelationshipCell.svelte" import RelationshipCell from "./RelationshipCell.svelte"
import { BBReferenceFieldSubType, RelationshipType } from "@budibase/types" import {
BBReferenceFieldSubType,
FieldType,
RelationshipType,
} from "@budibase/types"
export let api export let api
export let hideCounter = false export let hideCounter = false
export let schema
const { API } = getContext("grid") const { API } = getContext("grid")
const { subtype } = $$props.schema const { type, subtype } = schema
const schema = { $: schema = {
...$$props.schema, ...$$props.schema,
// This is not really used, just adding some content to be able to render the relationship cell // This is not really used, just adding some content to be able to render the relationship cell
tableId: "external", tableId: "external",
relationshipType: relationshipType:
subtype === BBReferenceFieldSubType.USER type === FieldType.BB_REFERENCE_SINGLE ||
helpers.schema.isDeprecatedSingleUserColumn(schema)
? RelationshipType.ONE_TO_MANY ? RelationshipType.ONE_TO_MANY
: RelationshipType.MANY_TO_MANY, : RelationshipType.MANY_TO_MANY,
} }
@ -45,7 +52,7 @@
<RelationshipCell <RelationshipCell
bind:api bind:api
{...$$props} {...$$restProps}
{schema} {schema}
{searchFunction} {searchFunction}
primaryDisplay={"email"} primaryDisplay={"email"}

View File

@ -7,11 +7,6 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { getContext } from "svelte" import { getContext } from "svelte"
import { ValidColumnNameRegex } from "@budibase/shared-core" import { ValidColumnNameRegex } from "@budibase/shared-core"
import {
BBReferenceFieldSubType,
FieldType,
RelationshipType,
} from "@budibase/types"
const { API, definition, rows } = getContext("grid") const { API, definition, rows } = getContext("grid")
@ -33,20 +28,11 @@
} }
const migrateUserColumn = async () => { const migrateUserColumn = async () => {
let subtype = BBReferenceFieldSubType.USERS
if (column.schema.relationshipType === RelationshipType.ONE_TO_MANY) {
subtype = BBReferenceFieldSubType.USER
}
try { try {
await API.migrateColumn({ await API.migrateColumn({
tableId: $definition._id, tableId: $definition._id,
oldColumn: column.schema, oldColumn: column.schema.name,
newColumn: { newColumn: newColumnName,
name: newColumnName,
type: FieldType.BB_REFERENCE,
subtype,
},
}) })
notifications.success("Column migrated") notifications.success("Column migrated")
} catch (e) { } catch (e) {

View File

@ -1,3 +1,4 @@
import { helpers } from "@budibase/shared-core"
import { TypeIconMap } from "../../../constants" import { TypeIconMap } from "../../../constants"
export const getColor = (idx, opacity = 0.3) => { export const getColor = (idx, opacity = 0.3) => {
@ -11,8 +12,12 @@ export const getColumnIcon = column => {
if (column.schema.autocolumn) { if (column.schema.autocolumn) {
return "MagicWand" return "MagicWand"
} }
const { type, subtype } = column.schema
if (helpers.schema.isDeprecatedSingleUserColumn(column.schema)) {
return "User"
}
const { type, subtype } = column.schema
const result = const result =
typeof TypeIconMap[type] === "object" && subtype typeof TypeIconMap[type] === "object" && subtype
? TypeIconMap[type][subtype] ? TypeIconMap[type][subtype]

View File

@ -132,7 +132,7 @@ export const TypeIconMap = {
[FieldType.BIGINT]: "TagBold", [FieldType.BIGINT]: "TagBold",
[FieldType.AUTO]: "MagicWand", [FieldType.AUTO]: "MagicWand",
[FieldType.BB_REFERENCE]: { [FieldType.BB_REFERENCE]: {
[BBReferenceFieldSubType.USER]: "User", [BBReferenceFieldSubType.USER]: "UserGroup",
[BBReferenceFieldSubType.USERS]: "UserGroup", [BBReferenceFieldSubType.USERS]: "UserGroup",
}, },
[FieldType.BB_REFERENCE_SINGLE]: { [FieldType.BB_REFERENCE_SINGLE]: {

View File

@ -1,5 +1,6 @@
// need to handle table name + field or just field, depending on if relationships used // need to handle table name + field or just field, depending on if relationships used
import { FieldType, Row, Table } from "@budibase/types" import { FieldType, Row, Table } from "@budibase/types"
import { helpers } from "@budibase/shared-core"
import { generateRowIdField } from "../../../../integrations/utils" import { generateRowIdField } from "../../../../integrations/utils"
import { CONSTANT_INTERNAL_ROW_COLS } from "../../../../db/utils" import { CONSTANT_INTERNAL_ROW_COLS } from "../../../../db/utils"
@ -111,8 +112,10 @@ export function fixArrayTypes(row: Row, table: Table) {
try { try {
row[fieldName] = JSON.parse(row[fieldName]) row[fieldName] = JSON.parse(row[fieldName])
} catch (err) { } catch (err) {
// couldn't convert back to array, ignore if (!helpers.schema.isDeprecatedSingleUserColumn(schema)) {
delete row[fieldName] // couldn't convert back to array, ignore
delete row[fieldName]
}
} }
} }
} }

View File

@ -180,5 +180,5 @@ export async function migrate(ctx: UserCtx<MigrateRequest, MigrateResponse>) {
} }
ctx.status = 200 ctx.status = 200
ctx.body = { message: `Column ${oldColumn.name} migrated.` } ctx.body = { message: `Column ${oldColumn} migrated.` }
} }

View File

@ -493,16 +493,16 @@ describe.each([
) )
await config.api.table.migrate(table._id!, { await config.api.table.migrate(table._id!, {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "user column",
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
},
}) })
const migratedTable = await config.api.table.get(table._id!) const migratedTable = await config.api.table.get(table._id!)
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toEqual({
name: "user column",
type: FieldType.BB_REFERENCE_SINGLE,
subtype: BBReferenceFieldSubType.USER,
})
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const migratedRows = await config.api.row.fetch(table._id!) const migratedRows = await config.api.row.fetch(table._id!)
@ -515,7 +515,7 @@ describe.each([
expect(migratedRow["user column"]).toBeDefined() expect(migratedRow["user column"]).toBeDefined()
expect(migratedRow["user relationship"]).not.toBeDefined() expect(migratedRow["user relationship"]).not.toBeDefined()
expect(row["user relationship"][0]._id).toEqual( expect(row["user relationship"][0]._id).toEqual(
migratedRow["user column"][0]._id migratedRow["user column"]._id
) )
} }
}) })
@ -558,16 +558,19 @@ describe.each([
) )
await config.api.table.migrate(table._id!, { await config.api.table.migrate(table._id!, {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "user column",
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}) })
const migratedTable = await config.api.table.get(table._id!) const migratedTable = await config.api.table.get(table._id!)
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toEqual({
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
constraints: {
type: "array",
},
})
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const migratedRow = await config.api.row.get(table._id!, testRow._id!) const migratedRow = await config.api.row.get(table._id!, testRow._id!)
@ -610,16 +613,19 @@ describe.each([
}) })
await config.api.table.migrate(table._id!, { await config.api.table.migrate(table._id!, {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "user column",
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}) })
const migratedTable = await config.api.table.get(table._id!) const migratedTable = await config.api.table.get(table._id!)
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toEqual({
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
constraints: {
type: "array",
},
})
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const row1Migrated = await config.api.row.get(table._id!, row1._id!) const row1Migrated = await config.api.row.get(table._id!, row1._id!)
@ -665,16 +671,19 @@ describe.each([
}) })
await config.api.table.migrate(table._id!, { await config.api.table.migrate(table._id!, {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "user column",
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}) })
const migratedTable = await config.api.table.get(table._id!) const migratedTable = await config.api.table.get(table._id!)
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toEqual({
name: "user column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
constraints: {
type: "array",
},
})
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const row1Migrated = await config.api.row.get(table._id!, row1._id!) const row1Migrated = await config.api.row.get(table._id!, row1._id!)
@ -724,12 +733,8 @@ describe.each([
await config.api.table.migrate( await config.api.table.migrate(
table._id!, table._id!,
{ {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "",
name: "",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}, },
{ status: 400 } { status: 400 }
) )
@ -739,12 +744,8 @@ describe.each([
await config.api.table.migrate( await config.api.table.migrate(
table._id!, table._id!,
{ {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "_id",
name: "_id",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}, },
{ status: 400 } { status: 400 }
) )
@ -754,12 +755,8 @@ describe.each([
await config.api.table.migrate( await config.api.table.migrate(
table._id!, table._id!,
{ {
oldColumn: table.schema["user relationship"], oldColumn: "user relationship",
newColumn: { newColumn: "num",
name: "num",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}, },
{ status: 400 } { status: 400 }
) )
@ -769,16 +766,8 @@ describe.each([
await config.api.table.migrate( await config.api.table.migrate(
table._id!, table._id!,
{ {
oldColumn: { oldColumn: "not a column",
name: "not a column", newColumn: "new column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
newColumn: {
name: "new column",
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USERS,
},
}, },
{ status: 400 } { status: 400 }
) )

View File

@ -12,7 +12,6 @@ import SqlTableQueryBuilder from "./sqlTable"
import { import {
BBReferenceFieldMetadata, BBReferenceFieldMetadata,
FieldSchema, FieldSchema,
BBReferenceFieldSubType,
FieldType, FieldType,
JsonFieldMetadata, JsonFieldMetadata,
Operation, Operation,
@ -767,7 +766,8 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
return ( return (
field.type === FieldType.JSON || field.type === FieldType.JSON ||
(field.type === FieldType.BB_REFERENCE && (field.type === FieldType.BB_REFERENCE &&
field.subtype === BBReferenceFieldSubType.USERS) // Handling old single user type
field.constraints?.type === "array")
) )
} }

View File

@ -1,6 +1,5 @@
import { Knex, knex } from "knex" import { Knex, knex } from "knex"
import { import {
BBReferenceFieldSubType,
FieldType, FieldType,
NumberFieldMetadata, NumberFieldMetadata,
Operation, Operation,
@ -63,20 +62,6 @@ function generateSchema(
case FieldType.BB_REFERENCE_SINGLE: case FieldType.BB_REFERENCE_SINGLE:
schema.text(key) schema.text(key)
break break
case FieldType.BB_REFERENCE: {
const subtype = column.subtype
switch (subtype) {
case BBReferenceFieldSubType.USER:
schema.text(key)
break
case BBReferenceFieldSubType.USERS:
schema.json(key)
break
default:
throw utils.unreachable(subtype)
}
break
}
case FieldType.NUMBER: case FieldType.NUMBER:
// if meta is specified then this is a junction table entry // if meta is specified then this is a junction table entry
if (column.meta && column.meta.toKey && column.meta.toTable) { if (column.meta && column.meta.toKey && column.meta.toTable) {
@ -99,6 +84,7 @@ function generateSchema(
}) })
break break
case FieldType.ARRAY: case FieldType.ARRAY:
case FieldType.BB_REFERENCE:
schema.json(key) schema.json(key)
break break
case FieldType.LINK: case FieldType.LINK:

View File

@ -99,15 +99,7 @@ export function searchInputMapping(table: Table, options: RowSearchParams) {
break break
} }
case FieldType.BB_REFERENCE: { case FieldType.BB_REFERENCE: {
const subtype = column.subtype userColumnMapping(key, options)
switch (subtype) {
case BBReferenceFieldSubType.USER:
case BBReferenceFieldSubType.USERS:
userColumnMapping(key, options)
break
default:
utils.unreachable(subtype)
}
break break
} }
} }

View File

@ -4,7 +4,6 @@ import {
FieldSchema, FieldSchema,
BBReferenceFieldSubType, BBReferenceFieldSubType,
InternalTable, InternalTable,
isBBReferenceField,
isRelationshipField, isRelationshipField,
LinkDocument, LinkDocument,
LinkInfo, LinkInfo,
@ -12,6 +11,8 @@ import {
RelationshipType, RelationshipType,
Row, Row,
Table, Table,
FieldType,
BBReferenceSingleFieldMetadata,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { isExternalTableID } from "../../../integrations/utils" import { isExternalTableID } from "../../../integrations/utils"
@ -24,25 +25,58 @@ export interface MigrationResult {
export async function migrate( export async function migrate(
table: Table, table: Table,
oldColumn: FieldSchema, oldColumnName: string,
newColumn: FieldSchema newColumnName: string
): Promise<MigrationResult> { ): Promise<MigrationResult> {
if (newColumn.name in table.schema) { if (newColumnName in table.schema) {
throw new BadRequestError(`Column "${newColumn.name}" already exists`) throw new BadRequestError(`Column "${newColumnName}" already exists`)
} }
if (newColumn.name === "") { if (newColumnName === "") {
throw new BadRequestError(`Column name cannot be empty`) throw new BadRequestError(`Column name cannot be empty`)
} }
if (dbCore.isInternalColumnName(newColumn.name)) { if (dbCore.isInternalColumnName(newColumnName)) {
throw new BadRequestError(`Column name cannot be a reserved column name`) throw new BadRequestError(`Column name cannot be a reserved column name`)
} }
const oldColumn = table.schema[oldColumnName]
if (!oldColumn) {
throw new BadRequestError(
`Column "${oldColumnName}" does not exist on table "${table.name}"`
)
}
if (
oldColumn.type !== FieldType.LINK ||
oldColumn.tableId !== InternalTable.USER_METADATA
) {
throw new BadRequestError(
`Only user relationship migration columns is currently supported`
)
}
const type =
oldColumn.relationshipType === RelationshipType.ONE_TO_MANY
? FieldType.BB_REFERENCE_SINGLE
: FieldType.BB_REFERENCE
const newColumn: FieldSchema = {
name: newColumnName,
type,
subtype: BBReferenceFieldSubType.USER,
}
if (newColumn.type === FieldType.BB_REFERENCE) {
newColumn.constraints = {
type: "array",
}
}
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) const migrator = getColumnMigrator(table, oldColumn, newColumn)
try { try {
return await migrator.doMigration() return await migrator.doMigration()
} catch (e) { } catch (e) {
@ -75,11 +109,14 @@ function getColumnMigrator(
throw new BadRequestError(`Column "${oldColumn.name}" does not exist`) throw new BadRequestError(`Column "${oldColumn.name}" does not exist`)
} }
if (!isBBReferenceField(newColumn)) { if (
newColumn.type !== FieldType.BB_REFERENCE_SINGLE &&
newColumn.type !== FieldType.BB_REFERENCE
) {
throw new BadRequestError(`Column "${newColumn.name}" is not a user column`) throw new BadRequestError(`Column "${newColumn.name}" is not a user column`)
} }
if (newColumn.subtype !== "user" && newColumn.subtype !== "users") { if (newColumn.subtype !== BBReferenceFieldSubType.USER) {
throw new BadRequestError(`Column "${newColumn.name}" is not a user column`) throw new BadRequestError(`Column "${newColumn.name}" is not a user column`)
} }
@ -96,7 +133,7 @@ function getColumnMigrator(
} }
if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) { if (oldColumn.relationshipType === RelationshipType.ONE_TO_MANY) {
if (newColumn.subtype !== BBReferenceFieldSubType.USER) { if (newColumn.type !== FieldType.BB_REFERENCE_SINGLE) {
throw new BadRequestError( throw new BadRequestError(
`Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column` `Column "${oldColumn.name}" is a one-to-many column but "${newColumn.name}" is not a single user column`
) )
@ -107,22 +144,23 @@ function getColumnMigrator(
oldColumn.relationshipType === RelationshipType.MANY_TO_MANY || oldColumn.relationshipType === RelationshipType.MANY_TO_MANY ||
oldColumn.relationshipType === RelationshipType.MANY_TO_ONE oldColumn.relationshipType === RelationshipType.MANY_TO_ONE
) { ) {
if (newColumn.subtype !== BBReferenceFieldSubType.USERS) { if (newColumn.type !== FieldType.BB_REFERENCE) {
throw new BadRequestError( throw new BadRequestError(
`Column "${oldColumn.name}" is a ${oldColumn.relationshipType} column but "${newColumn.name}" is not a multi user column` `Column "${oldColumn.name}" is a ${oldColumn.relationshipType} column but "${newColumn.name}" is not a multi user column`
) )
} }
return new MultiUserColumnMigrator(table, oldColumn, newColumn) return new MultiUserColumnMigrator(table, oldColumn, newColumn)
} }
throw new BadRequestError(`Unknown migration type`) throw new BadRequestError(`Unknown migration type`)
} }
abstract class UserColumnMigrator implements ColumnMigrator { abstract class UserColumnMigrator<T> implements ColumnMigrator {
constructor( constructor(
protected table: Table, protected table: Table,
protected oldColumn: RelationshipFieldMetadata, protected oldColumn: RelationshipFieldMetadata,
protected newColumn: BBReferenceFieldMetadata protected newColumn: T
) {} ) {}
abstract updateRow(row: Row, linkInfo: LinkInfo): void abstract updateRow(row: Row, linkInfo: LinkInfo): void
@ -192,7 +230,7 @@ abstract class UserColumnMigrator implements ColumnMigrator {
} }
} }
class SingleUserColumnMigrator extends UserColumnMigrator { class SingleUserColumnMigrator extends UserColumnMigrator<BBReferenceSingleFieldMetadata> {
updateRow(row: Row, linkInfo: LinkInfo): void { updateRow(row: Row, linkInfo: LinkInfo): void {
row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID( row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID(
linkInfo.rowId linkInfo.rowId
@ -200,7 +238,7 @@ class SingleUserColumnMigrator extends UserColumnMigrator {
} }
} }
class MultiUserColumnMigrator extends UserColumnMigrator { class MultiUserColumnMigrator extends UserColumnMigrator<BBReferenceFieldMetadata> {
updateRow(row: Row, linkInfo: LinkInfo): void { updateRow(row: Row, linkInfo: LinkInfo): void {
if (!row[this.newColumn.name]) { if (!row[this.newColumn.name]) {
row[this.newColumn.name] = [] row[this.newColumn.name] = []

View File

@ -129,7 +129,7 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
return return
} }
const { type: columnType, subtype: columnSubtype } = schema[columnName] const { type: columnType } = schema[columnName]
if (columnType === FieldType.NUMBER) { if (columnType === FieldType.NUMBER) {
// If provided must be a valid number // If provided must be a valid number
parsedRow[columnName] = columnData ? Number(columnData) : columnData parsedRow[columnName] = columnData ? Number(columnData) : columnData
@ -140,21 +140,9 @@ export function parse(rows: Rows, schema: TableSchema): Rows {
: columnData : columnData
} else if (columnType === FieldType.BB_REFERENCE) { } else if (columnType === FieldType.BB_REFERENCE) {
const parsedValues = const parsedValues =
!!columnData && parseCsvExport<{ _id: string }[]>(columnData) (!!columnData && parseCsvExport<{ _id: string }[]>(columnData)) || []
if (!parsedValues) {
parsedRow[columnName] = undefined parsedRow[columnName] = parsedValues?.map(u => u._id)
} else {
switch (columnSubtype) {
case BBReferenceFieldSubType.USER:
parsedRow[columnName] = parsedValues[0]?._id
break
case BBReferenceFieldSubType.USERS:
parsedRow[columnName] = parsedValues.map(u => u._id)
break
default:
utils.unreachable(columnSubtype)
}
}
} else if (columnType === FieldType.BB_REFERENCE_SINGLE) { } else if (columnType === FieldType.BB_REFERENCE_SINGLE) {
const parsedValue = const parsedValue =
columnData && parseCsvExport<{ _id: string }>(columnData) columnData && parseCsvExport<{ _id: string }>(columnData)
@ -200,10 +188,6 @@ function isValidBBReference(
return false return false
} }
if (subtype === BBReferenceFieldSubType.USER && userArray.length > 1) {
return false
}
const constainsWrongId = userArray.find( const constainsWrongId = userArray.find(
user => !db.isGlobalUserID(user._id) user => !db.isGlobalUserID(user._id)
) )

View File

@ -51,7 +51,7 @@ export const getValidOperatorsForType = (
value: string value: string
label: string label: string
}[] = [] }[] = []
const { type, subtype, formulaType } = fieldType const { type, formulaType } = fieldType
if (type === FieldType.STRING) { if (type === FieldType.STRING) {
ops = stringOps ops = stringOps
} else if (type === FieldType.NUMBER || type === FieldType.BIGINT) { } else if (type === FieldType.NUMBER || type === FieldType.BIGINT) {
@ -68,16 +68,9 @@ export const getValidOperatorsForType = (
ops = numOps ops = numOps
} else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) { } else if (type === FieldType.FORMULA && formulaType === FormulaType.STATIC) {
ops = stringOps.concat([Op.MoreThan, Op.LessThan]) ops = stringOps.concat([Op.MoreThan, Op.LessThan])
} else if ( } else if (type === FieldType.BB_REFERENCE_SINGLE) {
(type === FieldType.BB_REFERENCE_SINGLE ||
type === FieldType.BB_REFERENCE) &&
subtype == BBReferenceFieldSubType.USER
) {
ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In] ops = [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty, Op.In]
} else if ( } else if (type === FieldType.BB_REFERENCE) {
type === FieldType.BB_REFERENCE &&
subtype == BBReferenceFieldSubType.USERS
) {
ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty] ops = [Op.Contains, Op.NotContains, Op.ContainsAny, Op.Empty, Op.NotEmpty]
} }

View File

@ -1,3 +1,4 @@
export * from "./helpers" export * from "./helpers"
export * from "./integrations" export * from "./integrations"
export * as cron from "./cron" export * as cron from "./cron"
export * as schema from "./schema"

View File

@ -0,0 +1,13 @@
import {
BBReferenceFieldSubType,
FieldSchema,
FieldType,
} from "@budibase/types"
export function isDeprecatedSingleUserColumn(schema: FieldSchema) {
const result =
schema.type === FieldType.BB_REFERENCE &&
schema.subtype === BBReferenceFieldSubType.USER &&
schema.constraints?.type !== "array"
return result
}

View File

@ -1,5 +1,4 @@
import { import {
FieldSchema,
Row, Row,
Table, Table,
TableRequest, TableRequest,
@ -31,8 +30,8 @@ export interface BulkImportResponse {
} }
export interface MigrateRequest { export interface MigrateRequest {
oldColumn: FieldSchema oldColumn: string
newColumn: FieldSchema newColumn: string
} }
export interface MigrateResponse { export interface MigrateResponse {

View File

@ -214,15 +214,3 @@ export function isManyToOne(
): field is ManyToOneRelationshipFieldMetadata { ): field is ManyToOneRelationshipFieldMetadata {
return field.relationshipType === RelationshipType.MANY_TO_ONE return field.relationshipType === RelationshipType.MANY_TO_ONE
} }
export function isBBReferenceField(
field: FieldSchema
): field is BBReferenceFieldMetadata {
return field.type === FieldType.BB_REFERENCE
}
export function isAttachmentField(
field: FieldSchema
): field is AttachmentFieldMetadata {
return field.type === FieldType.ATTACHMENTS
}