diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index bb38c5094f..530595fe40 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -386,7 +386,7 @@ > Hide column - {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS} + {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS && !column.schema.autocolumn} Migrate to user column diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index be6ac885df..bd92413851 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -265,7 +265,10 @@ export class ExternalRequest { } } - inputProcessing(row: Row | undefined, table: Table) { + inputProcessing( + row: T, + table: Table + ): { row: T; manyRelationships: ManyRelationship[] } { if (!row) { return { row, manyRelationships: [] } } @@ -346,7 +349,7 @@ export class ExternalRequest { // we return the relationships that may need to be created in the through table // we do this so that if the ID is generated by the DB it can be inserted // after the fact - return { row: newRow, manyRelationships } + return { row: newRow as T, manyRelationships } } /** @@ -598,6 +601,18 @@ export class ExternalRequest { // clean up row on ingress using schema const processed = this.inputProcessing(row, table) row = processed.row + let manyRelationships = processed.manyRelationships + + if (!row && rows) { + manyRelationships = [] + for (let i = 0; i < rows.length; i++) { + const processed = this.inputProcessing(rows[i], table) + rows[i] = processed.row + if (processed.manyRelationships.length) { + manyRelationships.push(...processed.manyRelationships) + } + } + } if ( operation === Operation.DELETE && (filters == null || Object.keys(filters).length === 0) diff --git a/packages/server/src/api/controllers/table/external.ts b/packages/server/src/api/controllers/table/external.ts index e526af4ecb..bd674d7d38 100644 --- a/packages/server/src/api/controllers/table/external.ts +++ b/packages/server/src/api/controllers/table/external.ts @@ -15,6 +15,7 @@ import { } from "@budibase/types" import sdk from "../../../sdk" import { builderSocket } from "../../../websockets" +import { inputProcessing } from "../../../utilities/rowProcessor" function getDatasourceId(table: Table) { if (!table) { @@ -80,7 +81,7 @@ export async function destroy(ctx: UserCtx) { export async function bulkImport( ctx: UserCtx ) { - const table = await sdk.tables.getTable(ctx.params.tableId) + let table = await sdk.tables.getTable(ctx.params.tableId) const { rows } = ctx.request.body const schema = table.schema @@ -88,7 +89,15 @@ export async function bulkImport( ctx.throw(400, "Provided data import information is invalid.") } - const parsedRows = parse(rows, schema) + const parsedRows = [] + for (const row of parse(rows, schema)) { + const processed = await inputProcessing(ctx.user?._id, table, row, { + noAutoRelationships: true, + }) + parsedRows.push(processed.row) + table = processed.table + } + await handleRequest(Operation.BULK_CREATE, table._id!, { rows: parsedRows, }) diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 2d194ab196..e347a8657d 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -79,9 +79,7 @@ export async function search( } const table = await sdk.tables.getTable(options.tableId) - options = searchInputMapping(table, options, { - isSql: !!table.sql || !!env.SQS_SEARCH_ENABLE, - }) + options = searchInputMapping(table, options) if (isExternalTable) { return external.search(options, table) diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts index 62f5af2b70..797383eff0 100644 --- a/packages/server/src/sdk/app/rows/search/utils.ts +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -11,7 +11,7 @@ import { RowSearchParams, } from "@budibase/types" import { db as dbCore, context } from "@budibase/backend-core" -import { helpers, utils } from "@budibase/shared-core" +import { utils } from "@budibase/shared-core" export async function paginatedSearch( query: SearchFilters, @@ -49,12 +49,7 @@ function findColumnInQueries( } } -function userColumnMapping( - column: string, - options: RowSearchParams, - isDeprecatedSingleUserColumn: boolean = false, - isSql: boolean = false -) { +function userColumnMapping(column: string, options: RowSearchParams) { findColumnInQueries(column, options, (filterValue: any): any => { const isArray = Array.isArray(filterValue), isString = typeof filterValue === "string" @@ -71,33 +66,23 @@ function userColumnMapping( } } - let wrapper = (s: string) => s - if (isDeprecatedSingleUserColumn && filterValue && isSql) { - // Decreated single users are stored as stringified arrays of a single value - wrapper = (s: string) => JSON.stringify([s]) - } - if (isArray) { return filterValue.map(el => { if (typeof el === "string") { - return wrapper(processString(el)) + return processString(el) } else { return el } }) } else { - return wrapper(processString(filterValue)) + return processString(filterValue) } }) } // maps through the search parameters to check if any of the inputs are invalid // based on the table schema, converts them to something that is valid. -export function searchInputMapping( - table: Table, - options: RowSearchParams, - datasourceOptions: { isSql?: boolean } = {} -) { +export function searchInputMapping(table: Table, options: RowSearchParams) { if (!table?.schema) { return options } @@ -116,12 +101,7 @@ export function searchInputMapping( break } case FieldType.BB_REFERENCE: { - userColumnMapping( - key, - options, - helpers.schema.isDeprecatedSingleUserColumn(column), - datasourceOptions.isSql - ) + userColumnMapping(key, options) break } } diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index d69fe73052..874113f6f1 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -14,7 +14,13 @@ export async function processInputBBReference( subtype: BBReferenceFieldSubType.USER ): Promise { if (value && Array.isArray(value)) { - throw "BB_REFERENCE_SINGLE cannot be an array" + if (value.length > 1) { + throw new InvalidBBRefError( + JSON.stringify(value), + BBReferenceFieldSubType.USER + ) + } + value = value[0] } let id = typeof value === "string" ? value : value?._id diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index e7bc725285..59237be5f3 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -18,6 +18,7 @@ import { processOutputBBReferences, } from "./bbReferenceProcessor" import { isExternalTableID } from "../../integrations/utils" +import { helpers } from "@budibase/shared-core" export * from "./utils" export * from "./attachments" @@ -162,10 +163,14 @@ export async function inputProcessing( if (attachment?.url) { delete clonedRow[key].url } - } else if (field.type === FieldType.BB_REFERENCE && value) { - clonedRow[key] = await processInputBBReferences(value, field.subtype) - } else if (field.type === FieldType.BB_REFERENCE_SINGLE && value) { + } else if ( + value && + (field.type === FieldType.BB_REFERENCE_SINGLE || + helpers.schema.isDeprecatedSingleUserColumn(field)) + ) { clonedRow[key] = await processInputBBReference(value, field.subtype) + } else if (value && field.type === FieldType.BB_REFERENCE) { + clonedRow[key] = await processInputBBReferences(value, field.subtype) } } diff --git a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts index b1928b696b..81094583e2 100644 --- a/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/inputProcessing.spec.ts @@ -102,7 +102,7 @@ describe("rowProcessor - inputProcessing", () => { name: "user", constraints: { presence: true, - type: "string", + type: "array", }, }, }, @@ -154,7 +154,7 @@ describe("rowProcessor - inputProcessing", () => { name: "user", constraints: { presence: false, - type: "string", + type: "array", }, }, }, @@ -196,7 +196,7 @@ describe("rowProcessor - inputProcessing", () => { name: "user", constraints: { presence: false, - type: "string", + type: "array", }, }, }, diff --git a/packages/shared-core/src/helpers/schema.ts b/packages/shared-core/src/helpers/schema.ts index 985e068be0..ad4c237247 100644 --- a/packages/shared-core/src/helpers/schema.ts +++ b/packages/shared-core/src/helpers/schema.ts @@ -6,7 +6,10 @@ import { export function isDeprecatedSingleUserColumn( schema: Pick -) { +): schema is { + type: FieldType.BB_REFERENCE + subtype: BBReferenceFieldSubType.USER +} { const result = schema.type === FieldType.BB_REFERENCE && schema.subtype === BBReferenceFieldSubType.USER &&