@firecms/data_import_export 3.0.0-canary.42 → 3.0.0-canary.44
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 +3 -6
- package/dist/export_import/BasicExportAction.d.ts +7 -0
- package/dist/export_import/ExportCollectionAction.d.ts +1 -1
- package/dist/export_import/export.d.ts +4 -4
- package/dist/export_import/index.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +852 -607
- 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 +3 -7
- package/dist/useImportExportPlugin.d.ts +1 -1
- package/dist/utils/data.d.ts +3 -10
- 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 +123 -20
- package/src/export_import/BasicExportAction.tsx +137 -0
- package/src/export_import/ExportCollectionAction.tsx +10 -3
- package/src/export_import/ImportCollectionAction.tsx +23 -20
- package/src/export_import/export.ts +45 -29
- package/src/export_import/index.ts +4 -0
- package/src/hooks/useImportConfig.tsx +3 -0
- package/src/index.ts +1 -0
- package/src/types/column_mapping.ts +2 -6
- package/src/useImportExportPlugin.tsx +1 -1
- package/src/utils/data.ts +37 -126
- package/src/utils/get_properties_mapping.ts +63 -59
- package/src/utils/index.ts +0 -1
@@ -6,7 +6,7 @@ import { EntityCollection, FireCMSPlugin } from "@firecms/core";
|
|
6
6
|
export declare function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSPlugin<any, any, any, ImportExportPluginProps>;
|
7
7
|
export type ImportExportPluginProps = {
|
8
8
|
exportAllowed?: (props: ExportAllowedParams) => boolean;
|
9
|
-
notAllowedView
|
9
|
+
notAllowedView?: React.ReactNode;
|
10
10
|
onAnalyticsEvent?: (event: string, params?: any) => void;
|
11
11
|
};
|
12
12
|
export type ExportAllowedParams = {
|
package/dist/utils/data.d.ts
CHANGED
@@ -1,11 +1,4 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
from: DataType;
|
4
|
-
fromSubtype?: DataType;
|
5
|
-
to: DataType;
|
6
|
-
toSubtype?: DataType;
|
7
|
-
};
|
8
|
-
export declare function convertDataToEntity(data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, propertiesMapping: Record<string, DataTypeMapping>, path: string): Entity<any>;
|
1
|
+
import { Entity, Properties, PropertyOrBuilder } from "@firecms/core";
|
2
|
+
export declare function convertDataToEntity(data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, path: string, defaultValues: Record<string, any>): Entity<any>;
|
9
3
|
export declare function flattenEntry(obj: any, parent?: string): any;
|
10
|
-
export declare function processValueMapping(value: any,
|
11
|
-
export {};
|
4
|
+
export declare function processValueMapping(value: any, property?: PropertyOrBuilder): any;
|
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.44",
|
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.44",
|
12
|
+
"@firecms/formex": "^3.0.0-canary.44",
|
13
|
+
"@firecms/schema_inference": "^3.0.0-canary.44",
|
14
|
+
"@firecms/ui": "^3.0.0-canary.44",
|
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": "15e52171f137c2876a3566bbc2752d140b9d0c7a"
|
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,16 +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
|
-
headingsOrder: string[];
|
18
|
-
originProperties: Record<string, Property>;
|
21
|
+
importConfig: ImportConfig;
|
19
22
|
destinationProperties: Record<string, Property>;
|
20
|
-
onIdPropertyChanged: (value: string | null) => void;
|
21
23
|
buildPropertyView?: (props: {
|
22
24
|
isIdColumn: boolean,
|
23
25
|
property: Property | null,
|
@@ -27,26 +29,22 @@ export interface DataPropertyMappingProps {
|
|
27
29
|
}
|
28
30
|
|
29
31
|
export function DataNewPropertiesMapping({
|
30
|
-
|
31
|
-
headersMapping,
|
32
|
-
headingsOrder,
|
33
|
-
originProperties,
|
32
|
+
importConfig,
|
34
33
|
destinationProperties,
|
35
|
-
|
36
|
-
buildPropertyView,
|
34
|
+
buildPropertyView
|
37
35
|
}: DataPropertyMappingProps) {
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
const headersMapping = importConfig.headersMapping;
|
38
|
+
const headingsOrder = importConfig.headingsOrder;
|
39
|
+
const idColumn = importConfig.idColumn;
|
40
|
+
const originProperties = importConfig.originProperties;
|
43
41
|
|
44
42
|
return (
|
45
43
|
<>
|
46
44
|
|
47
45
|
<IdSelectField idColumn={idColumn}
|
48
46
|
headersMapping={headersMapping}
|
49
|
-
onChange={
|
47
|
+
onChange={(value) => importConfig.setIdColumn(value ?? undefined)}/>
|
50
48
|
|
51
49
|
<div className={"h-4"}/>
|
52
50
|
|
@@ -71,7 +69,8 @@ export function DataNewPropertiesMapping({
|
|
71
69
|
const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
|
72
70
|
|
73
71
|
const originProperty = getPropertyInPath(originProperties, importKey) as Property | undefined;
|
74
|
-
const originDataType = originProperty
|
72
|
+
const originDataType = originProperty
|
73
|
+
? (originProperty.dataType === "array" && typeof originProperty.of === "object"
|
75
74
|
? `${originProperty.dataType} - ${(originProperty.of as Property).dataType}`
|
76
75
|
: originProperty.dataType)
|
77
76
|
: undefined;
|
@@ -93,18 +92,79 @@ export function DataNewPropertiesMapping({
|
|
93
92
|
property,
|
94
93
|
propertyKey,
|
95
94
|
importKey
|
96
|
-
})
|
97
|
-
}
|
95
|
+
})}
|
98
96
|
</TableCell>
|
99
97
|
</TableRow>;
|
100
98
|
}
|
101
99
|
)}
|
102
100
|
</TableBody>
|
103
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" }}>
|
132
|
+
<TableCell style={{ width: "20%" }}>
|
133
|
+
<Typography variant={"body2"}>{key}</Typography>
|
134
|
+
</TableCell>
|
135
|
+
<TableCell>
|
136
|
+
<ChevronRightIcon/>
|
137
|
+
</TableCell>
|
138
|
+
<TableCell className={key === idColumn ? "text-center" : undefined}
|
139
|
+
style={{ width: "75%" }}>
|
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
|
+
}}/>
|
146
|
+
</TableCell>
|
147
|
+
</TableRow>;
|
148
|
+
}
|
149
|
+
)}
|
150
|
+
</TableBody>
|
151
|
+
</Table>
|
152
|
+
</ExpandablePanel>
|
104
153
|
</>
|
105
154
|
);
|
106
155
|
}
|
107
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
|
+
|
108
168
|
function IdSelectField({
|
109
169
|
idColumn,
|
110
170
|
headersMapping,
|
@@ -136,3 +196,46 @@ function IdSelectField({
|
|
136
196
|
</Select>
|
137
197
|
</div>;
|
138
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
|
+
}
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import React, { useCallback } from "react";
|
2
|
+
|
3
|
+
import { Entity, ResolvedProperties } from "@firecms/core";
|
4
|
+
import {
|
5
|
+
BooleanSwitchWithLabel,
|
6
|
+
Button,
|
7
|
+
cn,
|
8
|
+
Dialog,
|
9
|
+
DialogActions,
|
10
|
+
DialogContent,
|
11
|
+
focusedMixin,
|
12
|
+
GetAppIcon,
|
13
|
+
IconButton,
|
14
|
+
Tooltip,
|
15
|
+
Typography,
|
16
|
+
} from "@firecms/ui";
|
17
|
+
import { downloadEntitiesExport } from "./export";
|
18
|
+
|
19
|
+
export type BasicExportActionProps = {
|
20
|
+
data: Entity<any>[];
|
21
|
+
properties: ResolvedProperties;
|
22
|
+
propertiesOrder?: string[];
|
23
|
+
}
|
24
|
+
|
25
|
+
export function BasicExportAction({
|
26
|
+
data,
|
27
|
+
properties,
|
28
|
+
propertiesOrder
|
29
|
+
}: BasicExportActionProps) {
|
30
|
+
|
31
|
+
const dateRef = React.useRef<Date>(new Date());
|
32
|
+
const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);
|
33
|
+
const [exportType, setExportType] = React.useState<"csv" | "json">("csv");
|
34
|
+
const [dateExportType, setDateExportType] = React.useState<"timestamp" | "string">("string");
|
35
|
+
|
36
|
+
const [open, setOpen] = React.useState(false);
|
37
|
+
|
38
|
+
const handleClickOpen = useCallback(() => {
|
39
|
+
setOpen(true);
|
40
|
+
}, [setOpen]);
|
41
|
+
|
42
|
+
const handleClose = useCallback(() => {
|
43
|
+
setOpen(false);
|
44
|
+
}, [setOpen]);
|
45
|
+
|
46
|
+
const onOkClicked = useCallback(() => {
|
47
|
+
downloadEntitiesExport(data, [], properties, propertiesOrder, "export.csv", flattenArrays, [], exportType, dateExportType);
|
48
|
+
handleClose();
|
49
|
+
}, []);
|
50
|
+
|
51
|
+
return <>
|
52
|
+
|
53
|
+
<Tooltip title={"Export"}>
|
54
|
+
<IconButton color={"primary"} onClick={handleClickOpen}>
|
55
|
+
<GetAppIcon/>
|
56
|
+
</IconButton>
|
57
|
+
</Tooltip>
|
58
|
+
|
59
|
+
<Dialog
|
60
|
+
open={open}
|
61
|
+
onOpenChange={setOpen}
|
62
|
+
maxWidth={"xl"}>
|
63
|
+
<DialogContent className={"flex flex-col gap-4 my-4"}>
|
64
|
+
|
65
|
+
<Typography variant={"h6"}>Export data</Typography>
|
66
|
+
|
67
|
+
<div>Download the the content of this table as a CSV</div>
|
68
|
+
|
69
|
+
<div className={"flex flex-row gap-4"}>
|
70
|
+
<div className={"p-4 flex flex-col"}>
|
71
|
+
<div className="flex items-center">
|
72
|
+
<input id="radio-csv" type="radio" value="csv" name="exportType"
|
73
|
+
checked={exportType === "csv"}
|
74
|
+
onChange={() => setExportType("csv")}
|
75
|
+
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
76
|
+
<label htmlFor="radio-csv"
|
77
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">CSV</label>
|
78
|
+
</div>
|
79
|
+
<div className="flex items-center">
|
80
|
+
<input id="radio-json" type="radio" value="json" name="exportType"
|
81
|
+
checked={exportType === "json"}
|
82
|
+
onChange={() => setExportType("json")}
|
83
|
+
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
84
|
+
<label htmlFor="radio-json"
|
85
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">JSON</label>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
|
89
|
+
<div className={"p-4 flex flex-col"}>
|
90
|
+
<div className="flex items-center">
|
91
|
+
<input id="radio-timestamp" type="radio" value="timestamp" name="dateExportType"
|
92
|
+
checked={dateExportType === "timestamp"}
|
93
|
+
onChange={() => setDateExportType("timestamp")}
|
94
|
+
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
95
|
+
<label htmlFor="radio-timestamp"
|
96
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
|
97
|
+
timestamps ({dateRef.current.getTime()})</label>
|
98
|
+
</div>
|
99
|
+
<div className="flex items-center">
|
100
|
+
<input id="radio-string" type="radio" value="string" name="dateExportType"
|
101
|
+
checked={dateExportType === "string"}
|
102
|
+
onChange={() => setDateExportType("string")}
|
103
|
+
className={cn(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
|
104
|
+
<label htmlFor="radio-string"
|
105
|
+
className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
|
106
|
+
strings ({dateRef.current.toISOString()})</label>
|
107
|
+
</div>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
|
111
|
+
<BooleanSwitchWithLabel
|
112
|
+
size={"small"}
|
113
|
+
disabled={exportType !== "csv"}
|
114
|
+
value={flattenArrays}
|
115
|
+
onValueChange={setFlattenArrays}
|
116
|
+
label={"Flatten arrays"}/>
|
117
|
+
|
118
|
+
</DialogContent>
|
119
|
+
|
120
|
+
<DialogActions>
|
121
|
+
|
122
|
+
<Button onClick={handleClose}
|
123
|
+
variant={"text"}>
|
124
|
+
Cancel
|
125
|
+
</Button>
|
126
|
+
|
127
|
+
<Button variant="filled"
|
128
|
+
onClick={onOkClicked}>
|
129
|
+
Download
|
130
|
+
</Button>
|
131
|
+
|
132
|
+
</DialogActions>
|
133
|
+
|
134
|
+
</Dialog>
|
135
|
+
|
136
|
+
</>;
|
137
|
+
}
|
@@ -28,7 +28,7 @@ import {
|
|
28
28
|
Tooltip,
|
29
29
|
Typography,
|
30
30
|
} from "@firecms/ui";
|
31
|
-
import {
|
31
|
+
import { downloadEntitiesExport } from "./export";
|
32
32
|
|
33
33
|
const DOCS_LIMIT = 500;
|
34
34
|
|
@@ -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,
|
@@ -135,7 +139,10 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
135
139
|
...exportConfig?.additionalFields?.map(column => column.key) ?? [],
|
136
140
|
...collection.additionalFields?.map(field => field.key) ?? []
|
137
141
|
];
|
138
|
-
|
142
|
+
downloadEntitiesExport(data, additionalData, collection.properties, collection.propertiesOrder, collection.name, 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,13 +136,9 @@ 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}
|
138
|
-
headingsOrder={importConfig.headingsOrder}
|
139
|
+
<Typography variant={"h6"} className={"ml-3.5"}>Map fields</Typography>
|
140
|
+
<DataNewPropertiesMapping importConfig={importConfig}
|
139
141
|
destinationProperties={properties}
|
140
|
-
onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
|
141
142
|
buildPropertyView={({
|
142
143
|
isIdColumn,
|
143
144
|
property,
|
@@ -154,6 +155,7 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
|
|
154
155
|
}}
|
155
156
|
onPropertySelected={(newPropertyKey) => {
|
156
157
|
|
158
|
+
onAnalyticsEvent?.("import_mapping_field_updated");
|
157
159
|
const newHeadersMapping: Record<string, string | null> = Object.entries(importConfig.headersMapping)
|
158
160
|
.map(([currentImportKey, currentPropertyKey]) => {
|
159
161
|
if (currentPropertyKey === newPropertyKey) {
|
@@ -254,14 +256,15 @@ function PropertyTreeSelect({
|
|
254
256
|
}
|
255
257
|
|
256
258
|
if (!selectedPropertyKey || !selectedProperty) {
|
257
|
-
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>;
|
258
261
|
}
|
259
262
|
|
260
263
|
return <PropertySelectEntry propertyKey={selectedPropertyKey}
|
261
264
|
property={selectedProperty as Property}/>;
|
262
265
|
}, [selectedProperty]);
|
263
266
|
|
264
|
-
const onSelectValueChange =
|
267
|
+
const onSelectValueChange = (value: string) => {
|
265
268
|
if (value === internalIDValue) {
|
266
269
|
onIdSelected();
|
267
270
|
onPropertySelected(null);
|
@@ -270,14 +273,14 @@ function PropertyTreeSelect({
|
|
270
273
|
} else {
|
271
274
|
onPropertySelected(value);
|
272
275
|
}
|
273
|
-
}
|
276
|
+
};
|
274
277
|
|
275
278
|
return <Select value={isIdColumn ? internalIDValue : (selectedPropertyKey ?? undefined)}
|
276
279
|
onValueChange={onSelectValueChange}
|
277
280
|
renderValue={renderValue}>
|
278
281
|
|
279
282
|
<SelectItem value={"__do_not_import"}>
|
280
|
-
<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>
|
281
284
|
</SelectItem>
|
282
285
|
|
283
286
|
<SelectItem value={internalIDValue}>
|
@@ -380,8 +383,11 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
380
383
|
}) {
|
381
384
|
|
382
385
|
useEffect(() => {
|
383
|
-
const
|
384
|
-
|
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
|
+
})
|
385
391
|
importConfig.setEntities(mappedData);
|
386
392
|
}, []);
|
387
393
|
|
@@ -397,14 +403,11 @@ export function ImportDataPreview<M extends Record<string, any>>({
|
|
397
403
|
dataLoading: false,
|
398
404
|
noMoreToLoad: false
|
399
405
|
}}
|
406
|
+
enablePopupIcon={false}
|
400
407
|
endAdornment={<div className={"h-12"}/>}
|
401
408
|
filterable={false}
|
402
409
|
sortable={false}
|
403
410
|
selectionController={selectionController}
|
404
|
-
displayedColumnIds={propertiesOrder.map(p => ({
|
405
|
-
key: p,
|
406
|
-
disabled: false
|
407
|
-
}))}
|
408
411
|
properties={properties}/>
|
409
412
|
|
410
413
|
}
|