@nkhang1902/strapi-plugin-export-import-clsx 1.1.15 → 1.1.17
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,11 +1,5 @@
|
|
|
1
1
|
import { useState, useRef } from "react";
|
|
2
2
|
import {
|
|
3
|
-
ModalLayout,
|
|
4
|
-
ModalHeader,
|
|
5
|
-
ModalBody,
|
|
6
|
-
ModalFooter,
|
|
7
|
-
Typography,
|
|
8
|
-
Box,
|
|
9
3
|
Button,
|
|
10
4
|
} from "@strapi/design-system";
|
|
11
5
|
import { Download, Upload } from "@strapi/icons";
|
|
@@ -14,9 +8,6 @@ import { useNotification } from "@strapi/strapi/admin";
|
|
|
14
8
|
const ExportImportButtons = (props) => {
|
|
15
9
|
const [isExporting, setIsExporting] = useState(false);
|
|
16
10
|
const [isImporting, setIsImporting] = useState(false);
|
|
17
|
-
const [showErrorModal, setShowErrorModal] = useState(false);
|
|
18
|
-
const [importErrors, setImportErrors] = useState([]);
|
|
19
|
-
|
|
20
11
|
const { toggleNotification } = useNotification();
|
|
21
12
|
|
|
22
13
|
// Get current content type from props or URL
|
|
@@ -104,6 +95,46 @@ const ExportImportButtons = (props) => {
|
|
|
104
95
|
return filters;
|
|
105
96
|
};
|
|
106
97
|
|
|
98
|
+
function showLongNotification(toggle, {
|
|
99
|
+
title,
|
|
100
|
+
message,
|
|
101
|
+
type = 'warning',
|
|
102
|
+
duration = 15000,
|
|
103
|
+
maxLines = 8,
|
|
104
|
+
}) {
|
|
105
|
+
const lines = String(message).split('\n');
|
|
106
|
+
|
|
107
|
+
let displayText = message;
|
|
108
|
+
let extraCount = 0;
|
|
109
|
+
|
|
110
|
+
if (lines.length > maxLines) {
|
|
111
|
+
extraCount = lines.length - maxLines;
|
|
112
|
+
displayText =
|
|
113
|
+
lines.slice(0, maxLines).join('\n') +
|
|
114
|
+
`\n... (+${extraCount} more errors)`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
toggle({
|
|
118
|
+
type,
|
|
119
|
+
title,
|
|
120
|
+
message: (
|
|
121
|
+
<div
|
|
122
|
+
style={{
|
|
123
|
+
maxHeight: 260,
|
|
124
|
+
overflowY: 'auto',
|
|
125
|
+
whiteSpace: 'pre-wrap',
|
|
126
|
+
fontSize: 13,
|
|
127
|
+
lineHeight: 1.5,
|
|
128
|
+
paddingRight: 4,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{displayText}
|
|
132
|
+
</div>
|
|
133
|
+
),
|
|
134
|
+
timeout: duration,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
107
138
|
const handleExport = async () => {
|
|
108
139
|
const contentType = getContentType();
|
|
109
140
|
if (!contentType) {
|
|
@@ -188,7 +219,7 @@ const ExportImportButtons = (props) => {
|
|
|
188
219
|
|
|
189
220
|
const eventFilter = getEventFilter();
|
|
190
221
|
try {
|
|
191
|
-
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter
|
|
222
|
+
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter?.eventId || ""}`, {
|
|
192
223
|
method: "POST",
|
|
193
224
|
body: formData,
|
|
194
225
|
});
|
|
@@ -199,10 +230,14 @@ const ExportImportButtons = (props) => {
|
|
|
199
230
|
const updated = result.summary?.updated || result.result.updated || 0;
|
|
200
231
|
|
|
201
232
|
const errorList = result.result?.errors || [];
|
|
233
|
+
const total = created + updated;
|
|
202
234
|
|
|
203
235
|
if (errorList.length > 0) {
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
showLongNotification(toggleNotification, {
|
|
237
|
+
title: "Import errors",
|
|
238
|
+
message: errorList.join("\n"),
|
|
239
|
+
type: "danger",
|
|
240
|
+
})
|
|
206
241
|
} else if (total > 0) {
|
|
207
242
|
toggleNotification({
|
|
208
243
|
type: "success",
|
|
@@ -238,7 +273,6 @@ const ExportImportButtons = (props) => {
|
|
|
238
273
|
const fileInputRef = useRef(null);
|
|
239
274
|
|
|
240
275
|
return (
|
|
241
|
-
<>
|
|
242
276
|
<div
|
|
243
277
|
style={{
|
|
244
278
|
display: "flex",
|
|
@@ -276,39 +310,6 @@ const ExportImportButtons = (props) => {
|
|
|
276
310
|
Import
|
|
277
311
|
</Button>
|
|
278
312
|
</div>
|
|
279
|
-
{showErrorModal && (
|
|
280
|
-
<ModalLayout
|
|
281
|
-
onClose={() => setShowErrorModal(false)}
|
|
282
|
-
labelledBy="import-errors-title"
|
|
283
|
-
>
|
|
284
|
-
<ModalHeader>
|
|
285
|
-
<Typography id="import-errors-title" fontWeight="bold">
|
|
286
|
-
Import Errors ({importErrors.length})
|
|
287
|
-
</Typography>
|
|
288
|
-
</ModalHeader>
|
|
289
|
-
|
|
290
|
-
<ModalBody>
|
|
291
|
-
<Box padding={4}>
|
|
292
|
-
{importErrors.map((err, index) => (
|
|
293
|
-
<Box key={index} paddingBottom={2}>
|
|
294
|
-
<Typography textColor="danger600">
|
|
295
|
-
{index + 1}. {err}
|
|
296
|
-
</Typography>
|
|
297
|
-
</Box>
|
|
298
|
-
))}
|
|
299
|
-
</Box>
|
|
300
|
-
</ModalBody>
|
|
301
|
-
|
|
302
|
-
<ModalFooter
|
|
303
|
-
endActions={
|
|
304
|
-
<Button onClick={() => setShowErrorModal(false)}>
|
|
305
|
-
Close
|
|
306
|
-
</Button>
|
|
307
|
-
}
|
|
308
|
-
/>
|
|
309
|
-
</ModalLayout>
|
|
310
|
-
)}
|
|
311
|
-
</>
|
|
312
313
|
);
|
|
313
314
|
};
|
|
314
315
|
|
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.1.17",
|
|
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": {
|
|
@@ -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 '${JSON.stringify(value)}': ${err.message}`
|
|
219
215
|
);
|
|
220
216
|
}
|
|
221
217
|
}
|
|
@@ -342,10 +338,15 @@ module.exports = ({ strapi }) => ({
|
|
|
342
338
|
const f = parseFloat(value);
|
|
343
339
|
return Number.isNaN(f) ? 0 : f;
|
|
344
340
|
}
|
|
345
|
-
case 'date':
|
|
341
|
+
case 'date': {
|
|
342
|
+
const d = new Date(value);
|
|
343
|
+
if (isNaN(d.getTime())) return null;
|
|
344
|
+
return d.toISOString().slice(0, 10); // YYYY-MM-DD ONLY
|
|
345
|
+
}
|
|
346
346
|
case 'datetime': {
|
|
347
347
|
const d = new Date(value);
|
|
348
|
-
|
|
348
|
+
if (isNaN(d.getTime())) return null;
|
|
349
|
+
return d.toISOString(); // full ISO is valid here
|
|
349
350
|
}
|
|
350
351
|
default:
|
|
351
352
|
return value;
|
|
@@ -389,76 +390,74 @@ module.exports = ({ strapi }) => ({
|
|
|
389
390
|
|
|
390
391
|
async importEntries(entries, contentType, eventId) {
|
|
391
392
|
const results = { created: 0, updated: 0, errors: [] };
|
|
392
|
-
|
|
393
|
-
await strapi.db.transaction(async ({ trx
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
+
|
|
399
406
|
for (let i = 0; i < entries.length; i++) {
|
|
400
407
|
const entry = entries[i];
|
|
401
408
|
let existing = null;
|
|
402
|
-
|
|
409
|
+
|
|
403
410
|
try {
|
|
404
411
|
let { id, ...data } = entry;
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (id && id !== "null" && id !== "undefined") {
|
|
412
|
+
|
|
413
|
+
if (id) {
|
|
408
414
|
existing = await strapi.documents(contentType).findFirst(
|
|
409
|
-
{
|
|
410
|
-
filters: { id },
|
|
411
|
-
populate: "*",
|
|
412
|
-
},
|
|
415
|
+
{ filters: { id }, populate: "*" },
|
|
413
416
|
{ transaction: trx }
|
|
414
417
|
);
|
|
415
|
-
if (!existing) {
|
|
416
|
-
throw new Error(`Document with id ${id} not found`);
|
|
417
|
-
}
|
|
418
|
+
if (!existing) throw new Error(`Document with id ${id} not found`);
|
|
418
419
|
}
|
|
419
420
|
|
|
420
|
-
|
|
421
|
-
|
|
421
|
+
if (event) data.event = event.name;
|
|
422
|
+
|
|
423
|
+
data = await this.handleRelations(data, contentType);
|
|
422
424
|
data = await this.handleComponents(data, existing, contentType);
|
|
425
|
+
|
|
423
426
|
const sanitizeErrors = [];
|
|
424
427
|
data = this.sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
|
|
425
|
-
|
|
426
428
|
if (sanitizeErrors.length) {
|
|
427
|
-
throw new Error(
|
|
429
|
+
throw new Error(sanitizeErrors.join('\n'));
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
// Update
|
|
431
432
|
if (existing) {
|
|
432
433
|
await strapi.documents(contentType).update(
|
|
433
|
-
{
|
|
434
|
-
documentId: existing.documentId,
|
|
435
|
-
data,
|
|
436
|
-
},
|
|
434
|
+
{ documentId: existing.documentId, data },
|
|
437
435
|
{ transaction: trx }
|
|
438
436
|
);
|
|
439
437
|
results.updated++;
|
|
440
438
|
} else {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
439
|
+
await strapi.documents(contentType).create(
|
|
440
|
+
{ data },
|
|
441
|
+
{ transaction: trx }
|
|
442
|
+
);
|
|
445
443
|
results.created++;
|
|
446
444
|
}
|
|
447
445
|
} catch (err) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
i + 2
|
|
451
|
-
}: ${err.message}`
|
|
452
|
-
);
|
|
453
|
-
results.created = 0;
|
|
454
|
-
results.updated = 0;
|
|
455
|
-
|
|
456
|
-
// IMPORTANT: force rollback
|
|
457
|
-
throw err;
|
|
446
|
+
shouldRollback = true;
|
|
447
|
+
results.errors.push(`Row ${i + 2}: ${err.message}`);
|
|
458
448
|
}
|
|
459
449
|
}
|
|
450
|
+
|
|
451
|
+
if (shouldRollback) {
|
|
452
|
+
await trx.rollback();
|
|
453
|
+
}
|
|
460
454
|
});
|
|
461
455
|
|
|
456
|
+
if (results.errors.length) {
|
|
457
|
+
results.created = 0;
|
|
458
|
+
results.updated = 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
462
461
|
return results;
|
|
463
462
|
},
|
|
464
463
|
});
|