From 4f84643999729fe2edb7a62ea8f0fa39babdf5f7 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:40:35 -0700 Subject: [PATCH 01/32] Fixed JSON Object Dump As per issue #994, fixed the `Download JSON Dump`. The previous date-picker formatted the input as 'dd MMM yyyy', used luxon to format the object. This is a bit hacky - going to rewrite the 'ControlHelper' service, and completely remove the `moment()` altogether. --- www/js/services.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/www/js/services.js b/www/js/services.js index 0c9c6e2ac..cea063f60 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -2,6 +2,7 @@ import angular from 'angular'; import { getRawEntries } from './commHelper'; +import { DateTime } from 'luxon'; angular.module('emission.services', ['emission.plugin.logger']) @@ -114,8 +115,9 @@ angular.module('emission.services', ['emission.plugin.logger']) var fmt = "YYYY-MM-DD"; // We are only retrieving data for a single day to avoid // running out of memory on the phone - var startMoment = moment(startTs); - var endMoment = moment(startTs).endOf("day"); + var adjustedTs = DateTime.fromJSDate(startTs).toFormat('dd MMM yyyy'); + var startMoment = moment(adjustedTs); + var endMoment = moment(adjustedTs).endOf("day"); var dumpFile = startMoment.format(fmt) + "." + endMoment.format(fmt) + ".timeline"; From 1130508c09fbeff0fc10c4b85313d5205a4f67cf Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:28:45 -0700 Subject: [PATCH 02/32] Replaced moment objects with DateTime `js/services.js` has been rewritten so that it no longer relies on the legacy `moments.js` library, and instead uses `luxon`. --- www/js/services.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/www/js/services.js b/www/js/services.js index cea063f60..29f6dc491 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -112,14 +112,15 @@ angular.module('emission.services', ['emission.plugin.logger']) } this.getMyData = function(startTs) { - var fmt = "YYYY-MM-DD"; // We are only retrieving data for a single day to avoid // running out of memory on the phone - var adjustedTs = DateTime.fromJSDate(startTs).toFormat('dd MMM yyyy'); - var startMoment = moment(adjustedTs); - var endMoment = moment(adjustedTs).endOf("day"); - var dumpFile = startMoment.format(fmt) + "." - + endMoment.format(fmt) + var startTime = DateTime.fromJSDate(startTs); + var endTime = startTime.endOf("day"); + var startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); + var endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); + + var dumpFile = startTimeString + "." + + endTimeString + ".timeline"; alert("Going to retrieve data to "+dumpFile); @@ -182,7 +183,7 @@ angular.module('emission.services', ['emission.plugin.logger']) attachments: [ attachFile ], - subject: i18next.t('email-service.email-data.subject-data-dump-from-to', {start: startMoment.format(fmt),end: endMoment.format(fmt)}), + subject: i18next.t('email-service.email-data.subject-data-dump-from-to', {start: startTimeString ,end: endTimeString}), body: i18next.t('email-service.email-data.body-data-consists-of-list-of-entries') } $window.cordova.plugins.email.open(email).then(resolve()); @@ -198,7 +199,13 @@ angular.module('emission.services', ['emission.plugin.logger']) }); }; - getRawEntries(null, startMoment.unix(), endMoment.unix()) + // Simulate old conversion to get correct UnixInteger for fetching data + const getUnixNum = (dateData) => { + var tempDate = dateData.toFormat('dd MMM yyyy'); + return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); + }; + + getRawEntries(null, getUnixNum(startTime), getUnixNum(endTime)) .then(writeDumpFile) .then(emailData) .then(function() { From 2e9173b899fdcfe15a2d1094aca66107c05665a7 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:24:16 -0700 Subject: [PATCH 03/32] Fixed issue with UnixInteger conversion See comments on [the PR Draft](https://github.com/e-mission/e-mission-phone/pull/1052). Luxon and Moment.js seem to handle the `endOf('day')` slightly differently. The remedy for this was to simulate the old Unix Integer conversion for the end date, but use the contemporary one for the start date. This adjustment fixed the download, and worked for the multiple timelines tested. --- www/js/services.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/services.js b/www/js/services.js index 29f6dc491..99be7d094 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -199,13 +199,13 @@ angular.module('emission.services', ['emission.plugin.logger']) }); }; - // Simulate old conversion to get correct UnixInteger for fetching data + // Simulate old conversion to get correct UnixInteger for endMoment data const getUnixNum = (dateData) => { var tempDate = dateData.toFormat('dd MMM yyyy'); return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); }; - getRawEntries(null, getUnixNum(startTime), getUnixNum(endTime)) + getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) .then(writeDumpFile) .then(emailData) .then(function() { From 8825d06bee608149d83575cfb626509c5603a44f Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:16:13 -0700 Subject: [PATCH 04/32] Updated Logger usage, cleaned up controlHelper Adjusted `js/services.js` such that it only uses the constants exported from `js/plugins/logger.ts`. Removed the member function `writeFile` from the controlHelper, as it was not being used. --- www/js/services.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/www/js/services.js b/www/js/services.js index 99be7d094..1a667c757 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -3,7 +3,7 @@ import angular from 'angular'; import { getRawEntries } from './commHelper'; import { DateTime } from 'luxon'; - +import { logInfo, displayError } from './plugin/logger' angular.module('emission.services', ['emission.plugin.logger']) .service('ReferHelper', function($http) { @@ -23,7 +23,7 @@ angular.module('emission.services', ['emission.plugin.logger']) //}*/ } }) -.service('UnifiedDataLoader', function($window, Logger) { +.service('UnifiedDataLoader', function($window) { var combineWithDedup = function(list1, list2) { var combinedList = list1.concat(list2); return combinedList.filter(function(value, i, array) { @@ -52,10 +52,10 @@ angular.module('emission.services', ['emission.plugin.logger']) if (localError && remoteError) { reject([localError, remoteError]); } else { - Logger.log("About to dedup localResult = "+localResult.length + logInfo("About to dedup localResult = "+localResult.length +"remoteResult = "+remoteResult.length); var dedupedList = combiner(localResult, remoteResult); - Logger.log("Deduped list = "+dedupedList.length); + logInfo("Deduped list = "+dedupedList.length); resolve(dedupedList); } } @@ -104,13 +104,7 @@ angular.module('emission.services', ['emission.plugin.logger']) } }) .service('ControlHelper', function($window, - $ionicPopup, - Logger) { - - this.writeFile = function(fileEntry, resultList) { - // Create a FileWriter object for our FileEntry (log.txt). - } - + $ionicPopup) { this.getMyData = function(startTs) { // We are only retrieving data for a single day to avoid // running out of memory on the phone @@ -149,7 +143,6 @@ angular.module('emission.services', ['emission.plugin.logger']) { type: 'application/json' }); fileWriter.write(dataObj); }); - // this.writeFile(fileEntry, resultList); }); }); }); @@ -209,10 +202,10 @@ angular.module('emission.services', ['emission.plugin.logger']) .then(writeDumpFile) .then(emailData) .then(function() { - Logger.log("Email queued successfully"); + logInfo("Email queued successfully"); }) .catch(function(error) { - Logger.displayError("Error emailing JSON dump", error); + displayError(error, "Error emailing JSON dump"); }) }; From 46832c7e8a429c25ec8e361009a583161981e6f4 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:13:36 -0700 Subject: [PATCH 05/32] Rewrote controlHelper as a separate TS file controlHelper no longer utilizes Angular services. The internal functions were separated into their own exports. File write is confirmed to be working, need to write tests and verify all other functions are working as intended. --- www/js/control/DataDatePicker.tsx | 6 +- www/js/control/ProfileSettings.jsx | 6 +- www/js/controlHelper.ts | 125 +++++++++++++++++++++++++++++ www/js/services.js | 119 +-------------------------- 4 files changed, 131 insertions(+), 125 deletions(-) create mode 100644 www/js/controlHelper.ts diff --git a/www/js/control/DataDatePicker.tsx b/www/js/control/DataDatePicker.tsx index 83e0986b2..f2873ef66 100644 --- a/www/js/control/DataDatePicker.tsx +++ b/www/js/control/DataDatePicker.tsx @@ -4,12 +4,10 @@ import React from "react"; import { DatePickerModal } from 'react-native-paper-dates'; import { useTranslation } from "react-i18next"; -import { getAngularService } from "../angular-react-helper"; +import { getMyData } from "../controlHelper"; const DataDatePicker = ({date, setDate, open, setOpen, minDate}) => { const { t, i18n } = useTranslation(); //able to pull lang from this - const ControlHelper = getAngularService("ControlHelper"); - const onDismiss = React.useCallback(() => { setOpen(false); }, [setOpen]); @@ -18,7 +16,7 @@ const DataDatePicker = ({date, setDate, open, setOpen, minDate}) => { (params) => { setOpen(false); setDate(params.date); - ControlHelper.getMyData(params.date); + getMyData(params.date); }, [setOpen, setDate] ); diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 4a263acc5..72d75aa80 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -24,6 +24,7 @@ import { AppContext } from "../App"; import { shareQR } from "../components/QrCode"; import { storageClear } from "../plugin/storage"; import { getAppVersion } from "../plugin/clientStats"; +import { fetchOPCode, getSettings } from "../controlHelper"; //any pure functions can go outside const ProfileSettings = () => { @@ -38,7 +39,6 @@ const ProfileSettings = () => { const UploadHelper = getAngularService('UploadHelper'); const EmailHelper = getAngularService('EmailHelper'); const NotificationScheduler = getAngularService('NotificationScheduler'); - const ControlHelper = getAngularService('ControlHelper'); const StartPrefs = getAngularService('StartPrefs'); //functions that come directly from an Angular service @@ -196,7 +196,7 @@ const ProfileSettings = () => { }, [editSync]); async function getConnectURL() { - ControlHelper.getSettings().then(function(response) { + getSettings().then(function(response) { var newConnectSettings ={} newConnectSettings.url = response.connectUrl; console.log(response); @@ -208,7 +208,7 @@ const ProfileSettings = () => { async function getOPCode() { const newAuthSettings = {}; - const opcode = await ControlHelper.getOPCode(); + const opcode = await fetchOPCode(); if(opcode == null){ newAuthSettings.opcode = "Not logged in"; } else { diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts new file mode 100644 index 000000000..2f341a12a --- /dev/null +++ b/www/js/controlHelper.ts @@ -0,0 +1,125 @@ +import { DateTime } from "luxon"; + +import { getRawEntries } from "./commHelper"; +import { logInfo, displayError } from "./plugin/logger"; +import i18next from "./i18nextInit" ; + +interface fsWindow extends Window { + requestFileSystem: ( + type: number, + size: number, + successCallback: (fs: any) => void, + errorCallback?: (error: any) => void + ) => void; + LocalFileSystem: { + TEMPORARY: number; + PERSISTENT: number; + }; +}; + +declare let window: fsWindow; + +export const getMyData = function(startTs: Date) { + // We are only retrieving data for a single day to avoid + // running out of memory on the phone + var startTime = DateTime.fromJSDate(startTs); + var endTime = startTime.endOf("day"); + var startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); + var endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); + + var dumpFile = startTimeString + "." + + endTimeString + + ".timeline"; + alert(`Going to retrieve data to ${dumpFile}`); + + const writeDumpFile = function(result) { + var resultList = result.phone_data; + return new Promise(function(resolve, reject) { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { + console.log(`file system open: ${fs.name}`); + fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { + console.log(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function() { + console.log("Successful file write..."); + resolve(); + } + fileWriter.onerror = function(e) { + console.log(`Failed file write: ${e.toString()}`); + reject(); + } + + // if data object is not passed in, create a new blog instead. + var dataObj = new Blob([JSON.stringify(resultList, null, 2)], + { type: "application/json" }); + fileWriter.write(dataObj); + }) + + }); + console.log(`Done!`); + }); + }); + } + + var emailData = function() { + return new Promise(function(resolve, reject) { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { + console.log("During email, file system open: " + fs.name); + fs.root.getFile(dumpFile, null, function(fileEntry) { + console.log(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.file(function(file) { + var reader = new FileReader(); + + reader.onloadend = function() { + const readResult = this.result as string; + console.log(`Successfull file read with ${readResult.length} characters`); + var dataArray = JSON.parse(readResult); + console.log(`Successfully read resultList of size ${dataArray.length}`); + var attachFile = fileEntry.nativeURL; + if (window['device'].platform === "android") + attachFile = "app://cache/" + dumpFile; + if (window['device'].platform === "ios") + alert(i18next.t("email-service.email-account-mail-app")); + var email = { + attachments: [ + attachFile + ], + subject: i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), + body: i18next.t("email-service.email-data.body-data-consists-of-list-of-entries") + }; + window['cordova'].plugins.email.open(email).then(resolve()); + } + reader.readAsText(file); + }, function(error) { + displayError(error, "Error while downloading JSON dump"); + reject(error); + }) + }); + }); + }); + }; + + // Simulate old conversion to get correct UnixInteger for endMoment data + const getUnixNum = (dateData: DateTime) => { + var tempDate = dateData.toFormat("dd MMM yyyy"); + return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); + }; + + getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) + .then(writeDumpFile) + .then(emailData) + .then(function() { + logInfo("Email queued successfully"); + }) + .catch(function(error) { + displayError(error, "Error emailing JSON dump"); + }) +}; + +export const fetchOPCode = (() => { + return window["cordova"].plugins.OPCodeAuth.getOPCode(); + }); + +export const getSettings = (() => { + return window["cordova"].plugins.BEMConnectionSettings.getSettings(); +}); \ No newline at end of file diff --git a/www/js/services.js b/www/js/services.js index 1a667c757..e406be203 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -2,8 +2,7 @@ import angular from 'angular'; import { getRawEntries } from './commHelper'; -import { DateTime } from 'luxon'; -import { logInfo, displayError } from './plugin/logger' +import { logInfo} from './plugin/logger' angular.module('emission.services', ['emission.plugin.logger']) .service('ReferHelper', function($http) { @@ -103,122 +102,6 @@ angular.module('emission.services', ['emission.plugin.logger']) return combinedPromise(localPromise, remotePromise, combineWithDedup); } }) -.service('ControlHelper', function($window, - $ionicPopup) { - this.getMyData = function(startTs) { - // We are only retrieving data for a single day to avoid - // running out of memory on the phone - var startTime = DateTime.fromJSDate(startTs); - var endTime = startTime.endOf("day"); - var startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); - var endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); - - var dumpFile = startTimeString + "." - + endTimeString - + ".timeline"; - alert("Going to retrieve data to "+dumpFile); - - var writeDumpFile = function(result) { - return new Promise(function(resolve, reject) { - var resultList = result.phone_data; - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log('file system open: ' + fs.name); - fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { - console.log("fileEntry "+fileEntry.nativeURL+" is file?" + fileEntry.isFile.toString()); - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function() { - console.log("Successful file write..."); - resolve(); - // readFile(fileEntry); - }; - - fileWriter.onerror = function (e) { - console.log("Failed file write: " + e.toString()); - reject(); - }; - - // If data object is not passed in, - // create a new Blob instead. - var dataObj = new Blob([JSON.stringify(resultList, null, 2)], - { type: 'application/json' }); - fileWriter.write(dataObj); - }); - }); - }); - }); - } - - - var emailData = function(result) { - return new Promise(function(resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log("During email, file system open: "+fs.name); - fs.root.getFile(dumpFile, null, function(fileEntry) { - console.log("fileEntry "+fileEntry.nativeURL+" is file?"+fileEntry.isFile.toString()); - fileEntry.file(function (file) { - var reader = new FileReader(); - - reader.onloadend = function() { - console.log("Successful file read with " + this.result.length +" characters"); - var dataArray = JSON.parse(this.result); - console.log("Successfully read resultList of size "+dataArray.length); - // displayFileData(fileEntry.fullPath + ": " + this.result); - var attachFile = fileEntry.nativeURL; - if (ionic.Platform.isAndroid()) { - // At least on nexus, getting a temporary file puts it into - // the cache, so I can hardcode that for now - attachFile = "app://cache/"+dumpFile; - } - if (ionic.Platform.isIOS()) { - alert(i18next.t('email-service.email-account-mail-app')); - } - var email = { - attachments: [ - attachFile - ], - subject: i18next.t('email-service.email-data.subject-data-dump-from-to', {start: startTimeString ,end: endTimeString}), - body: i18next.t('email-service.email-data.body-data-consists-of-list-of-entries') - } - $window.cordova.plugins.email.open(email).then(resolve()); - } - reader.readAsText(file); - }, function(error) { - $ionicPopup.alert({title: "Error while downloading JSON dump", - template: error}); - reject(error); - }); - }); - }); - }); - }; - - // Simulate old conversion to get correct UnixInteger for endMoment data - const getUnixNum = (dateData) => { - var tempDate = dateData.toFormat('dd MMM yyyy'); - return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); - }; - - getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) - .then(writeDumpFile) - .then(emailData) - .then(function() { - logInfo("Email queued successfully"); - }) - .catch(function(error) { - displayError(error, "Error emailing JSON dump"); - }) - }; - - this.getOPCode = function() { - return window.cordova.plugins.OPCodeAuth.getOPCode(); - }; - - this.getSettings = function() { - return window.cordova.plugins.BEMConnectionSettings.getSettings(); - }; - -}) - .factory('Chats', function() { // Might use a resource here that returns a JSON array From 49608032ef4ecc8bb317229c23ed0c61adfda779 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:01:24 -0700 Subject: [PATCH 06/32] Small code-quality updates to controlHelper.ts Changed all variables that do not mutate to const. --- www/js/controlHelper.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index 2f341a12a..5f6ebd6b9 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -22,18 +22,18 @@ declare let window: fsWindow; export const getMyData = function(startTs: Date) { // We are only retrieving data for a single day to avoid // running out of memory on the phone - var startTime = DateTime.fromJSDate(startTs); - var endTime = startTime.endOf("day"); - var startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); - var endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); + const startTime = DateTime.fromJSDate(startTs); + const endTime = startTime.endOf("day"); + const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); + const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); - var dumpFile = startTimeString + "." + const dumpFile = startTimeString + "." + endTimeString + ".timeline"; alert(`Going to retrieve data to ${dumpFile}`); const writeDumpFile = function(result) { - var resultList = result.phone_data; + const resultList = result.phone_data; return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { console.log(`file system open: ${fs.name}`); @@ -50,7 +50,7 @@ export const getMyData = function(startTs: Date) { } // if data object is not passed in, create a new blog instead. - var dataObj = new Blob([JSON.stringify(resultList, null, 2)], + const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { type: "application/json" }); fileWriter.write(dataObj); }) @@ -61,26 +61,26 @@ export const getMyData = function(startTs: Date) { }); } - var emailData = function() { + const emailData = function() { return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { console.log("During email, file system open: " + fs.name); fs.root.getFile(dumpFile, null, function(fileEntry) { console.log(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { - var reader = new FileReader(); + const reader = new FileReader(); reader.onloadend = function() { const readResult = this.result as string; console.log(`Successfull file read with ${readResult.length} characters`); - var dataArray = JSON.parse(readResult); + const dataArray = JSON.parse(readResult); console.log(`Successfully read resultList of size ${dataArray.length}`); var attachFile = fileEntry.nativeURL; if (window['device'].platform === "android") attachFile = "app://cache/" + dumpFile; if (window['device'].platform === "ios") alert(i18next.t("email-service.email-account-mail-app")); - var email = { + const email = { attachments: [ attachFile ], @@ -101,7 +101,7 @@ export const getMyData = function(startTs: Date) { // Simulate old conversion to get correct UnixInteger for endMoment data const getUnixNum = (dateData: DateTime) => { - var tempDate = dateData.toFormat("dd MMM yyyy"); + const tempDate = dateData.toFormat("dd MMM yyyy"); return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); }; From 6fd2e737b19fd9ac0a84799732d049aeb65acbc1 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:22:55 -0700 Subject: [PATCH 07/32] Email now uses socialShare library See [PR 1052](https://github.com/e-mission/e-mission-phone/pull/1052). Using the socialShare library, as we do in the OpCode components, allows iOS users to share through apps other than the defauolt mail app. --- www/js/controlHelper.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index 5f6ebd6b9..2458d7e8a 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -56,7 +56,6 @@ export const getMyData = function(startTs: Date) { }) }); - console.log(`Done!`); }); }); } @@ -81,13 +80,17 @@ export const getMyData = function(startTs: Date) { if (window['device'].platform === "ios") alert(i18next.t("email-service.email-account-mail-app")); const email = { - attachments: [ - attachFile - ], - subject: i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), - body: i18next.t("email-service.email-data.body-data-consists-of-list-of-entries") - }; - window['cordova'].plugins.email.open(email).then(resolve()); + 'files': [attachFile], + 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), + 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), + } + window['plugins'].socialsharing.shareWithOptions(email, function (result) { + console.log(`Share Completed? ${result.completed}`); // On Android, most likely returns false + console.log(`Shared to app: ${result.app}`); + resolve(); + }, function (msg) { + console.log(`Sharing failed with message ${msg}`); + }); } reader.readAsText(file); }, function(error) { From 3420577d81fabb010f30a80dbffed4efa1cbe2ea Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:54:20 -0700 Subject: [PATCH 08/32] Updated debug statements, format changes Per discussion in PR, made adjustments to debug logs and funciton names. --- www/js/controlHelper.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index 2458d7e8a..6feaec85e 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -1,7 +1,7 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; -import { logInfo, displayError } from "./plugin/logger"; +import { logInfo, displayError, logDebug } from "./plugin/logger"; import i18next from "./i18nextInit" ; interface fsWindow extends Window { @@ -36,16 +36,16 @@ export const getMyData = function(startTs: Date) { const resultList = result.phone_data; return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log(`file system open: ${fs.name}`); + logDebug(`file system open: ${fs.name}`); fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { - console.log(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) fileEntry.createWriter(function (fileWriter) { fileWriter.onwriteend = function() { - console.log("Successful file write..."); + logDebug("Successful file write..."); resolve(); } fileWriter.onerror = function(e) { - console.log(`Failed file write: ${e.toString()}`); + logDebug(`Failed file write: ${e.toString()}`); reject(); } @@ -60,21 +60,21 @@ export const getMyData = function(startTs: Date) { }); } - const emailData = function() { + const shareData = function() { return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log("During email, file system open: " + fs.name); + logDebug("During email, file system open: " + fs.name); fs.root.getFile(dumpFile, null, function(fileEntry) { - console.log(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { const reader = new FileReader(); reader.onloadend = function() { const readResult = this.result as string; - console.log(`Successfull file read with ${readResult.length} characters`); + logDebug(`Successfull file read with ${readResult.length} characters`); const dataArray = JSON.parse(readResult); - console.log(`Successfully read resultList of size ${dataArray.length}`); - var attachFile = fileEntry.nativeURL; + logDebug(`Successfully read resultList of size ${dataArray.length}`); + let attachFile = fileEntry.nativeURL; if (window['device'].platform === "android") attachFile = "app://cache/" + dumpFile; if (window['device'].platform === "ios") @@ -85,11 +85,11 @@ export const getMyData = function(startTs: Date) { 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), } window['plugins'].socialsharing.shareWithOptions(email, function (result) { - console.log(`Share Completed? ${result.completed}`); // On Android, most likely returns false - console.log(`Shared to app: ${result.app}`); + logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false + logDebug(`Shared to app: ${result.app}`); resolve(); }, function (msg) { - console.log(`Sharing failed with message ${msg}`); + logDebug(`Sharing failed with message ${msg}`); }); } reader.readAsText(file); @@ -110,7 +110,7 @@ export const getMyData = function(startTs: Date) { getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) .then(writeDumpFile) - .then(emailData) + .then(shareData) .then(function() { logInfo("Email queued successfully"); }) From 92cd6fbac863af18b09642ac6e1101fe1cf07a94 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:30:45 -0700 Subject: [PATCH 09/32] Split getData into multiple functions By extracting the `writeFile` and `shareData` methods, we can now write unit tests for controlHelper; specifically, we are able to test the `writeFile` portion, as the `shareData` method relies on external plugins that are difficult to mock. --- www/js/controlHelper.ts | 137 +++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index 6feaec85e..bfb439e55 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -19,25 +19,18 @@ interface fsWindow extends Window { declare let window: fsWindow; -export const getMyData = function(startTs: Date) { - // We are only retrieving data for a single day to avoid - // running out of memory on the phone - const startTime = DateTime.fromJSDate(startTs); - const endTime = startTime.endOf("day"); - const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); - const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); - - const dumpFile = startTimeString + "." - + endTimeString - + ".timeline"; - alert(`Going to retrieve data to ${dumpFile}`); - - const writeDumpFile = function(result) { - const resultList = result.phone_data; +/** + * createWriteFile is a factory method for the JSON dump file creation + * @param fileName is the name of the file to be created + * @returns a function that returns a promise, which writes the file upon evaluation. + */ +const createWriteFile = function (fileName: string) { + return function(result) { + const resultList = result.phone_data; return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { logDebug(`file system open: ${fs.name}`); - fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { + fs.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) fileEntry.createWriter(function (fileWriter) { fileWriter.onwriteend = function() { @@ -54,53 +47,77 @@ export const getMyData = function(startTs: Date) { { type: "application/json" }); fileWriter.write(dataObj); }) - }); }); - }); - } + }); +}}; - const shareData = function() { - return new Promise(function(resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - logDebug("During email, file system open: " + fs.name); - fs.root.getFile(dumpFile, null, function(fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); - fileEntry.file(function(file) { - const reader = new FileReader(); +/** + * createShareData returns a shareData method, with the input parameters captured. + * @param fileName is the existing file to be sent + * @param startTimeString timestamp used to identify the file + * @param endTimeString " " + * @returns a function which returns a promise, which shares an existing file upon evaluation. + */ +const createShareData = function(fileName: string, startTimeString: string, endTimeString: string) { + return function() { + return new Promise(function(resolve, reject) { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { + logDebug("During email, file system open: " + fs.name); + fs.root.getFile(fileName, null, function(fileEntry) { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.file(function(file) { + const reader = new FileReader(); - reader.onloadend = function() { - const readResult = this.result as string; - logDebug(`Successfull file read with ${readResult.length} characters`); - const dataArray = JSON.parse(readResult); - logDebug(`Successfully read resultList of size ${dataArray.length}`); - let attachFile = fileEntry.nativeURL; - if (window['device'].platform === "android") - attachFile = "app://cache/" + dumpFile; - if (window['device'].platform === "ios") - alert(i18next.t("email-service.email-account-mail-app")); - const email = { - 'files': [attachFile], - 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), - 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), - } - window['plugins'].socialsharing.shareWithOptions(email, function (result) { - logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false - logDebug(`Shared to app: ${result.app}`); - resolve(); - }, function (msg) { - logDebug(`Sharing failed with message ${msg}`); - }); - } - reader.readAsText(file); - }, function(error) { - displayError(error, "Error while downloading JSON dump"); - reject(error); - }) - }); - }); + reader.onloadend = function() { + const readResult = this.result as string; + logDebug(`Successfull file read with ${readResult.length} characters`); + const dataArray = JSON.parse(readResult); + logDebug(`Successfully read resultList of size ${dataArray.length}`); + let attachFile = fileEntry.nativeURL; + if (window['device'].platform === "android") + attachFile = "app://cache/" + fileName; + if (window['device'].platform === "ios") + alert(i18next.t("email-service.email-account-mail-app")); + const email = { + 'files': [attachFile], + 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), + 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), + } + window['plugins'].socialsharing.shareWithOptions(email, function (result) { + logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false + logDebug(`Shared to app: ${result.app}`); + resolve(); + }, function (msg) { + logDebug(`Sharing failed with message ${msg}`); + }); + } + reader.readAsText(file); + }, function(error) { + displayError(error, "Error while downloading JSON dump"); + reject(error); + }) }); - }; + }); + }); +}}; + +/** + * getMyData fetches timeline data for a given day, and then gives the user a prompt to share the data + * @param startTs initial timestamp of the timeline to be fetched. + */ +export const getMyData = function(startTs: Date) { + // We are only retrieving data for a single day to avoid + // running out of memory on the phone + const startTime = DateTime.fromJSDate(startTs); + const endTime = startTime.endOf("day"); + const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); + const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); + + const dumpFile = startTimeString + "." + + endTimeString + + ".timeline"; + alert(`Going to retrieve data to ${dumpFile}`); // Simulate old conversion to get correct UnixInteger for endMoment data const getUnixNum = (dateData: DateTime) => { @@ -108,7 +125,11 @@ export const getMyData = function(startTs: Date) { return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); }; + const writeDumpFile = createWriteFile(dumpFile); + const shareData = createShareData(dumpFile, startTimeString, endTimeString); + getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) + .then(result => Promise.resolve(dumpFile).then(() => result)) .then(writeDumpFile) .then(shareData) .then(function() { From d31a7a18d64384b0dab73d5ef4fdecb04802d5ad Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:25:13 -0700 Subject: [PATCH 10/32] Set Up tests for controlHelper - Wrote interface for the objects that return from getRawData - Wrote small function to initialize test data - Minor adjustments to `controlHelper.ts` - Initial promise check; passes test data - TODO: Write tests to check if file is actually created, verify the contents of the file, test the function on empty data. --- www/__tests__/controlHelper.test.ts | 56 +++++++++++++++++++++++++++++ www/js/controlHelper.ts | 7 ++-- 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 www/__tests__/controlHelper.test.ts diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts new file mode 100644 index 000000000..599114676 --- /dev/null +++ b/www/__tests__/controlHelper.test.ts @@ -0,0 +1,56 @@ +import { mockLogger } from "../__mocks__/globalMocks"; +import { createWriteFile } from "../js/controlHelper"; + +mockLogger(); + +// See PR 1052 for a detailed interface +interface dataObj { + data: { + name: string, + ts: number, + reading: number + }, + metadata: any, + user_id: { + $uuid: string + }, + _id: { + $oid: string + } +} + +// These are fake values; createWriteFile does not require these objects +// specifically, but it is better to test with similar data - using real data +// would take up too much code space, and we cannot use getRawEnteries() in testing +const generateDummyValues = (arraySize: number) => { + const sampleDataObj = { + data: { + name: 'MODE', + ts: 1234567890.9876543, + reading: 0.1234567891011121 + }, + metadata: 'testValue #', + user_id: { + $uuid: '41t0l8e00s914tval1234567u9658699' + }, + _id: { + $oid: '12341x123afe3fbf541524d8' + } + }; + // The parse/stringify lets us "deep copy" the objects, to quickly populate/change the data + let values = Array.from({length: arraySize}, e => JSON.parse(JSON.stringify(sampleDataObj))); + values.forEach((element, index) => { + values[index].metadata = element.metadata + index.toString() + }); + + return values; +}; + +it(`writes a file for an array of objects`, async () => { + const testPhoneObj = { phone_data: generateDummyValues(100) }; + const writeFile = createWriteFile('testFile.temp'); + const testPromise = new Promise(() => { + return testPhoneObj; + }); + expect(testPromise.then(writeFile)).resolves.not.toThrow(); +}); diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index bfb439e55..c51e58b25 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -24,7 +24,7 @@ declare let window: fsWindow; * @param fileName is the name of the file to be created * @returns a function that returns a promise, which writes the file upon evaluation. */ -const createWriteFile = function (fileName: string) { +export const createWriteFile = function (fileName: string) { return function(result) { const resultList = result.phone_data; return new Promise(function(resolve, reject) { @@ -59,7 +59,7 @@ const createWriteFile = function (fileName: string) { * @param endTimeString " " * @returns a function which returns a promise, which shares an existing file upon evaluation. */ -const createShareData = function(fileName: string, startTimeString: string, endTimeString: string) { +export const createShareData = function(fileName: string, startTimeString: string, endTimeString: string) { return function() { return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { @@ -129,7 +129,6 @@ export const getMyData = function(startTs: Date) { const shareData = createShareData(dumpFile, startTimeString, endTimeString); getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) - .then(result => Promise.resolve(dumpFile).then(() => result)) .then(writeDumpFile) .then(shareData) .then(function() { @@ -146,4 +145,4 @@ export const fetchOPCode = (() => { export const getSettings = (() => { return window["cordova"].plugins.BEMConnectionSettings.getSettings(); -}); \ No newline at end of file +}); From b8404da8154cae034f3396d6a6d81fbd5361a1af Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 18 Oct 2023 12:32:05 -0700 Subject: [PATCH 11/32] Set up types folder, added new tests As discussed in PR #1061, Jiji is working on setting up a folder in `www/js/` to contain many of our type interfaces and templates. For now, I've added my own `fileIOTypes.ts`. We discussed the types added so far, and found we were working with overlapping types! As such, I plan to copy over her version of `DiaryTypes.ts` found in commit ffcc871, and will use that to replace the current dataObj interface in `controlHelper.test.ts`. Changes made: - Set up `www/js/types`, moved fsWindow interface there - Added basic tests for createWriteFile --- www/__tests__/controlHelper.test.ts | 73 +++++++++++++++++++++++------ www/js/controlHelper.ts | 15 +----- www/js/types/fileIOTypes.ts | 12 +++++ 3 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 www/js/types/fileIOTypes.ts diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 599114676..6842b44bf 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -1,7 +1,9 @@ import { mockLogger } from "../__mocks__/globalMocks"; import { createWriteFile } from "../js/controlHelper"; +import { fsWindow } from "../js/types/fileIOTypes" mockLogger(); +declare let window: fsWindow; // See PR 1052 for a detailed interface interface dataObj { @@ -19,22 +21,24 @@ interface dataObj { } } -// These are fake values; createWriteFile does not require these objects -// specifically, but it is better to test with similar data - using real data -// would take up too much code space, and we cannot use getRawEnteries() in testing -const generateDummyValues = (arraySize: number) => { +// createWriteFile does not require these objects specifically, but it +// is better to test with similar data - using real data would take +// up too much code space, and we cannot use getRawEnteries() in testing +const generateFakeValues = (arraySize: number) => { + if (arraySize <= 0) + return new Promise (() => {return []}); const sampleDataObj = { data: { name: 'MODE', ts: 1234567890.9876543, - reading: 0.1234567891011121 + reading: 0.1234567891011121, }, metadata: 'testValue #', user_id: { - $uuid: '41t0l8e00s914tval1234567u9658699' + $uuid: '41t0l8e00s914tval1234567u9658699', }, _id: { - $oid: '12341x123afe3fbf541524d8' + $oid: '12341x123afe3fbf541524d8', } }; // The parse/stringify lets us "deep copy" the objects, to quickly populate/change the data @@ -43,14 +47,53 @@ const generateDummyValues = (arraySize: number) => { values[index].metadata = element.metadata + index.toString() }); - return values; + return new Promise(() => { + return { phone_data: values }; + }); }; -it(`writes a file for an array of objects`, async () => { - const testPhoneObj = { phone_data: generateDummyValues(100) }; - const writeFile = createWriteFile('testFile.temp'); - const testPromise = new Promise(() => { - return testPhoneObj; - }); - expect(testPromise.then(writeFile)).resolves.not.toThrow(); +// A variation of createShareData; confirms the file has been written, +// without sharing the data. +const confirmFileExists = (fileName: string) => { + return function() { + return new Promise(function() { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { + fs.root.getFile(fileName, null, function(fileEntry) { + return fileEntry.isFile; + }); + }); + }); + }; +}; + +it('writes a file for an array of objects', async () => { + const testPromiseOne = generateFakeValues(1); + const testPromiseTwo= generateFakeValues(222); + const writeFile = createWriteFile('test_one.temp'); + + expect(testPromiseOne.then(writeFile)).resolves.not.toThrow(); + expect(testPromiseTwo.then(writeFile)).resolves.not.toThrow(); }); + +it('correctly writes the files', async () => { + const fileName = 'test_two.timeline' + const fileExists = confirmFileExists(fileName); + const testPromise = generateFakeValues(1); + const writeFile = createWriteFile(fileName); + expect(testPromise.then(writeFile).then(fileExists)).resolves.not.toThrow(); + expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(true); +}); + +it('rejects an empty input', async () => { + const writeFile = createWriteFile('test_one.temp'); + const testPromise = generateFakeValues(0); + expect(testPromise.then(writeFile)).rejects.toThrow(); +}); + +/* + createShareData() is not tested, because it relies on the phoneGap social + sharing plugin, which cannot be mocked. + + getMyData relies on createShareData, and likewise cannot be tested - it also + relies on getRawEnteries(). +*/ diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index c51e58b25..ebdf42398 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -2,21 +2,9 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; import { logInfo, displayError, logDebug } from "./plugin/logger"; +import { fsWindow } from "./types/fileIOTypes" import i18next from "./i18nextInit" ; -interface fsWindow extends Window { - requestFileSystem: ( - type: number, - size: number, - successCallback: (fs: any) => void, - errorCallback?: (error: any) => void - ) => void; - LocalFileSystem: { - TEMPORARY: number; - PERSISTENT: number; - }; -}; - declare let window: fsWindow; /** @@ -65,6 +53,7 @@ export const createShareData = function(fileName: string, startTimeString: strin window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { logDebug("During email, file system open: " + fs.name); fs.root.getFile(fileName, null, function(fileEntry) { + logDebug(`fileEntry is type ${typeof fileEntry.isFile}`) logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { const reader = new FileReader(); diff --git a/www/js/types/fileIOTypes.ts b/www/js/types/fileIOTypes.ts new file mode 100644 index 000000000..12797e6b8 --- /dev/null +++ b/www/js/types/fileIOTypes.ts @@ -0,0 +1,12 @@ +export interface fsWindow extends Window { + requestFileSystem: ( + type: number, + size: number, + successCallback: (fs: any) => void, + errorCallback?: (error: any) => void + ) => void; + LocalFileSystem: { + TEMPORARY: number; + PERSISTENT: number; + }; +}; From c22a605cb74a7b570d463104ab0201555922c27d Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:37:03 -0700 Subject: [PATCH 12/32] Updated type files, improved tests As mentioned in commit b8404da, a rudimentary version of `diaryTypes.ts` was added, so that controlHelper's typing was consistent with the new types being implemented. Further improvements include: - Checking file contents to confirm they match the input rawDataCluster - Better naming conventions - RawData, and RawDataCluster better reflect where the data comes from --- www/__tests__/controlHelper.test.ts | 78 +++++++++++++++++------------ www/js/controlHelper.ts | 7 ++- www/js/types/diaryTypes.ts | 12 +++++ www/js/types/fileIOTypes.ts | 12 ----- www/js/types/fileShareTypes.ts | 43 ++++++++++++++++ 5 files changed, 104 insertions(+), 48 deletions(-) create mode 100644 www/js/types/diaryTypes.ts delete mode 100644 www/js/types/fileIOTypes.ts create mode 100644 www/js/types/fileShareTypes.ts diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 6842b44bf..172186664 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -1,25 +1,11 @@ import { mockLogger } from "../__mocks__/globalMocks"; import { createWriteFile } from "../js/controlHelper"; -import { fsWindow } from "../js/types/fileIOTypes" +import { FsWindow, RawData, RawDataCluster } from "../js/types/fileShareTypes" mockLogger(); -declare let window: fsWindow; - -// See PR 1052 for a detailed interface -interface dataObj { - data: { - name: string, - ts: number, - reading: number - }, - metadata: any, - user_id: { - $uuid: string - }, - _id: { - $oid: string - } -} +declare let window: FsWindow; +const fileName = 'test.timeline' +const writeFile = createWriteFile(fileName); // createWriteFile does not require these objects specifically, but it // is better to test with similar data - using real data would take @@ -27,13 +13,30 @@ interface dataObj { const generateFakeValues = (arraySize: number) => { if (arraySize <= 0) return new Promise (() => {return []}); - const sampleDataObj = { + + const sampleDataObj : RawData = { data: { - name: 'MODE', + name: 'testValue #', ts: 1234567890.9876543, reading: 0.1234567891011121, }, - metadata: 'testValue #', + metadata: { + key: 'MyKey/test', + platform: 'dev_testing', + time_zone: 'America/Los_Angeles', + write_fmt_time: '2023-04-14T00:09:10.80023-07:00', + write_local_dt: { + minute: 1, + hour: 2, + second: 3, + day: 4, + weekday: 5, + month: 6, + year: 7, + timezone: 'America/Los_Angeles', + }, + write_ts: 12345.6789, + }, user_id: { $uuid: '41t0l8e00s914tval1234567u9658699', }, @@ -41,25 +44,33 @@ const generateFakeValues = (arraySize: number) => { $oid: '12341x123afe3fbf541524d8', } }; + // The parse/stringify lets us "deep copy" the objects, to quickly populate/change the data let values = Array.from({length: arraySize}, e => JSON.parse(JSON.stringify(sampleDataObj))); values.forEach((element, index) => { - values[index].metadata = element.metadata + index.toString() + values[index].data.name = element.data.name + index.toString() }); - return new Promise(() => { + return new Promise(() => { return { phone_data: values }; }); }; // A variation of createShareData; confirms the file has been written, // without sharing the data. -const confirmFileExists = (fileName: string) => { +const confirmFileExists = (fileName: string, dataCluster: RawDataCluster) => { return function() { return new Promise(function() { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { fs.root.getFile(fileName, null, function(fileEntry) { - return fileEntry.isFile; + if (!fileEntry.isFile) + return fileEntry.isFile; + const reader = new FileReader(); + reader.onloadend = function () { + const readResult = this.result as string; + const expectedResult = JSON.stringify(dataCluster); + return (readResult === expectedResult); + } }); }); }); @@ -68,26 +79,29 @@ const confirmFileExists = (fileName: string) => { it('writes a file for an array of objects', async () => { const testPromiseOne = generateFakeValues(1); - const testPromiseTwo= generateFakeValues(222); - const writeFile = createWriteFile('test_one.temp'); + const testPromiseTwo = generateFakeValues(2222); expect(testPromiseOne.then(writeFile)).resolves.not.toThrow(); expect(testPromiseTwo.then(writeFile)).resolves.not.toThrow(); }); it('correctly writes the files', async () => { - const fileName = 'test_two.timeline' - const fileExists = confirmFileExists(fileName); const testPromise = generateFakeValues(1); - const writeFile = createWriteFile(fileName); + let dataCluster = null; + testPromise.then((result) => {dataCluster = result}); + const fileExists = confirmFileExists(fileName, dataCluster); expect(testPromise.then(writeFile).then(fileExists)).resolves.not.toThrow(); expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(true); }); it('rejects an empty input', async () => { - const writeFile = createWriteFile('test_one.temp'); const testPromise = generateFakeValues(0); - expect(testPromise.then(writeFile)).rejects.toThrow(); + expect(testPromise.then(writeFile)).rejects.toThrow(); + + let dataCluster = null; + testPromise.then((result) => {dataCluster = result}); + const fileExists = confirmFileExists(fileName, dataCluster); + expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(false); }); /* diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index ebdf42398..1b1755395 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -2,10 +2,10 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; import { logInfo, displayError, logDebug } from "./plugin/logger"; -import { fsWindow } from "./types/fileIOTypes" +import { FsWindow, RawDataCluster } from "./types/fileShareTypes" import i18next from "./i18nextInit" ; -declare let window: fsWindow; +declare let window: FsWindow; /** * createWriteFile is a factory method for the JSON dump file creation @@ -13,7 +13,7 @@ declare let window: fsWindow; * @returns a function that returns a promise, which writes the file upon evaluation. */ export const createWriteFile = function (fileName: string) { - return function(result) { + return function(result: RawDataCluster) { const resultList = result.phone_data; return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { @@ -53,7 +53,6 @@ export const createShareData = function(fileName: string, startTimeString: strin window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { logDebug("During email, file system open: " + fs.name); fs.root.getFile(fileName, null, function(fileEntry) { - logDebug(`fileEntry is type ${typeof fileEntry.isFile}`) logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { const reader = new FileReader(); diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts new file mode 100644 index 000000000..1ecbd26cd --- /dev/null +++ b/www/js/types/diaryTypes.ts @@ -0,0 +1,12 @@ +/* This draft of diaryTypes was added to PR 1052, so that the LocalDT type is + consistent across PRs. Only LocalDt is needed for the controlHelper rewrite */ +export type LocalDt = { + minute: number, + hour: number, + second: number, + day: number, + weekday: number, + month: number, + year: number, + timezone: string, +} diff --git a/www/js/types/fileIOTypes.ts b/www/js/types/fileIOTypes.ts deleted file mode 100644 index 12797e6b8..000000000 --- a/www/js/types/fileIOTypes.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface fsWindow extends Window { - requestFileSystem: ( - type: number, - size: number, - successCallback: (fs: any) => void, - errorCallback?: (error: any) => void - ) => void; - LocalFileSystem: { - TEMPORARY: number; - PERSISTENT: number; - }; -}; diff --git a/www/js/types/fileShareTypes.ts b/www/js/types/fileShareTypes.ts new file mode 100644 index 000000000..e2b60bf49 --- /dev/null +++ b/www/js/types/fileShareTypes.ts @@ -0,0 +1,43 @@ +import { LocalDt } from "./diaryTypes"; + +export interface FsWindow extends Window { + requestFileSystem: ( + type: number, + size: number, + successCallback: (fs: any) => void, + errorCallback?: (error: any) => void + ) => void; + LocalFileSystem: { + TEMPORARY: number; + PERSISTENT: number; + }; +}; + +/* These are the objects returned from getRawEnteries when it is called by + the getMyData() method. */ +export interface RawDataCluster { + phone_data: Array +} + +export interface RawData { + data: { + name: string, + ts: number, + reading: number, + }, + metadata: { + key: string, + platform: string, + write_ts: number, + time_zone: string, + write_fmt_time: string, + write_local_dt: LocalDt, + }, + user_id: { + $uuid: string, + }, + _id: { + $oid: string, + } +} + From 20f6d04889eb801e147bfa11f1d76147870ff409 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:46:56 -0700 Subject: [PATCH 13/32] Minor adjustments to controlHelper tests --- www/__tests__/controlHelper.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 172186664..5e8836e80 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -57,7 +57,7 @@ const generateFakeValues = (arraySize: number) => { }; // A variation of createShareData; confirms the file has been written, -// without sharing the data. +// without calling the sharing components const confirmFileExists = (fileName: string, dataCluster: RawDataCluster) => { return function() { return new Promise(function() { @@ -90,6 +90,9 @@ it('correctly writes the files', async () => { let dataCluster = null; testPromise.then((result) => {dataCluster = result}); const fileExists = confirmFileExists(fileName, dataCluster); + const temp = createWriteFile('badFile.test') + + expect(testPromise.then(temp).then(fileExists)).resolves.toEqual(false); expect(testPromise.then(writeFile).then(fileExists)).resolves.not.toThrow(); expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(true); }); From f0da15e627b16a0172c0cff1530271d54c9e8314 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:48:05 -0700 Subject: [PATCH 14/32] Changed time conversion, fixed minor issues - Removed getUnixNumber, fixed time conversions - Fixed minor types, formatting issues - Removed redundant code --- www/js/controlHelper.ts | 48 ++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index 1b1755395..feade8622 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -30,7 +30,7 @@ export const createWriteFile = function (fileName: string) { reject(); } - // if data object is not passed in, create a new blog instead. + // if data object is not passed in, create a new blob instead. const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { type: "application/json" }); fileWriter.write(dataObj); @@ -51,7 +51,7 @@ export const createShareData = function(fileName: string, startTimeString: strin return function() { return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - logDebug("During email, file system open: " + fs.name); + logDebug(`During email, file system open: ${fs.name}`); fs.root.getFile(fileName, null, function(fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { @@ -63,22 +63,18 @@ export const createShareData = function(fileName: string, startTimeString: strin const dataArray = JSON.parse(readResult); logDebug(`Successfully read resultList of size ${dataArray.length}`); let attachFile = fileEntry.nativeURL; - if (window['device'].platform === "android") - attachFile = "app://cache/" + fileName; - if (window['device'].platform === "ios") - alert(i18next.t("email-service.email-account-mail-app")); - const email = { - 'files': [attachFile], - 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), - 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), - } - window['plugins'].socialsharing.shareWithOptions(email, function (result) { - logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false - logDebug(`Shared to app: ${result.app}`); - resolve(); - }, function (msg) { - logDebug(`Sharing failed with message ${msg}`); - }); + const email = { + 'files': [attachFile], + 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), + 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), + } + window['plugins'].socialsharing.shareWithOptions(email, function (result) { + logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false + logDebug(`Shared to app: ${result.app}`); + resolve(); + }, function (msg) { + logDebug(`Sharing failed with message ${msg}`); + }); } reader.readAsText(file); }, function(error) { @@ -92,13 +88,13 @@ export const createShareData = function(fileName: string, startTimeString: strin /** * getMyData fetches timeline data for a given day, and then gives the user a prompt to share the data - * @param startTs initial timestamp of the timeline to be fetched. + * @param timeStamp initial timestamp of the timeline to be fetched. */ -export const getMyData = function(startTs: Date) { +export const getMyData = function(timeStamp: Date) { // We are only retrieving data for a single day to avoid // running out of memory on the phone - const startTime = DateTime.fromJSDate(startTs); - const endTime = startTime.endOf("day"); + const endTime = DateTime.fromJSDate(timeStamp); + const startTime = endTime.startOf('day'); const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); @@ -107,16 +103,10 @@ export const getMyData = function(startTs: Date) { + ".timeline"; alert(`Going to retrieve data to ${dumpFile}`); - // Simulate old conversion to get correct UnixInteger for endMoment data - const getUnixNum = (dateData: DateTime) => { - const tempDate = dateData.toFormat("dd MMM yyyy"); - return DateTime.fromFormat(tempDate, "dd MMM yyyy").toUnixInteger(); - }; - const writeDumpFile = createWriteFile(dumpFile); const shareData = createShareData(dumpFile, startTimeString, endTimeString); - getRawEntries(null, getUnixNum(startTime), startTime.toUnixInteger()) + getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) .then(writeDumpFile) .then(shareData) .then(function() { From 5e61cb91066d7fb0d7d90b17c2cff518896e8d30 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Fri, 27 Oct 2023 10:53:41 -0700 Subject: [PATCH 15/32] Updated types to match UnifiedDataLoader, PR #1073 --- www/__tests__/controlHelper.test.ts | 9 ++++--- www/js/controlHelper.ts | 5 ++-- www/js/types/diaryTypes.ts | 12 --------- www/js/types/fileShareTypes.ts | 39 +++++++---------------------- www/js/types/serverData.ts | 32 +++++++++++++++++++++++ 5 files changed, 49 insertions(+), 48 deletions(-) delete mode 100644 www/js/types/diaryTypes.ts create mode 100644 www/js/types/serverData.ts diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 5e8836e80..dca43c039 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -1,6 +1,7 @@ import { mockLogger } from "../__mocks__/globalMocks"; import { createWriteFile } from "../js/controlHelper"; -import { FsWindow, RawData, RawDataCluster } from "../js/types/fileShareTypes" +import { FsWindow } from "../js/types/fileShareTypes" +import { ServerData, ServerResponse} from "../js/types/serverData" mockLogger(); declare let window: FsWindow; @@ -14,7 +15,7 @@ const generateFakeValues = (arraySize: number) => { if (arraySize <= 0) return new Promise (() => {return []}); - const sampleDataObj : RawData = { + const sampleDataObj : ServerData= { data: { name: 'testValue #', ts: 1234567890.9876543, @@ -51,14 +52,14 @@ const generateFakeValues = (arraySize: number) => { values[index].data.name = element.data.name + index.toString() }); - return new Promise(() => { + return new Promise>(() => { return { phone_data: values }; }); }; // A variation of createShareData; confirms the file has been written, // without calling the sharing components -const confirmFileExists = (fileName: string, dataCluster: RawDataCluster) => { +const confirmFileExists = (fileName: string, dataCluster: ServerResponse) => { return function() { return new Promise(function() { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { diff --git a/www/js/controlHelper.ts b/www/js/controlHelper.ts index feade8622..01f1e39f2 100644 --- a/www/js/controlHelper.ts +++ b/www/js/controlHelper.ts @@ -2,7 +2,8 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; import { logInfo, displayError, logDebug } from "./plugin/logger"; -import { FsWindow, RawDataCluster } from "./types/fileShareTypes" +import { FsWindow } from "./types/fileShareTypes" +import { ServerResponse} from "./types/serverData"; import i18next from "./i18nextInit" ; declare let window: FsWindow; @@ -13,7 +14,7 @@ declare let window: FsWindow; * @returns a function that returns a promise, which writes the file upon evaluation. */ export const createWriteFile = function (fileName: string) { - return function(result: RawDataCluster) { + return function(result: ServerResponse) { const resultList = result.phone_data; return new Promise(function(resolve, reject) { window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts deleted file mode 100644 index 1ecbd26cd..000000000 --- a/www/js/types/diaryTypes.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* This draft of diaryTypes was added to PR 1052, so that the LocalDT type is - consistent across PRs. Only LocalDt is needed for the controlHelper rewrite */ -export type LocalDt = { - minute: number, - hour: number, - second: number, - day: number, - weekday: number, - month: number, - year: number, - timezone: string, -} diff --git a/www/js/types/fileShareTypes.ts b/www/js/types/fileShareTypes.ts index e2b60bf49..89481624d 100644 --- a/www/js/types/fileShareTypes.ts +++ b/www/js/types/fileShareTypes.ts @@ -1,4 +1,12 @@ -import { LocalDt } from "./diaryTypes"; +import { ServerData } from './serverData'; + +export type TimeStampData = ServerData; + +export type RawTimelineData = { + name: string, + ts: number, + reading: number, +}; export interface FsWindow extends Window { requestFileSystem: ( @@ -12,32 +20,3 @@ export interface FsWindow extends Window { PERSISTENT: number; }; }; - -/* These are the objects returned from getRawEnteries when it is called by - the getMyData() method. */ -export interface RawDataCluster { - phone_data: Array -} - -export interface RawData { - data: { - name: string, - ts: number, - reading: number, - }, - metadata: { - key: string, - platform: string, - write_ts: number, - time_zone: string, - write_fmt_time: string, - write_local_dt: LocalDt, - }, - user_id: { - $uuid: string, - }, - _id: { - $oid: string, - } -} - diff --git a/www/js/types/serverData.ts b/www/js/types/serverData.ts new file mode 100644 index 000000000..35bd283bb --- /dev/null +++ b/www/js/types/serverData.ts @@ -0,0 +1,32 @@ +export type ServerResponse = { + phone_data: Array>, +} + +export type ServerData = { + data: Type, + metadata: MetaData, + key?: string, + user_id?: { $uuid: string, }, + _id?: { $oid: string, }, +}; + +export type MetaData = { + key: string, + platform: string, + write_ts: number, + time_zone: string, + write_fmt_time: string, + write_local_dt: LocalDt, +}; + +export type LocalDt = { + minute: number, + hour: number, + second: number, + day: number, + weekday: number, + month: number, + year: number, + timezone: string, +}; + \ No newline at end of file From 905e52c23f48e06a239e7c28742381bc299e020b Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:00:51 -0700 Subject: [PATCH 16/32] Moved rewrites into `www/js/services` - As discussed in PR #1064 - This should make merging easier down the road, and should help clean up the `/js/` directory. --- www/__tests__/commHelper.test.ts | 2 +- www/js/config/dynamicConfig.ts | 2 +- www/js/control/ControlSyncHelper.tsx | 2 +- www/js/diary/LabelTab.tsx | 2 +- www/js/diary/services.js | 2 +- www/js/metrics/MetricsTab.tsx | 2 +- www/js/onboarding/SaveQrPage.tsx | 2 +- www/js/services.js | 2 +- www/js/{ => services}/commHelper.ts | 2 +- www/js/{ => services}/controlHelper.ts | 0 www/js/splash/notifScheduler.js | 2 +- www/js/splash/pushnotify.js | 2 +- www/js/splash/storedevicesettings.js | 2 +- www/js/survey/enketo/EnketoModal.tsx | 2 +- www/js/survey/multilabel/confirmHelper.ts | 2 +- 15 files changed, 14 insertions(+), 14 deletions(-) rename www/js/{ => services}/commHelper.ts (99%) rename www/js/{ => services}/controlHelper.ts (100%) diff --git a/www/__tests__/commHelper.test.ts b/www/__tests__/commHelper.test.ts index 2e2dfc6af..ec8d8b4ff 100644 --- a/www/__tests__/commHelper.test.ts +++ b/www/__tests__/commHelper.test.ts @@ -1,5 +1,5 @@ import { mockLogger } from '../__mocks__/globalMocks'; -import { fetchUrlCached } from '../js/commHelper'; +import { fetchUrlCached } from '../js/services/commHelper'; mockLogger(); diff --git a/www/js/config/dynamicConfig.ts b/www/js/config/dynamicConfig.ts index 6d9b2b372..3bbd6906d 100644 --- a/www/js/config/dynamicConfig.ts +++ b/www/js/config/dynamicConfig.ts @@ -1,7 +1,7 @@ import i18next from "i18next"; import { displayError, logDebug, logWarn } from "../plugin/logger"; import { getAngularService } from "../angular-react-helper"; -import { fetchUrlCached } from "../commHelper"; +import { fetchUrlCached } from "../services/commHelper"; import { storageClear, storageGet, storageSet } from "../plugin/storage"; export const CONFIG_PHONE_UI="config/app_ui_config"; diff --git a/www/js/control/ControlSyncHelper.tsx b/www/js/control/ControlSyncHelper.tsx index edc0e7470..44bc661b2 100644 --- a/www/js/control/ControlSyncHelper.tsx +++ b/www/js/control/ControlSyncHelper.tsx @@ -9,7 +9,7 @@ import SettingRow from "./SettingRow"; import AlertBar from "./AlertBar"; import moment from "moment"; import { addStatEvent, statKeys } from "../plugin/clientStats"; -import { updateUser } from "../commHelper"; +import { updateUser } from "../services/commHelper"; /* * BEGIN: Simple read/write wrappers diff --git a/www/js/diary/LabelTab.tsx b/www/js/diary/LabelTab.tsx index bb2430481..b98c0eb6a 100644 --- a/www/js/diary/LabelTab.tsx +++ b/www/js/diary/LabelTab.tsx @@ -22,7 +22,7 @@ import { SurveyOptions } from "../survey/survey"; import { getLabelOptions } from "../survey/multilabel/confirmHelper"; import { displayError } from "../plugin/logger"; import { useTheme } from "react-native-paper"; -import { getPipelineRangeTs } from "../commHelper"; +import { getPipelineRangeTs } from "../services/commHelper"; let labelPopulateFactory, labelsResultMap, notesResultMap, showPlaces; const ONE_DAY = 24 * 60 * 60; // seconds diff --git a/www/js/diary/services.js b/www/js/diary/services.js index 774273fa2..d5dfc40ce 100644 --- a/www/js/diary/services.js +++ b/www/js/diary/services.js @@ -4,7 +4,7 @@ import angular from 'angular'; import { getBaseModeByKey, getBaseModeOfLabeledTrip } from './diaryHelper'; import { SurveyOptions } from '../survey/survey'; import { getConfig } from '../config/dynamicConfig'; -import { getRawEntries } from '../commHelper'; +import { getRawEntries } from '../services/commHelper'; angular.module('emission.main.diary.services', ['emission.plugin.logger', 'emission.services']) diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx index 450155622..8c9e2faee 100644 --- a/www/js/metrics/MetricsTab.tsx +++ b/www/js/metrics/MetricsTab.tsx @@ -16,7 +16,7 @@ import Carousel from "../components/Carousel"; import DailyActiveMinutesCard from "./DailyActiveMinutesCard"; import CarbonTextCard from "./CarbonTextCard"; import ActiveMinutesTableCard from "./ActiveMinutesTableCard"; -import { getAggregateData, getMetrics } from "../commHelper"; +import { getAggregateData, getMetrics } from "../services/commHelper"; export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const; diff --git a/www/js/onboarding/SaveQrPage.tsx b/www/js/onboarding/SaveQrPage.tsx index 406376cfa..dd9663f82 100644 --- a/www/js/onboarding/SaveQrPage.tsx +++ b/www/js/onboarding/SaveQrPage.tsx @@ -10,7 +10,7 @@ import QrCode, { shareQR } from "../components/QrCode"; import { onboardingStyles } from "./OnboardingStack"; import { preloadDemoSurveyResponse } from "./SurveyPage"; import { storageSet } from "../plugin/storage"; -import { registerUser } from "../commHelper"; +import { registerUser } from "../services/commHelper"; import { resetDataAndRefresh } from "../config/dynamicConfig"; import i18next from "i18next"; diff --git a/www/js/services.js b/www/js/services.js index e406be203..1b6a5b756 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -1,7 +1,7 @@ 'use strict'; import angular from 'angular'; -import { getRawEntries } from './commHelper'; +import { getRawEntries } from './services/commHelper'; import { logInfo} from './plugin/logger' angular.module('emission.services', ['emission.plugin.logger']) diff --git a/www/js/commHelper.ts b/www/js/services/commHelper.ts similarity index 99% rename from www/js/commHelper.ts rename to www/js/services/commHelper.ts index b9584a044..68e064abb 100644 --- a/www/js/commHelper.ts +++ b/www/js/services/commHelper.ts @@ -1,5 +1,5 @@ import { DateTime } from "luxon"; -import { logDebug } from "./plugin/logger"; +import { logDebug } from "../plugin/logger"; /** * @param url URL endpoint for the request diff --git a/www/js/controlHelper.ts b/www/js/services/controlHelper.ts similarity index 100% rename from www/js/controlHelper.ts rename to www/js/services/controlHelper.ts diff --git a/www/js/splash/notifScheduler.js b/www/js/splash/notifScheduler.js index 22f8407ee..0b7721c38 100644 --- a/www/js/splash/notifScheduler.js +++ b/www/js/splash/notifScheduler.js @@ -3,7 +3,7 @@ import angular from 'angular'; import { getConfig } from '../config/dynamicConfig'; import { addStatReading, statKeys } from '../plugin/clientStats'; -import { getUser, updateUser } from '../commHelper'; +import { getUser, updateUser } from '../services/commHelper'; angular.module('emission.splash.notifscheduler', ['emission.services', diff --git a/www/js/splash/pushnotify.js b/www/js/splash/pushnotify.js index 40d859f09..d38f66755 100644 --- a/www/js/splash/pushnotify.js +++ b/www/js/splash/pushnotify.js @@ -14,7 +14,7 @@ */ import angular from 'angular'; -import { updateUser } from '../commHelper'; +import { updateUser } from '../services/commHelper'; angular.module('emission.splash.pushnotify', ['emission.plugin.logger', 'emission.services', diff --git a/www/js/splash/storedevicesettings.js b/www/js/splash/storedevicesettings.js index d307feaa7..5fb3f8513 100644 --- a/www/js/splash/storedevicesettings.js +++ b/www/js/splash/storedevicesettings.js @@ -1,5 +1,5 @@ import angular from 'angular'; -import { updateUser } from '../commHelper'; +import { updateUser } from '../services/commHelper'; angular.module('emission.splash.storedevicesettings', ['emission.plugin.logger', 'emission.services', diff --git a/www/js/survey/enketo/EnketoModal.tsx b/www/js/survey/enketo/EnketoModal.tsx index 8b80b6dfe..82a1cef7e 100644 --- a/www/js/survey/enketo/EnketoModal.tsx +++ b/www/js/survey/enketo/EnketoModal.tsx @@ -5,7 +5,7 @@ import { ModalProps } from 'react-native-paper'; import useAppConfig from '../../useAppConfig'; import { useTranslation } from 'react-i18next'; import { SurveyOptions, getInstanceStr, saveResponse } from './enketoHelper'; -import { fetchUrlCached } from '../../commHelper'; +import { fetchUrlCached } from '../../services/commHelper'; import { displayError, displayErrorMsg } from '../../plugin/logger'; // import { transform } from 'enketo-transformer/web'; diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index 6350745eb..329b660e9 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -1,7 +1,7 @@ // may refactor this into a React hook once it's no longer used by any Angular screens import { getAngularService } from "../../angular-react-helper"; -import { fetchUrlCached } from "../../commHelper"; +import { fetchUrlCached } from "../../services/commHelper"; import i18next from "i18next"; import { logDebug } from "../../plugin/logger"; From 4b7b9d23abff444c2bceb4e7c3792a46abdc7f94 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:57:14 -0700 Subject: [PATCH 17/32] Updated `controlHelper` imports --- www/__tests__/controlHelper.test.ts | 2 +- www/js/control/DataDatePicker.tsx | 2 +- www/js/services/controlHelper.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index dca43c039..f205121f0 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -1,5 +1,5 @@ import { mockLogger } from "../__mocks__/globalMocks"; -import { createWriteFile } from "../js/controlHelper"; +import { createWriteFile } from "../js/services/controlHelper"; import { FsWindow } from "../js/types/fileShareTypes" import { ServerData, ServerResponse} from "../js/types/serverData" diff --git a/www/js/control/DataDatePicker.tsx b/www/js/control/DataDatePicker.tsx index f2873ef66..454e0dca0 100644 --- a/www/js/control/DataDatePicker.tsx +++ b/www/js/control/DataDatePicker.tsx @@ -4,7 +4,7 @@ import React from "react"; import { DatePickerModal } from 'react-native-paper-dates'; import { useTranslation } from "react-i18next"; -import { getMyData } from "../controlHelper"; +import { getMyData } from "../services/controlHelper"; const DataDatePicker = ({date, setDate, open, setOpen, minDate}) => { const { t, i18n } = useTranslation(); //able to pull lang from this diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index 01f1e39f2..d297c24c0 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -1,10 +1,10 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; -import { logInfo, displayError, logDebug } from "./plugin/logger"; -import { FsWindow } from "./types/fileShareTypes" -import { ServerResponse} from "./types/serverData"; -import i18next from "./i18nextInit" ; +import { logInfo, displayError, logDebug } from "../plugin/logger"; +import { FsWindow } from "../types/fileShareTypes" +import { ServerResponse} from "../types/serverData"; +import i18next from "../i18nextInit" ; declare let window: FsWindow; From 4dd295c14c5b4a2799dceb95b68ed2af105f310c Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:54:34 -0700 Subject: [PATCH 18/32] Refactored `controlHelper.ts`, updated fileIO - Changed fileIO to match `uplaodService.ts`, should allow proper jest mocking & testing - Refactored function structures; rather than 3 separate higher order functions, `getMyDataHelpers()` now returns an object containing each of the closures. --- www/js/control/ProfileSettings.jsx | 2 +- www/js/services/controlHelper.ts | 82 +++++++++++++++++------------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index f5b2b93d6..64d80c22e 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -25,7 +25,7 @@ import { AppContext } from "../App"; import { shareQR } from "../components/QrCode"; import { storageClear } from "../plugin/storage"; import { getAppVersion } from "../plugin/clientStats"; -import { fetchOPCode, getSettings } from "../controlHelper"; +import { fetchOPCode, getSettings } from "../services/controlHelper"; //any pure functions can go outside const ProfileSettings = () => { diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index d297c24c0..c88d92bf3 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -1,25 +1,20 @@ import { DateTime } from "luxon"; import { getRawEntries } from "./commHelper"; -import { logInfo, displayError, logDebug } from "../plugin/logger"; +import { logInfo, displayError, logDebug, logWarn } from "../plugin/logger"; import { FsWindow } from "../types/fileShareTypes" import { ServerResponse} from "../types/serverData"; import i18next from "../i18nextInit" ; declare let window: FsWindow; -/** - * createWriteFile is a factory method for the JSON dump file creation - * @param fileName is the name of the file to be created - * @returns a function that returns a promise, which writes the file upon evaluation. - */ -export const createWriteFile = function (fileName: string) { - return function(result: ServerResponse) { +export const getMyDataHelpers = function(fileName: string, startTimeString: string, endTimeString: string) { + const localWriteFile = function (result: ServerResponse) { const resultList = result.phone_data; return new Promise(function(resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { logDebug(`file system open: ${fs.name}`); - fs.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { + fs.filesystem.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) fileEntry.createWriter(function (fileWriter) { fileWriter.onwriteend = function() { @@ -39,21 +34,13 @@ export const createWriteFile = function (fileName: string) { }); }); }); -}}; + }; -/** - * createShareData returns a shareData method, with the input parameters captured. - * @param fileName is the existing file to be sent - * @param startTimeString timestamp used to identify the file - * @param endTimeString " " - * @returns a function which returns a promise, which shares an existing file upon evaluation. - */ -export const createShareData = function(fileName: string, startTimeString: string, endTimeString: string) { - return function() { + const localShareData = function () { return new Promise(function(resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - logDebug(`During email, file system open: ${fs.name}`); - fs.root.getFile(fileName, null, function(fileEntry) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { + logDebug(`During share, file system open: ${fs.name}`); + fs.filesystem.root.getFile(fileName, null, function(fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { const reader = new FileReader(); @@ -64,12 +51,12 @@ export const createShareData = function(fileName: string, startTimeString: strin const dataArray = JSON.parse(readResult); logDebug(`Successfully read resultList of size ${dataArray.length}`); let attachFile = fileEntry.nativeURL; - const email = { + const shareObj = { 'files': [attachFile], 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), - 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString ,end: endTimeString}), + 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString, end: endTimeString}), } - window['plugins'].socialsharing.shareWithOptions(email, function (result) { + window['plugins'].socialsharing.shareWithOptions(shareObj, function (result) { logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false logDebug(`Shared to app: ${result.app}`); resolve(); @@ -81,11 +68,38 @@ export const createShareData = function(fileName: string, startTimeString: strin }, function(error) { displayError(error, "Error while downloading JSON dump"); reject(error); - }) + }); + }); }); + }) + }; + + // window['cordova'].file.TempDirectory is not guaranteed to free up memory, + // so it's good practice to remove the file right after it's used! + const localClearData = function() { + return new Promise(function(resolve, reject) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { + fs.filesystem.root.getFile(fileName, null, function(fileEntry) { + fileEntry.remove(() => { + logDebug(`Successfully cleaned up file ${fileName}`); + resolve(); + }, + (err) => { + logWarn(`Error deleting ${fileName} : ${err}`); + reject(err); + }); + }); + }); }); -}}; + } + + return { + writeFile: localWriteFile, + shareData: localShareData, + clearData: localClearData, + }; +} /** * getMyData fetches timeline data for a given day, and then gives the user a prompt to share the data @@ -104,17 +118,17 @@ export const getMyData = function(timeStamp: Date) { + ".timeline"; alert(`Going to retrieve data to ${dumpFile}`); - const writeDumpFile = createWriteFile(dumpFile); - const shareData = createShareData(dumpFile, startTimeString, endTimeString); + const getDataMethods = getMyDataHelpers(dumpFile, startTimeString, endTimeString); getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) - .then(writeDumpFile) - .then(shareData) + .then(getDataMethods.writeFile) + .then(getDataMethods.shareData) + .then(getDataMethods.clearData) .then(function() { - logInfo("Email queued successfully"); + logInfo("Share queued successfully"); }) .catch(function(error) { - displayError(error, "Error emailing JSON dump"); + displayError(error, "Error sharing JSON dump"); }) }; From 24a9d95e71abad94e63a168f738b8ac6a80b1604 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:09:02 -0700 Subject: [PATCH 19/32] Updated jest testing, cordovaMocks - With the change in the previous commit, controlHelper methods can now be mocked via fileSystemMocks and cordovaMocks --- www/__mocks__/cordovaMocks.ts | 3 +- www/__mocks__/fileSystemMocks.ts | 15 +++++++- www/__tests__/controlHelper.test.ts | 55 +++++++++-------------------- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index c8f4b4c0f..181f6c07d 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -26,7 +26,8 @@ export const mockGetAppVersion = () => { export const mockFile = () => { window['cordova'].file = { "dataDirectory" : "../path/to/data/directory", - "applicationStorageDirectory" : "../path/to/app/storage/directory"}; + "applicationStorageDirectory" : "../path/to/app/storage/directory", + "tempDirectory" : "../path/to/temp/directory"}; } export const mockBEMUserCache = () => { diff --git a/www/__mocks__/fileSystemMocks.ts b/www/__mocks__/fileSystemMocks.ts index d7c2743ac..6c822311f 100644 --- a/www/__mocks__/fileSystemMocks.ts +++ b/www/__mocks__/fileSystemMocks.ts @@ -10,7 +10,20 @@ export const mockFileSystem = () => { file: (handleFile) => { let file = new File(["this is a mock"], "loggerDB"); handleFile(file); - } + }, + nativeURL: 'file:///Users/Jest/test/URL/', + isFile: true, + createWriter: (handleWriter) => { + const mockFileWriter = { + fileWriter: { + write: (myObect) => { + console.log(`Wrote: ${myObect}`) + }, + onwriteend: () => {}, + onerror: (error) => { return error; } + } + } + }, } onSuccess(fileEntry); } diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index f205121f0..941839c2f 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -1,19 +1,31 @@ import { mockLogger } from "../__mocks__/globalMocks"; -import { createWriteFile } from "../js/services/controlHelper"; +import { mockFileSystem } from "../__mocks__/fileSystemMocks"; +import { mockDevice, mockCordova, mockFile } from "../__mocks__/cordovaMocks"; + +import { getMyDataHelpers } from "../js/services/controlHelper"; import { FsWindow } from "../js/types/fileShareTypes" import { ServerData, ServerResponse} from "../js/types/serverData" +mockDevice(); +mockCordova(); +mockFile(); +mockFileSystem(); mockLogger(); declare let window: FsWindow; -const fileName = 'test.timeline' -const writeFile = createWriteFile(fileName); + +// Test constants: +const fileName = 'testOne' +const startTime = '1969-06-16' +const endTime = '1969-06-24' +const getDataMethodsOne = getMyDataHelpers(fileName, startTime, endTime); +const writeFile = getDataMethodsOne.writeFile; // createWriteFile does not require these objects specifically, but it // is better to test with similar data - using real data would take // up too much code space, and we cannot use getRawEnteries() in testing const generateFakeValues = (arraySize: number) => { if (arraySize <= 0) - return new Promise (() => {return []}); + return Promise.reject(); const sampleDataObj : ServerData= { data: { @@ -51,10 +63,7 @@ const generateFakeValues = (arraySize: number) => { values.forEach((element, index) => { values[index].data.name = element.data.name + index.toString() }); - - return new Promise>(() => { - return { phone_data: values }; - }); + return Promise.resolve({ phone_data: values }); }; // A variation of createShareData; confirms the file has been written, @@ -81,37 +90,7 @@ const confirmFileExists = (fileName: string, dataCluster: ServerResponse) = it('writes a file for an array of objects', async () => { const testPromiseOne = generateFakeValues(1); const testPromiseTwo = generateFakeValues(2222); - expect(testPromiseOne.then(writeFile)).resolves.not.toThrow(); expect(testPromiseTwo.then(writeFile)).resolves.not.toThrow(); }); -it('correctly writes the files', async () => { - const testPromise = generateFakeValues(1); - let dataCluster = null; - testPromise.then((result) => {dataCluster = result}); - const fileExists = confirmFileExists(fileName, dataCluster); - const temp = createWriteFile('badFile.test') - - expect(testPromise.then(temp).then(fileExists)).resolves.toEqual(false); - expect(testPromise.then(writeFile).then(fileExists)).resolves.not.toThrow(); - expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(true); -}); - -it('rejects an empty input', async () => { - const testPromise = generateFakeValues(0); - expect(testPromise.then(writeFile)).rejects.toThrow(); - - let dataCluster = null; - testPromise.then((result) => {dataCluster = result}); - const fileExists = confirmFileExists(fileName, dataCluster); - expect(testPromise.then(writeFile).then(fileExists)).resolves.toEqual(false); -}); - -/* - createShareData() is not tested, because it relies on the phoneGap social - sharing plugin, which cannot be mocked. - - getMyData relies on createShareData, and likewise cannot be tested - it also - relies on getRawEnteries(). -*/ From c2ab799f46a0526bbbf0eaa5b0c78b24d0dce1e6 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 1 Nov 2023 08:48:07 -0700 Subject: [PATCH 20/32] Added rejection test --- www/__tests__/controlHelper.test.ts | 25 +++++++++++++++---------- www/js/services/controlHelper.ts | 2 -- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 941839c2f..730e6e69f 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -13,19 +13,12 @@ mockFileSystem(); mockLogger(); declare let window: FsWindow; -// Test constants: -const fileName = 'testOne' -const startTime = '1969-06-16' -const endTime = '1969-06-24' -const getDataMethodsOne = getMyDataHelpers(fileName, startTime, endTime); -const writeFile = getDataMethodsOne.writeFile; - // createWriteFile does not require these objects specifically, but it // is better to test with similar data - using real data would take // up too much code space, and we cannot use getRawEnteries() in testing const generateFakeValues = (arraySize: number) => { if (arraySize <= 0) - return Promise.reject(); + return Promise.reject('reject'); const sampleDataObj : ServerData= { data: { @@ -87,10 +80,22 @@ const confirmFileExists = (fileName: string, dataCluster: ServerResponse) = }; }; +// Test constants: +const fileName = 'testOne' +const startTime = '1969-06-16' +const endTime = '1969-06-24' +const getDataMethodsOne = getMyDataHelpers(fileName, startTime, endTime); +const writeFile = getDataMethodsOne.writeFile; + +const testPromiseOne = generateFakeValues(1); +const testPromiseTwo = generateFakeValues(2222); +const badPromise = generateFakeValues(0); + it('writes a file for an array of objects', async () => { - const testPromiseOne = generateFakeValues(1); - const testPromiseTwo = generateFakeValues(2222); expect(testPromiseOne.then(writeFile)).resolves.not.toThrow(); expect(testPromiseTwo.then(writeFile)).resolves.not.toThrow(); }); +it('rejects an empty input', async () => { + expect(badPromise.then(writeFile)).rejects.toEqual('reject'); +}); \ No newline at end of file diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index c88d92bf3..22390a5dd 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -13,7 +13,6 @@ export const getMyDataHelpers = function(fileName: string, startTimeString: stri const resultList = result.phone_data; return new Promise(function(resolve, reject) { window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { - logDebug(`file system open: ${fs.name}`); fs.filesystem.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) fileEntry.createWriter(function (fileWriter) { @@ -39,7 +38,6 @@ export const getMyDataHelpers = function(fileName: string, startTimeString: stri const localShareData = function () { return new Promise(function(resolve, reject) { window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { - logDebug(`During share, file system open: ${fs.name}`); fs.filesystem.root.getFile(fileName, null, function(fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file(function(file) { From fffcd004517290d78047427dc2e13b8cc0fa8fca Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Wed, 1 Nov 2023 13:53:31 -0700 Subject: [PATCH 21/32] Expanded mocks, added tests - The controlHelper tests are _broken_ in this commit! Please see PR 1052 for discussion. --- www/__mocks__/fileSystemMocks.ts | 20 ++++++++++++-------- www/__tests__/controlHelper.test.ts | 23 +---------------------- www/js/services/controlHelper.ts | 2 +- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/www/__mocks__/fileSystemMocks.ts b/www/__mocks__/fileSystemMocks.ts index 6c822311f..996fa4450 100644 --- a/www/__mocks__/fileSystemMocks.ts +++ b/www/__mocks__/fileSystemMocks.ts @@ -1,4 +1,9 @@ export const mockFileSystem = () => { + type MockFileWriter = { + onreadend: any, + onerror: (e: any) => void, + write: (obj: Blob) => void, + } window['resolveLocalFileSystemURL'] = function (parentDir, handleFS) { const fs = { filesystem: @@ -14,15 +19,14 @@ export const mockFileSystem = () => { nativeURL: 'file:///Users/Jest/test/URL/', isFile: true, createWriter: (handleWriter) => { - const mockFileWriter = { - fileWriter: { - write: (myObect) => { - console.log(`Wrote: ${myObect}`) - }, - onwriteend: () => {}, - onerror: (error) => { return error; } - } + var mockFileWriter : MockFileWriter = { + onreadend: null, + onerror: null, + write: (obj) => { + console.log(`Mock this: ${obj}`); + }, } + handleWriter(mockFileWriter); }, } onSuccess(fileEntry); diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts index 730e6e69f..c62267d64 100644 --- a/www/__tests__/controlHelper.test.ts +++ b/www/__tests__/controlHelper.test.ts @@ -51,7 +51,7 @@ const generateFakeValues = (arraySize: number) => { } }; - // The parse/stringify lets us "deep copy" the objects, to quickly populate/change the data + // The parse/stringify lets us "deep copy" the objects, to quickly populate/change test data let values = Array.from({length: arraySize}, e => JSON.parse(JSON.stringify(sampleDataObj))); values.forEach((element, index) => { values[index].data.name = element.data.name + index.toString() @@ -59,27 +59,6 @@ const generateFakeValues = (arraySize: number) => { return Promise.resolve({ phone_data: values }); }; -// A variation of createShareData; confirms the file has been written, -// without calling the sharing components -const confirmFileExists = (fileName: string, dataCluster: ServerResponse) => { - return function() { - return new Promise(function() { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - fs.root.getFile(fileName, null, function(fileEntry) { - if (!fileEntry.isFile) - return fileEntry.isFile; - const reader = new FileReader(); - reader.onloadend = function () { - const readResult = this.result as string; - const expectedResult = JSON.stringify(dataCluster); - return (readResult === expectedResult); - } - }); - }); - }); - }; -}; - // Test constants: const fileName = 'testOne' const startTime = '1969-06-16' diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index 22390a5dd..58d6fe738 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -24,7 +24,7 @@ export const getMyDataHelpers = function(fileName: string, startTimeString: stri logDebug(`Failed file write: ${e.toString()}`); reject(); } - + logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`) // if data object is not passed in, create a new blob instead. const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { type: "application/json" }); From 58a341155200d21b5275df7a50f26ee248873178 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:26:49 -0700 Subject: [PATCH 22/32] Ran prettier on controlHelper --- www/js/services/controlHelper.ts | 210 +++++++++++++++++-------------- 1 file changed, 115 insertions(+), 95 deletions(-) diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index 58d6fe738..f2741b0dd 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -1,139 +1,159 @@ -import { DateTime } from "luxon"; +import { DateTime } from 'luxon'; -import { getRawEntries } from "./commHelper"; -import { logInfo, displayError, logDebug, logWarn } from "../plugin/logger"; -import { FsWindow } from "../types/fileShareTypes" -import { ServerResponse} from "../types/serverData"; -import i18next from "../i18nextInit" ; +import { getRawEntries } from './commHelper'; +import { logInfo, displayError, logDebug, logWarn } from '../plugin/logger'; +import { FsWindow } from '../types/fileShareTypes'; +import { ServerResponse } from '../types/serverData'; +import i18next from '../i18nextInit'; declare let window: FsWindow; -export const getMyDataHelpers = function(fileName: string, startTimeString: string, endTimeString: string) { +export const getMyDataHelpers = function ( + fileName: string, + startTimeString: string, + endTimeString: string, +) { const localWriteFile = function (result: ServerResponse) { const resultList = result.phone_data; - return new Promise(function(resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { - fs.filesystem.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`) + return new Promise(function (resolve, reject) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile( + fileName, + { create: true, exclusive: false }, + function (fileEntry) { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function() { - logDebug("Successful file write..."); + fileWriter.onwriteend = function () { + logDebug('Successful file write...'); resolve(); - } - fileWriter.onerror = function(e) { + }; + fileWriter.onerror = function (e) { logDebug(`Failed file write: ${e.toString()}`); reject(); - } - logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`) + }; + logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); // if data object is not passed in, create a new blob instead. - const dataObj = new Blob([JSON.stringify(resultList, null, 2)], - { type: "application/json" }); + const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { + type: 'application/json', + }); fileWriter.write(dataObj); - }) - }); - }); + }); + }, + ); + }); }); }; const localShareData = function () { - return new Promise(function(resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { - fs.filesystem.root.getFile(fileName, null, function(fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); - fileEntry.file(function(file) { - const reader = new FileReader(); + return new Promise(function (resolve, reject) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.file( + function (file) { + const reader = new FileReader(); - reader.onloadend = function() { - const readResult = this.result as string; - logDebug(`Successfull file read with ${readResult.length} characters`); - const dataArray = JSON.parse(readResult); - logDebug(`Successfully read resultList of size ${dataArray.length}`); - let attachFile = fileEntry.nativeURL; - const shareObj = { - 'files': [attachFile], - 'message': i18next.t("email-service.email-data.body-data-consists-of-list-of-entries"), - 'subject': i18next.t("email-service.email-data.subject-data-dump-from-to", {start: startTimeString, end: endTimeString}), - } - window['plugins'].socialsharing.shareWithOptions(shareObj, function (result) { - logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false - logDebug(`Shared to app: ${result.app}`); - resolve(); - }, function (msg) { - logDebug(`Sharing failed with message ${msg}`); - }); - } - reader.readAsText(file); - }, function(error) { - displayError(error, "Error while downloading JSON dump"); - reject(error); + reader.onloadend = function () { + const readResult = this.result as string; + logDebug(`Successfull file read with ${readResult.length} characters`); + const dataArray = JSON.parse(readResult); + logDebug(`Successfully read resultList of size ${dataArray.length}`); + let attachFile = fileEntry.nativeURL; + const shareObj = { + files: [attachFile], + message: i18next.t( + 'email-service.email-data.body-data-consists-of-list-of-entries', + ), + subject: i18next.t('email-service.email-data.subject-data-dump-from-to', { + start: startTimeString, + end: endTimeString, + }), + }; + window['plugins'].socialsharing.shareWithOptions( + shareObj, + function (result) { + logDebug(`Share Completed? ${result.completed}`); // On Android, most likely returns false + logDebug(`Shared to app: ${result.app}`); + resolve(); + }, + function (msg) { + logDebug(`Sharing failed with message ${msg}`); + }, + ); + }; + reader.readAsText(file); + }, + function (error) { + displayError(error, 'Error while downloading JSON dump'); + reject(error); + }, + ); }); - }); }); - }) }; // window['cordova'].file.TempDirectory is not guaranteed to free up memory, // so it's good practice to remove the file right after it's used! - const localClearData = function() { - return new Promise(function(resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function(fs) { - fs.filesystem.root.getFile(fileName, null, function(fileEntry) { - fileEntry.remove(() => { - logDebug(`Successfully cleaned up file ${fileName}`); - resolve(); - }, - (err) => { - logWarn(`Error deleting ${fileName} : ${err}`); - reject(err); - }); + const localClearData = function () { + return new Promise(function (resolve, reject) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + fileEntry.remove( + () => { + logDebug(`Successfully cleaned up file ${fileName}`); + resolve(); + }, + (err) => { + logWarn(`Error deleting ${fileName} : ${err}`); + reject(err); + }, + ); }); }); }); - } + }; return { writeFile: localWriteFile, shareData: localShareData, clearData: localClearData, }; -} +}; /** * getMyData fetches timeline data for a given day, and then gives the user a prompt to share the data * @param timeStamp initial timestamp of the timeline to be fetched. */ -export const getMyData = function(timeStamp: Date) { - // We are only retrieving data for a single day to avoid - // running out of memory on the phone - const endTime = DateTime.fromJSDate(timeStamp); - const startTime = endTime.startOf('day'); - const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); - const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); +export const getMyData = function (timeStamp: Date) { + // We are only retrieving data for a single day to avoid + // running out of memory on the phone + const endTime = DateTime.fromJSDate(timeStamp); + const startTime = endTime.startOf('day'); + const startTimeString = startTime.toFormat("yyyy'-'MM'-'dd"); + const endTimeString = endTime.toFormat("yyyy'-'MM'-'dd"); - const dumpFile = startTimeString + "." - + endTimeString - + ".timeline"; - alert(`Going to retrieve data to ${dumpFile}`); + const dumpFile = startTimeString + '.' + endTimeString + '.timeline'; + alert(`Going to retrieve data to ${dumpFile}`); - const getDataMethods = getMyDataHelpers(dumpFile, startTimeString, endTimeString); + const getDataMethods = getMyDataHelpers(dumpFile, startTimeString, endTimeString); - getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) - .then(getDataMethods.writeFile) - .then(getDataMethods.shareData) - .then(getDataMethods.clearData) - .then(function() { - logInfo("Share queued successfully"); - }) - .catch(function(error) { - displayError(error, "Error sharing JSON dump"); - }) + getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) + .then(getDataMethods.writeFile) + .then(getDataMethods.shareData) + .then(getDataMethods.clearData) + .then(function () { + logInfo('Share queued successfully'); + }) + .catch(function (error) { + displayError(error, 'Error sharing JSON dump'); + }); }; -export const fetchOPCode = (() => { - return window["cordova"].plugins.OPCodeAuth.getOPCode(); - }); +export const fetchOPCode = () => { + return window['cordova'].plugins.OPCodeAuth.getOPCode(); +}; -export const getSettings = (() => { - return window["cordova"].plugins.BEMConnectionSettings.getSettings(); -}); +export const getSettings = () => { + return window['cordova'].plugins.BEMConnectionSettings.getSettings(); +}; From 428f191b0274b8ae530fae4335063cad68e98e05 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:17:45 -0700 Subject: [PATCH 23/32] Fixed merge conflict artifact --- www/js/services.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/www/js/services.js b/www/js/services.js index dfd0ac778..e1bf1d1c5 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -119,12 +119,6 @@ angular }, ); return combinedPromise(localPromise, remotePromise, combineWithDedup); -<<<<<<< HEAD - } -}) -.factory('Chats', function() { - // Might use a resource here that returns a JSON array -======= }; this.getUnifiedMessagesForInterval = function (key, tq, withMetadata) { From 4afd9ec3d1dba0bec61f225d4f1d13b07a720462 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Sat, 4 Nov 2023 01:00:36 -0400 Subject: [PATCH 24/32] remove js/diary/diaryTypes.ts not needed since we are now keeping diaryTypes as js/types/diaryTypes.ts --- www/js/diary/diaryTypes.ts | 72 -------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 www/js/diary/diaryTypes.ts diff --git a/www/js/diary/diaryTypes.ts b/www/js/diary/diaryTypes.ts deleted file mode 100644 index 5755c91ab..000000000 --- a/www/js/diary/diaryTypes.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* These type definitions are a work in progress. The goal is to have a single source of truth for - the types of the trip / place / untracked objects and all properties they contain. - Since we are using TypeScript now, we should strive to enforce type safety and also benefit from - IntelliSense and other IDE features. */ - -// Since it is WIP, these types are not used anywhere yet. - -type ConfirmedPlace = any; // TODO - -/* These are the properties received from the server (basically matches Python code) - This should match what Timeline.readAllCompositeTrips returns (an array of these objects) */ -export type CompositeTrip = { - _id: { $oid: string }; - additions: any[]; // TODO - cleaned_section_summary: any; // TODO - cleaned_trip: { $oid: string }; - confidence_threshold: number; - confirmed_trip: { $oid: string }; - distance: number; - duration: number; - end_confirmed_place: ConfirmedPlace; - end_fmt_time: string; - end_loc: { type: string; coordinates: number[] }; - end_local_dt: any; // TODO - end_place: { $oid: string }; - end_ts: number; - expectation: any; // TODO "{to_label: boolean}" - expected_trip: { $oid: string }; - inferred_labels: any[]; // TODO - inferred_section_summary: any; // TODO - inferred_trip: { $oid: string }; - key: string; - locations: any[]; // TODO - origin_key: string; - raw_trip: { $oid: string }; - sections: any[]; // TODO - source: string; - start_confirmed_place: ConfirmedPlace; - start_fmt_time: string; - start_loc: { type: string; coordinates: number[] }; - start_local_dt: any; // TODO - start_place: { $oid: string }; - start_ts: number; - user_input: any; // TODO -}; - -/* These properties aren't received from the server, but are derived from the above properties. - They are used in the UI to display trip/place details and are computed by the useDerivedProperties hook. */ -export type DerivedProperties = { - displayDate: string; - displayStartTime: string; - displayEndTime: string; - displayTime: string; - displayStartDateAbbr: string; - displayEndDateAbbr: string; - formattedDistance: string; - formattedSectionProperties: any[]; // TODO - distanceSuffix: string; - detectedModes: { mode: string; icon: string; color: string; pct: number | string }[]; -}; - -/* These are the properties that are still filled in by some kind of 'populate' mechanism. - It would simplify the codebase to just compute them where they're needed - (using memoization when apt so performance is not impacted). */ -export type PopulatedTrip = CompositeTrip & { - additionsList?: any[]; // TODO - finalInference?: any; // TODO - geojson?: any; // TODO - getNextEntry?: () => PopulatedTrip | ConfirmedPlace; - userInput?: any; // TODO - verifiability?: string; -}; From 8cb369173abab4d4638f74a4a03cb3b9c499340a Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Sat, 4 Nov 2023 01:02:14 -0400 Subject: [PATCH 25/32] expand diaryTypes d96ff8d0d7c785f6914b2d080885df9030f6d033 --- www/js/diary/diaryHelper.ts | 4 +- www/js/types/diaryTypes.ts | 131 ++++++++++++++++++++++++------------ 2 files changed, 90 insertions(+), 45 deletions(-) diff --git a/www/js/diary/diaryHelper.ts b/www/js/diary/diaryHelper.ts index 48f40322d..5b060a236 100644 --- a/www/js/diary/diaryHelper.ts +++ b/www/js/diary/diaryHelper.ts @@ -24,7 +24,7 @@ type BaseMode = { }; // parallels the server-side MotionTypes enum: https://github.com/e-mission/e-mission-server/blob/94e7478e627fa8c171323662f951c611c0993031/emission/core/wrapper/motionactivity.py#L12 -type MotionTypeKey = +export type MotionTypeKey = | 'IN_VEHICLE' | 'BICYCLING' | 'ON_FOOT' @@ -64,7 +64,7 @@ const BaseModes: { [k: string]: BaseMode } = { OTHER: { name: 'OTHER', icon: 'pencil-circle', color: modeColors.taupe }, }; -type BaseModeKey = keyof typeof BaseModes; +export type BaseModeKey = keyof typeof BaseModes; /** * @param motionName A string like "WALKING" or "MotionTypes.WALKING" * @returns A BaseMode object containing the name, icon, and color of the motion type diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts index 14d8acc07..2c901df00 100644 --- a/www/js/types/diaryTypes.ts +++ b/www/js/types/diaryTypes.ts @@ -1,75 +1,120 @@ -import { LocalDt, ServerData } from './serverData'; +/* This file provides typings for use in '/diary', including timeline objects (trips and places) + and user input objects. + As much as possible, these types parallel the types used in the server code. */ -export type UserInput = ServerData; +import { BaseModeKey, MotionTypeKey } from '../diary/diaryHelper'; +import { LocalDt } from './serverData'; -export type UserInputData = { - end_ts: number; - start_ts: number; - label: string; - start_local_dt?: LocalDt; - end_local_dt?: LocalDt; - status?: string; - match_id?: string; +type ObjectId = { $oid: string }; +type ConfirmedPlace = { + _id: ObjectId; + additions: UserInputEntry[]; + cleaned_place: ObjectId; + ending_trip: ObjectId; + enter_fmt_time: string; // ISO string 2023-10-31T12:00:00.000-04:00 + enter_local_dt: LocalDt; + enter_ts: number; // Unix timestamp + key: string; + location: { type: string; coordinates: number[] }; + origin_key: string; + raw_places: ObjectId[]; + source: string; + user_input: { + /* for keys ending in 'user_input' (e.g. 'trip_user_input'), the server gives us the raw user + input object with 'data' and 'metadata' */ + [k: `${string}user_input`]: UserInputEntry; + /* for keys ending in 'confirm' (e.g. 'mode_confirm'), the server just gives us the user input value + as a string (e.g. 'walk', 'drove_alone') */ + [k: `${string}confirm`]: string; + }; }; -type ConfirmedPlace = any; // TODO - +/* These are the properties received from the server (basically matches Python code) + This should match what Timeline.readAllCompositeTrips returns (an array of these objects) */ export type CompositeTrip = { - _id: { $oid: string }; - additions: any[]; // TODO - cleaned_section_summary: any; // TODO - cleaned_trip: { $oid: string }; + _id: ObjectId; + additions: UserInputEntry[]; + cleaned_section_summary: SectionSummary; + cleaned_trip: ObjectId; confidence_threshold: number; - confirmed_trip: { $oid: string }; + confirmed_trip: ObjectId; distance: number; duration: number; end_confirmed_place: ConfirmedPlace; end_fmt_time: string; end_loc: { type: string; coordinates: number[] }; end_local_dt: LocalDt; - end_place: { $oid: string }; + end_place: ObjectId; end_ts: number; expectation: any; // TODO "{to_label: boolean}" - expected_trip: { $oid: string }; + expected_trip: ObjectId; inferred_labels: any[]; // TODO - inferred_section_summary: any; // TODO - inferred_trip: { $oid: string }; + inferred_section_summary: SectionSummary; + inferred_trip: ObjectId; key: string; locations: any[]; // TODO origin_key: string; - raw_trip: { $oid: string }; + raw_trip: ObjectId; sections: any[]; // TODO source: string; start_confirmed_place: ConfirmedPlace; start_fmt_time: string; start_loc: { type: string; coordinates: number[] }; start_local_dt: LocalDt; - start_place: { $oid: string }; + start_place: ObjectId; start_ts: number; - user_input: UserInput; + user_input: { + /* for keys ending in 'user_input' (e.g. 'trip_user_input'), the server gives us the raw user + input object with 'data' and 'metadata' */ + [k: `${string}user_input`]: UserInputEntry; + /* for keys ending in 'confirm' (e.g. 'mode_confirm'), the server just gives us the user input value + as a string (e.g. 'walk', 'drove_alone') */ + [k: `${string}confirm`]: string; + }; }; -export type PopulatedTrip = CompositeTrip & { - additionsList?: any[]; // TODO - finalInference?: any; // TODO - geojson?: any; // TODO - getNextEntry?: () => PopulatedTrip | ConfirmedPlace; - userInput?: UserInput; - verifiability?: string; +/* The 'timeline' for a user is a list of their trips and places, + so a 'timeline entry' is either a trip or a place. */ +export type TimelineEntry = ConfirmedPlace | CompositeTrip; + +/* These properties aren't received from the server, but are derived from the above properties. + They are used in the UI to display trip/place details and are computed by the useDerivedProperties hook. */ +export type DerivedProperties = { + displayDate: string; + displayStartTime: string; + displayEndTime: string; + displayTime: string; + displayStartDateAbbr: string; + displayEndDateAbbr: string; + formattedDistance: string; + formattedSectionProperties: any[]; // TODO + distanceSuffix: string; + detectedModes: { mode: string; icon: string; color: string; pct: number | string }[]; }; -export type Trip = { - end_ts: number; - start_ts: number; +export type SectionSummary = { + count: { [k: MotionTypeKey | BaseModeKey]: number }; + distance: { [k: MotionTypeKey | BaseModeKey]: number }; + duration: { [k: MotionTypeKey | BaseModeKey]: number }; }; -export type TlEntry = { - key: string; - origin_key: string; - start_ts: number; - end_ts: number; - enter_ts: number; - exit_ts: number; - duration: number; - getNextEntry?: () => PopulatedTrip | ConfirmedPlace; +export type UserInputEntry = { + data: { + end_ts: number; + start_ts: number; + label: string; + start_local_dt?: LocalDt; + end_local_dt?: LocalDt; + status?: string; + match_id?: string; + }; + metadata: { + time_zone: string; + plugin: string; + write_ts: number; + platform: string; + read_ts: number; + key: string; + }; + key?: string; }; From 12e80295e2600439d714cb0131cae98e252bb811 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:55:44 -0800 Subject: [PATCH 26/32] Expanded diaryTypes - these types are used by timelineHelper --- www/js/types/diaryTypes.ts | 85 ++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts index 14d8acc07..4f10fb50f 100644 --- a/www/js/types/diaryTypes.ts +++ b/www/js/types/diaryTypes.ts @@ -12,39 +12,71 @@ export type UserInputData = { match_id?: string; }; -type ConfirmedPlace = any; // TODO +export type TripTransition = { + currstate: string; + transition: string; + ts: number; +}; + +export type ObjId = { + $oid: string; // objIds have len 24 +}; + +export type LocationCoord = { + type: string; // e.x., "Point" + coordinates: [number, number]; +}; + +type ConfirmedPlace = { + cleaned_place: { + string; + }; + additions: Array; // Todo + ender_local_dt: LocalDt; + starting_trip: ObjId; + exit_fmt_time: string; // ISO + exit_local_dt: LocalDt; + enter_ts: number; + source: string; + enter_fmt_time: string; + raw_places: Array; + location: LocationCoord; + exit_ts: number; + ending_trip: ObjId; + user_input: {}; //todo +}; export type CompositeTrip = { - _id: { $oid: string }; + _id: ObjId; additions: any[]; // TODO cleaned_section_summary: any; // TODO - cleaned_trip: { $oid: string }; + cleaned_trip: ObjId; confidence_threshold: number; - confirmed_trip: { $oid: string }; + confirmed_trip: ObjId; distance: number; duration: number; end_confirmed_place: ConfirmedPlace; end_fmt_time: string; end_loc: { type: string; coordinates: number[] }; end_local_dt: LocalDt; - end_place: { $oid: string }; + end_place: ObjId; end_ts: number; expectation: any; // TODO "{to_label: boolean}" - expected_trip: { $oid: string }; + expected_trip: ObjId; inferred_labels: any[]; // TODO inferred_section_summary: any; // TODO - inferred_trip: { $oid: string }; + inferred_trip: ObjId; key: string; locations: any[]; // TODO origin_key: string; - raw_trip: { $oid: string }; + raw_trip: ObjId; sections: any[]; // TODO source: string; start_confirmed_place: ConfirmedPlace; start_fmt_time: string; start_loc: { type: string; coordinates: number[] }; start_local_dt: LocalDt; - start_place: { $oid: string }; + start_place: ObjId; start_ts: number; user_input: UserInput; }; @@ -73,3 +105,38 @@ export type TlEntry = { duration: number; getNextEntry?: () => PopulatedTrip | ConfirmedPlace; }; + +export type Location = { + speed: number; + heading: number; + local_dt: LocalDt; + idx: number; + section: ObjId; + longitude: number; + latitude: number; + fmt_time: string; // ISO + mode: number; + loc: LocationCoord; + ts: number; // Unix + altitude: number; + distance: number; +}; + +// used in readAllCompositeTrips +export type SectionData = { + end_ts: number; // Unix time, e.x. 1696352498.804 + end_loc: LocationCoord; + start_fmt_time: string; // ISO time + end_fmt_time: string; + trip_id: ObjId; + sensed_mode: number; + source: string; // e.x., "SmoothedHighConfidenceMotion" + start_ts: number; // Unix + start_loc: LocationCoord; + cleaned_section: ObjId; + start_local_dt: LocalDt; + end_local_dt: LocalDt; + sensed_mode_str: string; //e.x., "CAR" + duration: number; + distance: number; +}; From 4a96b9d146e549549ace56156ac9829af16d5c98 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:37:05 -0800 Subject: [PATCH 27/32] Minor adjustments to diaryTypes --- www/js/types/diaryTypes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/js/types/diaryTypes.ts b/www/js/types/diaryTypes.ts index cfde363d6..938b06851 100644 --- a/www/js/types/diaryTypes.ts +++ b/www/js/types/diaryTypes.ts @@ -3,7 +3,7 @@ As much as possible, these types parallel the types used in the server code. */ import { BaseModeKey, MotionTypeKey } from '../diary/diaryHelper'; -import { LocalDt } from './serverData'; +import { ServerData, LocalDt } from './serverData'; type ObjectId = { $oid: string }; type ConfirmedPlace = { @@ -51,7 +51,7 @@ export type CompositeTrip = { confirmed_trip: ObjectId; distance: number; duration: number; - end_confirmed_place: ConfirmedPlace; + end_confirmed_place: ServerData; end_fmt_time: string; end_loc: { type: string; coordinates: number[] }; end_local_dt: LocalDt; @@ -68,7 +68,7 @@ export type CompositeTrip = { raw_trip: ObjectId; sections: any[]; // TODO source: string; - start_confirmed_place: ConfirmedPlace; + start_confirmed_place: ServerData; start_fmt_time: string; start_loc: { type: string; coordinates: number[] }; start_local_dt: LocalDt; From 05b287a08e66e00ee4336ad1d577dc7f62d68ab0 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Fri, 10 Nov 2023 11:11:50 -0800 Subject: [PATCH 28/32] index.html fix --- www/index.html | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/www/index.html b/www/index.html index b46904cca..44fcb5bbf 100644 --- a/www/index.html +++ b/www/index.html @@ -1,22 +1,18 @@ - - - + + + - + - + -
+
- + \ No newline at end of file From 2c02e37a07d00e0a5c638b664e7408fae27abc9b Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:31:44 -0800 Subject: [PATCH 29/32] Ran prettier, removed broken tests Cleaned up branch for review --- www/__mocks__/fileSystemMocks.ts | 10 -- www/__tests__/controlHelper.test.ts | 80 ------------ www/js/control/ProfileSettings.jsx | 2 +- www/js/services.js | 182 +++++++++++++++------------- 4 files changed, 97 insertions(+), 177 deletions(-) delete mode 100644 www/__tests__/controlHelper.test.ts diff --git a/www/__mocks__/fileSystemMocks.ts b/www/__mocks__/fileSystemMocks.ts index 9ed351f02..1648c2f4b 100644 --- a/www/__mocks__/fileSystemMocks.ts +++ b/www/__mocks__/fileSystemMocks.ts @@ -16,16 +16,6 @@ export const mockFileSystem = () => { }, nativeURL: 'file:///Users/Jest/test/URL/', isFile: true, - createWriter: (handleWriter) => { - var mockFileWriter: MockFileWriter = { - onreadend: null, - onerror: null, - write: (obj) => { - console.log(`Mock this: ${obj}`); - }, - }; - handleWriter(mockFileWriter); - }, }; onSuccess(fileEntry); }, diff --git a/www/__tests__/controlHelper.test.ts b/www/__tests__/controlHelper.test.ts deleted file mode 100644 index c62267d64..000000000 --- a/www/__tests__/controlHelper.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { mockLogger } from "../__mocks__/globalMocks"; -import { mockFileSystem } from "../__mocks__/fileSystemMocks"; -import { mockDevice, mockCordova, mockFile } from "../__mocks__/cordovaMocks"; - -import { getMyDataHelpers } from "../js/services/controlHelper"; -import { FsWindow } from "../js/types/fileShareTypes" -import { ServerData, ServerResponse} from "../js/types/serverData" - -mockDevice(); -mockCordova(); -mockFile(); -mockFileSystem(); -mockLogger(); -declare let window: FsWindow; - -// createWriteFile does not require these objects specifically, but it -// is better to test with similar data - using real data would take -// up too much code space, and we cannot use getRawEnteries() in testing -const generateFakeValues = (arraySize: number) => { - if (arraySize <= 0) - return Promise.reject('reject'); - - const sampleDataObj : ServerData= { - data: { - name: 'testValue #', - ts: 1234567890.9876543, - reading: 0.1234567891011121, - }, - metadata: { - key: 'MyKey/test', - platform: 'dev_testing', - time_zone: 'America/Los_Angeles', - write_fmt_time: '2023-04-14T00:09:10.80023-07:00', - write_local_dt: { - minute: 1, - hour: 2, - second: 3, - day: 4, - weekday: 5, - month: 6, - year: 7, - timezone: 'America/Los_Angeles', - }, - write_ts: 12345.6789, - }, - user_id: { - $uuid: '41t0l8e00s914tval1234567u9658699', - }, - _id: { - $oid: '12341x123afe3fbf541524d8', - } - }; - - // The parse/stringify lets us "deep copy" the objects, to quickly populate/change test data - let values = Array.from({length: arraySize}, e => JSON.parse(JSON.stringify(sampleDataObj))); - values.forEach((element, index) => { - values[index].data.name = element.data.name + index.toString() - }); - return Promise.resolve({ phone_data: values }); -}; - -// Test constants: -const fileName = 'testOne' -const startTime = '1969-06-16' -const endTime = '1969-06-24' -const getDataMethodsOne = getMyDataHelpers(fileName, startTime, endTime); -const writeFile = getDataMethodsOne.writeFile; - -const testPromiseOne = generateFakeValues(1); -const testPromiseTwo = generateFakeValues(2222); -const badPromise = generateFakeValues(0); - -it('writes a file for an array of objects', async () => { - expect(testPromiseOne.then(writeFile)).resolves.not.toThrow(); - expect(testPromiseTwo.then(writeFile)).resolves.not.toThrow(); -}); - -it('rejects an empty input', async () => { - expect(badPromise.then(writeFile)).rejects.toEqual('reject'); -}); \ No newline at end of file diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index 2beeb2fde..110b24bfc 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -33,7 +33,7 @@ import { storageClear } from '../plugin/storage'; import { getAppVersion } from '../plugin/clientStats'; import { getConsentDocument } from '../splash/startprefs'; import { logDebug } from '../plugin/logger'; -import { fetchOPCode, getSettings } from "../services/controlHelper"; +import { fetchOPCode, getSettings } from '../services/controlHelper'; //any pure functions can go outside const ProfileSettings = () => { diff --git a/www/js/services.js b/www/js/services.js index d0d2b059a..6ed060ed9 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -34,110 +34,120 @@ angular .service('ControlHelper', function ($window, $ionicPopup, Logger) { this.writeFile = function (fileEntry, resultList) { // Create a FileWriter object for our FileEntry (log.txt). - } + }; - this.getMyData = function(startTs) { - var fmt = "YYYY-MM-DD"; - // We are only retrieving data for a single day to avoid - // running out of memory on the phone - var startMoment = moment(startTs); - var endMoment = moment(startTs).endOf("day"); - var dumpFile = startMoment.format(fmt) + "." - + endMoment.format(fmt) - + ".timeline"; - alert("Going to retrieve data to "+dumpFile); + this.getMyData = function (startTs) { + var fmt = 'YYYY-MM-DD'; + // We are only retrieving data for a single day to avoid + // running out of memory on the phone + var startMoment = moment(startTs); + var endMoment = moment(startTs).endOf('day'); + var dumpFile = startMoment.format(fmt) + '.' + endMoment.format(fmt) + '.timeline'; + alert('Going to retrieve data to ' + dumpFile); - var writeDumpFile = function(result) { - return new Promise(function(resolve, reject) { - var resultList = result.phone_data; - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log('file system open: ' + fs.name); - fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { - console.log("fileEntry "+fileEntry.nativeURL+" is file?" + fileEntry.isFile.toString()); - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function() { - console.log("Successful file write..."); - resolve(); - // readFile(fileEntry); - }; + var writeDumpFile = function (result) { + return new Promise(function (resolve, reject) { + var resultList = result.phone_data; + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { + console.log('file system open: ' + fs.name); + fs.root.getFile(dumpFile, { create: true, exclusive: false }, function (fileEntry) { + console.log( + 'fileEntry ' + fileEntry.nativeURL + ' is file?' + fileEntry.isFile.toString(), + ); + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function () { + console.log('Successful file write...'); + resolve(); + // readFile(fileEntry); + }; - fileWriter.onerror = function (e) { - console.log("Failed file write: " + e.toString()); - reject(); - }; + fileWriter.onerror = function (e) { + console.log('Failed file write: ' + e.toString()); + reject(); + }; - // If data object is not passed in, - // create a new Blob instead. - var dataObj = new Blob([JSON.stringify(resultList, null, 2)], - { type: 'application/json' }); - fileWriter.write(dataObj); - }); - // this.writeFile(fileEntry, resultList); + // If data object is not passed in, + // create a new Blob instead. + var dataObj = new Blob([JSON.stringify(resultList, null, 2)], { + type: 'application/json', }); + fileWriter.write(dataObj); }); + // this.writeFile(fileEntry, resultList); }); - } - + }); + }); + }; - var emailData = function(result) { - return new Promise(function(resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function(fs) { - console.log("During email, file system open: "+fs.name); - fs.root.getFile(dumpFile, null, function(fileEntry) { - console.log("fileEntry "+fileEntry.nativeURL+" is file?"+fileEntry.isFile.toString()); - fileEntry.file(function (file) { - var reader = new FileReader(); + var emailData = function (result) { + return new Promise(function (resolve, reject) { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { + console.log('During email, file system open: ' + fs.name); + fs.root.getFile(dumpFile, null, function (fileEntry) { + console.log( + 'fileEntry ' + fileEntry.nativeURL + ' is file?' + fileEntry.isFile.toString(), + ); + fileEntry.file( + function (file) { + var reader = new FileReader(); - reader.onloadend = function() { - console.log("Successful file read with " + this.result.length +" characters"); - var dataArray = JSON.parse(this.result); - console.log("Successfully read resultList of size "+dataArray.length); - // displayFileData(fileEntry.fullPath + ": " + this.result); - var attachFile = fileEntry.nativeURL; - if (ionic.Platform.isAndroid()) { - // At least on nexus, getting a temporary file puts it into - // the cache, so I can hardcode that for now - attachFile = "app://cache/"+dumpFile; - } - if (ionic.Platform.isIOS()) { - alert(i18next.t('email-service.email-account-mail-app')); - } - var email = { - attachments: [ - attachFile - ], - subject: i18next.t('email-service.email-data.subject-data-dump-from-to', {start: startMoment.format(fmt),end: endMoment.format(fmt)}), - body: i18next.t('email-service.email-data.body-data-consists-of-list-of-entries') - } - $window.cordova.plugins.email.open(email).then(resolve()); + reader.onloadend = function () { + console.log('Successful file read with ' + this.result.length + ' characters'); + var dataArray = JSON.parse(this.result); + console.log('Successfully read resultList of size ' + dataArray.length); + // displayFileData(fileEntry.fullPath + ": " + this.result); + var attachFile = fileEntry.nativeURL; + if (ionic.Platform.isAndroid()) { + // At least on nexus, getting a temporary file puts it into + // the cache, so I can hardcode that for now + attachFile = 'app://cache/' + dumpFile; } - reader.readAsText(file); - }, function(error) { - $ionicPopup.alert({title: "Error while downloading JSON dump", - template: error}); - reject(error); + if (ionic.Platform.isIOS()) { + alert(i18next.t('email-service.email-account-mail-app')); + } + var email = { + attachments: [attachFile], + subject: i18next.t('email-service.email-data.subject-data-dump-from-to', { + start: startMoment.format(fmt), + end: endMoment.format(fmt), + }), + body: i18next.t( + 'email-service.email-data.body-data-consists-of-list-of-entries', + ), + }; + $window.cordova.plugins.email.open(email).then(resolve()); + }; + reader.readAsText(file); + }, + function (error) { + $ionicPopup.alert({ + title: 'Error while downloading JSON dump', + template: error, }); - }); - }); + reject(error); + }, + ); }); - }; + }); + }); + }; - getRawEntries(null, startMoment.unix(), endMoment.unix()) - .then(writeDumpFile) - .then(emailData) - .then(function() { - Logger.log("Email queued successfully"); - }) - .catch(function(error) { - Logger.displayError("Error emailing JSON dump", error); - }) + getRawEntries(null, startMoment.unix(), endMoment.unix()) + .then(writeDumpFile) + .then(emailData) + .then(function () { + Logger.log('Email queued successfully'); + }) + .catch(function (error) { + Logger.displayError('Error emailing JSON dump', error); + }); }; - this.getOPCode = function() { + this.getOPCode = function () { return window.cordova.plugins.OPCodeAuth.getOPCode(); }; - this.getSettings = function() { + this.getSettings = function () { return window.cordova.plugins.BEMConnectionSettings.getSettings(); }; }); From dbe76d11d0011d1e349020bdd918beda6a154df6 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:01:08 -0800 Subject: [PATCH 30/32] Switched to old fileIO, fixes issue with android - Android was having issues with the new fileIO - since we are not mocking these functions, we can switch back for now. --- www/js/services/controlHelper.ts | 69 ++++++++++---------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index f2741b0dd..f3f93013c 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -16,38 +16,34 @@ export const getMyDataHelpers = function ( const localWriteFile = function (result: ServerResponse) { const resultList = result.phone_data; return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { - fs.filesystem.root.getFile( - fileName, - { create: true, exclusive: false }, - function (fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function () { - logDebug('Successful file write...'); - resolve(); - }; - fileWriter.onerror = function (e) { - logDebug(`Failed file write: ${e.toString()}`); - reject(); - }; - logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); - // if data object is not passed in, create a new blob instead. - const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { - type: 'application/json', - }); - fileWriter.write(dataObj); + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { + fs.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function () { + logDebug('Successful file write...'); + resolve(); + }; + fileWriter.onerror = function (e) { + logDebug(`Failed file write: ${e.toString()}`); + reject(); + }; + logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); + // if data object is not passed in, create a new blob instead. + const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { + type: 'application/json', }); - }, - ); + fileWriter.write(dataObj); + }); + }); }); }); }; const localShareData = function () { return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { - fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { + fs.root.getFile(fileName, null, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file( function (file) { @@ -93,31 +89,9 @@ export const getMyDataHelpers = function ( }); }; - // window['cordova'].file.TempDirectory is not guaranteed to free up memory, - // so it's good practice to remove the file right after it's used! - const localClearData = function () { - return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { - fs.filesystem.root.getFile(fileName, null, function (fileEntry) { - fileEntry.remove( - () => { - logDebug(`Successfully cleaned up file ${fileName}`); - resolve(); - }, - (err) => { - logWarn(`Error deleting ${fileName} : ${err}`); - reject(err); - }, - ); - }); - }); - }); - }; - return { writeFile: localWriteFile, shareData: localShareData, - clearData: localClearData, }; }; @@ -141,7 +115,6 @@ export const getMyData = function (timeStamp: Date) { getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) .then(getDataMethods.writeFile) .then(getDataMethods.shareData) - .then(getDataMethods.clearData) .then(function () { logInfo('Share queued successfully'); }) From a232e3f86eba687659710d398ff6f952091eea56 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:25:31 -0800 Subject: [PATCH 31/32] Revert "Switched to old fileIO, fixes issue with android" Found a fix for `['resolveLocalFileSystemURL']` method of fileIO, that works with Android and iOS. Reverting back to this method and updating, to make this consistent with `uploadService.ts` This reverts commit dbe76d11d0011d1e349020bdd918beda6a154df6. --- www/js/services/controlHelper.ts | 69 ++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index f3f93013c..f2741b0dd 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -16,34 +16,38 @@ export const getMyDataHelpers = function ( const localWriteFile = function (result: ServerResponse) { const resultList = result.phone_data; return new Promise(function (resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { - fs.root.getFile(fileName, { create: true, exclusive: false }, function (fileEntry) { - logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); - fileEntry.createWriter(function (fileWriter) { - fileWriter.onwriteend = function () { - logDebug('Successful file write...'); - resolve(); - }; - fileWriter.onerror = function (e) { - logDebug(`Failed file write: ${e.toString()}`); - reject(); - }; - logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); - // if data object is not passed in, create a new blob instead. - const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { - type: 'application/json', + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile( + fileName, + { create: true, exclusive: false }, + function (fileEntry) { + logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); + fileEntry.createWriter(function (fileWriter) { + fileWriter.onwriteend = function () { + logDebug('Successful file write...'); + resolve(); + }; + fileWriter.onerror = function (e) { + logDebug(`Failed file write: ${e.toString()}`); + reject(); + }; + logDebug(`fileWriter is: ${JSON.stringify(fileWriter.onwriteend, null, 2)}`); + // if data object is not passed in, create a new blob instead. + const dataObj = new Blob([JSON.stringify(resultList, null, 2)], { + type: 'application/json', + }); + fileWriter.write(dataObj); }); - fileWriter.write(dataObj); - }); - }); + }, + ); }); }); }; const localShareData = function () { return new Promise(function (resolve, reject) { - window.requestFileSystem(window.LocalFileSystem.TEMPORARY, 0, function (fs) { - fs.root.getFile(fileName, null, function (fileEntry) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile(fileName, null, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file( function (file) { @@ -89,9 +93,31 @@ export const getMyDataHelpers = function ( }); }; + // window['cordova'].file.TempDirectory is not guaranteed to free up memory, + // so it's good practice to remove the file right after it's used! + const localClearData = function () { + return new Promise(function (resolve, reject) { + window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + fs.filesystem.root.getFile(fileName, null, function (fileEntry) { + fileEntry.remove( + () => { + logDebug(`Successfully cleaned up file ${fileName}`); + resolve(); + }, + (err) => { + logWarn(`Error deleting ${fileName} : ${err}`); + reject(err); + }, + ); + }); + }); + }); + }; + return { writeFile: localWriteFile, shareData: localShareData, + clearData: localClearData, }; }; @@ -115,6 +141,7 @@ export const getMyData = function (timeStamp: Date) { getRawEntries(null, startTime.toUnixInteger(), endTime.toUnixInteger()) .then(getDataMethods.writeFile) .then(getDataMethods.shareData) + .then(getDataMethods.clearData) .then(function () { logInfo('Share queued successfully'); }) From 369b3adc5a9e63d16356ea2d5ad8fb567f9e7ea3 Mon Sep 17 00:00:00 2001 From: Katie Rischpater <98350084+the-bay-kay@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:30:33 -0800 Subject: [PATCH 32/32] Changed fileIO directory to fix android issue --- www/js/services/controlHelper.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/js/services/controlHelper.ts b/www/js/services/controlHelper.ts index f2741b0dd..9969327be 100644 --- a/www/js/services/controlHelper.ts +++ b/www/js/services/controlHelper.ts @@ -16,7 +16,7 @@ export const getMyDataHelpers = function ( const localWriteFile = function (result: ServerResponse) { const resultList = result.phone_data; return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { fs.filesystem.root.getFile( fileName, { create: true, exclusive: false }, @@ -46,7 +46,7 @@ export const getMyDataHelpers = function ( const localShareData = function () { return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { fs.filesystem.root.getFile(fileName, null, function (fileEntry) { logDebug(`fileEntry ${fileEntry.nativeURL} is file? ${fileEntry.isFile.toString()}`); fileEntry.file( @@ -93,11 +93,11 @@ export const getMyDataHelpers = function ( }); }; - // window['cordova'].file.TempDirectory is not guaranteed to free up memory, + // window['cordova'].file.cacheDirectory is not guaranteed to free up memory, // so it's good practice to remove the file right after it's used! const localClearData = function () { return new Promise(function (resolve, reject) { - window['resolveLocalFileSystemURL'](window['cordova'].file.tempDirectory, function (fs) { + window['resolveLocalFileSystemURL'](window['cordova'].file.cacheDirectory, function (fs) { fs.filesystem.root.getFile(fileName, null, function (fileEntry) { fileEntry.remove( () => {