@firecms/data_import_export 3.0.0-canary.7 → 3.0.0-canary.70

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.
Files changed (40) hide show
  1. package/LICENSE +113 -21
  2. package/README.md +1 -1
  3. package/dist/components/DataNewPropertiesMapping.d.ts +3 -5
  4. package/dist/components/ImportFileUpload.d.ts +1 -1
  5. package/dist/export_import/BasicExportAction.d.ts +7 -0
  6. package/dist/export_import/ExportCollectionAction.d.ts +2 -1
  7. package/dist/export_import/ImportCollectionAction.d.ts +3 -1
  8. package/dist/export_import/export.d.ts +15 -4
  9. package/dist/export_import/index.d.ts +4 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +954 -554
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +2 -2
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/types/column_mapping.d.ts +5 -7
  16. package/dist/useImportExportPlugin.d.ts +2 -1
  17. package/dist/utils/data.d.ts +3 -10
  18. package/dist/utils/file_headers.d.ts +1 -0
  19. package/dist/utils/file_to_json.d.ts +6 -1
  20. package/dist/utils/get_properties_mapping.d.ts +0 -3
  21. package/dist/utils/index.d.ts +0 -1
  22. package/package.json +25 -23
  23. package/src/components/DataNewPropertiesMapping.tsx +155 -41
  24. package/src/components/ImportFileUpload.tsx +12 -4
  25. package/src/components/ImportNewPropertyFieldPreview.tsx +7 -2
  26. package/src/components/ImportSaveInProgress.tsx +24 -2
  27. package/src/export_import/BasicExportAction.tsx +147 -0
  28. package/src/export_import/ExportCollectionAction.tsx +50 -13
  29. package/src/export_import/ImportCollectionAction.tsx +54 -33
  30. package/src/export_import/export.ts +64 -30
  31. package/src/export_import/index.ts +4 -0
  32. package/src/hooks/useImportConfig.tsx +6 -0
  33. package/src/index.ts +1 -0
  34. package/src/types/column_mapping.ts +6 -6
  35. package/src/useImportExportPlugin.tsx +4 -3
  36. package/src/utils/data.ts +50 -127
  37. package/src/utils/file_headers.ts +90 -0
  38. package/src/utils/file_to_json.ts +33 -15
  39. package/src/utils/get_properties_mapping.ts +63 -59
  40. package/src/utils/index.ts +0 -1
@@ -5,6 +5,7 @@ import {
5
5
  Entity,
6
6
  EntityCollection,
7
7
  ExportConfig,
8
+ getDefaultValuesFor,
8
9
  resolveCollection,
9
10
  ResolvedEntityCollection,
10
11
  useCustomizationController,
@@ -18,7 +19,7 @@ import {
18
19
  BooleanSwitchWithLabel,
19
20
  Button,
20
21
  CircularProgress,
21
- cn,
22
+ cls,
22
23
  Dialog,
23
24
  DialogActions,
24
25
  DialogContent,
@@ -26,9 +27,9 @@ import {
26
27
  GetAppIcon,
27
28
  IconButton,
28
29
  Tooltip,
29
- Typography,
30
+ Typography
30
31
  } from "@firecms/ui";
31
- import { downloadExport } from "./export";
32
+ import { downloadEntitiesExport } from "./export";
32
33
 
33
34
  const DOCS_LIMIT = 500;
34
35
 
@@ -36,11 +37,13 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
36
37
  collection: inputCollection,
37
38
  path: inputPath,
38
39
  collectionEntitiesCount,
40
+ onAnalyticsEvent,
39
41
  exportAllowed,
40
42
  notAllowedView
41
43
  }: CollectionActionsProps<M, UserType, EntityCollection<M, any>> & {
42
44
  exportAllowed?: (props: { collectionEntitiesCount: number, path: string, collection: EntityCollection }) => boolean;
43
45
  notAllowedView?: React.ReactNode;
46
+ onAnalyticsEvent?: (event: string, params?: any) => void;
44
47
  }) {
45
48
 
46
49
  const customizationController = useCustomizationController();
@@ -48,6 +51,8 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
48
51
  const exportConfig = typeof inputCollection.exportable === "object" ? inputCollection.exportable : undefined;
49
52
 
50
53
  const dateRef = React.useRef<Date>(new Date());
54
+
55
+ const [includeUndefinedValues, setIncludeUndefinedValues] = React.useState<boolean>(false);
51
56
  const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);
