@nocobase/plugin-action-import 2.1.0-alpha.3 → 2.1.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -661
- package/README.md +79 -10
- package/dist/client/buildImportFieldOptions.d.ts +9 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client/models/ImportActionModel.d.ts +1 -1
- package/dist/client/models/getOptionFields.d.ts +1 -1
- package/dist/client/useFields.d.ts +1 -1
- package/dist/externalVersion.js +9 -9
- package/dist/node_modules/exceljs/excel.js +15 -15
- package/dist/node_modules/exceljs/package.json +1 -1
- package/dist/node_modules/xlsx/dist/xlsx.core.min.js +14 -16
- package/dist/node_modules/xlsx/dist/xlsx.extendscript.js +3482 -6801
- package/dist/node_modules/xlsx/dist/xlsx.full.min.js +16 -18
- package/dist/node_modules/xlsx/dist/xlsx.mini.min.js +8 -9
- package/dist/node_modules/xlsx/dist/xlsx.zahl.js +1 -1
- package/dist/node_modules/xlsx/package.json +1 -1
- package/dist/node_modules/xlsx/types/index.d.ts +38 -171
- package/dist/node_modules/xlsx/xlsx.js +4 -4
- package/dist/node_modules/xlsx/xlsxworker.js +1 -1
- package/dist/server/actions/import-xlsx.js +2 -2
- package/dist/server/services/xlsx-importer.d.ts +12 -4
- package/dist/server/services/xlsx-importer.js +79 -41
- package/package.json +4 -4
- package/dist/node_modules/xlsx/dist/cpexcel.d.ts +0 -39
- package/dist/node_modules/xlsx/dist/zahl.d.ts +0 -4
|
@@ -8,7 +8,7 @@ postMessage({t:"ready"});
|
|
|
8
8
|
onmessage = function (evt) {
|
|
9
9
|
var v;
|
|
10
10
|
try {
|
|
11
|
-
v = XLSX.read(evt.data.d, {type: evt.data.b
|
|
11
|
+
v = XLSX.read(evt.data.d, {type: evt.data.b});
|
|
12
12
|
postMessage({t:"xlsx", d:JSON.stringify(v)});
|
|
13
13
|
} catch(e) { postMessage({t:"e",d:e.stack||e}); }
|
|
14
14
|
};
|
|
@@ -39,7 +39,7 @@ __export(import_xlsx_exports, {
|
|
|
39
39
|
importXlsx: () => importXlsx
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(import_xlsx_exports);
|
|
42
|
-
var
|
|
42
|
+
var XLSX = __toESM(require("xlsx"));
|
|
43
43
|
var import_async_mutex = require("async-mutex");
|
|
44
44
|
var import_xlsx_importer = require("../services/xlsx-importer");
|
|
45
45
|
const IMPORT_LIMIT_COUNT = 2e3;
|
|
@@ -55,7 +55,7 @@ async function importXlsxAction(ctx, next) {
|
|
|
55
55
|
if (ctx.request.body.explain) {
|
|
56
56
|
readLimit += 1;
|
|
57
57
|
}
|
|
58
|
-
const workbook =
|
|
58
|
+
const workbook = XLSX.read(ctx.file.buffer, {
|
|
59
59
|
type: "buffer",
|
|
60
60
|
sheetRows: readLimit,
|
|
61
61
|
cellDates: true
|
|
@@ -24,7 +24,15 @@ export type ImporterOptions = {
|
|
|
24
24
|
collectionManager: ICollectionManager;
|
|
25
25
|
collection: ICollection;
|
|
26
26
|
columns: Array<ImportColumn>;
|
|
27
|
-
workbook
|
|
27
|
+
/** Parsed SheetJS workbook. Used for the synchronous (small-file) import path. */
|
|
28
|
+
workbook?: any;
|
|
29
|
+
/**
|
|
30
|
+
* Absolute path to the XLSX file on disk. When set, the importer uses ExcelJS
|
|
31
|
+
* streaming reader so the file is never fully loaded into memory — rows are
|
|
32
|
+
* yielded one by one and processed in chunks. This is the preferred path for
|
|
33
|
+
* large async imports.
|
|
34
|
+
*/
|
|
35
|
+
filePath?: string;
|
|
28
36
|
chunkSize?: number;
|
|
29
37
|
explain?: string;
|
|
30
38
|
repository?: any;
|
|
@@ -47,10 +55,10 @@ export declare class XlsxImporter extends EventEmitter {
|
|
|
47
55
|
run(options?: RunOptions): Promise<any>;
|
|
48
56
|
resetSeq(options?: RunOptions): Promise<void>;
|
|
49
57
|
private getColumnsByPermission;
|
|
50
|
-
|
|
58
|
+
protected validateColumns(ctx?: Context): void;
|
|
51
59
|
performImport(data: string[][], options?: RunOptions): Promise<any>;
|
|
52
60
|
protected getModel(): typeof Model;
|
|
53
|
-
handleRowValuesWithColumns(row: any, rowValues: any, options: RunOptions, columns: ImportColumn[]): Promise<void>;
|
|
61
|
+
handleRowValuesWithColumns(row: any, rowValues: any, options: RunOptions, columns: ImportColumn[], rowIndex?: number): Promise<void>;
|
|
54
62
|
handleChuckRows(chunkRows: string[][], runOptions?: RunOptions, options?: {
|
|
55
63
|
handingRowIndex: number;
|
|
56
64
|
context: any;
|
|
@@ -64,7 +72,7 @@ export declare class XlsxImporter extends EventEmitter {
|
|
|
64
72
|
associateRecords(targets: Model[], options?: any): Promise<void>;
|
|
65
73
|
renderErrorMessage(error: any): any;
|
|
66
74
|
trimString(str: string): string;
|
|
67
|
-
|
|
75
|
+
protected getExpectedHeaders(ctx?: Context): string[];
|
|
68
76
|
getData(ctx?: Context): Promise<string[][]>;
|
|
69
77
|
private alignWithHeaders;
|
|
70
78
|
private findAndValidateHeaders;
|
|
@@ -84,10 +84,33 @@ class XlsxImporter extends import_events.default {
|
|
|
84
84
|
return data;
|
|
85
85
|
}
|
|
86
86
|
async run(options = {}) {
|
|
87
|
-
var _a, _b;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
var _a, _b, _c, _d;
|
|
88
|
+
const hasExternalTransaction = !!options.transaction;
|
|
89
|
+
const { db } = this.options.collectionManager;
|
|
90
|
+
if (hasExternalTransaction) {
|
|
91
|
+
try {
|
|
92
|
+
const data = await this.loggerService.measureExecutedTime(
|
|
93
|
+
async () => this.validate(options.context),
|
|
94
|
+
"Validation completed in {time}ms"
|
|
95
|
+
);
|
|
96
|
+
const imported = await this.loggerService.measureExecutedTime(
|
|
97
|
+
async () => this.performImport(data, options),
|
|
98
|
+
"Data import completed in {time}ms"
|
|
99
|
+
);
|
|
100
|
+
(_a = this.logger) == null ? void 0 : _a.info(`Import completed successfully, imported ${imported} records`);
|
|
101
|
+
if (db) {
|
|
102
|
+
await this.loggerService.measureExecutedTime(
|
|
103
|
+
async () => this.resetSeq(options),
|
|
104
|
+
"Sequence reset completed in {time}ms"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return imported;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
(_b = this.logger) == null ? void 0 : _b.error(`Import failed: ${this.renderErrorMessage(error)}`, {
|
|
110
|
+
originalError: error.stack || error.toString()
|
|
111
|
+
});
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
91
114
|
}
|
|
92
115
|
try {
|
|
93
116
|
const data = await this.loggerService.measureExecutedTime(
|
|
@@ -98,18 +121,16 @@ class XlsxImporter extends import_events.default {
|
|
|
98
121
|
async () => this.performImport(data, options),
|
|
99
122
|
"Data import completed in {time}ms"
|
|
100
123
|
);
|
|
101
|
-
(
|
|
102
|
-
if (
|
|
124
|
+
(_c = this.logger) == null ? void 0 : _c.info(`Import completed successfully, imported ${imported} records`);
|
|
125
|
+
if (db) {
|
|
103
126
|
await this.loggerService.measureExecutedTime(
|
|
104
|
-
async () => this.resetSeq(
|
|
127
|
+
async () => this.resetSeq({}),
|
|
105
128
|
"Sequence reset completed in {time}ms"
|
|
106
129
|
);
|
|
107
130
|
}
|
|
108
|
-
transaction && await transaction.commit();
|
|
109
131
|
return imported;
|
|
110
132
|
} catch (error) {
|
|
111
|
-
|
|
112
|
-
(_b = this.logger) == null ? void 0 : _b.error(`Import failed: ${this.renderErrorMessage(error)}`, {
|
|
133
|
+
(_d = this.logger) == null ? void 0 : _d.error(`Import failed: ${this.renderErrorMessage(error)}`, {
|
|
113
134
|
originalError: error.stack || error.toString()
|
|
114
135
|
});
|
|
115
136
|
throw error;
|
|
@@ -117,7 +138,7 @@ class XlsxImporter extends import_events.default {
|
|
|
117
138
|
}
|
|
118
139
|
async resetSeq(options) {
|
|
119
140
|
const { transaction } = options;
|
|
120
|
-
const db = this.options.collectionManager
|
|
141
|
+
const { db } = this.options.collectionManager;
|
|
121
142
|
const collection = this.options.collection;
|
|
122
143
|
const autoIncrementAttribute = collection.model.autoIncrementAttribute;
|
|
123
144
|
if (!autoIncrementAttribute) {
|
|
@@ -166,13 +187,13 @@ class XlsxImporter extends import_events.default {
|
|
|
166
187
|
this.emit("seqReset", { maxVal, seqName: autoIncrInfo.seqName });
|
|
167
188
|
}
|
|
168
189
|
getColumnsByPermission(ctx) {
|
|
190
|
+
var _a, _b, _c;
|
|
169
191
|
const columns = this.options.columns;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
192
|
+
const fields = (_c = (_b = (_a = ctx == null ? void 0 : ctx.permission) == null ? void 0 : _a.can) == null ? void 0 : _b.params) == null ? void 0 : _c.fields;
|
|
193
|
+
if (!Array.isArray(fields)) {
|
|
194
|
+
return columns;
|
|
195
|
+
}
|
|
196
|
+
return columns.filter((x) => import_lodash2.default.includes(fields, x.dataIndex[0]));
|
|
176
197
|
}
|
|
177
198
|
validateColumns(ctx) {
|
|
178
199
|
var _a, _b, _c;
|
|
@@ -233,8 +254,10 @@ class XlsxImporter extends import_events.default {
|
|
|
233
254
|
if (this.options.explain) {
|
|
234
255
|
handingRowIndex += 1;
|
|
235
256
|
}
|
|
236
|
-
|
|
257
|
+
let chunkRows;
|
|
258
|
+
while ((chunkRows = chunks.shift()) !== void 0) {
|
|
237
259
|
await this.handleChuckRows(chunkRows, options, { handingRowIndex, context: options == null ? void 0 : options.context });
|
|
260
|
+
handingRowIndex += chunkRows.length;
|
|
238
261
|
imported += chunkRows.length;
|
|
239
262
|
this.emit("progress", {
|
|
240
263
|
total,
|
|
@@ -246,8 +269,7 @@ class XlsxImporter extends import_events.default {
|
|
|
246
269
|
getModel() {
|
|
247
270
|
return this.repository instanceof import_database.RelationRepository ? this.repository.targetModel : this.repository.model;
|
|
248
271
|
}
|
|
249
|
-
async handleRowValuesWithColumns(row, rowValues, options, columns) {
|
|
250
|
-
var _a;
|
|
272
|
+
async handleRowValuesWithColumns(row, rowValues, options, columns, rowIndex = 1) {
|
|
251
273
|
for (let index = 0; index < columns.length; index++) {
|
|
252
274
|
const column = columns[index];
|
|
253
275
|
const field = this.options.collection.getField(column.dataIndex[0]);
|
|
@@ -279,7 +301,7 @@ class XlsxImporter extends import_events.default {
|
|
|
279
301
|
rowValues[dataKey] = str == null ? null : await interfaceInstance.toValue(this.trimString(str), ctx);
|
|
280
302
|
} catch (error) {
|
|
281
303
|
throw new import_errors.ImportValidationError("Failed to parse field {{field}} in row {{rowIndex}}: {{message}}", {
|
|
282
|
-
rowIndex
|
|
304
|
+
rowIndex,
|
|
283
305
|
field: dataKey,
|
|
284
306
|
message: error.message
|
|
285
307
|
});
|
|
@@ -295,18 +317,12 @@ class XlsxImporter extends import_events.default {
|
|
|
295
317
|
}
|
|
296
318
|
async handleChuckRows(chunkRows, runOptions, options) {
|
|
297
319
|
var _a, _b;
|
|
298
|
-
|
|
299
|
-
const
|
|
320
|
+
const { handingRowIndex: chunkStartRowIndex = 1 } = options;
|
|
321
|
+
const db = this.options.collectionManager.db;
|
|
322
|
+
const externalTransaction = runOptions == null ? void 0 : runOptions.transaction;
|
|
323
|
+
const transaction = externalTransaction || (db ? await db.sequelize.transaction() : null);
|
|
324
|
+
const chunkRunOptions = { ...runOptions, transaction };
|
|
300
325
|
const columns = this.getColumnsByPermission(options == null ? void 0 : options.context);
|
|
301
|
-
const rows = [];
|
|
302
|
-
for (const row of chunkRows) {
|
|
303
|
-
const rowValues = {};
|
|
304
|
-
await this.handleRowValuesWithColumns(row, rowValues, runOptions, columns);
|
|
305
|
-
rows.push({
|
|
306
|
-
...this.options.rowDefaultValues || {},
|
|
307
|
-
...rowValues
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
326
|
const translate = (message) => {
|
|
311
327
|
var _a2;
|
|
312
328
|
if ((_a2 = options.context) == null ? void 0 : _a2.t) {
|
|
@@ -315,7 +331,19 @@ class XlsxImporter extends import_events.default {
|
|
|
315
331
|
return message;
|
|
316
332
|
}
|
|
317
333
|
};
|
|
334
|
+
const rows = [];
|
|
335
|
+
let inChunkRowIndex = 0;
|
|
318
336
|
try {
|
|
337
|
+
for (let i = 0; i < chunkRows.length; i++) {
|
|
338
|
+
inChunkRowIndex = i;
|
|
339
|
+
const row = chunkRows[i];
|
|
340
|
+
const rowValues = {};
|
|
341
|
+
await this.handleRowValuesWithColumns(row, rowValues, chunkRunOptions, columns, chunkStartRowIndex + i);
|
|
342
|
+
rows.push({
|
|
343
|
+
...this.options.rowDefaultValues || {},
|
|
344
|
+
...rowValues
|
|
345
|
+
});
|
|
346
|
+
}
|
|
319
347
|
await this.loggerService.measureExecutedTime(
|
|
320
348
|
async () => this.performInsert({
|
|
321
349
|
values: rows,
|
|
@@ -325,23 +353,31 @@ class XlsxImporter extends import_events.default {
|
|
|
325
353
|
"Record insertion completed in {time}ms"
|
|
326
354
|
);
|
|
327
355
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
328
|
-
|
|
356
|
+
if (!externalTransaction) {
|
|
357
|
+
await (transaction == null ? void 0 : transaction.commit());
|
|
358
|
+
}
|
|
329
359
|
} catch (error) {
|
|
360
|
+
if (!externalTransaction) {
|
|
361
|
+
await (transaction == null ? void 0 : transaction.rollback());
|
|
362
|
+
}
|
|
363
|
+
if (error.name === "ImportValidationError") {
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
330
366
|
if (error.name === "SequelizeUniqueConstraintError") {
|
|
331
367
|
throw new Error(`${translate("Unique constraint error, fields:")} ${JSON.stringify(error.fields)}`);
|
|
332
368
|
}
|
|
369
|
+
const failedRowIndex = chunkStartRowIndex + inChunkRowIndex;
|
|
333
370
|
if ((_a = error.params) == null ? void 0 : _a.rowIndex) {
|
|
334
|
-
|
|
335
|
-
error.params.rowIndex = handingRowIndex;
|
|
371
|
+
error.params.rowIndex = failedRowIndex;
|
|
336
372
|
}
|
|
337
|
-
(_b = this.logger) == null ? void 0 : _b.error(`Import error at row ${
|
|
338
|
-
rowIndex:
|
|
339
|
-
rowData:
|
|
373
|
+
(_b = this.logger) == null ? void 0 : _b.error(`Import error at row ${failedRowIndex}: ${error.message}`, {
|
|
374
|
+
rowIndex: failedRowIndex,
|
|
375
|
+
rowData: chunkRows[inChunkRowIndex],
|
|
340
376
|
originalError: error.stack || error.toString()
|
|
341
377
|
});
|
|
342
|
-
throw new import_errors.ImportError(`Import failed at row ${
|
|
343
|
-
rowIndex:
|
|
344
|
-
rowData:
|
|
378
|
+
throw new import_errors.ImportError(`Import failed at row ${failedRowIndex}`, {
|
|
379
|
+
rowIndex: failedRowIndex,
|
|
380
|
+
rowData: chunkRows[inChunkRowIndex],
|
|
345
381
|
cause: error
|
|
346
382
|
});
|
|
347
383
|
}
|
|
@@ -400,6 +436,7 @@ class XlsxImporter extends import_events.default {
|
|
|
400
436
|
"debug"
|
|
401
437
|
);
|
|
402
438
|
}
|
|
439
|
+
instances[i] = null;
|
|
403
440
|
}
|
|
404
441
|
return instances;
|
|
405
442
|
}
|
|
@@ -448,6 +485,7 @@ class XlsxImporter extends import_events.default {
|
|
|
448
485
|
const workbook = this.options.workbook;
|
|
449
486
|
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
450
487
|
let data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null, blankrows: false });
|
|
488
|
+
this.options.workbook = null;
|
|
451
489
|
const expectedHeaders = this.getExpectedHeaders(ctx);
|
|
452
490
|
const { headerRowIndex, headers } = this.findAndValidateHeaders({ data, expectedHeaders });
|
|
453
491
|
if (headerRowIndex === -1) {
|
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.",
|
|
7
7
|
"description.ru-RU": "Импорт записей с помощью шаблонов Excel: можно настроить, какие поля импортировать, шаблоны будут генерироваться автоматически.",
|
|
8
8
|
"description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。",
|
|
9
|
-
"version": "2.1.0-alpha.
|
|
10
|
-
"license": "
|
|
9
|
+
"version": "2.1.0-alpha.30",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/action-import",
|
|
13
13
|
"homepage.ru-RU": "https://docs.nocobase.ru/handbook/action-import",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"react": "^18.2.0",
|
|
37
37
|
"react-dom": "^18.2.0",
|
|
38
38
|
"react-i18next": "^11.15.1",
|
|
39
|
-
"xlsx": "
|
|
39
|
+
"xlsx": "^0.18.5"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@nocobase/actions": "2.x",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@nocobase/test": "2.x",
|
|
47
47
|
"@nocobase/utils": "2.x"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "292ae0ad87f195ed201b274902d21ecd96f5ddd0",
|
|
50
50
|
"keywords": [
|
|
51
51
|
"Actions"
|
|
52
52
|
]
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/* codepage.js (C) 2013-present SheetJS -- http://sheetjs.com */
|
|
2
|
-
// TypeScript Version: 2.2
|
|
3
|
-
|
|
4
|
-
/** Codepage index type (integer or string representation) */
|
|
5
|
-
export type CP$Index = number | string;
|
|
6
|
-
|
|
7
|
-
/* Individual codepage converter */
|
|
8
|
-
export interface CP$Conv {
|
|
9
|
-
enc: {[n: string]: number; };
|
|
10
|
-
dec: {[n: number]: string; };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Encode input type (string, array of characters, Buffer) */
|
|
14
|
-
export type CP$String = string | string[] | Uint8Array;
|
|
15
|
-
|
|
16
|
-
/** Encode output / decode input type */
|
|
17
|
-
export type CP$Data = string | number[] | Uint8Array;
|
|
18
|
-
|
|
19
|
-
/** General utilities */
|
|
20
|
-
export interface CP$Utils {
|
|
21
|
-
decode(cp: CP$Index, data: CP$Data): string;
|
|
22
|
-
encode(cp: CP$Index, data: CP$String, opts?: any): CP$Data;
|
|
23
|
-
hascp(n: number): boolean;
|
|
24
|
-
magic: {[cp: string]: string};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/* note: TS cannot export top-level indexer, hence default workaround */
|
|
28
|
-
export interface CP$Module {
|
|
29
|
-
/** Version string */
|
|
30
|
-
version: string;
|
|
31
|
-
|
|
32
|
-
/** Utility Functions */
|
|
33
|
-
utils: CP$Utils;
|
|
34
|
-
|
|
35
|
-
/** Codepage Converters */
|
|
36
|
-
[cp: number]: CP$Conv;
|
|
37
|
-
}
|
|
38
|
-
export const cptable: CP$Module;
|
|
39
|
-
export default cptable;
|