Browse Source

Nc fix: rename id column if exist in import table (#8984)

* fix(nc-gui): rename id column if exist in import table

* fix(nc-gui): record already exist issue

* fix(nc-gui): import issue

* fix(nc-gui): duplicate table name issue

* Update QuickImport.vue

* fix(nc-gui): import data mapping issue

* fix(nc-gui): prevent user from using id as column name while importing csv

* fix(nc-gui): pr review changes

---------

Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/8991/head
Ramesh Mane 5 months ago committed by GitHub
parent
commit
2bd3b71648
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/nc-gui/components/dlg/QuickImport.vue
  2. 57
      packages/nc-gui/components/template/Editor.vue
  3. 4
      packages/nc-gui/helpers/parsers/CSVTemplateAdapter.ts
  4. 2
      packages/nc-gui/helpers/parsers/ExcelTemplateAdapter.ts
  5. 14
      packages/nc-gui/utils/validation.ts

2
packages/nc-gui/components/dlg/QuickImport.vue

@ -258,7 +258,7 @@ function populateUniqueTableName(tn: string, draftTn: string[] = []) {
const s = t.table_name.split('___') const s = t.table_name.split('___')
let target = t.table_name let target = t.table_name
if (s.length > 1) target = s[1] if (s.length > 1) target = s[1]
return target === `${tn}` return target === `${tn}` || t.table_name === `${tn}`
}) })
) { ) {
tn = `${tn}_${c++}` tn = `${tn}_${c++}`

57
packages/nc-gui/components/template/Editor.vue

@ -145,7 +145,11 @@ const validators = computed(() =>
hasSelectColumn.value[tableIdx] = false hasSelectColumn.value[tableIdx] = false
table.columns?.forEach((column, columnIdx) => { table.columns?.forEach((column, columnIdx) => {
acc[`tables.${tableIdx}.columns.${columnIdx}.title`] = [fieldRequiredValidator(), fieldLengthValidator()] acc[`tables.${tableIdx}.columns.${columnIdx}.title`] = [
fieldRequiredValidator(),
fieldLengthValidator(),
reservedFieldNameValidator(),
]
acc[`tables.${tableIdx}.columns.${columnIdx}.uidt`] = [fieldRequiredValidator()] acc[`tables.${tableIdx}.columns.${columnIdx}.uidt`] = [fieldRequiredValidator()]
if (isSelect(column)) { if (isSelect(column)) {
hasSelectColumn.value[tableIdx] = true hasSelectColumn.value[tableIdx] = true
@ -237,6 +241,10 @@ function parseTemplate({ tables = [], ...rest }: Props['baseTemplate']) {
...rest, ...rest,
columns: [ columns: [
...columns.map((c: any, idx: number) => { ...columns.map((c: any, idx: number) => {
if (!importDataOnly && c.column_name?.toLowerCase() === 'id') {
const cn = populateUniqueColumnName('id', [], columns)
c.column_name = cn
}
c.key = idx c.key = idx
return c return c
}), }),
@ -282,6 +290,9 @@ function remapColNames(batchData: any[], columns: ColumnType[]) {
const dateFormatMap: Record<number, string> = {} const dateFormatMap: Record<number, string> = {}
return batchData.map((data) => return batchData.map((data) =>
(columns || []).reduce((aggObj, col: Record<string, any>) => { (columns || []).reduce((aggObj, col: Record<string, any>) => {
// we renaming existing id column and using our own auto increment id
if (col.uidt === UITypes.ID) return aggObj
// for excel & json, if the column name is changed in TemplateEditor, // for excel & json, if the column name is changed in TemplateEditor,
// then only col.column_name exists in data, else col.ref_column_name // then only col.column_name exists in data, else col.ref_column_name
// for csv, col.column_name always exists in data // for csv, col.column_name always exists in data
@ -570,6 +581,7 @@ async function importTemplate() {
await $api.dbTableColumn.primaryColumnSet(createdTable.columns[0].id as string) await $api.dbTableColumn.primaryColumnSet(createdTable.columns[0].id as string)
} }
} }
// bulk insert data // bulk insert data
if (importData) { if (importData) {
const offset = maxRowsToParse const offset = maxRowsToParse
@ -623,7 +635,7 @@ function mapDefaultColumns() {
srcDestMapping.value = {} srcDestMapping.value = {}
for (let i = 0; i < data.tables.length; i++) { for (let i = 0; i < data.tables.length; i++) {
for (const col of importColumns[i]) { for (const col of importColumns[i]) {
const o = { srcCn: col.column_name, destCn: '', enabled: true } const o = { srcCn: col.column_name, srcTitle: col.title, destCn: '', enabled: true }
if (columns.value) { if (columns.value) {
const tableColumn = columns.value.find((c) => c.column_name === col.column_name) const tableColumn = columns.value.find((c) => c.column_name === col.column_name)
if (tableColumn) { if (tableColumn) {
@ -712,6 +724,20 @@ const setErrorState = (errorsFields: any[]) => {
formError.value = errorMap formError.value = errorMap
} }
function populateUniqueColumnName(cn: string, draftCn: string[] = [], columns: ColumnType[]) {
let c = 2
let columnName = `${cn}${1}`
while (
draftCn.includes(columnName) ||
columns?.some((c) => {
return c.column_name === columnName || c.title === columnName
})
) {
columnName = `${cn}${c++}`
}
return columnName
}
watch(formRef, () => { watch(formRef, () => {
setTimeout(async () => { setTimeout(async () => {
try { try {
@ -801,8 +827,8 @@ watch(modelRef, async () => {
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'source_column'"> <template v-if="column.key === 'source_column'">
<NcTooltip class="truncate inline-block"> <NcTooltip class="truncate inline-block">
<template #title>{{ record.srcCn }}</template> <template #title>{{ record.srcTitle }}</template>
{{ record.srcCn }} {{ record.srcTitle }}
</NcTooltip> </NcTooltip>
</template> </template>
@ -917,17 +943,29 @@ watch(modelRef, async () => {
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'column_name'"> <template v-if="column.key === 'column_name'">
<a-form-item v-bind="validateInfos[`tables.${tableIdx}.columns.${record.key}.${column.key}`]"> <a-form-item
v-bind="validateInfos[`tables.${tableIdx}.columns.${record.key}.title`]"
class="nc-table-field-name"
>
<a-input <a-input
:ref="(el: HTMLInputElement) => (inputRefs[record.key] = el)" :ref="(el: HTMLInputElement) => (inputRefs[record.key] = el)"
v-model:value="record.title" v-model:value="record.title"
class="!rounded-md" class="!rounded-md"
/> >
<template #suffix>
<NcTooltip v-if="formError?.[`tables.${tableIdx}.columns.${record.key}.title`]" class="flex">
<template #title
>{{ formError?.[`tables.${tableIdx}.columns.${record.key}.title`].join('\n') }}
</template>
<GeneralIcon icon="info" class="h-4 w-4 text-red-500 flex-none" />
</NcTooltip>
</template>
</a-input>
</a-form-item> </a-form-item>
</template> </template>
<template v-else-if="column.key === 'uidt'"> <template v-else-if="column.key === 'uidt'">
<a-form-item v-bind="validateInfos[`tables.${tableIdx}.columns.${record.key}.${column.key}`]"> <a-form-item v-bind="validateInfos[`tables.${tableIdx}.columns.${record.key}.uidt`]">
<NcTooltip :disabled="importDataOnly"> <NcTooltip :disabled="importDataOnly">
<template #title> <template #title>
{{ $t('tooltip.useFieldEditMenuToConfigFieldType') }} {{ $t('tooltip.useFieldEditMenuToConfigFieldType') }}
@ -1014,4 +1052,9 @@ watch(modelRef, async () => {
@apply flex; @apply flex;
} }
} }
.nc-table-field-name {
:deep(.ant-form-item-explain) {
@apply hidden;
}
}
</style> </style>

4
packages/nc-gui/helpers/parsers/CSVTemplateAdapter.ts

@ -45,7 +45,7 @@ export default class CSVTemplateAdapter {
initTemplate(tableIdx: number, tn: string, columnNames: string[]) { initTemplate(tableIdx: number, tn: string, columnNames: string[]) {
const columnNameRowExist = +columnNames.every((v: any) => v === null || typeof v === 'string') const columnNameRowExist = +columnNames.every((v: any) => v === null || typeof v === 'string')
const columnNamePrefixRef: Record<string, any> = { id: 0 } const columnNamePrefixRef: Record<string, any> = { id: 0, Id: 0 }
const tableObj: Record<string, any> = { const tableObj: Record<string, any> = {
table_name: tn, table_name: tn,
@ -61,9 +61,11 @@ export default class CSVTemplateAdapter {
let cn: string = ((columnNameRowExist && columnName.toString().trim()) || `field_${columnIdx + 1}`) let cn: string = ((columnNameRowExist && columnName.toString().trim()) || `field_${columnIdx + 1}`)
.replace(/[` ~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/g, '_') .replace(/[` ~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/g, '_')
.trim() .trim()
while (cn in columnNamePrefixRef) { while (cn in columnNamePrefixRef) {
cn = `${cn}${++columnNamePrefixRef[cn]}` cn = `${cn}${++columnNamePrefixRef[cn]}`
} }
columnNamePrefixRef[cn] = 0 columnNamePrefixRef[cn] = 0
this.detectedColumnTypes[columnIdx] = {} this.detectedColumnTypes[columnIdx] = {}

2
packages/nc-gui/helpers/parsers/ExcelTemplateAdapter.ts

@ -65,7 +65,7 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
this.progress(`Parsing sheet ${sheetName}`) this.progress(`Parsing sheet ${sheetName}`)
await new Promise((resolve) => { await new Promise((resolve) => {
const columnNamePrefixRef: Record<string, any> = { id: 0 } const columnNamePrefixRef: Record<string, any> = { id: 0, Id: 0 }
let tn: string = (sheet || 'table').replace(/[` ~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/g, '_').trim() let tn: string = (sheet || 'table').replace(/[` ~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/g, '_').trim()
while (tn in tableNamePrefixRef) { while (tn in tableNamePrefixRef) {

14
packages/nc-gui/utils/validation.ts

@ -123,6 +123,20 @@ export const fieldLengthValidator = () => {
}, },
} }
} }
export const reservedFieldNameValidator = () => {
return {
validator: (rule: any, value: any) => {
const { t } = getI18n().global
return new Promise((resolve, reject) => {
if (value?.toLowerCase() === 'id') {
reject(new Error(t('msg.error.duplicateSystemColumnName')))
}
resolve(true)
})
},
}
}
export const importUrlValidator = { export const importUrlValidator = {
validator: (rule: any, value: any) => { validator: (rule: any, value: any) => {

Loading…
Cancel
Save