fix: 修复有互相引用的字段导入问题 (#1568)

Co-authored-by: sealday <zhanglin@daoyoucloud.com>
Reviewed-on: daoyoucloud/tachybase#1568
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: Toby <2287769986@qq.com>
Co-committed-by: Toby <2287769986@qq.com>
This commit is contained in:
Toby 2024-09-25 13:33:35 +08:00 committed by sealday
parent b7ee485cd1
commit f411efb1bc
2 changed files with 124 additions and 43 deletions

View File

@ -75,8 +75,10 @@ const ImportUpload = (props: any) => {
message.success(`${info.file.name} ` + t('file uploaded successfully'));
refresh();
await refreshCM();
window.location.reload();
} else if (status === 'error') {
// message.error(`${info.file.name} ` + t('file upload failed'));
message.error(`${info.file.name} ` + t('file upload failed'));
window.location.reload();
}
},
onDrop(e) {
@ -137,7 +139,6 @@ export const ImportCollectionMetaAction = React.forwardRef((props, ref) => {
const { t } = useTranslation();
const [dataTypes, setDataTypes] = useState<any[]>(['required']);
const [isModalOpen, setIsModalOpen] = useState(false);
// const [restoreData, setRestoreData] = useState<any>(null);
const [loading, setLoading] = useState(false);
const showModal = () => {
@ -150,12 +151,10 @@ export const ImportCollectionMetaAction = React.forwardRef((props, ref) => {
const handleCancel = () => {
setIsModalOpen(false);
// setRestoreData(null);
setDataTypes(['required']);
};
return (
<>
{/* <ButtonComponent onClick={showModal}>{t('Import')}</ButtonComponent> */}
<Modal
title={t('Import')}
width={800}

View File

@ -1,12 +1,9 @@
import fsPromises from 'fs/promises';
import os from 'os';
import { Readable } from 'stream';
import { Context } from '@tachybase/actions';
import { ResourceOptions } from '@tachybase/resourcer';
import { koaMulter as multer, uid } from '@tachybase/utils';
import lodash from 'lodash';
export const collectionImportExportMeta: ResourceOptions = {
name: 'collections',
middleware: async (ctx, next) => {
@ -38,20 +35,34 @@ export const collectionImportExportMeta: ResourceOptions = {
if (metaList.some((item) => item.name === name)) {
return;
}
const meta = await Collections.repository.findOne({
const metaRecord = await Collections.repository.findOne({
filterByTk: name,
context: ctx,
appends: ['category', 'fields'],
});
if (!meta) {
if (!metaRecord) {
return;
}
const meta = metaRecord.toJSON();
if (meta.inherits) {
for (const inherit of meta.inherits) {
await getAssociationCollection(inherit);
}
}
if (meta.view) {
const collection = ctx.db.getCollection(meta.viewName);
const viewDef = await ctx.db.queryInterface.viewDef(collection.getTableNameWithSchemaAsString());
const viewSql = [
`DROP VIEW IF EXISTS ${collection.getTableNameWithSchemaAsString()}`,
`CREATE VIEW ${collection.getTableNameWithSchemaAsString()} AS ${viewDef}`,
];
meta.viewSql = viewSql;
}
metaList.push(meta);
const fields = meta.dataValues.fields;
const fields = meta.fields;
for (const field of fields) {
ctx.logger.info('field.dataValues', field.dataValues);
if (field?.dataValues?.options?.target) {
await getAssociationCollection(field?.dataValues?.options?.target);
if (field?.options?.target) {
await getAssociationCollection(field?.options?.target);
}
}
}
@ -76,49 +87,102 @@ export const collectionImportExportMeta: ResourceOptions = {
}
let createCount = 0;
const reverseKeys = [];
// 检查field是否已经存在
const fieldRepo = ctx.db.getCollection('fields');
// old name => new name
const collectionNameMap = {};
const completedCollections = new Set();
const createList = [];
const existCollections = await ctx.db.getCollection('collections').repository.find({
filter: {
$or: {
name: { $in: metaList.map((item) => item.name) },
key: { $in: metaList.map((item) => item.key) },
},
},
context: ctx,
});
const allFieldKeys = [];
for (const meta of metaList) {
allFieldKeys.push(...meta.fields.map((item) => item.key));
}
const views = [];
const CollectionRepo = ctx.db.getCollection('collections');
async function importCollection(meta) {
const CollectionRepo = ctx.db.getCollection('collections');
// 检查表是否存在
let collection = await CollectionRepo.repository.findOne({
filterByTk: meta.name,
context: ctx,
});
if (collection) {
if (!meta) {
return;
throw new Error('collection name already exists, please change the name');
}
collection = await CollectionRepo.repository.findOne({
filter: { key: meta.key },
context: ctx,
});
if (collection) {
if (completedCollections.has(meta.name)) {
return;
}
if (meta.inherits && meta.inherits.length > 0) {
for (const inherit of meta.inherits) {
if (!completedCollections.has(inherit)) {
const inheritCollection = metaList.find((v) => v.name === inherit);
await importCollection(inheritCollection);
}
}
}
let collection = existCollections.find((v) => v.key === meta.key);
// key和name完全相同的两个collection, 出现重复
if (collection && collection.name === meta.name) {
completedCollections.add(collection.name);
return;
throw new Error('collection key already exists, please change the key');
}
// generate unique key
// meta.fields.forEach((field) => {
// field.key = uid();
// });
// 检查field是否已经存在
const fieldRepo = ctx.db.getCollection('fields');
const fieldKeys = meta.fields.map((field) => field.key);
const existFields = await fieldRepo.repository.findOne({
filter: { key: { $in: fieldKeys } },
context: ctx,
});
if (existFields) {
// key一样 name不一样则需要生成新的key
if (collection && collection.name !== meta.name) {
meta.key = uid();
return;
throw new Error('field key already exists, please change the key');
} else {
collection = existCollections.find((v) => v.name === meta.name);
if (collection) {
// 重复导入则放过
collection.name = `t_${uid()}`;
collectionNameMap[meta.name] = collection.name;
meta.name = collection.name;
if (meta.category && meta.category.length) {
meta.category.forEach((v) => {
v.collectionCategory.collectionName = collection.name;
});
}
}
}
const normalFieldKeys = [];
if (meta.fields) {
for (const field of meta.fields) {
if (!field.reverseKey) {
if (field.collectionName && collectionNameMap[field.collectionName]) {
field.collectionName = collectionNameMap[field.collectionName];
}
if (field.target && collectionNameMap[field.target]) {
field.target = collectionNameMap[field.target];
}
normalFieldKeys.push(field);
} else if (!reverseKeys.some((v) => v === field.key || v === field.reverseKey)) {
reverseKeys.push({ ...field, collectionName: meta.key });
}
}
meta.fields = normalFieldKeys;
}
// 视图
if (meta.view) {
views.push(meta);
return;
}
await CollectionRepo.repository.create({
values: meta,
context: ctx,
});
completedCollections.add(meta.name);
createCount++;
}
@ -126,8 +190,26 @@ export const collectionImportExportMeta: ResourceOptions = {
await importCollection(meta);
}
for (const meta of views) {
if (!meta.viewSql || !meta.viewSql.length) {
continue;
}
for (const sql of meta.viewSql) {
await ctx.db.sequelize.query(sql);
}
await CollectionRepo.repository.create({
values: meta,
context: ctx,
});
createCount++;
}
for (const reverseKey of reverseKeys) {
await fieldRepo.repository.create(reverseKey);
}
if (!createCount) {
throw new Error('no collection created, please check the key or name');
throw new Error('no collection created, please check the name');
}
ctx.body = { count: createCount };