@nkhang1902/strapi-plugin-export-import-clsx 1.1.152 → 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
|
|
@@ -101,6 +95,49 @@ const ExportImportButtons = (props) => {
|
|
|
101
95
|
return filters;
|
|
102
96
|
};
|
|
103
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
|
+
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
|
+
|
|
118
|
+
toggle({
|
|
119
|
+
type,
|
|
120
|
+
title,
|
|
121
|
+
message: (
|
|
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}
|
|
134
|
+
</div>
|
|
135
|
+
),
|
|
136
|
+
timeout: duration,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const currentContentType = getContentType();
|
|
140
|
+
|
|
104
141
|
const handleExport = async () => {
|
|
105
142
|
const contentType = getContentType();
|
|
106
143
|
if (!contentType) {
|
|
@@ -185,7 +222,7 @@ const ExportImportButtons = (props) => {
|
|
|
185
222
|
|
|
186
223
|
const eventFilter = getEventFilter();
|
|
187
224
|
try {
|
|
188
|
-
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter
|
|
225
|
+
const response = await fetch(`/export-import-clsx/import?eventId=${eventFilter?.eventId || ""}`, {
|
|
189
226
|
method: "POST",
|
|
190
227
|
body: formData,
|
|
191
228
|
});
|
|
@@ -199,8 +236,11 @@ const ExportImportButtons = (props) => {
|
|
|
199
236
|
const total = created + updated;
|
|
200
237
|
|
|
201
238
|
if (errorList.length > 0) {
|
|
202
|
-
|
|
203
|
-
|
|
239
|
+
showLongNotification(toggleNotification, {
|
|
240
|
+
title: "Import errors",
|
|
241
|
+
message: errorList.join("\n"),
|
|
242
|
+
type: "danger",
|
|
243
|
+
})
|
|
204
244
|
} else if (total > 0) {
|
|
205
245
|
toggleNotification({
|
|
206
246
|
type: "success",
|
|
@@ -235,8 +275,10 @@ const ExportImportButtons = (props) => {
|
|
|
235
275
|
// Create ref for file input
|
|
236
276
|
const fileInputRef = useRef(null);
|
|
237
277
|
|
|
278
|
+
if (["api::event.event", "api::event-content.event-content", "api::meeting-participation-status.meeting-participation-status"].includes(currentContentType)) {
|
|
279
|
+
return null
|
|
280
|
+
}
|
|
238
281
|
return (
|
|
239
|
-
<>
|
|
240
282
|
<div
|
|
241
283
|
style={{
|
|
242
284
|
display: "flex",
|
|
@@ -274,41 +316,6 @@ const ExportImportButtons = (props) => {
|
|
|
274
316
|
Import
|
|
275
317
|
</Button>
|
|
276
318
|
</div>
|
|
277
|
-
{showErrorModal && (
|
|
278
|
-
<Modal.Root
|
|
279
|
-
onClose={() => setShowErrorModal(false)}
|
|
280
|
-
labelledBy="import-errors-title"
|
|
281
|
-
>
|
|
282
|
-
<Modal.Content>
|
|
283
|
-
<Modal.Header>
|
|
284
|
-
<Typography id="import-errors-title" fontWeight="bold">
|
|
285
|
-
Import Errors ({importErrors.length})
|
|
286
|
-
</Typography>
|
|
287
|
-
</Modal.Header>
|
|
288
|
-
|
|
289
|
-
<Modal.Body>
|
|
290
|
-
<Box padding={4}>
|
|
291
|
-
{importErrors.map((err, index) => (
|
|
292
|
-
<Box key={index} paddingBottom={2}>
|
|
293
|
-
<Typography textColor="danger600">
|
|
294
|
-
{index + 1}. {err}
|
|
295
|
-
</Typography>
|
|
296
|
-
</Box>
|
|
297
|
-
))}
|
|
298
|
-
</Box>
|
|
299
|
-
</Modal.Body>
|
|
300
|
-
|
|
301
|
-
<Modal.Footer
|
|
302
|
-
endActions={
|
|
303
|
-
<Button onClick={() => setShowErrorModal(false)}>
|
|
304
|
-
Close
|
|
305
|
-
</Button>
|
|
306
|
-
}
|
|
307
|
-
/>
|
|
308
|
-
</Modal.Content>
|
|
309
|
-
</Modal.Root>
|
|
310
|
-
)}
|
|
311
|
-
</>
|
|
312
319
|
);
|
|
313
320
|
};
|
|
314
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
|
});
|