@firecms/data_import_export 3.0.0-canary.7 → 3.0.0-canary.8
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.
- package/dist/components/DataNewPropertiesMapping.d.ts +1 -1
- package/dist/export_import/ExportCollectionAction.d.ts +1 -0
- package/dist/export_import/ImportCollectionAction.d.ts +3 -1
- package/dist/index.es.js +489 -466
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2 -2
- package/dist/index.umd.js.map +1 -1
- package/dist/useImportExportPlugin.d.ts +1 -0
- package/package.json +16 -16
- package/src/components/DataNewPropertiesMapping.tsx +5 -4
- package/src/components/ImportSaveInProgress.tsx +24 -2
- package/src/export_import/ExportCollectionAction.tsx +5 -4
- package/src/export_import/ImportCollectionAction.tsx +33 -12
- package/src/export_import/export.ts +1 -1
- package/src/useImportExportPlugin.tsx +2 -1
- package/src/utils/data.ts +14 -1
@@ -16,7 +16,7 @@ export interface DataPropertyMappingProps {
|
|
16
16
|
headersMapping: Record<string, string | null>;
|
17
17
|
originProperties: Record<string, Property>;
|
18
18
|
destinationProperties: Record<string, Property>;
|
19
|
-
onIdPropertyChanged: (value: string) => void;
|
19
|
+
onIdPropertyChanged: (value: string | null) => void;
|
20
20
|
buildPropertyView?: (props: {
|
21
21
|
isIdColumn: boolean,
|
22
22
|
property: Property | null,
|
@@ -103,14 +103,15 @@ function IdSelectField({
|
|
103
103
|
}: {
|
104
104
|
idColumn?: string,
|
105
105
|
headersMapping: Record<string, string | null>;
|
106
|
-
onChange: (value: string) => void
|
106
|
+
onChange: (value: string | null) => void
|
107
107
|
}) {
|
108
108
|
return <div>
|
109
109
|
<Select
|
110
110
|
size={"small"}
|
111
111
|
value={idColumn ?? ""}
|
112
112
|
onChange={(event) => {
|
113
|
-
|
113
|
+
const value = event.target.value;
|
114
|
+
onChange(value === "none" ? null : value);
|
114
115
|
}}
|
115
116
|
renderValue={(value) => {
|
116
117
|
return <Typography variant={"body2"}>
|
@@ -118,7 +119,7 @@ function IdSelectField({
|
|
118
119
|
</Typography>;
|
119
120
|
}}
|
120
121
|
label={"Column that will be used as ID for each document"}>
|
121
|
-
<SelectItem value={""}>Autogenerate ID</SelectItem>
|
122
|
+
<SelectItem value={"none"}>Autogenerate ID</SelectItem>
|
122
123
|
{Object.entries(headersMapping).map(([key, value]) => {
|
123
124
|
return <SelectItem key={key} value={key}>{key}</SelectItem>;
|
124
125
|
})}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { DataSource, Entity, EntityCollection, useDataSource } from "@firecms/core";
|
2
|
-
import { CenteredView, CircularProgress, Typography, } from "@firecms/ui";
|
2
|
+
import { Button, CenteredView, CircularProgress, Typography, } from "@firecms/ui";
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
4
4
|
import { ImportConfig } from "../types";
|
5
5
|
|
@@ -17,7 +17,7 @@ export function ImportSaveInProgress<C extends EntityCollection>
|
|
17
17
|
onImportSuccess: (collection: C) => void
|
18
18
|
}) {
|
19
19
|
|
20
|
-
|
20
|
+
const [errorSaving, setErrorSaving] = useState<Error | undefined>(undefined);
|
21
21
|
const dataSource = useDataSource();
|
22
22
|
|
23
23
|
const savingRef = useRef<boolean>(false);
|
@@ -42,6 +42,9 @@ export function ImportSaveInProgress<C extends EntityCollection>
|
|
42
42
|
).then(() => {
|
43
43
|
onImportSuccess(collection);
|
44
44
|
savingRef.current = false;
|
45
|
+
}).catch((e) => {
|
46
|
+
setErrorSaving(e);
|
47
|
+
savingRef.current = false;
|
45
48
|
});
|
46
49
|
}
|
47
50
|
|
@@ -49,6 +52,25 @@ export function ImportSaveInProgress<C extends EntityCollection>
|
|
49
52
|
save();
|
50
53
|
}, []);
|
51
54
|
|
55
|
+
if (errorSaving) {
|
56
|
+
return (
|
57
|
+
<CenteredView className={"flex flex-col gap-4 items-center"}>
|
58
|
+
<Typography variant={"h6"}>
|
59
|
+
Error saving data
|
60
|
+
</Typography>
|
61
|
+
|
62
|
+
<Typography variant={"body2"} color={"error"}>
|
63
|
+
{errorSaving.message}
|
64
|
+
</Typography>
|
65
|
+
<Button
|
66
|
+
onClick={save}
|
67
|
+
variant={"outlined"}>
|
68
|
+
Retry
|
69
|
+
</Button>
|
70
|
+
</CenteredView>
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
52
74
|
return (
|
53
75
|
<CenteredView className={"flex flex-col gap-4 items-center"}>
|
54
76
|
<CircularProgress/>
|
@@ -41,6 +41,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
41
41
|
}: CollectionActionsProps<M, UserType, EntityCollection<M, any>> & {
|
42
42
|
exportAllowed?: (props: { collectionEntitiesCount: number, path: string, collection: EntityCollection }) => boolean;
|
43
43
|
notAllowedView?: React.ReactNode;
|
44
|
+
onAnalyticsEvent?: (event: string, params?: any) => void;
|
44
45
|
}) {
|
45
46
|
|
46
47
|
const customizationController = useCustomizationController();
|
@@ -183,7 +184,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
183
184
|
onChange={() => setExportType("csv")}
|
184
185
|
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
185
186
|
<label htmlFor="radio-csv"
|
186
|
-
className="p-2 text-sm font-medium text-gray-900 dark:text-
|
187
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">CSV</label>
|
187
188
|
</div>
|
188
189
|
<div className="flex items-center">
|
189
190
|
<input id="radio-json" type="radio" value="json" name="exportType"
|
@@ -191,7 +192,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
191
192
|
onChange={() => setExportType("json")}
|
192
193
|
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
193
194
|
<label htmlFor="radio-json"
|
194
|
-
className="p-2 text-sm font-medium text-gray-900 dark:text-
|
195
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">JSON</label>
|
195
196
|
</div>
|
196
197
|
</div>
|
197
198
|
|
@@ -202,7 +203,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
202
203
|
onChange={() => setDateExportType("timestamp")}
|
203
204
|
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
204
205
|
<label htmlFor="radio-timestamp"
|
205
|
-
className="p-2 text-sm font-medium text-gray-900 dark:text-
|
206
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
|
206
207
|
timestamps ({dateRef.current.getTime()})</label>
|
207
208
|
</div>
|
208
209
|
<div className="flex items-center">
|
@@ -211,7 +212,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
211
212
|
onChange={() => setDateExportType("string")}
|
212
213
|
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
213
214
|
<label htmlFor="radio-string"
|
214
|
-
className="p-2 text-sm font-medium text-gray-900 dark:text-
|
215
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
|
215
216
|
strings ({dateRef.current.toISOString()})</label>
|
216
217
|
</div>
|
217
218
|
</div>
|
@@ -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,
|
@@ -40,8 +42,12 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
40
42
|
collection,
|
41
43
|
path,
|
42
44
|
collectionEntitiesCount,
|
43
|
-
|
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();
|
@@ -76,12 +82,11 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
76
82
|
const originProperties = await buildEntityPropertiesFromData(data, getInferenceType);
|
77
83
|
importConfig.setOriginProperties(originProperties);
|
78
84
|
|
79
|
-
const headersMapping = buildHeadersMappingFromData(data);
|
85
|
+
const headersMapping = buildHeadersMappingFromData(data, collection?.properties);
|
80
86
|
importConfig.setHeadersMapping(headersMapping);
|
81
87
|
const firstKey = Object.keys(headersMapping)?.[0];
|
82
88
|
if (firstKey?.includes("id") || firstKey?.includes("key")) {
|
83
|
-
|
84
|
-
importConfig.setIdColumn(idColumn);
|
89
|
+
importConfig.setIdColumn(firstKey);
|
85
90
|
}
|
86
91
|
}
|
87
92
|
setTimeout(() => {
|
@@ -131,7 +136,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
131
136
|
idColumn={importConfig.idColumn}
|
132
137
|
originProperties={importConfig.originProperties}
|
133
138
|
destinationProperties={properties}
|
134
|
-
onIdPropertyChanged={(value) => importConfig.setIdColumn(value)}
|
139
|
+
onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
|
135
140
|
buildPropertyView={({
|
136
141
|
isIdColumn,
|
137
142
|
property,
|
@@ -259,7 +264,7 @@ function PropertyTreeSelect({
|
|
259
264
|
if (value === internalIDValue) {
|
260
265
|
onIdSelected();
|
261
266
|
onPropertySelected(null);
|
262
|
-
} else if (value === "") {
|
267
|
+
} else if (value === "__do_not_import") {
|
263
268
|
onPropertySelected(null);
|
264
269
|
} else {
|
265
270
|
onPropertySelected(value);
|
@@ -270,7 +275,7 @@ function PropertyTreeSelect({
|
|
270
275
|
onValueChange={onSelectValueChange}
|
271
276
|
renderValue={renderValue}>
|
272
277
|
|
273
|
-
<SelectItem value={""}>
|
278
|
+
<SelectItem value={"__do_not_import"}>
|
274
279
|
<Typography variant={"body2"} className={"p-4"}>Do not import this property</Typography>
|
275
280
|
</SelectItem>
|
276
281
|
|
@@ -370,7 +375,7 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
370
375
|
}: {
|
371
376
|
importConfig: ImportConfig,
|
372
377
|
properties: ResolvedProperties<M>,
|
373
|
-
propertiesOrder: Extract<keyof M, string>[]
|
378
|
+
propertiesOrder: Extract<keyof M, string>[],
|
374
379
|
}) {
|
375
380
|
|
376
381
|
useEffect(() => {
|
@@ -403,18 +408,34 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
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
|
-
|
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
|
-
|
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;
|
@@ -149,7 +149,7 @@ function processValueForExport(inputValue: any,
|
|
149
149
|
} else {
|
150
150
|
value = inputValue;
|
151
151
|
}
|
152
|
-
} else if (property.dataType === "reference" && inputValue.isEntityReference && inputValue.isEntityReference()) {
|
152
|
+
} else if (property.dataType === "reference" && inputValue && inputValue.isEntityReference && inputValue.isEntityReference()) {
|
153
153
|
const ref = inputValue ? inputValue as EntityReference : undefined;
|
154
154
|
value = ref ? ref.pathWithId : null;
|
155
155
|
} else if (property.dataType === "date" && inputValue instanceof Date) {
|
@@ -10,7 +10,7 @@ export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSP
|
|
10
10
|
|
11
11
|
return useMemo(() => ({
|
12
12
|
name: "Import/Export",
|
13
|
-
|
13
|
+
collectionView: {
|
14
14
|
CollectionActions: [ImportCollectionAction, ExportCollectionAction],
|
15
15
|
collectionActionsProps: props
|
16
16
|
}
|
@@ -20,5 +20,6 @@ export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSP
|
|
20
20
|
export type ImportExportPluginProps = {
|
21
21
|
exportAllowed?: (props: ExportAllowedParams) => boolean;
|
22
22
|
notAllowedView: React.ReactNode;
|
23
|
+
onAnalyticsEvent?: (event: string, params?: any) => void;
|
23
24
|
}
|
24
25
|
export type ExportAllowedParams = { collectionEntitiesCount: number, path: string, collection: EntityCollection };
|
package/src/utils/data.ts
CHANGED
@@ -36,8 +36,21 @@ export function convertDataToEntity(data: Record<any, any>,
|
|
36
36
|
})
|
37
37
|
.reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
38
38
|
const values = unflattenObject(mappedKeysObject);
|
39
|
+
let id = idColumn ? data[idColumn] : undefined;
|
40
|
+
if (typeof id === "string") {
|
41
|
+
id = id.trim();
|
42
|
+
} else if (typeof id === "number") {
|
43
|
+
id = id.toString();
|
44
|
+
} else if (typeof id === "boolean") {
|
45
|
+
id = id.toString();
|
46
|
+
} else if (id instanceof Date) {
|
47
|
+
id = id.toISOString();
|
48
|
+
} else if (id && "toString" in id) {
|
49
|
+
id = id.toString();
|
50
|
+
}
|
51
|
+
|
39
52
|
return {
|
40
|
-
id
|
53
|
+
id,
|
41
54
|
values,
|
42
55
|
path
|
43
56
|
};
|