@nkhang1902/strapi-plugin-export-import-clsx 1.1.153 → 1.2.1
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.
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { useState, useRef } from "react";
|
|
2
2
|
import {
|
|
3
|
-
Modal,
|
|
4
|
-
Typography,
|
|
5
|
-
Box,
|
|
6
3
|
Button,
|
|
7
4
|
} from "@strapi/design-system";
|
|
8
5
|
import { Download, Upload } from "@strapi/icons";
|
|
@@ -11,9 +8,6 @@ import { useNotification } from "@strapi/strapi/admin";
|
|
|
11
8
|
const ExportImportButtons = (props) => {
|
|
12
9
|
const [isExporting, setIsExporting] = useState(false);
|
|
13
10
|
const [isImporting, setIsImporting] = useState(false);
|
|
14
|
-
const [showErrorModal, setShowErrorModal] = useState(false);
|
|
15
|
-
const [importErrors, setImportErrors] = useState([]);
|
|
16
|
-
|
|
17
11
|
const { toggleNotification } = useNotification();
|
|
18
12
|
|
|
19
13
|
// Get current content type from props or URL
|
|
@@ -105,26 +99,44 @@ const ExportImportButtons = (props) => {
|
|
|
105
99
|
title,
|
|
106
100
|
message,
|
|
107
101
|
type = 'warning',
|
|
108
|
-
duration = 15000,
|
|
102
|
+
duration = 15000,
|
|
103
|
+
maxLines = 8,
|
|
109
104
|
}) {
|
|
105
|
+
const lines = String(message).split('\n');
|
|
106
|
+
|
|
107
|
+
let displayText = message;
|
|
108
|
+
let extraCount = 0;
|
|
109
|
+
if (lines.length > maxLines) {
|
|
110
|
+
extraCount = lines.length - maxLines;
|
|
111
|
+
displayText =
|
|
112
|
+
lines.slice(0, maxLines).join('\n') +
|
|
113
|
+
`\n... (+${extraCount} more errors)`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
displayText = '\n' + displayText;
|
|
117
|
+
|
|
110
118
|
toggle({
|
|
111
119
|
type,
|
|
112
120
|
title,
|
|
113
121
|
message: (
|
|
114
|
-
<div
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
<div
|
|
123
|
+
style={{
|
|
124
|
+
maxHeight: 260,
|
|
125
|
+
overflowY: 'auto',
|
|
126
|
+
whiteSpace: 'pre-wrap',
|
|
127
|
+
fontSize: 13,
|
|
128
|
+
lineHeight: 1.5,
|
|
129
|
+
paddingTop: 4,
|
|
130
|
+
paddingRight: 4,
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
{displayText}
|
|
123
134
|
</div>
|
|
124
135
|
),
|
|
125
136
|
timeout: duration,
|
|
126
137
|
});
|
|
127
138
|
}
|
|
139
|
+
const currentContentType = getContentType();
|
|
128
140
|
|
|
129
141
|
const handleExport = async () => {
|
|
130
142
|
const contentType = getContentType();
|
|
@@ -210,7 +222,7 @@ const ExportImportButtons = (props) => {
|
|
|
210
222
|
|
|
211
223
|
const eventFilter = getEventFilter();
|
|
212
224
|
try {
|
|
213
|
-
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter
|
|
225
|
+
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter?.eventId || ""}`, {
|
|
214
226
|
method: "POST",
|
|
215
227
|
body: formData,
|
|
216
228
|
});
|
|
@@ -224,8 +236,6 @@ const ExportImportButtons = (props) => {
|
|
|
224
236
|
const total = created + updated;
|
|
225
237
|
|
|
226
238
|
if (errorList.length > 0) {
|
|
227
|
-
setImportErrors(errorList);
|
|
228
|
-
setShowErrorModal(true);
|
|
229
239
|
showLongNotification(toggleNotification, {
|
|
230
240
|
title: "Import errors",
|
|
231
241
|
message: errorList.join("\n"),
|
|
@@ -265,8 +275,10 @@ const ExportImportButtons = (props) => {
|
|
|
265
275
|
// Create ref for file input
|
|
266
276
|
const fileInputRef = useRef(null);
|
|
267
277
|
|
|
278
|
+
if (["api::event.event", "api::event-content.event-content", "api::meeting-participation-status.meeting-participation-status"].includes(currentContentType)) {
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
268
281
|
return (
|
|
269
|
-
<>
|
|
270
282
|
<div
|
|
271
283
|
style={{
|
|
272
284
|
display: "flex",
|
|
@@ -304,41 +316,6 @@ const ExportImportButtons = (props) => {
|
|
|
304
316
|
Import
|
|
305
317
|
</Button>
|
|
306
318
|
</div>
|
|
307
|
-
{showErrorModal && (
|
|
308
|
-
<Modal.Root
|
|
309
|
-
onClose={() => setShowErrorModal(false)}
|
|
310
|
-
labelledBy="import-errors-title"
|
|
311
|
-
>
|
|
312
|
-
<Modal.Content>
|
|
313
|
-
<Modal.Header>
|
|
314
|
-
<Typography id="import-errors-title" fontWeight="bold">
|
|
315
|
-
Import Errors ({importErrors.length})
|
|
316
|
-
</Typography>
|
|
317
|
-
</Modal.Header>
|
|
318
|
-
|
|
319
|
-
<Modal.Body>
|
|
320
|
-
<Box padding={4}>
|
|
321
|
-
{importErrors.map((err, index) => (
|
|
322
|
-
<Box key={index} paddingBottom={2}>
|
|
323
|
-
<Typography textColor="danger600">
|
|
324
|
-
{index + 1}. {err}
|
|
325
|
-
</Typography>
|
|
326
|
-
</Box>
|
|
327
|
-
))}
|
|
328
|
-
</Box>
|
|
329
|
-
</Modal.Body>
|
|
330
|
-
|
|
331
|
-
<Modal.Footer
|
|
332
|
-
endActions={
|
|
333
|
-
<Button onClick={() => setShowErrorModal(false)}>
|
|
334
|
-
Close
|
|
335
|
-
</Button>
|
|
336
|
-
}
|
|
337
|
-
/>
|
|
338
|
-
</Modal.Content>
|
|
339
|
-
</Modal.Root>
|
|
340
|
-
)}
|
|
341
|
-
</>
|
|
342
319
|
);
|
|
343
320
|
};
|
|
344
321
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkhang1902/strapi-plugin-export-import-clsx",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "A powerful Strapi plugin for exporting and importing data with Excel support and advanced filtering",
|
|
5
5
|
"main": "./strapi-server.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,7 +14,10 @@ module.exports = ({ strapi }) => ({
|
|
|
14
14
|
.plugin("export-import-clsx")
|
|
15
15
|
.service("import-service");
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
let { eventId } = ctx.request.query;
|
|
18
|
+
if (eventId == "") {
|
|
19
|
+
eventId = null;
|
|
20
|
+
}
|
|
18
21
|
|
|
19
22
|
const result = await importService.importData(file, targetContentType, eventId);
|
|
20
23
|
|
|
@@ -298,7 +298,7 @@ module.exports = ({ strapi }) => ({
|
|
|
298
298
|
|
|
299
299
|
if (componentFields.includes(key)) {
|
|
300
300
|
for (const subKey in value) {
|
|
301
|
-
if (["id", "createdAt", "updatedAt", "lastUpdate", "passcode"]) continue;
|
|
301
|
+
if (["id", "createdAt", "updatedAt", "lastUpdate", "passcode"].includes(subKey)) continue;
|
|
302
302
|
result[`${key}_${subKey}`] = value[subKey];
|
|
303
303
|
}
|
|
304
304
|
continue;
|
|
@@ -87,6 +87,9 @@ module.exports = ({ strapi }) => ({
|
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
workbook.SheetNames.forEach((sheetName) => {
|
|
90
|
+
if (targetContentType && mapSheetNameToContentType(sheetName) !== targetContentType) {
|
|
91
|
+
throw new Error(`Sheet name "${sheetName}" does not match target content type "${targetContentType.split(".")[1]}"`);
|
|
92
|
+
}
|
|
90
93
|
const worksheet = workbook.Sheets[sheetName];
|
|
91
94
|
const rows = XLSX.utils.sheet_to_json(worksheet);
|
|
92
95
|
|
|
@@ -147,7 +150,7 @@ module.exports = ({ strapi }) => ({
|
|
|
147
150
|
.map(([fieldName, attr]) => toCamel(fieldName));
|
|
148
151
|
},
|
|
149
152
|
|
|
150
|
-
async handleRelations(entry, contentType
|
|
153
|
+
async handleRelations(entry, contentType) {
|
|
151
154
|
const resolveRelationValue = async (field, value, target) => {
|
|
152
155
|
const targetAttr = strapi.contentTypes[target].attributes;
|
|
153
156
|
for (const field of SHORTCUT_FIELDS) {
|
|
@@ -156,7 +159,7 @@ module.exports = ({ strapi }) => ({
|
|
|
156
159
|
filters: { [field]: { $eq: value } },
|
|
157
160
|
});
|
|
158
161
|
if (existing) return { id: existing.id };
|
|
159
|
-
throw new Error(`
|
|
162
|
+
throw new Error(`Not found.`);
|
|
160
163
|
}
|
|
161
164
|
return null;
|
|
162
165
|
};
|
|
@@ -181,11 +184,7 @@ module.exports = ({ strapi }) => ({
|
|
|
181
184
|
if (relation === "manyToMany" || relation === "oneToMany") {
|
|
182
185
|
updatedEntry[field] = [];
|
|
183
186
|
} else {
|
|
184
|
-
|
|
185
|
-
updatedEntry[field] = { documentId: eventId };
|
|
186
|
-
} else {
|
|
187
|
-
updatedEntry[field] = null;
|
|
188
|
-
}
|
|
187
|
+
updatedEntry[field] = null;
|
|
189
188
|
}
|
|
190
189
|
continue;
|
|
191
190
|
}
|
|
@@ -215,7 +214,7 @@ module.exports = ({ strapi }) => ({
|
|
|
215
214
|
updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
|
|
216
215
|
} catch (err) {
|
|
217
216
|
throw new Error(
|
|
218
|
-
`
|
|
217
|
+
`Error resolving field "${field}" with value "${value}": ${err.message}`
|
|
219
218
|
);
|
|
220
219
|
}
|
|
221
220
|
}
|
|
@@ -394,76 +393,74 @@ module.exports = ({ strapi }) => ({
|
|
|
394
393
|
|
|
395
394
|
async importEntries(entries, contentType, eventId) {
|
|
396
395
|
const results = { created: 0, updated: 0, errors: [] };
|
|
397
|
-
|
|
398
|
-
await strapi.db.transaction(async ({ trx
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
396
|
+
|
|
397
|
+
await strapi.db.transaction(async ({ trx }) => {
|
|
398
|
+
let event = null;
|
|
399
|
+
|
|
400
|
+
if (eventId) {
|
|
401
|
+
event = await strapi.documents("api::event.event").findFirst(
|
|
402
|
+
{ filters: { documentId: eventId } },
|
|
403
|
+
{ transaction: trx }
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let shouldRollback = false;
|
|
408
|
+
|
|
404
409
|
for (let i = 0; i < entries.length; i++) {
|
|
405
410
|
const entry = entries[i];
|
|
406
411
|
let existing = null;
|
|
407
|
-
|
|
412
|
+
|
|
408
413
|
try {
|
|
409
414
|
let { id, ...data } = entry;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (id && id !== "null" && id !== "undefined") {
|
|
415
|
+
|
|
416
|
+
if (id) {
|
|
413
417
|
existing = await strapi.documents(contentType).findFirst(
|
|
414
|
-
{
|
|
415
|
-
filters: { id },
|
|
416
|
-
populate: "*",
|
|
417
|
-
},
|
|
418
|
+
{ filters: { id }, populate: "*" },
|
|
418
419
|
{ transaction: trx }
|
|
419
420
|
);
|
|
420
|
-
if (!existing) {
|
|
421
|
-
throw new Error(`Document with id ${id} not found`);
|
|
422
|
-
}
|
|
421
|
+
if (!existing) throw new Error(`Document with id ${id} not found`);
|
|
423
422
|
}
|
|
424
423
|
|
|
425
|
-
|
|
426
|
-
|
|
424
|
+
if (event && !data.event) data.event = event.name;
|
|
425
|
+
|
|
426
|
+
data = await this.handleRelations(data, contentType);
|
|
427
427
|
data = await this.handleComponents(data, existing, contentType);
|
|
428
|
+
|
|
428
429
|
const sanitizeErrors = [];
|
|
429
430
|
data = this.sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
|
|
430
|
-
|
|
431
431
|
if (sanitizeErrors.length) {
|
|
432
|
-
throw new Error(
|
|
432
|
+
throw new Error(sanitizeErrors.join('\n'));
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
// Update
|
|
436
435
|
if (existing) {
|
|
437
436
|
await strapi.documents(contentType).update(
|
|
438
|
-
{
|
|
439
|
-
documentId: existing.documentId,
|
|
440
|
-
data,
|
|
441
|
-
},
|
|
437
|
+
{ documentId: existing.documentId, data },
|
|
442
438
|
{ transaction: trx }
|
|
443
439
|
);
|
|
444
440
|
results.updated++;
|
|
445
441
|
} else {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
442
|
+
await strapi.documents(contentType).create(
|
|
443
|
+
{ data },
|
|
444
|
+
{ transaction: trx }
|
|
445
|
+
);
|
|
450
446
|
results.created++;
|
|
451
447
|
}
|
|
452
448
|
} catch (err) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
i + 2
|
|
456
|
-
}: ${err.message}`
|
|
457
|
-
);
|
|
458
|
-
results.created = 0;
|
|
459
|
-
results.updated = 0;
|
|
460
|
-
|
|
461
|
-
// IMPORTANT: force rollback
|
|
462
|
-
throw err;
|
|
449
|
+
shouldRollback = true;
|
|
450
|
+
results.errors.push(`Row ${i + 2}: ${err.message}`);
|
|
463
451
|
}
|
|
464
452
|
}
|
|
453
|
+
|
|
454
|
+
if (shouldRollback) {
|
|
455
|
+
await trx.rollback();
|
|
456
|
+
}
|
|
465
457
|
});
|
|
466
458
|
|
|
459
|
+
if (results.errors.length) {
|
|
460
|
+
results.created = 0;
|
|
461
|
+
results.updated = 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
467
464
|
return results;
|
|
468
465
|
},
|
|
469
466
|
});
|