Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite StartPrefs #1072

Merged
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0e31d15
remove unused imports from startprefts.js
Oct 17, 2023
4fc3982
convert startprefs from .js to .ts
Oct 17, 2023
416a111
remove all references to angular service, replace with imports
Oct 17, 2023
0584278
add a comment about the notifications
Oct 18, 2023
1d4752d
remove extra line
Oct 18, 2023
e0dc3bb
correct typo
Oct 18, 2023
8a3320d
make a deep copy for storage
Oct 19, 2023
a26536c
updating "keys"
Oct 19, 2023
d8d1a37
update event protocol
Oct 19, 2023
8a99ba9
move plugin calls
Oct 19, 2023
b40761a
Merge remote-tracking branch 'upstream/service_rewrite_2023' into sta…
Oct 19, 2023
1edfc8c
add docstrings for functions
Oct 19, 2023
d4a29b1
first test for startprefs
Oct 20, 2023
40f6b93
test markConsented
Oct 20, 2023
9849fa2
test all functions in sequence
Oct 20, 2023
ef4a9d8
Merge remote-tracking branch 'upstream/onboarding_routing_sept_2023' …
Oct 20, 2023
8ab5bf1
add timeout
Oct 20, 2023
2c92dbf
resolve merge conflicts by merging upstream
Oct 23, 2023
60caee5
remove "protocol id"
Oct 23, 2023
c1d76ad
update consent/no consent messages
Oct 23, 2023
3130741
remove unneeded type declaration
Oct 25, 2023
39aa2f2
move "after consent"
Oct 25, 2023
657ae34
rework "intro done event"
Oct 25, 2023
a7a75d1
remove unneeded object
Oct 25, 2023
c54f939
console.log -> logDebug
Oct 27, 2023
2516b2d
_is_consented assigned but never used
Oct 27, 2023
8bbfb82
add a catch block
Oct 27, 2023
e19a346
less hacky undefined catch
Oct 27, 2023
34b997c
Merge remote-tracking branch 'upstream/service_rewrite_2023' into sta…
Oct 30, 2023
b2d6aa9
add comment and discussion link
Oct 30, 2023
fcbefb1
add comments about naming
Oct 30, 2023
3c23808
remove old imports
Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions www/__mocks__/cordovaMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export const mockFile = () => {
"applicationStorageDirectory" : "../path/to/app/storage/directory"};
}

//for consent document
const _storage = {};

export const mockBEMUserCache = () => {
const _cache = {};
const messages = [];
Expand Down Expand Up @@ -92,9 +95,37 @@ export const mockBEMUserCache = () => {
rs(messages.filter(m => m.key == key).map(m => m.value));
}, 100)
);
},
getDocument: (key: string, withMetadata?: boolean) => {
return new Promise<any[]>((rs, rj) =>
setTimeout(() => {
rs(_storage[key]);
}, 100)
);
},
isEmptyDoc: (doc) => {
if (doc == undefined) { return true }
let string = doc.toString();
if (string.length == 0) {
return true;
} else {
return false;
}
}
}
window['cordova'] ||= {};
window['cordova'].plugins ||= {};
window['cordova'].plugins.BEMUserCache = mockBEMUserCache;
}

export const mockBEMDataCollection = () => {
const mockBEMDataCollection = {
markConsented: (consentDoc) => {
setTimeout(() => {
_storage['config/consent'] = consentDoc;
}, 100)
}
}
window['cordova'] ||= {};
window['cordova'].plugins.BEMDataCollection = mockBEMDataCollection;
}
24 changes: 24 additions & 0 deletions www/__tests__/startprefs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { markConsented, isConsented, readConsentState, getConsentDocument } from '../js/splash/startprefs';

import { mockBEMUserCache, mockBEMDataCollection } from "../__mocks__/cordovaMocks";
import { mockLogger } from "../__mocks__/globalMocks";

mockBEMUserCache();
mockBEMDataCollection();
mockLogger();

global.fetch = (url: string) => new Promise((rs, rj) => {
setTimeout(() => rs({
json: () => new Promise((rs, rj) => {
let myJSON = { "emSensorDataCollectionProtocol": { "protocol_id": "2014-04-6267", "approval_date": "2016-07-14" } };
setTimeout(() => rs(myJSON), 100);
})
}));
}) as any;

