@firecms/data_import_export 3.0.0-alpha.38
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 +21 -0
- package/README.md +87 -0
- package/dist/components/DataNewPropertiesMapping.d.ts +15 -0
- package/dist/components/ImportFileUpload.d.ts +3 -0
- package/dist/components/ImportNewPropertyFieldPreview.d.ts +10 -0
- package/dist/components/ImportSaveInProgress.d.ts +9 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/export_import/ExportCollectionAction.d.ts +10 -0
- package/dist/export_import/ImportCollectionAction.d.ts +13 -0
- package/dist/export_import/export.d.ts +10 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useImportConfig.d.ts +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +966 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +3 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/column_mapping.d.ts +22 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/useImportExportPlugin.d.ts +14 -0
- package/dist/utils/data.d.ts +11 -0
- package/dist/utils/file_to_json.d.ts +11 -0
- package/dist/utils/get_import_inference_type.d.ts +2 -0
- package/dist/utils/get_properties_mapping.d.ts +3 -0
- package/dist/utils/index.d.ts +4 -0
- package/package.json +103 -0
- package/src/components/DataNewPropertiesMapping.tsx +128 -0
- package/src/components/ImportFileUpload.tsx +28 -0
- package/src/components/ImportNewPropertyFieldPreview.tsx +67 -0
- package/src/components/ImportSaveInProgress.tsx +103 -0
- package/src/components/index.ts +4 -0
- package/src/export_import/ExportCollectionAction.tsx +243 -0
- package/src/export_import/ImportCollectionAction.tsx +419 -0
- package/src/export_import/export.ts +198 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useImportConfig.tsx +28 -0
- package/src/index.ts +5 -0
- package/src/types/column_mapping.ts +32 -0
- package/src/types/index.ts +1 -0
- package/src/useImportExportPlugin.tsx +23 -0
- package/src/utils/data.ts +210 -0
- package/src/utils/file_to_json.ts +83 -0
- package/src/utils/get_import_inference_type.ts +27 -0
- package/src/utils/get_properties_mapping.ts +60 -0
- package/src/utils/index.ts +4 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayValuesCount,
|
|
3
|
+
Entity,
|
|
4
|
+
EntityReference,
|
|
5
|
+
getArrayValuesCount,
|
|
6
|
+
getValueInPath,
|
|
7
|
+
ResolvedEntityCollection,
|
|
8
|
+
ResolvedProperties,
|
|
9
|
+
ResolvedProperty
|
|
10
|
+
} from "@firecms/core";
|
|
11
|
+
|
|
12
|
+
interface Header {
|
|
13
|
+
key: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
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"
|
|
24
|
+
) {
|
|
25
|
+
const properties = collection.properties;
|
|
26
|
+
|
|
27
|
+
if (exportType === "csv") {
|
|
28
|
+
const arrayValuesCount = flattenArrays ? getArrayValuesCount(data.map(d => d.values)) : {};
|
|
29
|
+
const headers = getExportHeaders(properties, additionalHeaders, arrayValuesCount);
|
|
30
|
+
const exportableData = getCSVExportableData(data, additionalData, properties, headers, dateExportType);
|
|
31
|
+
const headersData = entryToCSVRow(headers.map(h => h.label));
|
|
32
|
+
const csvData = exportableData.map(entry => entryToCSVRow(entry));
|
|
33
|
+
downloadBlob([headersData, ...csvData], `${collection.name}.csv`, "text/csv");
|
|
34
|
+
} else {
|
|
35
|
+
const exportableData = getJsonExportableData(data, additionalData, properties, dateExportType);
|
|
36
|
+
const json = JSON.stringify(exportableData, null, 2);
|
|
37
|
+
downloadBlob([json], `${collection.name}.json`, "application/json");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getCSVExportableData(data: Entity<any>[],
|
|
42
|
+
additionalData: Record<string, any>[] | undefined,
|
|
43
|
+
properties: ResolvedProperties,
|
|
44
|
+
headers: Header[],
|
|
45
|
+
dateExportType: "timestamp" | "string"
|
|
46
|
+
) {
|
|
47
|
+
|
|
48
|
+
const mergedData: any[] = data.map(e => ({
|
|
49
|
+
id: e.id,
|
|
50
|
+
...processValuesForExport(e.values, properties, "csv", dateExportType)
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
if (additionalData) {
|
|
54
|
+
additionalData.forEach((additional, index) => {
|
|
55
|
+
mergedData[index] = { ...mergedData[index], ...additional };
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return mergedData && mergedData.map((entry) => {
|
|
60
|
+
return headers.map((header) => getValueInPath(entry, header.key));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getJsonExportableData(data: Entity<any>[],
|
|
65
|
+
additionalData: Record<string, any>[] | undefined,
|
|
66
|
+
properties: ResolvedProperties,
|
|
67
|
+
dateExportType: "timestamp" | "string"
|
|
68
|
+
) {
|
|
69
|
+
|
|
70
|
+
const mergedData: any[] = data.map(e => ({
|
|
71
|
+
id: e.id,
|
|
72
|
+
...processValuesForExport(e.values, properties, "json", dateExportType)
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
if (additionalData) {
|
|
76
|
+
additionalData.forEach((additional, index) => {
|
|
77
|
+
mergedData[index] = { ...mergedData[index], ...additional };
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return mergedData;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getExportHeaders<M extends Record<string, any>>(properties: ResolvedProperties<M>,
|
|
85
|
+
additionalHeaders: string[] | undefined,
|
|
86
|
+
arrayValuesCount?: ArrayValuesCount): Header[] {
|
|
87
|
+
|
|
88
|
+
const headers: Header[] = [
|
|
89
|
+
{ label: "id", key: "id" },
|
|
90
|
+
...Object.entries(properties)
|
|
91
|
+
.flatMap(([childKey, property]) => {
|
|
92
|
+
if (arrayValuesCount && arrayValuesCount[childKey] > 1) {
|
|
93
|
+
return Array.from({ length: arrayValuesCount[childKey] },
|
|
94
|
+
(_, i) => getHeaders(property as ResolvedProperty, `${childKey}[${i}]`, ""))
|
|
95
|
+
.flat();
|
|
96
|
+
} else {
|
|
97
|
+
return getHeaders(property as ResolvedProperty, childKey, "");
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (additionalHeaders) {
|
|
103
|
+
headers.push(...additionalHeaders.map(h => ({ label: h, key: h })));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return headers;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get headers for property. There could be more than one header per property
|
|
111
|
+
* @param property
|
|
112
|
+
* @param propertyKey
|
|
113
|
+
* @param prefix
|
|
114
|
+
*/
|
|
115
|
+
function getHeaders(property: ResolvedProperty, propertyKey: string, prefix = ""): Header[] {
|
|
116
|
+
const currentKey = prefix ? `${prefix}.${propertyKey}` : propertyKey;
|
|
117
|
+
if (property.dataType === "map" && property.properties) {
|
|
118
|
+
return Object.entries(property.properties)
|
|
119
|
+
.map(([childKey, p]) => getHeaders(p, childKey, currentKey))
|
|
120
|
+
.flat();
|
|
121
|
+
} else {
|
|
122
|
+
return [{ label: currentKey, key: currentKey }];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function processValueForExport(inputValue: any,
|
|
127
|
+
property: ResolvedProperty,
|
|
128
|
+
exportType: "csv" | "json",
|
|
129
|
+
dateExportType: "timestamp" | "string"
|
|
130
|
+
): any {
|
|
131
|
+
|
|
132
|
+
let value;
|
|
133
|
+
if (property.dataType === "map" && property.properties) {
|
|
134
|
+
value = processValuesForExport(inputValue, property.properties as ResolvedProperties, exportType, dateExportType);
|
|
135
|
+
} else if (property.dataType === "array") {
|
|
136
|
+
if (property.of && Array.isArray(inputValue)) {
|
|
137
|
+
if (Array.isArray(property.of)) {
|
|
138
|
+
value = property.of.map((p, i) => processValueForExport(inputValue[i], p, exportType, dateExportType));
|
|
139
|
+
} else if (property.of.dataType === "map") {
|
|
140
|
+
value = exportType === "csv"
|
|
141
|
+
? inputValue.map((e) => JSON.stringify(e))
|
|
142
|
+
: inputValue.map((e) => processValueForExport(e, property.of as ResolvedProperty, exportType, dateExportType));
|
|
143
|
+
;
|
|
144
|
+
} else {
|
|
145
|
+
value = inputValue.map((e) => processValueForExport(e, property.of as ResolvedProperty, exportType, dateExportType));
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
value = inputValue;
|
|
149
|
+
}
|
|
150
|
+
} else if (property.dataType === "reference" && inputValue instanceof EntityReference) {
|
|
151
|
+
const ref = inputValue ? inputValue as EntityReference : undefined;
|
|
152
|
+
value = ref ? ref.pathWithId : null;
|
|
153
|
+
} else if (property.dataType === "date" && inputValue instanceof Date) {
|
|
154
|
+
value = inputValue ? (dateExportType === "timestamp" ? inputValue.getTime() : inputValue.toISOString()) : null;
|
|
155
|
+
} else {
|
|
156
|
+
value = inputValue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function processValuesForExport<M extends Record<string, any>>
|
|
163
|
+
(inputValues: Record<keyof M, any>,
|
|
164
|
+
properties: ResolvedProperties<M>,
|
|
165
|
+
exportType: "csv" | "json",
|
|
166
|
+
dateExportType: "timestamp" | "string"
|
|
167
|
+
): Record<keyof M, any> {
|
|
168
|
+
const updatedValues = Object.entries(properties)
|
|
169
|
+
.map(([key, property]) => {
|
|
170
|
+
const inputValue = inputValues && (inputValues)[key];
|
|
171
|
+
const updatedValue = processValueForExport(inputValue, property as ResolvedProperty, exportType, dateExportType);
|
|
172
|
+
if (updatedValue === undefined) return {};
|
|
173
|
+
return ({ [key]: updatedValue });
|
|
174
|
+
})
|
|
175
|
+
.reduce((a, b) => ({ ...a, ...b }), {}) as Record<keyof M, any>;
|
|
176
|
+
return { ...inputValues, ...updatedValues };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function entryToCSVRow(entry: any[]) {
|
|
180
|
+
return entry
|
|
181
|
+
.map((v: any) => {
|
|
182
|
+
if (v === null || v === undefined) return "";
|
|
183
|
+
if (Array.isArray(v))
|
|
184
|
+
return "\"" + JSON.stringify(v).replaceAll("\"", "\\\"") + "\"";
|
|
185
|
+
const s = String(v);
|
|
186
|
+
return "\"" + s.replaceAll("\"", "\"\"") + "\"";
|
|
187
|
+
})
|
|
188
|
+
.join(",") + "\r\n";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function downloadBlob(content: BlobPart[], filename: string, contentType: string) {
|
|
192
|
+
const blob = new Blob(content, { type: contentType });
|
|
193
|
+
const url = URL.createObjectURL(blob);
|
|
194
|
+
const pom = document.createElement("a");
|
|
195
|
+
pom.href = url;
|
|
196
|
+
pom.setAttribute("download", filename);
|
|
197
|
+
pom.click();
|
|
198
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useImportConfig";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { DataType, Entity, getPropertyInPath, Properties, Property } from "@firecms/core";
|
|
3
|
+
import { DataTypeMapping, ImportConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export const useImportConfig = (): ImportConfig => {
|
|
6
|
+
|
|
7
|
+
const [inUse, setInUse] = useState<boolean>(false);
|
|
8
|
+
const [idColumn, setIdColumn] = useState<string | undefined>();
|
|
9
|
+
const [importData, setImportData] = useState<object[]>([]);
|
|
10
|
+
const [entities, setEntities] = useState<Entity<any>[]>([]);
|
|
11
|
+
const [headersMapping, setHeadersMapping] = useState<Record<string, string | null>>({});
|
|
12
|
+
const [originProperties, setOriginProperties] = useState<Record<string, Property>>({});
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
inUse,
|
|
16
|
+
setInUse,
|
|
17
|
+
idColumn,
|
|
18
|
+
setIdColumn,
|
|
19
|
+
entities,
|
|
20
|
+
setEntities,
|
|
21
|
+
importData,
|
|
22
|
+
setImportData,
|
|
23
|
+
headersMapping,
|
|
24
|
+
setHeadersMapping,
|
|
25
|
+
originProperties,
|
|
26
|
+
setOriginProperties,
|
|
27
|
+
};
|
|
28
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DataType, Entity, Property } from "@firecms/core";
|
|
3
|
+
|
|
4
|
+
export type ImportConfig = {
|
|
5
|
+
|
|
6
|
+
inUse: boolean;
|
|
7
|
+
setInUse: React.Dispatch<React.SetStateAction<boolean>>;
|
|
8
|
+
|
|
9
|
+
idColumn: string | undefined;
|
|
10
|
+
setIdColumn: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
11
|
+
|
|
12
|
+
importData: object[];
|
|
13
|
+
setImportData: React.Dispatch<React.SetStateAction<object[]>>;
|
|
14
|
+
|
|
15
|
+
entities: Entity<any>[];
|
|
16
|
+
setEntities: React.Dispatch<React.SetStateAction<Entity<any>[]>>;
|
|
17
|
+
|
|
18
|
+
// mapping of the column name in the import file to the property key in the data model
|
|
19
|
+
headersMapping: Record<string, string | null>;
|
|
20
|
+
setHeadersMapping: React.Dispatch<React.SetStateAction<Record<string, string | null>>>;
|
|
21
|
+
|
|
22
|
+
originProperties: Record<string, Property>;
|
|
23
|
+
setOriginProperties: React.Dispatch<React.SetStateAction<Record<string, Property>>>;
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DataTypeMapping = {
|
|
28
|
+
from: DataType;
|
|
29
|
+
fromSubtype?: DataType;
|
|
30
|
+
to: DataType;
|
|
31
|
+
toSubtype?: DataType;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./column_mapping";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { EntityCollection, FireCMSPlugin } from "@firecms/core";
|
|
3
|
+
import { ImportCollectionAction } from "./export_import/ImportCollectionAction";
|
|
4
|
+
import { ExportCollectionAction } from "./export_import/ExportCollectionAction";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
export function useImportExportPlugin(props?: ImportExportPluginProps): FireCMSPlugin<any, any, any, ImportExportPluginProps> {
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
name: "Import/Export",
|
|
13
|
+
collections: {
|
|
14
|
+
CollectionActions: [ImportCollectionAction, ExportCollectionAction],
|
|
15
|
+
collectionActionsProps: props
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ImportExportPluginProps = {
|
|
21
|
+
exportAllowed?: (props: { collectionEntitiesCount: number, path: string, collection: EntityCollection }) => boolean;
|
|
22
|
+
notAllowedView: React.ReactNode;
|
|
23
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { DataType, Entity, EntityReference, getPropertyInPath, Properties } from "@firecms/core";
|
|
2
|
+
import { unflattenObject } from "./file_to_json";
|
|
3
|
+
|
|
4
|
+
type DataTypeMapping = {
|
|
5
|
+
from: DataType;
|
|
6
|
+
fromSubtype?: DataType;
|
|
7
|
+
to: DataType;
|
|
8
|
+
toSubtype?: DataType;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function convertDataToEntity(data: Record<any, any>,
|
|
12
|
+
idColumn: string | undefined,
|
|
13
|
+
headersMapping: Record<string, string | null>,
|
|
14
|
+
properties: Properties,
|
|
15
|
+
propertiesMapping: Record<string, DataTypeMapping>,
|
|
16
|
+
path: string): Entity<any> {
|
|
17
|
+
const flatObject = flattenEntry(data);
|
|
18
|
+
if (idColumn)
|
|
19
|
+
delete flatObject[idColumn];
|
|
20
|
+
const mappedKeysObject = Object.entries(flatObject)
|
|
21
|
+
.map(([key, value]) => {
|
|
22
|
+
const mappedKey = headersMapping[key] ?? key;
|
|
23
|
+
|
|
24
|
+
const mappedProperty = getPropertyInPath(properties, mappedKey);
|
|
25
|
+
if (!mappedProperty)
|
|
26
|
+
return {};
|
|
27
|
+
|
|
28
|
+
const propertyMapping = propertiesMapping[mappedKey];
|
|
29
|
+
let valueResult = value;
|
|
30
|
+
if (propertyMapping) {
|
|
31
|
+
valueResult = processValueMapping(value, propertyMapping);
|
|
32
|
+
}
|
|
33
|
+
return ({
|
|
34
|
+
[mappedKey]: valueResult
|
|
35
|
+
});
|
|
36
|
+
})
|
|
37
|
+
.reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
|
38
|
+
const values = unflattenObject(mappedKeysObject);
|
|
39
|
+
return {
|
|
40
|
+
id: idColumn ? data[idColumn] : undefined,
|
|
41
|
+
values,
|
|
42
|
+
path
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function flattenEntry(obj: any, parent = ""): any {
|
|
47
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
48
|
+
const prefixedKey = parent ? `${parent}.${key}` : key;
|
|
49
|
+
|
|
50
|
+
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
51
|
+
Object.assign(acc, flattenEntry(obj[key], prefixedKey));
|
|
52
|
+
} else {
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
acc[prefixedKey] = obj[key];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return acc;
|
|
58
|
+
}, {});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// export function convertDataEntryValue({
|
|
62
|
+
// key,
|
|
63
|
+
// fullKey,
|
|
64
|
+
// value,
|
|
65
|
+
// idColumn,
|
|
66
|
+
// headersMapping,
|
|
67
|
+
// properties,
|
|
68
|
+
// propertiesMapping
|
|
69
|
+
// }: {
|
|
70
|
+
// key?: string,
|
|
71
|
+
// fullKey: string,
|
|
72
|
+
// value: any,
|
|
73
|
+
// idColumn?: string,
|
|
74
|
+
// headersMapping: Record<string, string | null>,
|
|
75
|
+
// properties: Properties,
|
|
76
|
+
// propertiesMapping: Record<string, DataTypeMapping>
|
|
77
|
+
// }): any {
|
|
78
|
+
//
|
|
79
|
+
// if (value === undefined) return value;
|
|
80
|
+
// if (value === null) return value;
|
|
81
|
+
//
|
|
82
|
+
// if (Array.isArray(value)) {
|
|
83
|
+
// const mappedKey = headersMapping[fullKey] || fullKey;
|
|
84
|
+
// const valueMapping = propertiesMapping[mappedKey];
|
|
85
|
+
// return processValueMapping(value, valueMapping);
|
|
86
|
+
// // return value.map(v => convertDataEntryValue({ value: v, fullKey, idColumn, headersMapping, propertiesMapping }));
|
|
87
|
+
// } else if (typeof value === "object") {
|
|
88
|
+
// return convertDataObjectValue({
|
|
89
|
+
// key,
|
|
90
|
+
// fullKey,
|
|
91
|
+
// value,
|
|
92
|
+
// idColumn,
|
|
93
|
+
// headersMapping,
|
|
94
|
+
// properties,
|
|
95
|
+
// propertiesMapping
|
|
96
|
+
// });
|
|
97
|
+
// } else {
|
|
98
|
+
// const mappedKey = headersMapping[fullKey] || fullKey;
|
|
99
|
+
// const valueMapping = propertiesMapping[mappedKey];
|
|
100
|
+
// return processValueMapping(value, valueMapping);
|
|
101
|
+
// }
|
|
102
|
+
// }
|
|
103
|
+
|
|
104
|
+
// export function convertDataObjectValue({
|
|
105
|
+
// key,
|
|
106
|
+
// fullKey,
|
|
107
|
+
// value,
|
|
108
|
+
// idColumn,
|
|
109
|
+
// headersMapping,
|
|
110
|
+
// properties,
|
|
111
|
+
// propertiesMapping
|
|
112
|
+
// }: {
|
|
113
|
+
// key?: string,
|
|
114
|
+
// fullKey?: string,
|
|
115
|
+
// value: object,
|
|
116
|
+
// idColumn?: string,
|
|
117
|
+
// headersMapping: Record<string, string | null>,
|
|
118
|
+
// properties: Properties,
|
|
119
|
+
// propertiesMapping: Record<string, DataTypeMapping>
|
|
120
|
+
// }): object {
|
|
121
|
+
//
|
|
122
|
+
// if (value instanceof Date) {
|
|
123
|
+
// const mappedKey = fullKey ? headersMapping[fullKey] || fullKey : undefined;
|
|
124
|
+
// const valueMapping = mappedKey ? propertiesMapping[mappedKey] : undefined;
|
|
125
|
+
// return processValueMapping(value, valueMapping);
|
|
126
|
+
// }
|
|
127
|
+
//
|
|
128
|
+
// return Object.entries(value)
|
|
129
|
+
// .map(([childKey, childValue]) => {
|
|
130
|
+
// const childFullKey = fullKey ? `${fullKey}.${childKey}` : childKey;
|
|
131
|
+
// const mappedKey = headersMapping[childFullKey] ?? childFullKey;
|
|
132
|
+
// const mappedKeyLastPart = mappedKey.split(".").slice(-1)[0];
|
|
133
|
+
// const property = getPropertyInPath(properties, mappedKey);
|
|
134
|
+
// // if (!property) {
|
|
135
|
+
// // console.log("ppp", { properties, mappedKey, })
|
|
136
|
+
// // return {};
|
|
137
|
+
// // }
|
|
138
|
+
// return ({
|
|
139
|
+
// [mappedKeyLastPart]: convertDataEntryValue({
|
|
140
|
+
// key: childKey,
|
|
141
|
+
// fullKey: childFullKey,
|
|
142
|
+
// value: childValue,
|
|
143
|
+
// idColumn,
|
|
144
|
+
// headersMapping,
|
|
145
|
+
// properties,
|
|
146
|
+
// propertiesMapping
|
|
147
|
+
// })
|
|
148
|
+
// });
|
|
149
|
+
// })
|
|
150
|
+
// .reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
|
151
|
+
// }
|
|
152
|
+
|
|
153
|
+
export function processValueMapping(value: any, valueMapping?: DataTypeMapping): any {
|
|
154
|
+
if (valueMapping === undefined) return value;
|
|
155
|
+
const {
|
|
156
|
+
from,
|
|
157
|
+
to
|
|
158
|
+
} = valueMapping;
|
|
159
|
+
if (from === "array" && to === "array" && valueMapping.fromSubtype && valueMapping.toSubtype && Array.isArray(value)) {
|
|
160
|
+
return value.map(v => processValueMapping(v, {
|
|
161
|
+
from: valueMapping.fromSubtype!,
|
|
162
|
+
to: valueMapping.toSubtype!
|
|
163
|
+
}));
|
|
164
|
+
} else if (from === "string" && to === "number" && typeof value === "string") {
|
|
165
|
+
return Number(value);
|
|
166
|
+
} else if (from === "string" && to === "array" && valueMapping.toSubtype && typeof value === "string") {
|
|
167
|
+
return value.split(",").map((v: string) => processValueMapping(v, {
|
|
168
|
+
from: "string",
|
|
169
|
+
to: valueMapping.toSubtype!
|
|
170
|
+
}));
|
|
171
|
+
} else if (from === "string" && to === "boolean") {
|
|
172
|
+
return value === "true";
|
|
173
|
+
} else if (from === "number" && to === "boolean") {
|
|
174
|
+
return value === 1;
|
|
175
|
+
} else if (from === "boolean" && to === "number") {
|
|
176
|
+
return value ? 1 : 0;
|
|
177
|
+
} else if (from === "boolean" && to === "string") {
|
|
178
|
+
return value ? "true" : "false";
|
|
179
|
+
} else if (from === "number" && to === "string" && typeof value === "number") {
|
|
180
|
+
return value.toString();
|
|
181
|
+
} else if (from === "string" && to === "array" && typeof value === "string") {
|
|
182
|
+
return value.split(",").map((v: string) => v.trim());
|
|
183
|
+
} else if (from === "string" && to === "date" && typeof value === "string") {
|
|
184
|
+
try {
|
|
185
|
+
return new Date(value);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
} else if (from === "date" && to === "string") {
|
|
190
|
+
return value instanceof Date && value.toISOString();
|
|
191
|
+
} else if (from === "number" && to === "date" && typeof value === "number") {
|
|
192
|
+
try {
|
|
193
|
+
return new Date(value);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
} else if (from === "string" && to === "reference" && typeof value === "string") {
|
|
198
|
+
// split value into path and entityId (entityId is the last part of the path, after the last /)
|
|
199
|
+
const path = value.split("/").slice(0, -1).join("/");
|
|
200
|
+
const entityId = value.split("/").slice(-1)[0];
|
|
201
|
+
return new EntityReference(entityId, path);
|
|
202
|
+
|
|
203
|
+
} else if (from === to) {
|
|
204
|
+
return value;
|
|
205
|
+
} else if (from === "array" && to === "string" && Array.isArray(value)) {
|
|
206
|
+
return value.join(",");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as XLSX from "xlsx";
|
|
2
|
+
|
|
3
|
+
export function convertFileToJson(file: File): Promise<object[]> {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
// check if file is a JSON file
|
|
6
|
+
if (file.type === "application/json") {
|
|
7
|
+
const reader = new FileReader();
|
|
8
|
+
reader.onload = function (e) {
|
|
9
|
+
const data = e.target?.result as string;
|
|
10
|
+
const jsonData = JSON.parse(data);
|
|
11
|
+
resolve(jsonData);
|
|
12
|
+
};
|
|
13
|
+
reader.readAsText(file);
|
|
14
|
+
} else {
|
|
15
|
+
const reader = new FileReader();
|
|
16
|
+
reader.onload = function (e) {
|
|
17
|
+
|
|
18
|
+
const data = new Uint8Array(e.target?.result as ArrayBuffer);
|
|
19
|
+
const workbook = XLSX.read(data,
|
|
20
|
+
{
|
|
21
|
+
type: "array",
|
|
22
|
+
codepage: 65001,
|
|
23
|
+
cellDates: true,
|
|
24
|
+
});
|
|
25
|
+
const worksheetName = workbook.SheetNames[0];
|
|
26
|
+
const worksheet = workbook.Sheets[worksheetName];
|
|
27
|
+
const parsedData: Array<any> = XLSX.utils.sheet_to_json(worksheet);
|
|
28
|
+
const cleanedData = parsedData.map(mapJsonParse);
|
|
29
|
+
const jsonData = cleanedData.map(unflattenObject);
|
|
30
|
+
resolve(jsonData);
|
|
31
|
+
};
|
|
32
|
+
reader.readAsArrayBuffer(file);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mapJsonParse(obj: Record<string, any>) {
|
|
38
|
+
return Object.keys(obj).reduce((acc: Record<string, any>, key) => {
|
|
39
|
+
try {
|
|
40
|
+
acc[key] = JSON.parse(obj[key]);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
acc[key] = obj[key];
|
|
43
|
+
}
|
|
44
|
+
return acc;
|
|
45
|
+
}, {});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Take an object with keys of type `address.street`, `address.city` and
|
|
50
|
+
* convert it to an object with nested objects like `{ address: { street: ..., city: ... } }`
|
|
51
|
+
* @param flatObj
|
|
52
|
+
*/
|
|
53
|
+
export function unflattenObject(flatObj: { [key: string]: any }) {
|
|
54
|
+
return Object.keys(flatObj).reduce((nestedObj, key) => {
|
|
55
|
+
let currentObj = nestedObj;
|
|
56
|
+
const keyParts = key.split(".");
|
|
57
|
+
keyParts.forEach((keyPart, i) => {
|
|
58
|
+
|
|
59
|
+
if (/^[\w]+\[\d+\]$/.test(keyPart)) {
|
|
60
|
+
const mainPropertyName = keyPart.slice(0, keyPart.indexOf("["));
|
|
61
|
+
const index = parseInt(keyPart.slice(keyPart.indexOf("[") + 1, keyPart.indexOf("]")));
|
|
62
|
+
|
|
63
|
+
if (!currentObj[mainPropertyName]) {
|
|
64
|
+
currentObj[mainPropertyName] = []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (i !== keyParts.length - 1) {
|
|
68
|
+
currentObj[mainPropertyName][index] = currentObj[mainPropertyName][index] || {};
|
|
69
|
+
currentObj = currentObj[mainPropertyName][index];
|
|
70
|
+
} else {
|
|
71
|
+
currentObj[mainPropertyName][index] = flatObj[key];
|
|
72
|
+
}
|
|
73
|
+
} else if (i !== keyParts.length - 1) {
|
|
74
|
+
currentObj[keyPart] = currentObj[keyPart] || {};
|
|
75
|
+
currentObj = currentObj[keyPart];
|
|
76
|
+
} else {
|
|
77
|
+
currentObj[keyPart] = flatObj[key];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
});
|
|
81
|
+
return nestedObj;
|
|
82
|
+
}, {} as { [key: string]: any });
|
|
83
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DataType } from "@firecms/core";
|
|
2
|
+
|
|
3
|
+
export function getInferenceType(value: any): DataType {
|
|
4
|
+
if (typeof value === "number")
|
|
5
|
+
return "number";
|
|
6
|
+
else if (typeof value === "string")
|
|
7
|
+
return "string";
|
|
8
|
+
else if (typeof value === "boolean")
|
|
9
|
+
return "boolean";
|
|
10
|
+
else if (value instanceof Date)
|
|
11
|
+
return "date";
|
|
12
|
+
else if (Array.isArray(value))
|
|
13
|
+
return "array";
|
|
14
|
+
return "map";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
function isUnixTimestamp(num: number): boolean {
|
|
19
|
+
const numString = num.toString();
|
|
20
|
+
// check if the number has 13 digits
|
|
21
|
+
const isLengthValid = numString.length === 13;
|
|
22
|
+
|
|
23
|
+
// check if it falls in the expected Unix timestamp range (from 1970 to 2100)
|
|
24
|
+
const isInRange = num >= 0 && num <= 4102444800000;
|
|
25
|
+
|
|
26
|
+
return isLengthValid && isInRange;
|
|
27
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DataType, getPropertyInPath, Properties, Property } from "@firecms/core";
|
|
2
|
+
import { DataTypeMapping } from "../types";
|
|
3
|
+
|
|
4
|
+
export function getPropertiesMapping(originProperties: Properties, newProperties: Properties): Record<string, DataTypeMapping> {
|
|
5
|
+
|
|
6
|
+
function updateMapping(properties: Record<string, Property>, namespace?: string): Record<string, DataTypeMapping> {
|
|
7
|
+
|
|
8
|
+
const dataMapping: Record<string, DataTypeMapping> = {};
|
|
9
|
+
|
|
10
|
+
Object.keys(properties).forEach((key) => {
|
|
11
|
+
|
|
12
|
+
const currentKey = namespace ? `${namespace}.${key}` : key;
|
|
13
|
+
|
|
14
|
+
const property = getPropertyInPath(properties, key) as Property;
|
|
15
|
+
const inferredProperty = getPropertyInPath(originProperties, currentKey) as Property;
|
|
16
|
+
|
|
17
|
+
if (property) {
|
|
18
|
+
if (property.dataType === "map" && property.properties) {
|
|
19
|
+
const nestedMapping = updateMapping(property.properties as Record<string, Property>, currentKey);
|
|
20
|
+
Object.keys(nestedMapping).forEach((nestedKey) => {
|
|
21
|
+
dataMapping[`${currentKey}.${nestedKey}`] = nestedMapping[nestedKey];
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (inferredProperty) {
|
|
27
|
+
|
|
28
|
+
const from = inferredProperty.dataType;
|
|
29
|
+
const to = property.dataType;
|
|
30
|
+
let fromSubtype: DataType | undefined;
|
|
31
|
+
let toSubtype: DataType | undefined;
|
|
32
|
+
|
|
33
|
+
if (property.dataType === "array" && property.of) {
|
|
34
|
+
toSubtype = (property.of as Property).dataType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (inferredProperty?.dataType === "array" && inferredProperty?.of) {
|
|
38
|
+
fromSubtype = (inferredProperty.of as Property).dataType;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (from !== to || fromSubtype !== toSubtype) {
|
|
42
|
+
console.log(`Mapping`, { currentKey, inferredProperty, property, from, to, fromSubtype, toSubtype });
|
|
43
|
+
dataMapping[key] = {
|
|
44
|
+
from,
|
|
45
|
+
to,
|
|
46
|
+
fromSubtype,
|
|
47
|
+
toSubtype
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return dataMapping;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return updateMapping(newProperties);
|
|
60
|
+
}
|