52
57
  const [exportType, setExportType] = React.useState<"csv" | "json">("csv");
53
58
  const [dateExportType, setDateExportType] = React.useState<"timestamp" | "string">("string");
@@ -122,6 +127,9 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
122
127
  const doDownload = useCallback(async (collection: ResolvedEntityCollection<M>,
123
128
  exportConfig: ExportConfig<any> | undefined) => {
124
129
 
130
+ onAnalyticsEvent?.("export_collection", {
131
+ collection: collection.path
132
+ });
125
133
  setDataLoading(true);
126
134
  dataSource.fetchCollection<M>({
127
135
  path,
@@ -134,7 +142,30 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
134
142
  ...exportConfig?.additionalFields?.map(column => column.key) ?? [],
135
143
  ...collection.additionalFields?.map(field => field.key) ?? []
136
144
  ];
137
- downloadExport(data, additionalData, collection, flattenArrays, additionalHeaders, exportType, dateExportType);
145
+
146
+ const dataWithDefaults = includeUndefinedValues
147
+ ? data.map(entity => {
148
+ const defaultValues = getDefaultValuesFor(collection.properties);
149
+ return {
150
+ ...entity,
151
+ values: { ...defaultValues, ...entity.values }
152
+ };
153
+ })
154
+ : data;
155
+ downloadEntitiesExport({
156
+ data: dataWithDefaults,
157
+ additionalData,
158
+ properties: collection.properties,
159
+ propertiesOrder: collection.propertiesOrder,
160
+ name: collection.name,
161
+ flattenArrays,
162
+ additionalHeaders,
163
+ exportType,
164
+ dateExportType
165
+ });
166
+ onAnalyticsEvent?.("export_collection_success", {
167
+ collection: collection.path
168
+ });
138
169
  })
139
170
  .catch((e) => {
140
171
  console.error("Error loading export data", e);
@@ -142,7 +173,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
142
173
  })
143
174
  .finally(() => setDataLoading(false));
144
175
 
145
- }, [dataSource, path, fetchAdditionalFields, flattenArrays, exportType, dateExportType]);
176
+ }, [onAnalyticsEvent, dataSource, path, fetchAdditionalFields, includeUndefinedValues, flattenArrays, exportType, dateExportType]);
146
177
 
