Merge branch '114-add-import-button' into 'master'
Resolve "Add Import button" Closes #114 See merge request bloodyhealth/drip!48
This commit is contained in:
@@ -137,6 +137,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-fs')
|
||||
compile project(':react-native-document-picker')
|
||||
compile project(':react-native-share')
|
||||
compile project(':realm')
|
||||
compile project(':react-native-svg')
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.drip;
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.rnfs.RNFSPackage;
|
||||
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
|
||||
import cl.json.RNSharePackage;
|
||||
import cl.json.ShareApplication;
|
||||
import io.realm.react.RealmReactPackage;
|
||||
@@ -27,6 +29,8 @@ public class MainApplication extends Application implements ReactApplication, Sh
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new RNFSPackage(),
|
||||
new ReactNativeDocumentPicker(),
|
||||
new RNSharePackage(),
|
||||
new RealmReactPackage(),
|
||||
new SvgPackage()
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
rootProject.name = 'drip'
|
||||
include ':react-native-fs'
|
||||
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
|
||||
include ':react-native-document-picker'
|
||||
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
|
||||
include ':react-native-share'
|
||||
project(':react-native-share').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-share/android')
|
||||
include ':realm'
|
||||
|
||||
+30
-7
@@ -1,10 +1,33 @@
|
||||
export const settings = {
|
||||
errors: {
|
||||
noData: 'There is no data to export',
|
||||
couldNotConvert: 'Could not convert data to CSV',
|
||||
problemSharing: 'There was a problem sharing the data export file'
|
||||
shared: {
|
||||
cancel: 'Cancel',
|
||||
errorTitle: 'Error',
|
||||
successTitle: 'Success'
|
||||
},
|
||||
exportTitle: 'My Drip data export',
|
||||
exportSubject: 'My Drip data export',
|
||||
buttonLabel: 'Export data'
|
||||
export: {
|
||||
errors: {
|
||||
noData: 'There is no data to export',
|
||||
couldNotConvert: 'Could not convert data to CSV',
|
||||
problemSharing: 'There was a problem sharing the data export file'
|
||||
},
|
||||
title: 'My Drip data export',
|
||||
subject: 'My Drip data export',
|
||||
button: 'Export data',
|
||||
},
|
||||
import: {
|
||||
button: 'Import data',
|
||||
title: 'Keep existing data?',
|
||||
message: `There are two options for the import:
|
||||
1. Keep existing cycle days and replace only the ones in the import file.
|
||||
2. Delete all existing cycle days and import cycle days from file.`,
|
||||
replaceOption: 'Import and replace',
|
||||
deleteOption: 'Import and delete existing',
|
||||
errors: {
|
||||
couldNotOpenFile: 'Could not open file',
|
||||
postFix: 'No data was imported or changed'
|
||||
},
|
||||
success: {
|
||||
message: 'Data successfully imported'
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
-27
@@ -7,9 +7,12 @@ import {
|
||||
} from 'react-native'
|
||||
|
||||
import Share from 'react-native-share'
|
||||
import getDataAsCsvDataUri from '../lib/export-to-csv'
|
||||
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker'
|
||||
import rnfs from 'react-native-fs'
|
||||
import styles from '../styles/index'
|
||||
import { settings as labels } from './labels'
|
||||
import getDataAsCsvDataUri from '../lib/import-export/export-to-csv'
|
||||
import importCsv from '../lib/import-export/import-from-csv'
|
||||
|
||||
export default class Settings extends Component {
|
||||
render() {
|
||||
@@ -18,32 +21,14 @@ export default class Settings extends Component {
|
||||
<View style={styles.homeButtons}>
|
||||
<View style={styles.homeButton}>
|
||||
<Button
|
||||
onPress={async () => {
|
||||
let data
|
||||
try {
|
||||
data = getDataAsCsvDataUri()
|
||||
if (!data) {
|
||||
return Alert.alert(labels.errors.noData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return Alert.alert(labels.errors.couldNotConvert)
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.open({
|
||||
title: labels.exportTitle,
|
||||
url: data,
|
||||
subject: labels.exportSubject,
|
||||
type: 'text/csv',
|
||||
showAppsToView: true
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return Alert.alert(labels.errors.problemSharing)
|
||||
}
|
||||
}}
|
||||
title={labels.buttonLabel}>
|
||||
onPress={ openShareDialogAndExport }
|
||||
title={labels.export.button}>
|
||||
</Button>
|
||||
</View>
|
||||
<View style={styles.homeButton}>
|
||||
<Button
|
||||
title={labels.import.button}
|
||||
onPress={ openImportDialogAndImport }>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
@@ -51,3 +36,85 @@ export default class Settings extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function openShareDialogAndExport() {
|
||||
let data
|
||||
try {
|
||||
data = getDataAsCsvDataUri()
|
||||
if (!data) {
|
||||
return alertError(labels.errors.noData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return alertError(labels.errors.couldNotConvert)
|
||||
}
|
||||
|
||||
try {
|
||||
await Share.open({
|
||||
title: labels.export.title,
|
||||
url: data,
|
||||
subject: labels.export.subject,
|
||||
type: 'text/csv',
|
||||
showAppsToView: true
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return alertError(labels.export.errors.problemSharing)
|
||||
}
|
||||
}
|
||||
|
||||
function openImportDialogAndImport() {
|
||||
Alert.alert(
|
||||
labels.import.title,
|
||||
labels.import.message,
|
||||
[{
|
||||
text: labels.import.replaceOption,
|
||||
onPress: () => getFileContentAndImport({ deleteExisting: false })
|
||||
}, {
|
||||
text: labels.import.deleteOption,
|
||||
onPress: () => getFileContentAndImport({ deleteExisting: true })
|
||||
}, {
|
||||
text: labels.shared.cancel, style: 'cancel', onPress: () => { }
|
||||
}]
|
||||
)
|
||||
}
|
||||
|
||||
async function getFileContentAndImport({ deleteExisting }) {
|
||||
let fileInfo
|
||||
try {
|
||||
fileInfo = await new Promise((resolve, reject) => {
|
||||
DocumentPicker.show({
|
||||
filetype: [DocumentPickerUtil.allFiles()],
|
||||
}, (err, res) => {
|
||||
if (err) return reject(err)
|
||||
resolve(res)
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
// because cancelling also triggers an error, we do nothing here
|
||||
return
|
||||
}
|
||||
|
||||
let fileContent
|
||||
try {
|
||||
fileContent = await rnfs.readFile(fileInfo.uri, 'utf8')
|
||||
} catch (err) {
|
||||
return importError(labels.import.errors.couldNotOpenFile)
|
||||
}
|
||||
|
||||
try {
|
||||
await importCsv(fileContent, deleteExisting)
|
||||
Alert.alert(labels.import.success.title, labels.import.success.message)
|
||||
} catch(err) {
|
||||
importError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
function alertError(msg) {
|
||||
Alert.alert(labels.shared.errorTitle, msg)
|
||||
}
|
||||
|
||||
function importError(msg) {
|
||||
const postFixed = `${msg}\n\n${labels.import.errors.postFix}`
|
||||
alertError(postFixed)
|
||||
}
|
||||
+31
-15
@@ -1,5 +1,6 @@
|
||||
import Realm from 'realm'
|
||||
import { LocalDate } from 'js-joda'
|
||||
|
||||
import {
|
||||
cycleWithTempAndNoMucusShift,
|
||||
cycleWithFhm,
|
||||
@@ -196,24 +197,37 @@ function getPreviousTemperature(cycleDay) {
|
||||
return winner.temperature.value
|
||||
}
|
||||
|
||||
function getColumnNamesForCsv() {
|
||||
return getPrefixedKeys('CycleDay')
|
||||
const schema = db.schema.reduce((acc, curr) => {
|
||||
acc[curr.name] = curr.properties
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
function getPrefixedKeys(schemaName, prefix) {
|
||||
const schema = db.schema.find(x => x.name === schemaName).properties
|
||||
return Object.keys(schema).reduce((acc, key) => {
|
||||
const prefixedKey = prefix ? [prefix, key].join('.') : key
|
||||
const childSchemaName = schema[key].objectType
|
||||
if (!childSchemaName) {
|
||||
acc.push(prefixedKey)
|
||||
return acc
|
||||
}
|
||||
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
|
||||
return acc
|
||||
}, [])
|
||||
function tryToCreateCycleDay(day, i) {
|
||||
try {
|
||||
db.create('CycleDay', day)
|
||||
} catch (err) {
|
||||
const msg = `Line ${i + 1}(${day.date}): ${err.message}`
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
function tryToImportWithDelete(cycleDays) {
|
||||
db.write(() => {
|
||||
db.delete(db.objects('CycleDay'))
|
||||
cycleDays.forEach(tryToCreateCycleDay)
|
||||
})
|
||||
}
|
||||
|
||||
function tryToImportWithoutDelete(cycleDays) {
|
||||
db.write(() => {
|
||||
cycleDays.forEach((day, i) => {
|
||||
const existing = getCycleDay(day.date)
|
||||
if (existing) db.delete(existing)
|
||||
tryToCreateCycleDay(day, i)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
saveSymptom,
|
||||
getOrCreateCycleDay,
|
||||
@@ -224,5 +238,7 @@ export {
|
||||
deleteAll,
|
||||
getPreviousTemperature,
|
||||
getCycleDay,
|
||||
getColumnNamesForCsv
|
||||
schema,
|
||||
tryToImportWithDelete,
|
||||
tryToImportWithoutDelete
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEBF0735214455AAEDF56D5 /* libc++.tbd */; };
|
||||
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CD8C8B91E0A747B3883A0D56 /* libz.tbd */; };
|
||||
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */; };
|
||||
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */; };
|
||||
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 84CCEBD3B2C44758853BC941 /* libRNFS.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -357,6 +359,10 @@
|
||||
CD8C8B91E0A747B3883A0D56 /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
|
||||
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */ = {isa = PBXFileReference; name = "RNShare.xcodeproj"; path = "../node_modules/react-native-share/ios/RNShare.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
A8B59389C2FC4F19BD30ABC3 /* libRNShare.a */ = {isa = PBXFileReference; name = "libRNShare.a"; path = "libRNShare.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; name = "RNDocumentPicker.xcodeproj"; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
D211D71BE5A8436A978770A9 /* libRNDocumentPicker.a */ = {isa = PBXFileReference; name = "libRNDocumentPicker.a"; path = "libRNDocumentPicker.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */ = {isa = PBXFileReference; name = "RNFS.xcodeproj"; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
|
||||
84CCEBD3B2C44758853BC941 /* libRNFS.a */ = {isa = PBXFileReference; name = "libRNFS.a"; path = "libRNFS.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -390,6 +396,8 @@
|
||||
62F2A4645AC84CDC9506FF27 /* libc++.tbd in Frameworks */,
|
||||
D91133DCE120440893E2FD2E /* libz.tbd in Frameworks */,
|
||||
26DC04B498C64CE5AAA0C4F8 /* libRNShare.a in Frameworks */,
|
||||
29DF0CCC1AEA4C92BCA0BCCD /* libRNDocumentPicker.a in Frameworks */,
|
||||
17AD822C42A44BADA96BD860 /* libRNFS.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -582,6 +590,8 @@
|
||||
8316A5AD64274E6FBA6C9FFE /* RNSVG.xcodeproj */,
|
||||
7F6C9FA9B66B453CA602B334 /* RealmReact.xcodeproj */,
|
||||
4E6AB77B55F2491487B6124E /* RNShare.xcodeproj */,
|
||||
1F05FE29622E4F21AF70C2B7 /* RNDocumentPicker.xcodeproj */,
|
||||
49089E09BFCF4F3DB209B6E9 /* RNFS.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -1209,12 +1219,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1236,12 +1250,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1266,6 +1284,8 @@
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1289,6 +1309,8 @@
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1319,12 +1341,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1355,12 +1381,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1390,12 +1420,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1425,12 +1459,16 @@
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(SRCROOT)/../node_modules/react-native-svg/ios/**",
|
||||
"$(SRCROOT)/../node_modules/realm/src/**",
|
||||
"$(SRCROOT)/../node_modules/react-native-share/ios",
|
||||
"$(SRCROOT)/../node_modules/react-native-document-picker/ios/RNDocumentPicker",
|
||||
"$(SRCROOT)/../node_modules/react-native-fs/**",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import objectPath from 'object-path'
|
||||
import { Base64 } from 'js-base64'
|
||||
|
||||
import { getColumnNamesForCsv, cycleDaysSortedByDate } from '../db'
|
||||
import { cycleDaysSortedByDate } from '../../db'
|
||||
import getColumnNamesForCsv from './get-csv-column-names'
|
||||
|
||||
export default function makeDataURI() {
|
||||
if (!cycleDaysSortedByDate.length) return null
|
||||
@@ -0,0 +1,19 @@
|
||||
import { schema } from '../../db'
|
||||
|
||||
export default function getColumnNamesForCsv() {
|
||||
return getPrefixedKeys('CycleDay')
|
||||
|
||||
function getPrefixedKeys(schemaName, prefix) {
|
||||
const model = schema[schemaName]
|
||||
return Object.keys(model).reduce((acc, key) => {
|
||||
const prefixedKey = prefix ? [prefix, key].join('.') : key
|
||||
const childSchemaName = model[key].objectType
|
||||
if (!childSchemaName) {
|
||||
acc.push(prefixedKey)
|
||||
return acc
|
||||
}
|
||||
acc.push(...getPrefixedKeys(childSchemaName, prefixedKey))
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import csvParser from 'csvtojson'
|
||||
import isObject from 'isobject'
|
||||
import { schema, tryToImportWithDelete, tryToImportWithoutDelete } from '../../db'
|
||||
import getColumnNamesForCsv from './get-csv-column-names'
|
||||
|
||||
export default async function importCsv(csv, deleteFirst) {
|
||||
const parseFuncs = {
|
||||
bool: val => {
|
||||
if (val.toLowerCase() === 'true') return true
|
||||
if (val.toLowerCase() === 'false') return false
|
||||
return val
|
||||
},
|
||||
int: parseNumberIfPossible,
|
||||
float: parseNumberIfPossible,
|
||||
double: parseNumberIfPossible,
|
||||
string: val => val
|
||||
}
|
||||
|
||||
function parseNumberIfPossible(val) {
|
||||
// Number and parseFloat catch different cases of weirdness,
|
||||
// so we test them both
|
||||
if (isNaN(Number(val)) || isNaN(parseFloat(val))) return val
|
||||
return Number(val)
|
||||
}
|
||||
|
||||
const config = {
|
||||
ignoreEmpty: true,
|
||||
colParser: getColumnNamesForCsv().reduce((acc, colName) => {
|
||||
const path = colName.split('.')
|
||||
const dbType = getDbType(schema.CycleDay, path)
|
||||
acc[colName] = item => {
|
||||
if (item === '') return null
|
||||
return parseFuncs[dbType](item)
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
const cycleDays = await csvParser(config)
|
||||
.fromString(csv)
|
||||
.on('header', validateHeaders)
|
||||
|
||||
//remove symptoms where all fields are null
|
||||
putNullForEmptySymptoms(cycleDays)
|
||||
|
||||
if (deleteFirst) {
|
||||
tryToImportWithDelete(cycleDays)
|
||||
} else {
|
||||
tryToImportWithoutDelete(cycleDays)
|
||||
}
|
||||
}
|
||||
|
||||
function validateHeaders(headers) {
|
||||
const expectedHeaders = getColumnNamesForCsv()
|
||||
if (!headers.every(header => {
|
||||
return expectedHeaders.indexOf(header) > -1
|
||||
})) {
|
||||
const msg = `Expected CSV column titles to be ${expectedHeaders.join()}`
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
function putNullForEmptySymptoms(data) {
|
||||
data.forEach(replaceWithNullIfAllPropertiesAreNull)
|
||||
|
||||
function replaceWithNullIfAllPropertiesAreNull(obj) {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (!isObject(obj[key])) return
|
||||
if (Object.values(obj[key]).every(val => val === null)) {
|
||||
obj[key] = null
|
||||
return
|
||||
}
|
||||
replaceWithNullIfAllPropertiesAreNull(obj[key])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDbType(modelProperties, path) {
|
||||
if (path.length === 1) return modelProperties[path[0]].type
|
||||
const modelName = modelProperties[path[0]].objectType
|
||||
return getDbType(schema[modelName], path.slice(1))
|
||||
}
|
||||
Generated
+1561
-1500
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"assert": "^1.4.1",
|
||||
"csvtojson": "^2.0.8",
|
||||
"date-range": "0.0.2",
|
||||
"isobject": "^3.0.1",
|
||||
"js-base64": "^2.4.8",
|
||||
"js-joda": "^1.8.2",
|
||||
"moment": "^2.22.1",
|
||||
@@ -24,6 +26,8 @@
|
||||
"react": "16.4.1",
|
||||
"react-native": "^0.56.0",
|
||||
"react-native-calendars": "^1.19.3",
|
||||
"react-native-document-picker": "^2.1.0",
|
||||
"react-native-fs": "^2.10.14",
|
||||
"react-native-modal-datetime-picker-nevo": "^4.11.0",
|
||||
"react-native-share": "^1.1.0",
|
||||
"react-native-simple-radio-button": "^2.7.1",
|
||||
|
||||
Reference in New Issue
Block a user