@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.eventId}`, {
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
- setImportErrors(errorList);
203
- setShowErrorModal(true);
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.1.152",
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
- const { eventId } = ctx.request.query;
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, eventId) {
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(`Data with ${field} ${value} not found. Cannot map to ${contentType}`);
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
- if (field === 'event') {
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
- `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
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, rollback, onRollback }) => {
399
- onRollback(() => {
400
- strapi.log.error("Transaction rolled back due to an error!");
401
- strapi.log.error(results.errors);
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
- // Check if document exists
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
- // Handle relations & components
426
- data = await this.handleRelations(data, contentType, eventId);
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(`Data validation failed:\n${sanitizeErrors.join('\n')}`);
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
- // Create
447
- await strapi
448
- .documents(contentType)
449
- .create({ data }, { transaction: trx });
439
+ await strapi.documents(contentType).create(
440
+ { data },
441
+ { transaction: trx }
442
+ );
450
443
  results.created++;
451
444
  }
452
445
  } catch (err) {
453
- results.errors.push(
454
- `Failed ${existing ? "updating" : "creating"} on row ${
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
  });