@firecms/data_import_export 3.0.0-canary.41 → 3.0.0-canary.43
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/LICENSE +113 -21
- package/dist/components/DataNewPropertiesMapping.d.ts +3 -5
- package/dist/components/ImportFileUpload.d.ts +1 -1
- package/dist/export_import/ExportCollectionAction.d.ts +1 -1
- package/dist/index.es.js +697 -550
- 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/types/column_mapping.d.ts +5 -7
- package/dist/utils/data.d.ts +3 -10
- package/dist/utils/file_headers.d.ts +1 -0
- package/dist/utils/file_to_json.d.ts +6 -1
- package/dist/utils/get_properties_mapping.d.ts +0 -3
- package/dist/utils/index.d.ts +0 -1
- package/package.json +12 -10
- package/src/components/DataNewPropertiesMapping.tsx +153 -40
- package/src/components/ImportFileUpload.tsx +12 -4
- package/src/components/ImportNewPropertyFieldPreview.tsx +7 -2
- package/src/export_import/ExportCollectionAction.tsx +8 -1
- package/src/export_import/ImportCollectionAction.tsx +23 -20
- package/src/hooks/useImportConfig.tsx +6 -0
- package/src/types/column_mapping.ts +6 -6
- package/src/utils/data.ts +37 -126
- package/src/utils/file_headers.ts +90 -0
- package/src/utils/file_to_json.ts +24 -11
- package/src/utils/get_properties_mapping.ts +63 -59
- package/src/utils/index.ts +0 -1
package/dist/utils/index.d.ts
CHANGED
package/package.json
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
{
|
2
2
|
"name": "@firecms/data_import_export",
|
3
3
|
"type": "module",
|
4
|
-
"version": "3.0.0-canary.
|
4
|
+
"version": "3.0.0-canary.43",
|
5
5
|
"access": "public",
|
6
6
|
"main": "./dist/index.umd.js",
|
7
7
|
"module": "./dist/index.es.js",
|
8
8
|
"types": "./dist/index.d.ts",
|
9
9
|
"source": "src/index.ts",
|
10
10
|
"dependencies": {
|
11
|
-
"@firecms/core": "^3.0.0-canary.
|
12
|
-
"@firecms/
|
11
|
+
"@firecms/core": "^3.0.0-canary.43",
|
12
|
+
"@firecms/formex": "^3.0.0-canary.43",
|
13
|
+
"@firecms/schema_inference": "^3.0.0-canary.43",
|
14
|
+
"@firecms/ui": "^3.0.0-canary.43",
|
13
15
|
"xlsx": "^0.18.5"
|
14
16
|
},
|
15
17
|
"peerDependencies": {
|
@@ -56,10 +58,10 @@
|
|
56
58
|
"@jest/globals": "^29.7.0",
|
57
59
|
"@testing-library/jest-dom": "^6.4.2",
|
58
60
|
"@types/jest": "^29.5.12",
|
59
|
-
"@types/react": "^18.2.
|
60
|
-
"@types/react-dom": "^18.2.
|
61
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
62
|
-
"@typescript-eslint/parser": "^7.
|
61
|
+
"@types/react": "^18.2.79",
|
62
|
+
"@types/react-dom": "^18.2.25",
|
63
|
+
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
64
|
+
"@typescript-eslint/parser": "^7.7.0",
|
63
65
|
"@vitejs/plugin-react": "^4.2.1",
|
64
66
|
"babel-jest": "^29.7.0",
|
65
67
|
"eslint": "^8.57.0",
|
@@ -71,8 +73,8 @@
|
|
71
73
|
"eslint-plugin-react-hooks": "^4.6.0",
|
72
74
|
"jest": "^29.7.0",
|
73
75
|
"ts-jest": "^29.1.2",
|
74
|
-
"typescript": "^5.4.
|
75
|
-
"vite": "^5.2.
|
76
|
+
"typescript": "^5.4.5",
|
77
|
+
"vite": "^5.2.9",
|
76
78
|
"vite-plugin-fonts": "^0.7.0"
|
77
79
|
},
|
78
80
|
"jest": {
|
@@ -99,5 +101,5 @@
|
|
99
101
|
"publishConfig": {
|
100
102
|
"access": "public"
|
101
103
|
},
|
102
|
-
"gitHead": "
|
104
|
+
"gitHead": "fedcb0d43c504245dd76b702e4ab4479fa8592df"
|
103
105
|
}
|
@@ -1,6 +1,9 @@
|
|
1
|
-
import { getPropertyInPath,
|
1
|
+
import { getPropertyInPath, PropertiesOrBuilders, Property } from "@firecms/core";
|
2
2
|
import {
|
3
|
+
BooleanSwitchWithLabel,
|
3
4
|
ChevronRightIcon,
|
5
|
+
DateTimeField,
|
6
|
+
ExpandablePanel,
|
4
7
|
Select,
|
5
8
|
SelectItem,
|
6
9
|
Table,
|
@@ -8,15 +11,15 @@ import {
|
|
8
11
|
TableCell,
|
9
12
|
TableHeader,
|
10
13
|
TableRow,
|
14
|
+
TextField,
|
11
15
|
Typography
|
12
16
|
} from "@firecms/ui";
|
17
|
+
import { ImportConfig } from "../types";
|
18
|
+
import { getIn, setIn } from "@firecms/formex";
|
13
19
|
|
14
20
|
export interface DataPropertyMappingProps {
|
15
|
-
|
16
|
-
headersMapping: Record<string, string | null>;
|
17
|
-
originProperties: Record<string, Property>;
|
21
|
+
importConfig: ImportConfig;
|
18
22
|
destinationProperties: Record<string, Property>;
|
19
|
-
onIdPropertyChanged: (value: string | null) => void;
|
20
23
|
buildPropertyView?: (props: {
|
21
24
|
isIdColumn: boolean,
|
22
25
|
property: Property | null,
|
@@ -26,20 +29,24 @@ export interface DataPropertyMappingProps {
|
|
26
29
|
}
|
27
30
|
|
28
31
|
export function DataNewPropertiesMapping({
|
29
|
-
|
30
|
-
headersMapping,
|
31
|
-
originProperties,
|
32
|
+
importConfig,
|
32
33
|
destinationProperties,
|
33
|
-
|
34
|
-
buildPropertyView,
|
34
|
+
buildPropertyView
|
35
35
|
}: DataPropertyMappingProps) {
|
36
36
|
|
37
|
+
const headersMapping = importConfig.headersMapping;
|
38
|
+
const headingsOrder = importConfig.headingsOrder;
|
39
|
+
const idColumn = importConfig.idColumn;
|
40
|
+
const originProperties = importConfig.originProperties;
|
41
|
+
|
37
42
|
return (
|
38
43
|
<>
|
39
44
|
|
40
45
|
<IdSelectField idColumn={idColumn}
|
41
46
|
headersMapping={headersMapping}
|
42
|
-
onChange={
|
47
|
+
onChange={(value) => importConfig.setIdColumn(value ?? undefined)}/>
|
48
|
+
|
49
|
+
<div className={"h-4"}/>
|
43
50
|
|
44
51
|
<Table style={{
|
45
52
|
tableLayout: "fixed"
|
@@ -51,51 +58,113 @@ export function DataNewPropertiesMapping({
|
|
51
58
|
<TableCell header={true}>
|
52
59
|
</TableCell>
|
53
60
|
<TableCell header={true} style={{ width: "75%" }}>
|
54
|
-
Property
|
61
|
+
Map to Property
|
55
62
|
</TableCell>
|
56
63
|
</TableHeader>
|
57
64
|
<TableBody>
|
58
65
|
{destinationProperties &&
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
66
|
+
headingsOrder.map((importKey) => {
|
67
|
+
const mappedKey = headersMapping[importKey];
|
68
|
+
const propertyKey = headersMapping[importKey];
|
69
|
+
const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
|
63
70
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
:
|
69
|
-
|
71
|
+
const originProperty = getPropertyInPath(originProperties, importKey) as Property | undefined;
|
72
|
+
const originDataType = originProperty
|
73
|
+
? (originProperty.dataType === "array" && typeof originProperty.of === "object"
|
74
|
+
? `${originProperty.dataType} - ${(originProperty.of as Property).dataType}`
|
75
|
+
: originProperty.dataType)
|
76
|
+
: undefined;
|
77
|
+
return <TableRow key={importKey} style={{ height: "90px" }}>
|
78
|
+
<TableCell style={{ width: "20%" }}>
|
79
|
+
<Typography variant={"body2"}>{importKey}</Typography>
|
80
|
+
{originProperty && <Typography
|
81
|
+
variant={"caption"}
|
82
|
+
color={"secondary"}
|
83
|
+
>{originDataType}</Typography>}
|
84
|
+
</TableCell>
|
85
|
+
<TableCell>
|
86
|
+
<ChevronRightIcon/>
|
87
|
+
</TableCell>
|
88
|
+
<TableCell className={importKey === idColumn ? "text-center" : undefined}
|
89
|
+
style={{ width: "75%" }}>
|
90
|
+
{buildPropertyView?.({
|
91
|
+
isIdColumn: importKey === idColumn,
|
92
|
+
property,
|
93
|
+
propertyKey,
|
94
|
+
importKey
|
95
|
+
})}
|
96
|
+
</TableCell>
|
97
|
+
</TableRow>;
|
98
|
+
}
|
99
|
+
)}
|
100
|
+
</TableBody>
|
101
|
+
</Table>
|
102
|
+
|
103
|
+
<ExpandablePanel title="Default values" initiallyExpanded={false} className={"p-4 mt-4"}>
|
104
|
+
|
105
|
+
<div className={"text-sm text-slate-500 dark:text-slate-300 font-medium ml-3.5 mb-1"}>
|
106
|
+
You can select a default value for unmapped columns and empty values:
|
107
|
+
</div>
|
108
|
+
<Table style={{
|
109
|
+
tableLayout: "fixed"
|
110
|
+
}}>
|
111
|
+
<TableHeader>
|
112
|
+
<TableCell header={true} style={{ width: "30%" }}>
|
113
|
+
Property
|
114
|
+
</TableCell>
|
115
|
+
<TableCell header={true}>
|
116
|
+
</TableCell>
|
117
|
+
<TableCell header={true} style={{ width: "65%" }}>
|
118
|
+
Default value
|
119
|
+
</TableCell>
|
120
|
+
</TableHeader>
|
121
|
+
<TableBody>
|
122
|
+
{destinationProperties &&
|
123
|
+
getAllPropertyKeys(destinationProperties).map((key) => {
|
124
|
+
const property = getPropertyInPath(destinationProperties, key);
|
125
|
+
if (typeof property !== "object" || property === null) {
|
126
|
+
return null;
|
127
|
+
}
|
128
|
+
if (!["number", "string", "boolean", "map"].includes(property.dataType)) {
|
129
|
+
return null;
|
130
|
+
}
|
131
|
+
return <TableRow key={key} style={{ height: "70px" }}>
|
70
132
|
<TableCell style={{ width: "20%" }}>
|
71
|
-
<Typography variant={"body2"}>{
|
72
|
-
{originProperty && <Typography
|
73
|
-
variant={"caption"}
|
74
|
-
color={"secondary"}
|
75
|
-
>{originDataType}</Typography>}
|
133
|
+
<Typography variant={"body2"}>{key}</Typography>
|
76
134
|
</TableCell>
|
77
135
|
<TableCell>
|
78
136
|
<ChevronRightIcon/>
|
79
137
|
</TableCell>
|
80
|
-
<TableCell className={
|
138
|
+
<TableCell className={key === idColumn ? "text-center" : undefined}
|
81
139
|
style={{ width: "75%" }}>
|
82
|
-
{
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
}
|
140
|
+
<DefaultValuesField property={property}
|
141
|
+
defaultValue={getIn(importConfig.defaultValues, key)}
|
142
|
+
onValueChange={(value) => {
|
143
|
+
const newValues = setIn(importConfig.defaultValues, key, value);
|
144
|
+
importConfig.setDefaultValues(newValues);
|
145
|
+
}}/>
|
89
146
|
</TableCell>
|
90
147
|
</TableRow>;
|
91
148
|
}
|
92
149
|
)}
|
93
|
-
|
94
|
-
|
150
|
+
</TableBody>
|
151
|
+
</Table>
|
152
|
+
</ExpandablePanel>
|
95
153
|
</>
|
96
154
|
);
|
97
155
|
}
|
98
156
|
|
157
|
+
function getAllPropertyKeys(properties: PropertiesOrBuilders, currentKey?: string): string[] {
|
158
|
+
return Object.entries(properties).reduce((acc, [key, property]) => {
|
159
|
+
const accumulatedKey = currentKey ? `${currentKey}.${key}` : key;
|
160
|
+
if (typeof property !== "function" && property.dataType === "map" && property.properties) {
|
161
|
+
const childProperties = getAllPropertyKeys(property.properties, accumulatedKey);
|
162
|
+
return [...acc, ...childProperties];
|
163
|
+
}
|
164
|
+
return [...acc, accumulatedKey];
|
165
|
+
}, [] as string[]);
|
166
|
+
}
|
167
|
+
|
99
168
|
function IdSelectField({
|
100
169
|
idColumn,
|
101
170
|
headersMapping,
|
@@ -111,18 +180,62 @@ function IdSelectField({
|
|
111
180
|
value={idColumn ?? ""}
|
112
181
|
onChange={(event) => {
|
113
182
|
const value = event.target.value;
|
114
|
-
onChange(value === "
|
183
|
+
onChange(value === "__none__" ? null : value);
|
115
184
|
}}
|
185
|
+
placeholder={"Autogenerate ID"}
|
116
186
|
renderValue={(value) => {
|
117
187
|
return <Typography variant={"body2"}>
|
118
|
-
{value !== "" ? value : "Autogenerate ID"}
|
188
|
+
{value !== "__none__" ? value : "Autogenerate ID"}
|
119
189
|
</Typography>;
|
120
190
|
}}
|
121
191
|
label={"Column that will be used as ID for each document"}>
|
122
|
-
<SelectItem value={"
|
192
|
+
<SelectItem value={"__none__"}>Autogenerate ID</SelectItem>
|
123
193
|
{Object.entries(headersMapping).map(([key, value]) => {
|
124
194
|
return <SelectItem key={key} value={key}>{key}</SelectItem>;
|
125
195
|
})}
|
126
196
|
</Select>
|
127
197
|
</div>;
|
128
198
|
}
|
199
|
+
|
200
|
+
function DefaultValuesField({
|
201
|
+
property,
|
202
|
+
onValueChange,
|
203
|
+
defaultValue
|
204
|
+
}: { property: Property, onValueChange: (value: any) => void, defaultValue?: any }) {
|
205
|
+
if (property.dataType === "string") {
|
206
|
+
return <TextField size={"small"}
|
207
|
+
placeholder={"Default value"}
|
208
|
+
value={defaultValue ?? ""}
|
209
|
+
onChange={(event) => onValueChange(event.target.value)}/>;
|
210
|
+
} else if (property.dataType === "number") {
|
211
|
+
return <TextField size={"small"}
|
212
|
+
type={"number"}
|
213
|
+
value={defaultValue ?? ""}
|
214
|
+
placeholder={"Default value"}
|
215
|
+
onChange={(event) => onValueChange(event.target.value)}/>;
|
216
|
+
} else if (property.dataType === "boolean") {
|
217
|
+
return <BooleanSwitchWithLabel
|
218
|
+
value={defaultValue ?? null}
|
219
|
+
allowIndeterminate={true}
|
220
|
+
size={"small"}
|
221
|
+
onValueChange={(v: boolean | null) => onValueChange(v === null ? undefined : v)}
|
222
|
+
label={defaultValue === undefined
|
223
|
+
? "Do not set value"
|
224
|
+
: defaultValue === true
|
225
|
+
? "Set value to true"
|
226
|
+
: "Set value to false"}
|
227
|
+
/>
|
228
|
+
} else if (property.dataType === "date") {
|
229
|
+
return <DateTimeField
|
230
|
+
mode={property.mode ?? "date"}
|
231
|
+
size={"small"}
|
232
|
+
value={defaultValue ?? undefined}
|
233
|
+
onChange={(dateValue: Date | undefined) => {
|
234
|
+
onValueChange(dateValue);
|
235
|
+
}}
|
236
|
+
clearable={true}
|
237
|
+
/>
|
238
|
+
}
|
239
|
+
|
240
|
+
return null;
|
241
|
+
}
|
@@ -2,7 +2,9 @@ import { FileUpload, UploadIcon } from "@firecms/ui";
|
|
2
2
|
import { convertFileToJson } from "../utils/file_to_json";
|
3
3
|
import { useSnackbarController } from "@firecms/core";
|
4
4
|
|
5
|
-
export function ImportFileUpload({ onDataAdded }: {
|
5
|
+
export function ImportFileUpload({ onDataAdded }: {
|
6
|
+
onDataAdded: (data: object[], propertiesOrder?: string[]) => void
|
7
|
+
}) {
|
6
8
|
const snackbarController = useSnackbarController();
|
7
9
|
return <FileUpload
|
8
10
|
accept={{
|
@@ -22,12 +24,18 @@ export function ImportFileUpload({ onDataAdded }: { onDataAdded: (data: object[]
|
|
22
24
|
onFilesAdded={(files: File[]) => {
|
23
25
|
if (files.length > 0) {
|
24
26
|
convertFileToJson(files[0])
|
25
|
-
.then((
|
26
|
-
|
27
|
+
.then(({
|
28
|
+
data,
|
29
|
+
propertiesOrder
|
30
|
+
}) => {
|
31
|
+
onDataAdded(data, propertiesOrder);
|
27
32
|
})
|
28
33
|
.catch((error) => {
|
29
34
|
console.error("Error parsing file", error);
|
30
|
-
snackbarController.open({
|
35
|
+
snackbarController.open({
|
36
|
+
type: "error",
|
37
|
+
message: error.message
|
38
|
+
});
|
31
39
|
});
|
32
40
|
}
|
33
41
|
}}/>
|
@@ -1,5 +1,11 @@
|
|
1
1
|
import React from "react";
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
ErrorBoundary,
|
4
|
+
getFieldConfig,
|
5
|
+
Property,
|
6
|
+
PropertyConfigBadge,
|
7
|
+
useCustomizationController
|
8
|
+
} from "@firecms/core";
|
3
9
|
import { EditIcon, IconButton, TextField, } from "@firecms/ui";
|
4
10
|
|
5
11
|
export function ImportNewPropertyFieldPreview({
|
@@ -49,7 +55,6 @@ export function ImportNewPropertyFieldPreview({
|
|
49
55
|
|
50
56
|
</div>
|
51
57
|
|
52
|
-
|
53
58
|
</div>
|
54
59
|
</ErrorBoundary>
|
55
60
|
}
|
@@ -36,6 +36,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
36
36
|
collection: inputCollection,
|
37
37
|
path: inputPath,
|
38
38
|
collectionEntitiesCount,
|
39
|
+
onAnalyticsEvent,
|
39
40
|
exportAllowed,
|
40
41
|
notAllowedView
|
41
42
|
}: CollectionActionsProps<M, UserType, EntityCollection<M, any>> & {
|
@@ -123,6 +124,9 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
123
124
|
const doDownload = useCallback(async (collection: ResolvedEntityCollection<M>,
|
124
125
|
exportConfig: ExportConfig<any> | undefined) => {
|
125
126
|
|
127
|
+
onAnalyticsEvent?.("export_collection", {
|
128
|
+
collection: collection.path
|
129
|
+
});
|
126
130
|
setDataLoading(true);
|
127
131
|
dataSource.fetchCollection<M>({
|
128
132
|
path,
|
@@ -136,6 +140,9 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
136
140
|
...collection.additionalFields?.map(field => field.key) ?? []
|
137
141
|
];
|
138
142
|
downloadExport(data, additionalData, collection, flattenArrays, additionalHeaders, exportType, dateExportType);
|
143
|
+
onAnalyticsEvent?.("export_collection_success", {
|
144
|
+
collection: collection.path
|
145
|
+
});
|
139
146
|
})
|
140
147
|
.catch((e) => {
|
141
148
|
console.error("Error loading export data", e);
|
@@ -143,7 +150,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
143
150
|
})
|
144
151
|
.finally(() => setDataLoading(false));
|
145
152
|
|
146
|
-
}, [dataSource, path, fetchAdditionalFields, flattenArrays, exportType, dateExportType]);
|
153
|
+
}, [onAnalyticsEvent, dataSource, path, fetchAdditionalFields, flattenArrays, exportType, dateExportType]);
|
147
154
|
|
148
155
|
const onOkClicked = useCallback(() => {
|
149
156
|
doDownload(collection, exportConfig);
|
@@ -32,7 +32,7 @@ import {
|
|
32
32
|
} from "@firecms/ui";
|
33
33
|
import { buildEntityPropertiesFromData } from "@firecms/schema_inference";
|
34
34
|
import { useImportConfig } from "../hooks";
|
35
|
-
import { convertDataToEntity, getInferenceType
|
35
|
+
import { convertDataToEntity, getInferenceType } from "../utils";
|
36
36
|
import { DataNewPropertiesMapping, ImportFileUpload, ImportSaveInProgress } from "../components";
|
37
37
|
import { ImportConfig } from "../types";
|
38
38
|
|
@@ -60,20 +60,23 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
60
60
|
|
61
61
|
const handleClickOpen = useCallback(() => {
|
62
62
|
setOpen(true);
|
63
|
+
onAnalyticsEvent?.("import_open");
|
63
64
|
setStep("initial");
|
64
|
-
}, [
|
65
|
+
}, [onAnalyticsEvent]);
|
65
66
|
|
66
67
|
const handleClose = useCallback(() => {
|
67
68
|
setOpen(false);
|
68
69
|
}, [setOpen]);
|
69
70
|
|
70
71
|
const onMappingComplete = useCallback(() => {
|
72
|
+
onAnalyticsEvent?.("import_mapping_complete");
|
71
73
|
setStep("preview");
|
72
|
-
}, []);
|
74
|
+
}, [onAnalyticsEvent]);
|
73
75
|
|
74
76
|
const onPreviewComplete = useCallback(() => {
|
77
|
+
onAnalyticsEvent?.("import_data_save");
|
75
78
|
setStep("import_data_saving");
|
76
|
-
}, []);
|
79
|
+
}, [onAnalyticsEvent]);
|
77
80
|
|
78
81
|
const onDataAdded = async (data: object[]) => {
|
79
82
|
importConfig.setImportData(data);
|
@@ -90,6 +93,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
90
93
|
}
|
91
94
|
}
|
92
95
|
setTimeout(() => {
|
96
|
+
onAnalyticsEvent?.("import_data_added");
|
93
97
|
setStep("mapping");
|
94
98
|
}, 100);
|
95
99
|
// setStep("mapping");
|
@@ -109,6 +113,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
109
113
|
if (collection.collectionGroup) {
|
110
114
|
return null;
|
111
115
|
}
|
116
|
+
|
112
117
|
return <>
|
113
118
|
|
114
119
|
<Tooltip title={"Import"}>
|
@@ -131,17 +136,14 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
131
136
|
</>}
|
132
137
|
|
133
138
|
{step === "mapping" && <>
|
134
|
-
<Typography variant={"h6"}>Map fields</Typography>
|
135
|
-
<DataNewPropertiesMapping
|
136
|
-
idColumn={importConfig.idColumn}
|
137
|
-
originProperties={importConfig.originProperties}
|
139
|
+
<Typography variant={"h6"} className={"ml-3.5"}>Map fields</Typography>
|
140
|
+
<DataNewPropertiesMapping importConfig={importConfig}
|
138
141
|
destinationProperties={properties}
|
139
|
-
onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
|
140
142
|
buildPropertyView={({
|
141
143
|
isIdColumn,
|
142
144
|
property,
|
143
145
|
propertyKey,
|
144
|
-
importKey
|
146
|
+
importKey,
|
145
147
|
}) => {
|
146
148
|
return <PropertyTreeSelect
|
147
149
|
selectedPropertyKey={propertyKey ?? ""}
|
@@ -153,6 +155,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
153
155
|
}}
|
154
156
|
onPropertySelected={(newPropertyKey) => {
|
155
157
|
|
158
|
+
onAnalyticsEvent?.("import_mapping_field_updated");
|
156
159
|
const newHeadersMapping: Record<string, string | null> = Object.entries(importConfig.headersMapping)
|
157
160
|
.map(([currentImportKey, currentPropertyKey]) => {
|
158
161
|
if (currentPropertyKey === newPropertyKey) {
|
@@ -253,14 +256,15 @@ function PropertyTreeSelect({
|
|
253
256
|
}
|
254
257
|
|
255
258
|
if (!selectedPropertyKey || !selectedProperty) {
|
256
|
-
return <Typography variant={"body2"} className={"p-4"}>Do not import this
|
259
|
+
return <Typography variant={"body2"} color="disabled" className={"p-4"}>Do not import this
|
260
|
+
property</Typography>;
|
257
261
|
}
|
258
262
|
|
259
263
|
return <PropertySelectEntry propertyKey={selectedPropertyKey}
|
260
264
|
property={selectedProperty as Property}/>;
|
261
265
|
}, [selectedProperty]);
|
262
266
|
|
263
|
-
const onSelectValueChange =
|
267
|
+
const onSelectValueChange = (value: string) => {
|
264
268
|
if (value === internalIDValue) {
|
265
269
|
onIdSelected();
|
266
270
|
onPropertySelected(null);
|
@@ -269,14 +273,14 @@ function PropertyTreeSelect({
|
|
269
273
|
} else {
|
270
274
|
onPropertySelected(value);
|
271
275
|
}
|
272
|
-
}
|
276
|
+
};
|
273
277
|
|
274
278
|
return <Select value={isIdColumn ? internalIDValue : (selectedPropertyKey ?? undefined)}
|
275
279
|
onValueChange={onSelectValueChange}
|
276
280
|
renderValue={renderValue}>
|
277
281
|
|
278
282
|
<SelectItem value={"__do_not_import"}>
|
279
|
-
<Typography variant={"body2"} className={"p-4"}>Do not import this property</Typography>
|
283
|
+
<Typography variant={"body2"} color={"disabled"} className={"p-4"}>Do not import this property</Typography>
|
280
284
|
</SelectItem>
|
281
285
|
|
282
286
|
<SelectItem value={internalIDValue}>
|
@@ -379,8 +383,11 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
379
383
|
}) {
|
380
384
|
|
381
385
|
useEffect(() => {
|
382
|
-
const
|
383
|
-
|
386
|
+
const mappedData = importConfig.importData.map(d => convertDataToEntity(d, importConfig.idColumn, importConfig.headersMapping, properties, "TEMP_PATH", importConfig.defaultValues));
|
387
|
+
console.log("Mapped data", {
|
388
|
+
importConfig,
|
389
|
+
mappedData
|
390
|
+
})
|
384
391
|
importConfig.setEntities(mappedData);
|
385
392
|
}, []);
|
386
393
|
|
@@ -400,10 +407,6 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
400
407
|
filterable={false}
|
401
408
|
sortable={false}
|
402
409
|
selectionController={selectionController}
|
403
|
-
displayedColumnIds={propertiesOrder.map(p => ({
|
404
|
-
key: p,
|
405
|
-
disabled: false
|
406
|
-
}))}
|
407
410
|
properties={properties}/>
|
408
411
|
|
409
412
|
}
|
@@ -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
|
};
|
@@ -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
|
}
|