it('checks state of consent before and after marking consent', async () => {
expect(await readConsentState().then(isConsented)).toBeFalsy();
let marked = await markConsented();
expect(await readConsentState().then(isConsented)).toBeTruthy();
expect(await getConsentDocument()).toEqual({"approval_date": "2016-07-14", "protocol_id": "2014-04-6267"});
});
3 changes: 2 additions & 1 deletion www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@
"confirm": "Confirm",
"user-data-erased": "User data erased.",
"consent-not-found": "Consent for data collection not found, consent now?",
"no-consent-logout": "Consent for data collection not found, please save your opcode, log out, and log back in with the same opcode. Note that you won't get any personalized stats until you do!",
"no-consent-message": "OK! Note that you won't get any personalized stats until you do!",
"consent-found": "Consent found!",
"consented-to": "Consented to protocol {{protocol_id}}, {{approval_date}}",
"consented-to": "Consented to protocol last updated on {{approval_date}}",
"consented-ok": "OK",
"qrcode": "My OPcode",
"qrcode-share-title": "You can save your OPcode to login easily in the future!"
Expand Down
1 change: 0 additions & 1 deletion www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'leaflet/dist/leaflet.css';

import './js/ngApp.js';
import './js/splash/referral.js';
import './js/splash/startprefs.js';
import './js/splash/pushnotify.js';
import './js/splash/storedevicesettings.js';
import './js/splash/localnotify.js';
Expand Down
8 changes: 3 additions & 5 deletions www/js/App.tsx
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ const App = () => {
const { colors } = useTheme();
const { t } = useTranslation();

const StartPrefs = getAngularService('StartPrefs');

const routes = useMemo(() => {
const showMetrics = appConfig?.survey_info?.['trip-labels'] == 'MULTILABEL';
return showMetrics ? defaultRoutes(t) : defaultRoutes(t).filter(r => r.key != 'metrics');
Expand Down Expand Up @@ -91,9 +89,9 @@ const App = () => {
<AppContext.Provider value={appContextValue}>
{appContent}

{ /* If we are past the consent page (route > CONSENT), the permissions popup can show if needed.
This also includes if onboarding is DONE altogether (because "DONE" is > "CONSENT") */ }
{(onboardingState && onboardingState.route > OnboardingRoute.CONSENT) &&
{ /* If we are fully consented, (route > PROTOCOL), the permissions popup can show if needed.
This also includes if onboarding is DONE altogether (because "DONE" is > "PROTOCOL") */ }
{(onboardingState && onboardingState.route > OnboardingRoute.PROTOCOL) &&
<AppStatusModal permitVis={permissionsPopupVis} setPermitVis={setPermissionsPopupVis} />
}
</AppContext.Provider>
Expand Down
22 changes: 10 additions & 12 deletions www/js/control/ProfileSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { AppContext } from "../App";
import { shareQR } from "../components/QrCode";
import { storageClear } from "../plugin/storage";
import { getAppVersion } from "../plugin/clientStats";
import { getConsentDocument } from "../splash/startprefs";
import { logDebug } from "../plugin/logger";

//any pure functions can go outside
const ProfileSettings = () => {
Expand All @@ -39,7 +41,6 @@ const ProfileSettings = () => {
const EmailHelper = getAngularService('EmailHelper');
const NotificationScheduler = getAngularService('NotificationScheduler');
const ControlHelper = getAngularService('ControlHelper');
const StartPrefs = getAngularService('StartPrefs');

//functions that come directly from an Angular service
const editCollectionConfig = () => setEditCollection(true);
Expand Down Expand Up @@ -307,8 +308,9 @@ const ProfileSettings = () => {

//in ProfileSettings in DevZone (above two functions are helpers)
async function checkConsent() {
StartPrefs.getConsentDocument().then(function(resultDoc){
getConsentDocument().then(function(resultDoc){
setConsentDoc(resultDoc);
logDebug("In profile settings, consent doc found", resultDoc);
if (resultDoc == null) {
setNoConsentVis(true);
} else {
Expand Down Expand Up @@ -475,17 +477,13 @@ const ProfileSettings = () => {
onDismiss={()=>setNoConsentVis(false)}
style={settingStyles.dialog(colors.elevation.level3)}>
<Dialog.Title>{t('general-settings.consent-not-found')}</Dialog.Title>
<Dialog.Content>
<Text variant="">{t('general-settings.no-consent-logout')}</Text>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={()=>{
setNoConsentVis(false);
setNoConsentMessageVis(true)}}>
{t('general-settings.cancel')}
</Button>
<Button onPress={()=>{
setNoConsentVis(false);
// $state.go("root.reconsent"); //don't know how to do this yet
}}>
{t('general-settings.confirm')}
setNoConsentVis(false); }}>
{t('general-settings.consented-ok')}
shankari marked this conversation as resolved.
Show resolved Hide resolved
</Button>
</Dialog.Actions>
</Dialog>
Expand All @@ -496,7 +494,7 @@ const ProfileSettings = () => {
<Dialog visible={consentVis}
onDismiss={()=>setConsentVis(false)}
style={settingStyles.dialog(colors.elevation.level3)}>
<Dialog.Title>{t('general-settings.consented-to', {protocol_id: consentDoc.protocol_id, approval_date: consentDoc.approval_date})}</Dialog.Title>
<Dialog.Title>{t('general-settings.consented-to', {approval_date: consentDoc.approval_date})}</Dialog.Title>
<Dialog.Actions>
<Button onPress={()=>{
setConsentDoc({});
Expand Down
8 changes: 4 additions & 4 deletions www/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import angular from 'angular';
import { addStatError, addStatReading, statKeys } from './plugin/clientStats';
import { getPendingOnboardingState } from './onboarding/onboardingHelper';

angular.module('emission.controllers', ['emission.splash.startprefs',
'emission.splash.pushnotify',
angular.module('emission.controllers', ['emission.splash.pushnotify',
'emission.splash.storedevicesettings',
'emission.splash.localnotify',
'emission.splash.remotenotify'])
Expand All @@ -14,7 +14,7 @@ angular.module('emission.controllers', ['emission.splash.startprefs',
.controller('DashCtrl', function($scope) {})

.controller('SplashCtrl', function($scope, $state, $interval, $rootScope,
StartPrefs, PushNotify, StoreDeviceSettings,
PushNotify, StoreDeviceSettings,
LocalNotify, RemoteNotify) {
console.log('SplashCtrl invoked');
// alert("attach debugger!");
Expand Down Expand Up @@ -49,7 +49,7 @@ angular.module('emission.controllers', ['emission.splash.startprefs',
'root.main.metrics']
if (isInList(toState.name, personalTabs)) {
// toState is in the personalTabs list
StartPrefs.getPendingOnboardingState().then(function(result) {
getPendingOnboardingState().then(function(result) {
if (result != null) {
event.preventDefault();
$state.go(result);
Expand Down
6 changes: 3 additions & 3 deletions www/js/onboarding/OnboardingStack.tsx
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { StyleSheet } from "react-native";
import { AppContext } from "../App";
import WelcomePage from "./WelcomePage";
import ConsentPage from "./ConsentPage";
import ProtocolPage from "./ProtocolPage";
import SurveyPage from "./SurveyPage";
import SaveQrPage from "./SaveQrPage";
import SummaryPage from "./SummaryPage";
Expand All @@ -19,8 +19,8 @@ const OnboardingStack = () => {
return <WelcomePage />;
} else if (onboardingState.route == OnboardingRoute.SUMMARY) {
return <SummaryPage />;
} else if (onboardingState.route == OnboardingRoute.CONSENT) {
return <ConsentPage />;
} else if (onboardingState.route == OnboardingRoute.PROTOCOL) {
return <ProtocolPage />;
} else if (onboardingState.route == OnboardingRoute.SAVE_QR) {
return <SaveQrPage />;
} else if (onboardingState.route == OnboardingRoute.SURVEY) {
Expand Down
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { View, ScrollView } from 'react-native';
import { Button, Surface } from 'react-native-paper';
import { resetDataAndRefresh } from '../config/dynamicConfig';
import { AppContext } from '../App';
import { getAngularService } from '../angular-react-helper';
import PrivacyPolicy from './PrivacyPolicy';
import { onboardingStyles } from './OnboardingStack';
import { markConsented } from '../splash/startprefs';
import { setProtocolDone } from './onboardingHelper';

const ConsentPage = () => {
const ProtocolPage = () => {

const { t } = useTranslation();
const context = useContext(AppContext);
Expand All @@ -20,10 +21,8 @@ const ConsentPage = () => {
};

function agree() {
const StartPrefs = getAngularService('StartPrefs');
StartPrefs.markConsented().then((response) => {
refreshOnboardingState();
});
setProtocolDone(true);
refreshOnboardingState();
};

// privacy policy and data collection info, followed by accept/reject buttons
Expand All @@ -40,4 +39,4 @@ const ConsentPage = () => {
</>);
}

export default ConsentPage;
export default ProtocolPage;
16 changes: 10 additions & 6 deletions www/js/onboarding/SaveQrPage.tsx
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { preloadDemoSurveyResponse } from "./SurveyPage";
import { storageSet } from "../plugin/storage";
import { registerUser } from "../commHelper";
import { resetDataAndRefresh } from "../config/dynamicConfig";
import { markConsented } from "../splash/startprefs";
import i18next from "i18next";

const SaveQrPage = ({ }) => {
Expand All @@ -23,12 +24,15 @@ const SaveQrPage = ({ }) => {
useEffect(() => {
if (overallStatus == true && !registerUserDone) {
logDebug('permissions done, going to log in');
login(onboardingState.opcode).then((response) => {
logDebug('login done, refreshing onboarding state');
setRegisterUserDone(true);
preloadDemoSurveyResponse();
refreshOnboardingState();
});
markConsented()
.then(login(onboardingState.opcode)
.then((response) => {
logDebug('login done, refreshing onboarding state');
setRegisterUserDone(true);
preloadDemoSurveyResponse();
refreshOnboardingState();
})
);
} else {
logDebug('permissions not done, waiting');
}
Expand Down
35 changes: 23 additions & 12 deletions www/js/onboarding/onboardingHelper.ts
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { DateTime } from "luxon";
import { getAngularService } from "../angular-react-helper";
import { getConfig, resetDataAndRefresh } from "../config/dynamicConfig";
import { storageGet, storageSet } from "../plugin/storage";
import { logDebug } from "../plugin/logger";
import { readConsentState, isConsented } from "../splash/startprefs";
import { getAngularService } from "../angular-react-helper";

export const INTRO_DONE_KEY = 'intro_done';

// route = WELCOME if no config present
// route = SUMMARY if config present, but not consented and summary not done
// route = CONSENT if config present, but not consented and summary done
// route = SAVE_QR if config present, consented, but save qr not done
// route = SUMMARY if config present, but protocol not done and summary not done
// route = PROTOCOL if config present, but protocol not done and summary done
// route = SAVE_QR if config present, protocol done, but save qr not done
// route = SURVEY if config present, consented and save qr done
// route = DONE if onboarding is finished (intro_done marked)
export enum OnboardingRoute { WELCOME, SUMMARY, CONSENT, SAVE_QR, SURVEY, DONE };
export enum OnboardingRoute { WELCOME, SUMMARY, PROTOCOL, SAVE_QR, SURVEY, DONE };
export type OnboardingState = {
opcode: string,
route: OnboardingRoute,
Expand All @@ -21,6 +22,9 @@ export type OnboardingState = {
export let summaryDone = false;
export const setSummaryDone = (b) => summaryDone = b;

export let protocolDone = false;
export const setProtocolDone = (b) => protocolDone = b;

export let saveQrDone = false;
export const setSaveQrDone = (b) => saveQrDone = b;

Expand All @@ -41,10 +45,10 @@ export function getPendingOnboardingState(): Promise<OnboardingState> {
route = OnboardingRoute.DONE;
} else if (!config) {
route = OnboardingRoute.WELCOME;
} else if (!isConsented && !summaryDone) {
} else if (!protocolDone && !summaryDone) {
route = OnboardingRoute.SUMMARY;
} else if (!isConsented) {
route = OnboardingRoute.CONSENT;
} else if (!protocolDone) {
route = OnboardingRoute.PROTOCOL;
} else if (!saveQrDone) {
route = OnboardingRoute.SAVE_QR;
} else {
Expand All @@ -58,15 +62,22 @@ export function getPendingOnboardingState(): Promise<OnboardingState> {
};

async function readConsented() {
const StartPrefs = getAngularService('StartPrefs');
return StartPrefs.readConsentState().then(StartPrefs.isConsented) as Promise<boolean>;
return readConsentState().then(isConsented) as Promise<boolean>;
}

async function readIntroDone() {
export async function readIntroDone() {
return storageGet(INTRO_DONE_KEY).then((read_val) => !!read_val) as Promise<boolean>;
}

export async function markIntroDone() {
const currDateTime = DateTime.now().toISO();
return storageSet(INTRO_DONE_KEY, currDateTime);
return storageSet(INTRO_DONE_KEY, currDateTime)
.then(() => {
//handle "on intro" events
shankari marked this conversation as resolved.
Show resolved Hide resolved
logDebug("intro done, calling registerPush and storeDeviceSettings");
const PushNotify = getAngularService("PushNotify");
const StoreSeviceSettings = getAngularService("StoreDeviceSettings");
shankari marked this conversation as resolved.
Show resolved Hide resolved
PushNotify.registerPush();
StoreSeviceSettings.storeDeviceSettings();
});
}
4 changes: 3 additions & 1 deletion www/js/plugin/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ const unmungeValue = (key, retData) => {
}

const localStorageSet = (key: string, value: {[k: string]: any}) => {
localStorage.setItem(key, JSON.stringify(value));
if (value) {
localStorage.setItem(key, JSON.stringify(value));
}
Abby-Wheelis marked this conversation as resolved.
Show resolved Hide resolved
}

const localStorageGet = (key: string) => {
Expand Down
1 change: 0 additions & 1 deletion www/js/splash/localnotify.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import angular from 'angular';

angular.module('emission.splash.localnotify', ['emission.plugin.logger',
'emission.splash.startprefs',
'ionic-toast'])
.factory('LocalNotify', function($window, $ionicPlatform, $ionicPopup,
$state, $rootScope, ionicToast, Logger) {
Expand Down
Loading