147
178
  const onOkClicked = useCallback(() => {
148
179
  doDownload(collection, exportConfig);
@@ -181,17 +212,17 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
181
212
  <input id="radio-csv" type="radio" value="csv" name="exportType"
182
213
  checked={exportType === "csv"}
183
214
  onChange={() => setExportType("csv")}
184
- className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
215
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
185
216
  <label htmlFor="radio-csv"
186
- className="p-2 text-sm font-medium text-gray-900 dark:text-gray-300">CSV</label>
217
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">CSV</label>
187
218
  </div>
188
219
  <div className="flex items-center">
189
220
  <input id="radio-json" type="radio" value="json" name="exportType"
190
221
  checked={exportType === "json"}
191
222
  onChange={() => setExportType("json")}
192
- className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
223
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
193
224
  <label htmlFor="radio-json"
194
- className="p-2 text-sm font-medium text-gray-900 dark:text-gray-300">JSON</label>
225
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">JSON</label>
195
226
  </div>
196
227
  </div>
197
228
 
@@ -200,18 +231,18 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
200
231
  <input id="radio-timestamp" type="radio" value="timestamp" name="dateExportType"
201
232
  checked={dateExportType === "timestamp"}
202
233
  onChange={() => setDateExportType("timestamp")}
203
- className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
234
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
204
235
  <label htmlFor="radio-timestamp"
205
- className="p-2 text-sm font-medium text-gray-900 dark:text-gray-300">Dates as
236
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
206
237
  timestamps ({dateRef.current.getTime()})</label>
207
238
  </div>
208
239
  <div className="flex items-center">
209
240
  <input id="radio-string" type="radio" value="string" name="dateExportType"
210
241
  checked={dateExportType === "string"}
211
242
  onChange={() => setDateExportType("string")}
212
- className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
243
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
213
244
  <label htmlFor="radio-string"
214
- className="p-2 text-sm font-medium text-gray-900 dark:text-gray-300">Dates as
245
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
215
246
  strings ({dateRef.current.toISOString()})</label>
216
247
  </div>
217
248
  </div>
@@ -224,6 +255,12 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
224
255
  onValueChange={setFlattenArrays}
225
256
  label={"Flatten arrays"}/>
226
257
 
258
+ <BooleanSwitchWithLabel
259
+ size={"small"}
260
+ value={includeUndefinedValues}
261
+ onValueChange={setIncludeUndefinedValues}
262
+ label={"Include undefined values"}/>
263
+
227
264
  {!canExport && notAllowedView}
228
265
 
229
266
  </DialogContent>
@@ -2,13 +2,15 @@ import React, { useCallback, useEffect } from "react";
2
2
  import {
3
3
  CollectionActionsProps,
4
4
  EntityCollectionTable,
5
- PropertyConfigBadge,
6
5
  getFieldConfig,
7
6
  getPropertiesWithPropertiesOrder,
8
7
  getPropertyInPath,
8
+ PropertiesOrBuilders,
9
9
  Property,
10
+ PropertyConfigBadge,
10
11
  resolveCollection,
11
12
  ResolvedProperties,
13
+ slugify,
12
14
  useCustomizationController,
13
15
  User,
14
16
  useSelectionController,
@@ -16,7 +18,7 @@ import {
16
18
  } from "@firecms/core";
17
19
  import {
18
20
  Button,
19
- cn,
21
+ cls,
20
22
  defaultBorderMixin,
21
23
  Dialog,
22
24
  DialogActions,
@@ -30,7 +32,7 @@ import {
30
32
  } from "@firecms/ui";
31
33
  import { buildEntityPropertiesFromData } from "@firecms/schema_inference";
32
34
  import { useImportConfig } from "../hooks";
33
- import { convertDataToEntity, getInferenceType, getPropertiesMapping } from "../utils";
35
+ import { convertDataToEntity, getInferenceType } from "../utils";
34
36
  import { DataNewPropertiesMapping, ImportFileUpload, ImportSaveInProgress } from "../components";
35
37
  import { ImportConfig } from "../types";
36
38
 
@@ -40,8 +42,12 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
40
42
  collection,
41
43
  path,
42
44
  collectionEntitiesCount,
43
- }: CollectionActionsProps<M, UserType>
45
+ onAnalyticsEvent
46
+ }: CollectionActionsProps<M, UserType> & {
47
+ onAnalyticsEvent?: (event: string, params?: any) => void;
48
+ }
44
49
  ) {
50
+
45
51
  const customizationController = useCustomizationController();
46
52
 
47
53
  const snackbarController = useSnackbarController();
@@ -54,20 +60,23 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
54
60
 
55
61
  const handleClickOpen = useCallback(() => {
56
62
  setOpen(true);
63
+ onAnalyticsEvent?.("import_open");
57
64
  setStep("initial");
58
- }, [setOpen]);
65
+ }, [onAnalyticsEvent]);
59
66
 
60
67
  const handleClose = useCallback(() => {
61
68
  setOpen(false);
62
69
  }, [setOpen]);
