diff --git a/packages/backend-core/src/docIds/ids.ts b/packages/backend-core/src/docIds/ids.ts index e0ac85b3df..4c9eb713c8 100644 --- a/packages/backend-core/src/docIds/ids.ts +++ b/packages/backend-core/src/docIds/ids.ts @@ -45,6 +45,11 @@ export function generateGlobalUserID(id?: any) { return `${DocumentType.USER}${SEPARATOR}${id || newid()}` } +const isGlobalUserIDRegex = new RegExp(`^${DocumentType.USER}${SEPARATOR}.+`) +export function isGlobalUserID(id: string) { + return isGlobalUserIDRegex.test(id) +} + /** * Generates a new user ID based on the passed in global ID. * @param {string} globalId The ID of the global user. diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 6f44135006..82b883c4d4 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -3,6 +3,7 @@ import { FIELDS } from "constants/backend" import { API } from "api" import { parseFile } from "./utils" + import { FieldType } from "@budibase/types" export let rows = [] export let schema = {} @@ -86,10 +87,35 @@ let validateHash = "" let errors = {} let selectedColumnTypes = {} + let rawRows = [] $: displayColumnOptions = Object.keys(schema || {}).filter(column => { return validation[column] }) + + $: { + rows = [] + + for (const row of rawRows) { + const castedRow = { ...row } + for (const fieldSchema of Object.values(schema || {})) { + if (fieldSchema.type === FieldType.BB_REFERENCE) { + try { + castedRow[fieldSchema.name] = JSON.parse( + row[fieldSchema.name].replace(/'/g, '"') + ) + } catch { + console.warn("Not a valid json", { + field: fieldSchema.name, + data: row[fieldSchema.name], + }) + } + } + } + rows.push(castedRow) + } + } + $: { // binding in consumer is causing double renders here const newValidateHash = JSON.stringify(rows) + JSON.stringify(schema) @@ -107,7 +133,7 @@ try { const response = await parseFile(e) - rows = response.rows + rawRows = response.rows schema = response.schema fileName = response.fileName selectedColumnTypes = Object.entries(response.schema).reduce( diff --git a/packages/server/src/utilities/schema.ts b/packages/server/src/utilities/schema.ts index 5ced82d8cf..e66bc97c8f 100644 --- a/packages/server/src/utilities/schema.ts +++ b/packages/server/src/utilities/schema.ts @@ -1,9 +1,12 @@ +import { FieldSubtype } from "@budibase/types" import { FieldTypes } from "../constants" -import { ValidColumnNameRegex } from "@budibase/shared-core" +import { ValidColumnNameRegex, utils } from "@budibase/shared-core" +import { db } from "@budibase/backend-core" interface SchemaColumn { readonly name: string readonly type: FieldTypes + readonly subtype: FieldSubtype readonly autocolumn?: boolean readonly constraints?: { presence: boolean @@ -77,8 +80,14 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { rows.forEach(row => { Object.entries(row).forEach(([columnName, columnData]) => { const columnType = schema[columnName]?.type + const columnSubtype = schema[columnName]?.subtype const isAutoColumn = schema[columnName]?.autocolumn + // If the column had an invalid value we don't want to override it + if (results.schemaValidation[columnName] === false) { + return + } + // If the columnType is not a string, then it's not present in the schema, and should be added to the invalid columns array if (typeof columnType !== "string") { results.invalidColumns.push(columnName) @@ -112,6 +121,11 @@ export function validate(rows: Rows, schema: Schema): ValidationResults { isNaN(new Date(columnData).getTime()) ) { results.schemaValidation[columnName] = false + } else if ( + columnType === FieldTypes.BB_REFERENCE && + !isValidBBReference(columnData, columnSubtype) + ) { + results.schemaValidation[columnName] = false } else { results.schemaValidation[columnName] = true } @@ -155,3 +169,37 @@ export function parse(rows: Rows, schema: Schema): Rows { return parsedRow }) } + +function isValidBBReference( + columnData: any, + columnSubtype: FieldSubtype +): boolean { + switch (columnSubtype) { + case FieldSubtype.USER: + case FieldSubtype.USERS: + if (!columnData) { + // Empty columns are valid by default + return true + } + + if (!Array.isArray(columnData)) { + // It must be an array field + return false + } + + if (columnSubtype === FieldSubtype.USER && columnData.length > 1) { + return false + } + + for (const d of columnData) { + if (!db.isGlobalUserID(d._id)) { + return false + } + } + + return true + + default: + throw utils.unreachable(columnSubtype) + } +}