mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
9.6 KiB
311 lines
9.6 KiB
import dayjs from 'dayjs' |
|
import type { ColumnType, LinkToAnotherRecordType, SelectOptionsType } from 'nocodb-sdk' |
|
import { UITypes } from 'nocodb-sdk' |
|
import type { AppInfo } from '~/composables/useGlobal' |
|
import { isBt, isMm, parseProp } from '#imports' |
|
|
|
export default function convertCellData( |
|
args: { to: UITypes; value: string; column: ColumnType; appInfo: AppInfo; files?: FileList | File[]; oldValue?: unknown }, |
|
isMysql = false, |
|
isMultiple = false, |
|
) { |
|
const { to, value, column, files = [], oldValue } = args |
|
|
|
const dateFormat = isMysql ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ' |
|
|
|
// return null if value is empty |
|
if (value === '' && to !== UITypes.Attachment) return null |
|
|
|
switch (to) { |
|
case UITypes.SingleLineText: |
|
case UITypes.LongText: |
|
// This is to remove the quotes added from LongText |
|
// TODO (refactor): remove this when we have a better way to handle this |
|
if (value.match(/^".*"$/)) { |
|
return value.slice(1, -1) |
|
} |
|
return value |
|
case UITypes.Number: { |
|
const parsedNumber = Number(value) |
|
if (isNaN(parsedNumber)) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new TypeError(`Cannot convert '${value}' to number`) |
|
} |
|
} |
|
return parsedNumber |
|
} |
|
case UITypes.Rating: { |
|
const parsedNumber = Number(value ?? 0) |
|
if (isNaN(parsedNumber)) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new TypeError(`Cannot convert '${value}' to rating`) |
|
} |
|
} |
|
return parsedNumber |
|
} |
|
case UITypes.Checkbox: |
|
if (typeof value === 'boolean') return value |
|
if (typeof value === 'string') { |
|
const strval = value.trim().toLowerCase() |
|
if (strval === 'true' || strval === '1') return true |
|
if (strval === 'false' || strval === '0' || strval === '') return false |
|
} |
|
return null |
|
case UITypes.Date: { |
|
const parsedDate = dayjs(value, parseProp(column?.meta)?.date_format ?? 'YYYY-MM-DD') |
|
if (!parsedDate.isValid()) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Not a valid date') |
|
} |
|
} |
|
return parsedDate.format('YYYY-MM-DD') |
|
} |
|
case UITypes.DateTime: { |
|
const parsedDateTime = dayjs( |
|
value, |
|
`${parseProp(column?.meta)?.date_format ?? 'YYYY-MM-DD'} ${parseProp(column?.meta)?.time_format ?? 'HH:mm'}`, |
|
) |
|
if (!parsedDateTime.isValid()) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Not a valid datetime value') |
|
} |
|
} |
|
return parsedDateTime.utc().format('YYYY-MM-DD HH:mm:ssZ') |
|
} |
|
case UITypes.Time: { |
|
let parsedTime = dayjs(value) |
|
|
|
if (!parsedTime.isValid()) { |
|
parsedTime = dayjs(value, 'HH:mm:ss') |
|
} |
|
if (!parsedTime.isValid()) { |
|
parsedTime = dayjs(`1999-01-01 ${value}`) |
|
} |
|
if (!parsedTime.isValid()) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Not a valid time value') |
|
} |
|
} |
|
return parsedTime.format(dateFormat) |
|
} |
|
case UITypes.Year: { |
|
if (/^\d+$/.test(value)) { |
|
return +value |
|
} |
|
|
|
const parsedDate = dayjs(value) |
|
|
|
if (parsedDate.isValid()) { |
|
return parsedDate.format('YYYY') |
|
} |
|
|
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Not a valid year value') |
|
} |
|
} |
|
case UITypes.Attachment: { |
|
const parsedOldValue = parseProp(oldValue) |
|
const oldAttachments = parsedOldValue && Array.isArray(parsedOldValue) ? parsedOldValue : [] |
|
|
|
if (!value && !files.length) { |
|
if (oldAttachments.length) return undefined |
|
return null |
|
} |
|
|
|
let parsedVal = [] |
|
if (value) { |
|
try { |
|
parsedVal = parseProp(value) |
|
parsedVal = Array.isArray(parsedVal) |
|
? parsedVal |
|
: typeof parsedVal === 'object' && Object.keys(parsedVal).length |
|
? [parsedVal] |
|
: [] |
|
} catch (e) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Invalid attachment data') |
|
} |
|
} |
|
|
|
if (parsedVal.some((v: any) => v && !(v.url || v.data || v.path))) { |
|
return null |
|
} |
|
} |
|
|
|
// TODO(refactor): duplicate logic in attachment/utils.ts |
|
const defaultAttachmentMeta = { |
|
...(args.appInfo.ee && { |
|
// Maximum Number of Attachments per cell |
|
maxNumberOfAttachments: Math.max(1, +args.appInfo.ncMaxAttachmentsAllowed || 50) || 50, |
|
// Maximum File Size per file |
|
maxAttachmentSize: Math.max(1, +args.appInfo.ncMaxAttachmentsAllowed || 20) || 20, |
|
supportedAttachmentMimeTypes: ['*'], |
|
}), |
|
} |
|
|
|
const attachmentMeta = { |
|
...defaultAttachmentMeta, |
|
...parseProp(column?.meta), |
|
} |
|
|
|
const attachments = [] |
|
|
|
for (const attachment of value ? parsedVal : files) { |
|
if (args.appInfo.ee) { |
|
// verify number of files |
|
if (parsedVal.length > attachmentMeta.maxNumberOfAttachments) { |
|
message.error( |
|
`You can only upload at most ${attachmentMeta.maxNumberOfAttachments} file${ |
|
attachmentMeta.maxNumberOfAttachments > 1 ? 's' : '' |
|
} to this cell.`, |
|
) |
|
return |
|
} |
|
|
|
// verify file size |
|
if (attachment.size > attachmentMeta.maxAttachmentSize * 1024 * 1024) { |
|
message.error(`The size of ${attachment.name} exceeds the maximum file size ${attachmentMeta.maxAttachmentSize} MB.`) |
|
continue |
|
} |
|
// verify mime type |
|
if ( |
|
!attachmentMeta.supportedAttachmentMimeTypes.includes('*') && |
|
!attachmentMeta.supportedAttachmentMimeTypes.includes(attachment.type) && |
|
!attachmentMeta.supportedAttachmentMimeTypes.includes(attachment.type.split('/')[0]) |
|
) { |
|
message.error(`${attachment.name} has the mime type ${attachment.type} which is not allowed in this column.`) |
|
continue |
|
} |
|
} |
|
// this prevent file with same names |
|
const isFileNameAlreadyExist = oldAttachments.some((el) => el.title === (attachment?.title || attachment?.name)) |
|
if (isFileNameAlreadyExist) { |
|
if (isMultiple) { |
|
message.error(`File with name ${attachment?.title || attachment?.name} already attached`) |
|
continue |
|
} else { |
|
throw new Error(`File with name ${attachment?.title || attachment?.name} already attached`) |
|
} |
|
} |
|
|
|
attachments.push(attachment) |
|
} |
|
|
|
if (oldAttachments.length && !attachments.length) { |
|
return undefined |
|
} else if (value && attachments.length) { |
|
return JSON.stringify([...oldAttachments, ...attachments]) |
|
} else if (files.length && attachments.length) { |
|
return attachments |
|
} else { |
|
return null |
|
} |
|
} |
|
case UITypes.SingleSelect: |
|
case UITypes.MultiSelect: { |
|
// return null if value is empty |
|
if (value === '') return null |
|
|
|
const availableOptions = ((column.colOptions as SelectOptionsType)?.options || []).map((o) => o.title) |
|
const vals = value.split(',') |
|
const validVals = vals.filter((v) => availableOptions.includes(v)) |
|
|
|
// return null if no valid values |
|
if (validVals.length === 0) return null |
|
|
|
return validVals.join(',') |
|
} |
|
case UITypes.User: |
|
case UITypes.CreatedBy: |
|
case UITypes.LastModifiedBy: { |
|
let parsedVal |
|
try { |
|
try { |
|
parsedVal = typeof value === 'string' ? JSON.parse(value) : value |
|
} catch { |
|
parsedVal = value |
|
} |
|
} catch (e) { |
|
if (isMultiple) { |
|
return null |
|
} else { |
|
throw new Error('Invalid user data') |
|
} |
|
} |
|
|
|
return parsedVal || value |
|
} |
|
case UITypes.LinkToAnotherRecord: { |
|
if (isMultiple) { |
|
return undefined |
|
} |
|
|
|
if (isBt(column)) { |
|
const parsedVal = typeof value === 'string' ? JSON.parse(value) : value |
|
|
|
if ( |
|
!(parsedVal && typeof parsedVal === 'object' && !Array.isArray(parsedVal) && Object.keys(parsedVal)) || |
|
parsedVal?.fk_related_model_id !== (column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id |
|
) { |
|
throw new Error(`Unsupported conversion for ${to}`) |
|
} |
|
|
|
return parsedVal |
|
} else { |
|
throw new Error(`Unsupported conversion for ${to}`) |
|
} |
|
} |
|
case UITypes.Links: { |
|
if (isMultiple) { |
|
return undefined |
|
} |
|
|
|
if (isMm(column)) { |
|
const parsedVal = typeof value === 'string' ? JSON.parse(value) : value |
|
|
|
if ( |
|
!( |
|
parsedVal && |
|
typeof parsedVal === 'object' && |
|
!Array.isArray(parsedVal) && |
|
// eslint-disable-next-line no-prototype-builtins |
|
['rowId', 'columnId', 'fk_related_model_id', 'value'].every((key) => (parsedVal as Object).hasOwnProperty(key)) |
|
) || |
|
parsedVal?.fk_related_model_id !== (column.colOptions as LinkToAnotherRecordType).fk_related_model_id |
|
) { |
|
throw new Error(`Unsupported conversion for ${to}`) |
|
} |
|
|
|
return parsedVal |
|
} else { |
|
throw new Error(`Unsupported conversion for ${to}`) |
|
} |
|
} |
|
case UITypes.Lookup: |
|
case UITypes.Rollup: |
|
case UITypes.Formula: |
|
case UITypes.QrCode: { |
|
if (isMultiple) { |
|
return undefined |
|
} else { |
|
throw new Error(`Unsupported conversion for ${to}`) |
|
} |
|
} |
|
default: |
|
return value |
|
} |
|
}
|
|
|