63
70
 
64
71
  const onMappingComplete = useCallback(() => {
72
+ onAnalyticsEvent?.("import_mapping_complete");
65
73
  setStep("preview");
66
- }, []);
74
+ }, [onAnalyticsEvent]);
67
75
 
68
76
  const onPreviewComplete = useCallback(() => {
77
+ onAnalyticsEvent?.("import_data_save");
69
78
  setStep("import_data_saving");
70
- }, []);
79
+ }, [onAnalyticsEvent]);
71
80
 
72
81
  const onDataAdded = async (data: object[]) => {
73
82
  importConfig.setImportData(data);
@@ -76,15 +85,15 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
76
85
  const originProperties = await buildEntityPropertiesFromData(data, getInferenceType);
77
86
  importConfig.setOriginProperties(originProperties);
78
87
 
79
- const headersMapping = buildHeadersMappingFromData(data);
88
+ const headersMapping = buildHeadersMappingFromData(data, collection?.properties);
80
89
  importConfig.setHeadersMapping(headersMapping);
81
90
  const firstKey = Object.keys(headersMapping)?.[0];
82
91
  if (firstKey?.includes("id") || firstKey?.includes("key")) {
83
- const idColumn = firstKey;
84
- importConfig.setIdColumn(idColumn);
92
+ importConfig.setIdColumn(firstKey);
85
93
  }
86
94
  }
87
95
  setTimeout(() => {
96
+ onAnalyticsEvent?.("import_data_added");
88
97
  setStep("mapping");
89
98
  }, 100);
90
99
  // setStep("mapping");
@@ -104,6 +113,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
104
113
  if (collection.collectionGroup) {
105
114
  return null;
106
115
  }
116
+
107
117
  return <>
108
118
 
109
119
  <Tooltip title={"Import"}>
@@ -126,17 +136,14 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
126
136
  </>}
127
137
 
