@firecms/data_import_export 3.0.0-canary.43 → 3.0.0-canary.45
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/export_import/BasicExportAction.d.ts +7 -0
- 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 +624 -439
- 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 -1
- package/package.json +6 -6
- package/src/export_import/BasicExportAction.tsx +137 -0
- package/src/export_import/ExportCollectionAction.tsx +2 -2
- package/src/export_import/ImportCollectionAction.tsx +1 -0
- package/src/export_import/export.ts +45 -29
- package/src/export_import/index.ts +4 -0
- package/src/index.ts +1 -0
- package/src/useImportExportPlugin.tsx +1 -1
package/package.json
CHANGED
@@ -1,17 +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.45",
|
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/formex": "^3.0.0-canary.
|
13
|
-
"@firecms/schema_inference": "^3.0.0-canary.
|
14
|
-
"@firecms/ui": "^3.0.0-canary.
|
11
|
+
"@firecms/core": "^3.0.0-canary.45",
|
12
|
+
"@firecms/formex": "^3.0.0-canary.45",
|
13
|
+
"@firecms/schema_inference": "^3.0.0-canary.45",
|
14
|
+
"@firecms/ui": "^3.0.0-canary.45",
|
15
15
|
"xlsx": "^0.18.5"
|
16
16
|
},
|
17
17
|
"peerDependencies": {
|
@@ -101,5 +101,5 @@
|
|
101
101
|
"publishConfig": {
|
102
102
|
"access": "public"
|
103
103
|
},
|
104
|
-
"gitHead": "
|
104
|
+
"gitHead": "7f22bfa3bb20479ad23becd9c10f78cac1a3bc32"
|
105
105
|
}
|
@@ -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
|
|
@@ -139,7 +139,7 @@ export function ExportCollectionAction<M extends Record<string, any>, UserType e
|
|
139
139
|
...exportConfig?.additionalFields?.map(column => column.key) ?? [],
|
140
140
|
...collection.additionalFields?.map(field => field.key) ?? []
|
141
141
|
];
|
142
|
-
|
142
|
+
downloadEntitiesExport(data, additionalData, collection.properties, collection.propertiesOrder, collection.name, flattenArrays, additionalHeaders, exportType, dateExportType);
|
143
143
|
onAnalyticsEvent?.("export_collection_success", {
|
144
144
|
collection: collection.path
|
145
145
|
});
|
@@ -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,43 @@ interface Header {
|
|
14
13
|
label: string;
|
15
14
|
}
|
16
15
|
|
17
|
-
export function
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
export function downloadEntitiesExport<M extends Record<string, any>>(data: Entity<M>[],
|
17
|
+
additionalData: Record<string, any>[] | undefined,
|
18
|
+
properties: ResolvedProperties<M>,
|
19
|
+
propertiesOrder: string[] | undefined,
|
20
|
+
name: string,
|
21
|
+
flattenArrays: boolean,
|
22
|
+
additionalHeaders: string[] | undefined,
|
23
|
+
exportType: "csv" | "json",
|
24
|
+
dateExportType: "timestamp" | "string"
|
24
25
|
) {
|
25
26
|
|
26
|
-
console.debug("Downloading export", {
|
27
|
-
|
27
|
+
console.debug("Downloading export", {
|
28
|
+
dataLength: data.length,
|
29
|
+
properties,
|
30
|
+
exportType,
|
31
|
+
dateExportType
|
32
|
+
});
|
28
33
|
|
29
34
|
if (exportType === "csv") {
|
30
35
|
const arrayValuesCount = flattenArrays ? getArrayValuesCount(data.map(d => d.values)) : {};
|
31
|
-
const headers = getExportHeaders(properties, additionalHeaders, arrayValuesCount);
|
32
|
-
const exportableData =
|
36
|
+
const headers = getExportHeaders(properties, propertiesOrder, additionalHeaders, arrayValuesCount);
|
37
|
+
const exportableData = getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType);
|
33
38
|
const headersData = entryToCSVRow(headers.map(h => h.label));
|
34
39
|
const csvData = exportableData.map(entry => entryToCSVRow(entry));
|
35
|
-
downloadBlob([headersData, ...csvData], `${
|
40
|
+
downloadBlob([headersData, ...csvData], `${name}.csv`, "text/csv");
|
36
41
|
} else {
|
37
|
-
const exportableData =
|
42
|
+
const exportableData = getEntityJsonExportableData(data, additionalData, properties, dateExportType);
|
38
43
|
const json = JSON.stringify(exportableData, null, 2);
|
39
|
-
downloadBlob([json], `${
|
44
|
+
downloadBlob([json], `${name}.json`, "application/json");
|
40
45
|
}
|
41
46
|
}
|
42
47
|
|
43
|
-
export function
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
export function getEntityCSVExportableData(data: Entity<any>[],
|
49
|
+
additionalData: Record<string, any>[] | undefined,
|
50
|
+
properties: ResolvedProperties,
|
51
|
+
headers: Header[],
|
52
|
+
dateExportType: "timestamp" | "string"
|
48
53
|
) {
|
49
54
|
|
50
55
|
const mergedData: any[] = data.map(e => ({
|
@@ -63,10 +68,10 @@ export function getCSVExportableData(data: Entity<any>[],
|
|
63
68
|
});
|
64
69
|
}
|
65
70
|
|
66
|
-
export function
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
export function getEntityJsonExportableData(data: Entity<any>[],
|
72
|
+
additionalData: Record<string, any>[] | undefined,
|
73
|
+
properties: ResolvedProperties,
|
74
|
+
dateExportType: "timestamp" | "string"
|
70
75
|
) {
|
71
76
|
|
72
77
|
const mergedData: any[] = data.map(e => ({
|
@@ -84,13 +89,18 @@ export function getJsonExportableData(data: Entity<any>[],
|
|
84
89
|
}
|
85
90
|
|
86
91
|
function getExportHeaders<M extends Record<string, any>>(properties: ResolvedProperties<M>,
|
92
|
+
propertiesOrder: string[] | undefined,
|
87
93
|
additionalHeaders: string[] | undefined,
|
88
94
|
arrayValuesCount?: ArrayValuesCount): Header[] {
|
89
95
|
|
90
96
|
const headers: Header[] = [
|
91
|
-
{
|
92
|
-
|
93
|
-
|
97
|
+
{
|
98
|
+
label: "id",
|
99
|
+
key: "id"
|
100
|
+
},
|
101
|
+
...(propertiesOrder ?? Object.keys(properties))
|
102
|
+
.flatMap((childKey) => {
|
103
|
+
const property = properties[childKey];
|
94
104
|
if (arrayValuesCount && arrayValuesCount[childKey] > 1) {
|
95
105
|
return Array.from({ length: arrayValuesCount[childKey] },
|
96
106
|
(_, i) => getHeaders(property as ResolvedProperty, `${childKey}[${i}]`, ""))
|
@@ -102,7 +112,10 @@ function getExportHeaders<M extends Record<string, any>>(properties: ResolvedPro
|
|
102
112
|
];
|
103
113
|
|
104
114
|
if (additionalHeaders) {
|
105
|
-
headers.push(...additionalHeaders.map(h => ({
|
115
|
+
headers.push(...additionalHeaders.map(h => ({
|
116
|
+
label: h,
|
117
|
+
key: h
|
118
|
+
})));
|
106
119
|
}
|
107
120
|
|
108
121
|
return headers;
|
@@ -121,7 +134,10 @@ function getHeaders(property: ResolvedProperty, propertyKey: string, prefix = ""
|
|
121
134
|
.map(([childKey, p]) => getHeaders(p, childKey, currentKey))
|
122
135
|
.flat();
|
123
136
|
} else {
|
124
|
-
return [{
|
137
|
+
return [{
|
138
|
+
label: currentKey,
|
139
|
+
key: currentKey
|
140
|
+
}];
|
125
141
|
}
|
126
142
|
}
|
127
143
|
|
package/src/index.ts
CHANGED
@@ -19,7 +19,7 @@ export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSP
|
|
19
19
|
|
20
20
|
export type ImportExportPluginProps = {
|
21
21
|
exportAllowed?: (props: ExportAllowedParams) => boolean;
|
22
|
-
notAllowedView
|
22
|
+
notAllowedView?: React.ReactNode;
|
23
23
|
onAnalyticsEvent?: (event: string, params?: any) => void;
|
24
24
|
}
|
25
25
|
export type ExportAllowedParams = { collectionEntitiesCount: number, path: string, collection: EntityCollection };
|