@nkhang1902/strapi-plugin-export-import-clsx 1.1.153 → 1.2.0
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.
|
|
3
|
+
"version": "1.2.0",
|
|
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;
|
|
@@ -147,7 +147,7 @@ module.exports = ({ strapi }) => ({
|
|
|
147
147
|
.map(([fieldName, attr]) => toCamel(fieldName));
|
|
148
148
|
},
|
|
149
149
|
|
|
150
|
-
async handleRelations(entry, contentType
|
|
150
|
+
async handleRelations(entry, contentType) {
|
|
151
151
|
const resolveRelationValue = async (field, value, target) => {
|
|
152
152
|
const targetAttr = strapi.contentTypes[target].attributes;
|
|
153
153
|
for (const field of SHORTCUT_FIELDS) {
|
|
@@ -156,7 +156,7 @@ module.exports = ({ strapi }) => ({
|
|
|
156
156
|
filters: { [field]: { $eq: value } },
|
|
157
157
|
});
|
|
158
158
|
if (existing) return { id: existing.id };
|
|
159
|
-
throw new Error(`
|
|
159
|
+
throw new Error(`Not found.`);
|
|
160
160
|
}
|
|
161
161
|
return null;
|
|
162
162
|
};
|
|
@@ -181,11 +181,7 @@ module.exports = ({ strapi }) => ({
|
|
|
181
181
|
if (relation === "manyToMany" || relation === "oneToMany") {
|
|
182
182
|
updatedEntry[field] = [];
|
|
183
183
|
} else {
|
|
184
|
-
|
|
185
|
-
updatedEntry[field] = { documentId: eventId };
|
|
186
|
-
} else {
|
|
187
|
-
updatedEntry[field] = null;
|
|
188
|
-
}
|
|
184
|
+
updatedEntry[field] = null;
|
|
189
185
|
}
|
|
190
186
|
continue;
|
|
191
187
|
}
|
|
@@ -215,7 +211,7 @@ module.exports = ({ strapi }) => ({
|
|
|
215
211
|
updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
|
|
216
212
|
} catch (err) {
|
|
217
213
|
throw new Error(
|
|
218
|
-
`
|
|
214
|
+
`Error resolving field "${field}" with value "${value}": ${err.message}`
|
|
219
215
|
);
|
|
220
216
|
}
|
|
221
217
|
}
|
|
@@ -394,76 +390,74 @@ module.exports = ({ strapi }) => ({
|
|
|
394
390
|
|
|
395
391
|
async importEntries(entries, contentType, eventId) {
|
|
396
392
|
const results = { created: 0, updated: 0, errors: [] };
|
|
397
|
-
|
|
398
|
-
await strapi.db.transaction(async ({ trx
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
393
|
+
|
|
394
|
+
await strapi.db.transaction(async ({ trx }) => {
|
|
395
|
+
let event = null;
|
|
396
|
+
|
|
397
|
+
if (eventId) {
|
|
398
|
+
event = await strapi.documents("api::event.event").findFirst(
|
|
399
|
+
{ filters: { documentId: eventId } },
|
|
400
|
+
{ transaction: trx }
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let shouldRollback = false;
|
|
405
|
+
|
|
404
406
|
for (let i = 0; i < entries.length; i++) {
|
|
405
407
|
const entry = entries[i];
|
|
406
408
|
let existing = null;
|
|
407
|
-
|
|
409
|
+
|
|
408
410
|
try {
|
|
409
411
|
let { id, ...data } = entry;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (id && id !== "null" && id !== "undefined") {
|
|
412
|
+
|
|
413
|
+
if (id) {
|
|
413
414
|
existing = await strapi.documents(contentType).findFirst(
|
|
414
|
-
{
|
|
415
|
-
filters: { id },
|
|
416
|
-
populate: "*",
|
|
417
|
-
},
|
|
415
|
+
{ filters: { id }, populate: "*" },
|
|
418
416
|
{ transaction: trx }
|
|
419
417
|
);
|
|
420
|
-
if (!existing) {
|
|
421
|
-
throw new Error(`Document with id ${id} not found`);
|
|
422
|
-
}
|
|
418
|
+
if (!existing) throw new Error(`Document with id ${id} not found`);
|
|
423
419
|
}
|
|
424
420
|
|
|
425
|
-
|
|
426
|
-
|
|
421
|
+
if (event && !data.event) data.event = event.name;
|
|
422
|
+
|
|
423
|
+
data = await this.handleRelations(data, contentType);
|
|
427
424
|
data = await this.handleComponents(data, existing, contentType);
|
|
425
|
+
|
|
428
426
|
const sanitizeErrors = [];
|
|
429
427
|
data = this.sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
|
|
430
|
-
|
|
431
428
|
if (sanitizeErrors.length) {
|
|
432
|
-
throw new Error(
|
|
429
|
+
throw new Error(sanitizeErrors.join('\n'));
|
|
433
430
|
}
|
|
434
431
|
|
|
435
|
-
// Update
|
|
436
432
|
if (existing) {
|
|
437
433
|
await strapi.documents(contentType).update(
|
|
438
|
-
{
|
|
439
|
-
documentId: existing.documentId,
|
|
440
|
-
data,
|
|
441
|
-
},
|
|
434
|
+
{ documentId: existing.documentId, data },
|
|
442
435
|
{ transaction: trx }
|
|
443
436
|
);
|
|
444
437
|
results.updated++;
|
|
445
438
|
} else {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
439
|
+
await strapi.documents(contentType).create(
|
|
440
|
+
{ data },
|
|
441
|
+
{ transaction: trx }
|
|
442
|
+
);
|
|
450
443
|
results.created++;
|
|
451
444
|
}
|
|
452
445
|
} 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;
|
|
446
|
+
shouldRollback = true;
|
|
447
|
+
results.errors.push(`Row ${i + 2}: ${err.message}`);
|
|
463
448
|
}
|
|
464
449
|
}
|
|
450
|
+
|
|
451
|
+
if (shouldRollback) {
|
|
452
|
+
await trx.rollback();
|
|
453
|
+
}
|
|
465
454
|
});
|
|
466
455
|
|
|
456
|
+
if (results.errors.length) {
|
|
457
|
+
results.created = 0;
|
|
458
|
+
results.updated = 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
467
461
|
return results;
|
|
468
462
|
},
|
|
469
463
|
});
|