128
138
  {step === "mapping" && <>
129
- <Typography variant={"h6"}>Map fields</Typography>
130
- <DataNewPropertiesMapping headersMapping={importConfig.headersMapping}
131
- idColumn={importConfig.idColumn}
132
- originProperties={importConfig.originProperties}
139
+ <Typography variant={"h6"} className={"ml-3.5"}>Map fields</Typography>
140
+ <DataNewPropertiesMapping importConfig={importConfig}
133
141
  destinationProperties={properties}
134
- onIdPropertyChanged={(value) => importConfig.setIdColumn(value)}
135
142
  buildPropertyView={({
136
143
  isIdColumn,
137
144
  property,
138
145
  propertyKey,
139
- importKey
146
+ importKey,
140
147
  }) => {
141
148
  return <PropertyTreeSelect
142
149
  selectedPropertyKey={propertyKey ?? ""}
@@ -148,6 +155,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
148
155
  }}
149
156
  onPropertySelected={(newPropertyKey) => {
150
157
 
158
+ onAnalyticsEvent?.("import_mapping_field_updated");
151
159
  const newHeadersMapping: Record<string, string | null> = Object.entries(importConfig.headersMapping)
152
160
  .map(([currentImportKey, currentPropertyKey]) => {
153
161
  if (currentPropertyKey === newPropertyKey) {
@@ -248,30 +256,31 @@ function PropertyTreeSelect({
248
256
  }
249
257
 
250
258
  if (!selectedPropertyKey || !selectedProperty) {
251
- return <Typography variant={"body2"} className={"p-4"}>Do not import this property</Typography>;
259
+ return <Typography variant={"body2"} color="disabled" className={"p-4"}>Do not import this
260
+ property</Typography>;
252
261
  }
253
262
 
254
263
  return <PropertySelectEntry propertyKey={selectedPropertyKey}
255
264
  property={selectedProperty as Property}/>;
256
265
  }, [selectedProperty]);
257
266
 
258
- const onSelectValueChange = useCallback((value: string) => {
267
+ const onSelectValueChange = (value: string) => {
259
268
  if (value === internalIDValue) {
260
269
  onIdSelected();
261
270
  onPropertySelected(null);
262
- } else if (value === "") {
271
+ } else if (value === "__do_not_import") {
263
272
  onPropertySelected(null);
264
273
  } else {
265
274
  onPropertySelected(value);
266
275
  }
267
- }, []);
276
+ };
268
277
 
269
278
  return <Select value={isIdColumn ? internalIDValue : (selectedPropertyKey ?? undefined)}
270
279
  onValueChange={onSelectValueChange}
271
280
  renderValue={renderValue}>
272
281
 
273
- <SelectItem value={""}>
274
- <Typography variant={"body2"} className={"p-4"}>Do not import this property</Typography>
282
+ <SelectItem value={"__do_not_import"}>
283
+ <Typography variant={"body2"} color={"disabled"} className={"p-4"}>Do not import this property</Typography>
275
284
  </SelectItem>
276
285
 
277
286
  <SelectItem value={internalIDValue}>
@@ -333,7 +342,7 @@ export function PropertySelectEntry({
333
342
  className="flex flex-row w-full text-start items-center h-full">
334
343
 
335
344
  {new Array(level).fill(0).map((_, index) =>
336
- <div className={cn(defaultBorderMixin, "ml-8 border-l h-12")} key={index}/>)}
345
+ <div className={cls(defaultBorderMixin, "ml-8 border-l h-12")} key={index}/>)}
337
346
 
338
347
  <div className={"m-4"}>
339
348
  <Tooltip title={widget?.name}>
@@ -370,12 +379,11 @@ export function ImportDataPreview<M extends Record<string, any>>({
370
379
  }: {
371
380
  importConfig: ImportConfig,
372
381
  properties: ResolvedProperties<M>,
373
- propertiesOrder: Extract<keyof M, string>[]
382
+ propertiesOrder: Extract<keyof M, string>[],
374
383
  }) {
375
384
 
376
385
  useEffect(() => {
377
- const propertiesMapping = getPropertiesMapping(importConfig.originProperties, properties);
378
- const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, propertiesMapping, "TEMP_PATH"));
386
+ const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, "TEMP_PATH", importConfig.defaultValues));
379
387
  importConfig.setEntities(mappedData);
380
388
  }, []);
381
389
 
@@ -391,30 +399,43 @@ export function ImportDataPreview<M extends Record<string, any>>({
391
399
  dataLoading: false,
392
400
  noMoreToLoad: false
393
401
  }}
402
+ enablePopupIcon={false}
394
403
  endAdornment={<div className={"h-12"}/>}
395
404
  filterable={false}
396
405
  sortable={false}
397
406
  selectionController={selectionController}
398
- displayedColumnIds={propertiesOrder.map(p => ({
399
- key: p,
400
- disabled: false
401
- }))}
402
407
  properties={properties}/>
403
408
 
404
409
  }
405
410
 
406
- function buildHeadersMappingFromData(objArr: object[]) {
411
+ function buildHeadersMappingFromData(objArr: object[], properties?: PropertiesOrBuilders<any>) {
407
412
  const headersMapping: Record<string, string> = {};
408
413
  objArr.filter(Boolean).forEach((obj) => {
409
414
  Object.keys(obj).forEach((key) => {
410
415
  // @ts-ignore
411
416
  const child = obj[key];
412
417
  if (typeof child === "object" && !Array.isArray(child)) {
413
- Object.entries(buildHeadersMappingFromData([child])).forEach(([subKey, mapping]) => {
418
+ const childProperty = properties?.[key];
419
+ const childProperties = childProperty && "properties" in childProperty ? childProperty.properties : undefined;
420
+ const childHeadersMapping = buildHeadersMappingFromData([child], childProperties);
421
+ Object.entries(childHeadersMapping).forEach(([subKey, mapping]) => {
414
422
  headersMapping[`${key}.${subKey}`] = `${key}.${mapping}`;
415
423
  });
416
424
  }
417
- headersMapping[key] = key;
425
+
426
+ if (!properties) {
427
+ headersMapping[key] = key;
428
+ } else if (key in properties) {
429
+ headersMapping[key] = key;
430
+ } else {
431
+ const slug = slugify(key);
432
+ if (slug in properties) {
433
+ headersMapping[key] = slug;
434
+ } else {
435
+ headersMapping[key] = key;
436
+ }
437
+ }
438
+
418
439
  });
419
440
  });
420
441
  return headersMapping;
@@ -4,7 +4,6 @@ import {
4
4
  EntityReference,
5
5
  getArrayValuesCount,
6
6
  getValueInPath,
7
- ResolvedEntityCollection,
8
7
  ResolvedProperties,
9
8
  ResolvedProperty
10
9
  } from "@firecms/core";
@@ -14,37 +13,57 @@ interface Header {
14
13
  label: string;
15
14
  }
16
15
 
17
- export function downloadExport<M extends Record<string, any>>(data: Entity<M>[],
18
- additionalData: Record<string, any>[] | undefined,
19
- collection: ResolvedEntityCollection<M>,
20
- flattenArrays: boolean,
21
- additionalHeaders: string[] | undefined,
22
- exportType: "csv" | "json",
23
- dateExportType: "timestamp" | "string"
16
+ export interface DownloadEntitiesExportParams<M extends Record<string, any>> {
17
+ data: Entity<M>[];
18
+ additionalData: Record<string, any>[] | undefined;
19
+ properties: ResolvedProperties<M>;
20
+ propertiesOrder: string[] | undefined;
21
+ name: string;
22
+ flattenArrays: boolean;
23
+ additionalHeaders: string[] | undefined;
24
+ exportType: "csv" | "json";
25
+ dateExportType: "timestamp" | "string";
26
+ }
27
+
28
+ export function downloadEntitiesExport<M extends Record<string, any>>({
29
+ data,
30
+ additionalData,
31
+ properties,
32
+ propertiesOrder,
33
+ name,
34
+ flattenArrays,
35
+ additionalHeaders,
36
+ exportType,
37
+ dateExportType
38
+ }: DownloadEntitiesExportParams<M>
24
39
  ) {
25
40
 
26
- console.debug("Downloading export", { dataLength: data.length, collection, exportType, dateExportType });
27
- const properties = collection.properties;
41
+ console.debug("Downloading export", {
42
+ dataLength: data.length,
43
+ properties,
44
+ exportType,
45
+ dateExportType
46
+ });
28
47
 
29
48
  if (exportType === "csv") {
30
49
  const arrayValuesCount = flattenArrays ? getArrayValuesCount(data.map(d => d.values)) : {};
31
- const headers = getExportHeaders(properties, additionalHeaders, arrayValuesCount);
32
- const exportableData = getCSVExportableData(data, additionalData, properties, headers, dateExportType);
50
+ const headers = getExportHeaders(properties, propertiesOrder, additionalHeaders, arrayValuesCount);
51
+ const exportableData = getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType);
33
52
  const headersData = entryToCSVRow(headers.map(h => h.label));
34
53
  const csvData = exportableData.map(entry => entryToCSVRow(entry));
35
- downloadBlob([headersData, ...csvData], `${collection.name}.csv`, "text/csv");
54
+ downloadBlob([headersData, ...csvData], `${name}.csv`, "text/csv");
36
55
  } else {
37
- const exportableData = getJsonExportableData(data, additionalData, properties, dateExportType);
56
+ const exportableData = getEntityJsonExportableData(data, additionalData, properties, dateExportType);
38
57
  const json = JSON.stringify(exportableData, null, 2);
39
- downloadBlob([json], `${collection.name}.json`, "application/json");
58
+ downloadBlob([json], `${name}.json`, "application/json");
40
59
  }
41
60
  }
42
61
 
43
- export function getCSVExportableData(data: Entity<any>[],
44
- additionalData: Record<string, any>[] | undefined,
45
- properties: ResolvedProperties,
46
- headers: Header[],
47
- dateExportType: "timestamp" | "string"
62
+ export function getEntityCSVExportableData(data: Entity<any>[],
63
+ additionalData: Record<string, any>[] | undefined,
64
+ properties: ResolvedProperties,
65
+ headers: Header[],
66
+ dateExportType: "timestamp" | "string"
48
67
  ) {
49
68
 
50
69
  const mergedData: any[] = data.map(e => ({
@@ -63,10 +82,10 @@ export function getCSVExportableData(data: Entity<any>[],
63
82
  });
64
83
  }
65
84
 
66
- export function getJsonExportableData(data: Entity<any>[],
67
- additionalData: Record<string, any>[] | undefined,
68
- properties: ResolvedProperties,
69
- dateExportType: "timestamp" | "string"
85
+ export function getEntityJsonExportableData(data: Entity<any>[],
86
+ additionalData: Record<string, any>[] | undefined,
87
+ properties: ResolvedProperties,
88
+ dateExportType: "timestamp" | "string"
70
89
  ) {
71
90
 
72
91
  const mergedData: any[] = data.map(e => ({
@@ -84,13 +103,22 @@ export function getJsonExportableData(data: Entity<any>[],
84
103
  }
85
104
 
86
105
  function getExportHeaders<M extends Record<string, any>>(properties: ResolvedProperties<M>,
106
+ propertiesOrder: string[] | undefined,
87
107
  additionalHeaders: string[] | undefined,
88
108
  arrayValuesCount?: ArrayValuesCount): Header[] {
89
109
 
90
110
  const headers: Header[] = [
91
- { label: "id", key: "id" },
92
- ...Object.entries(properties)
93
- .flatMap(([childKey, property]) => {
111
+ {
112
+ label: "id",
113
+ key: "id"
114
+ },
115
+ ...(propertiesOrder ?? Object.keys(properties))
116
+ .flatMap((childKey) => {
117
+ const property = properties[childKey];
118
+ if (!property) {
119
+ console.warn("Property not found", childKey, properties);
120
+ return [];
121
+ }
94
122
  if (arrayValuesCount && arrayValuesCount[childKey] > 1) {
95
123
  return Array.from({ length: arrayValuesCount[childKey] },
96
124
  (_, i) => getHeaders(property as ResolvedProperty, `${childKey}[${i}]`, ""))
@@ -102,7 +130,10 @@ function getExportHeaders<M extends Record<string, any>>(properties: ResolvedPro
102
130
  ];
103
131
 
104
132
  if (additionalHeaders) {
105
- headers.push(...additionalHeaders.map(h => ({ label: h, key: h })));
133
+ headers.push(...additionalHeaders.map(h => ({
134
+ label: h,
135
+ key: h
136
+ })));
106
137
  }
107
138
 
108
139
  return headers;
@@ -121,7 +152,10 @@ function getHeaders(property: ResolvedProperty, propertyKey: string, prefix = ""
121
152
  .map(([childKey, p]) => getHeaders(p, childKey, currentKey))
122
153
  .flat();
123
154
  } else {
124
- return [{ label: currentKey, key: currentKey }];
155
+ return [{
156
+ label: currentKey,
157
+ key: currentKey
158
+ }];
125
159
  }
126
160
  }
127
161
 
@@ -149,7 +183,7 @@ function processValueForExport(inputValue: any,
149
183
  } else {
150
184
  value = inputValue;
151
185
  }
152
- } else if (property.dataType === "reference" && inputValue.isEntityReference && inputValue.isEntityReference()) {
186
+ } else if (property.dataType === "reference" && inputValue && inputValue.isEntityReference && inputValue.isEntityReference()) {
153
187
  const ref = inputValue ? inputValue as EntityReference : undefined;
154
188
  value = ref ? ref.pathWithId : null;
155
189
  } else if (property.dataType === "date" && inputValue instanceof Date) {
@@ -0,0 +1,4 @@
1
+ export * from "./export";
2
+ export * from "./BasicExportAction";
3
+ export * from "./ExportCollectionAction";
4
+ export * from "./ImportCollectionAction";
@@ -5,10 +5,12 @@ import { ImportConfig } from "../types";
5
5
  export const useImportConfig = (): ImportConfig => {
6
6
 
7
7
  const [inUse, setInUse] = useState<boolean>(false);
8
+ const [defaultValues, setDefaultValues] = useState<Record<string, any>>({});
8
9
  const [idColumn, setIdColumn] = useState<string | undefined>();
9
10
  const [importData, setImportData] = useState<object[]>([]);
10
11
  const [entities, setEntities] = useState<Entity<any>[]>([]);
11
12
  const [headersMapping, setHeadersMapping] = useState<Record<string, string | null>>({});
13
+ const [headingsOrder, setHeadingsOrder] = useState<string[]>([]);
12
14
  const [originProperties, setOriginProperties] = useState<Record<string, Property>>({});
13
15
 
14
16
  return {
@@ -20,9 +22,13 @@ export const useImportConfig = (): ImportConfig => {
20
22
  setEntities,
21
23
  importData,
22
24
  setImportData,
25
+ headingsOrder: (headingsOrder ?? []).length > 0 ? headingsOrder : Object.keys(headersMapping),
26
+ setHeadingsOrder,
23
27
  headersMapping,
24
28
  setHeadersMapping,
25
29
  originProperties,
26
30
  setOriginProperties,
31
+ defaultValues,
32
+ setDefaultValues
27
33
  };
28
34
  };
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./components";
3
3
  export * from "./types";
4
4
  export * from "./utils";
5
5
  export * from "./hooks";
6
+ export * from "./export_import";
@@ -22,11 +22,11 @@ export type ImportConfig = {
22
22
  originProperties: Record<string, Property>;
23
23
  setOriginProperties: React.Dispatch<React.SetStateAction<Record<string, Property>>>;
24
24
 
25
- }
25
+ // unmapped headings order
26
+ headingsOrder: string[];
27
+ setHeadingsOrder: React.Dispatch<React.SetStateAction<string[]>>;
28
+
29
+ defaultValues: Record<string, any>;
30
+ setDefaultValues: React.Dispatch<React.SetStateAction<Record<string, any>>>;
26
31
 
27
- export type DataTypeMapping = {
28
- from: DataType;
29
- fromSubtype?: DataType;
30
- to: DataType;
31
- toSubtype?: DataType;
32
32
  }
@@ -9,8 +9,8 @@ import { ExportCollectionAction } from "./export_import/ExportCollectionAction";
9
9
  export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSPlugin<any, any, any, ImportExportPluginProps> {
10
10
 
11
11
  return useMemo(() => ({
12
- name: "Import/Export",
13
- collections: {
12
+ key: "import_export",
13
+ collectionView: {
14
14
  CollectionActions: [ImportCollectionAction, ExportCollectionAction],
15
15
  collectionActionsProps: props
16
16
  }
@@ -19,6 +19,7 @@ export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSP
19
19
 
20
20
  export type ImportExportPluginProps = {
21
21
  exportAllowed?: (props: ExportAllowedParams) => boolean;
22
- notAllowedView: React.ReactNode;
22
+ notAllowedView?: React.ReactNode;
23
+ onAnalyticsEvent?: (event: string, params?: any) => void;
23
24
  }
24
25
  export type ExportAllowedParams = { collectionEntitiesCount: number, path: string, collection: EntityCollection };