Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
Replacing outbreakStatus with outbreakHistory (#1425)
Browse files Browse the repository at this point in the history
* defined outbreak history items and wrote some functions to update this

* stubbed in tests

* adding new outbreak exposures to outbreak history, using this instead of the outbreak status

* added a clear outbreak history function for use in the test menu

* fixed bug where outbreak history was not updating ui correctly

* update last checked even if no exposures are detected
  • Loading branch information
smcmurtry authored Feb 18, 2021
1 parent 1158cb1 commit 92d3490
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 83 deletions.
6 changes: 3 additions & 3 deletions src/screens/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {getRegionCase} from 'shared/RegionLogic';
import {usePrevious} from 'shared/usePrevious';
import {ForceScreen} from 'shared/ForceScreen';
import {useRegionalI18n} from 'locale';
import {OutbreakStatusType} from 'shared/qr';
import {isExposedToOutbreak} from 'shared/qr';
import {useOutbreakService} from 'shared/OutbreakProvider';

import {useDeepLinks} from '../qr/utils';
Expand Down Expand Up @@ -66,7 +66,7 @@ const Content = ({isBottomSheetExpanded}: ContentProps) => {
const regionCase = getRegionCase(region, regionalI18n.activeRegions);
const exposureStatus = useExposureStatus();
const [systemStatus] = useSystemStatus();
const {outbreakStatus} = useOutbreakService();
const {outbreakHistory} = useOutbreakService();
const [, turnNotificationsOn] = useNotificationPermissionStatus();
useEffect(() => {
return turnNotificationsOn();
Expand Down Expand Up @@ -106,7 +106,7 @@ const Content = ({isBottomSheetExpanded}: ContentProps) => {
}
}

if (outbreakStatus.type === OutbreakStatusType.Exposed) {
if (isExposedToOutbreak(outbreakHistory)) {
return <OutbreakExposedView isBottomSheetExpanded={isBottomSheetExpanded} />;
}

Expand Down
11 changes: 3 additions & 8 deletions src/screens/testScreen/TestScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import {useNavigation} from '@react-navigation/native';
import {ContagiousDateType} from 'shared/DataSharing';
import {getLogUUID, setLogUUID} from 'shared/logging/uuid';
import {ForceScreen} from 'shared/ForceScreen';
import {OutbreakStatusType} from 'shared/qr';
import {useOutbreakService} from 'shared/OutbreakProvider';
import {getCurrentDate} from 'shared/date-fns';
import {PollNotifications} from 'services/PollNotificationService';

import {RadioButton} from './components/RadioButtons';
Expand Down Expand Up @@ -105,14 +103,11 @@ const Content = () => {
const navigation = useNavigation();

const {reset} = useStorage();
const {checkForOutbreaks, setOutbreakStatus} = useOutbreakService();
const {checkForOutbreaks, clearOutbreakHistory} = useOutbreakService();

const onClearOutbreak = useCallback(async () => {
setOutbreakStatus({
type: OutbreakStatusType.Monitoring,
lastChecked: getCurrentDate().getTime(),
});
}, [setOutbreakStatus]);
clearOutbreakHistory();
}, [clearOutbreakHistory]);

const onCheckForOutbreak = useCallback(async () => {
checkForOutbreaks(true);
Expand Down
2 changes: 1 addition & 1 deletion src/services/StorageService/StorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export enum Key {
SkipAllSet = 'SkipAllSet',
UserStopped = 'UserStopped',
CheckInHistory = 'CheckInHistory',
OutbreakStatus = 'OutbreakStatus',
OutbreakHistory = 'OutbreakHistory',
HasViewedQrInstructions = 'HasViewedQRInstructions',
}
export class StorageService {
Expand Down
73 changes: 54 additions & 19 deletions src/shared/OutbreakProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ import PQueue from 'p-queue';
import {DefaultSecureKeyValueStore, SecureKeyValueStore} from '../services/MetricsService/SecureKeyValueStorage';

import {Observable} from './Observable';
import {CheckInData, getNewOutbreakStatus, getOutbreakEvents, initialOutbreakStatus, OutbreakStatus} from './qr';
import {
CheckInData,
getNewOutbreakExposures,
getNewOutbreakHistoryItems,
getOutbreakEvents,
isExposedToOutbreak,
OutbreakHistoryItem,
} from './qr';
import {createCancellableCallbackPromise} from './cancellablePromise';
import {getCurrentDate, minutesBetween} from './date-fns';
import {log} from './logging/config';

const OutbreaksLastCheckedStorageKey = 'A436ED42-707E-11EB-9439-0242AC130002';

Expand All @@ -28,24 +36,32 @@ export class OutbreakService implements OutbreakService {
return this.instance;
}

outbreakStatus: Observable<OutbreakStatus>;
outbreakHistory: Observable<OutbreakHistoryItem[]>;
checkInHistory: Observable<CheckInData[]>;
i18n: I18n;
secureKeyValueStore: SecureKeyValueStore;

private serialPromiseQueue: PQueue;

constructor(i18n: I18n) {
this.outbreakStatus = new Observable<OutbreakStatus>(initialOutbreakStatus);
this.outbreakHistory = new Observable<OutbreakHistoryItem[]>([]);
this.checkInHistory = new Observable<CheckInData[]>([]);
this.i18n = i18n;
this.secureKeyValueStore = new DefaultSecureKeyValueStore();
this.serialPromiseQueue = new PQueue({concurrency: 1});
}

setOutbreakStatus = async (value: OutbreakStatus) => {
await AsyncStorage.setItem(Key.OutbreakStatus, JSON.stringify(value));
this.outbreakStatus.set(value);
clearOutbreakHistory = async () => {
await AsyncStorage.setItem(Key.OutbreakHistory, JSON.stringify([]));
this.outbreakHistory.set([]);
};

addToOutbreakHistory = async (value: OutbreakHistoryItem[]) => {
const _outbreakHistory = (await AsyncStorage.getItem(Key.OutbreakHistory)) || '[]';
const outbreakHistory = JSON.parse(_outbreakHistory);
const newOutbreakHistory = outbreakHistory.concat(value);
await AsyncStorage.setItem(Key.OutbreakHistory, JSON.stringify(newOutbreakHistory));
this.outbreakHistory.set(newOutbreakHistory);
};

addCheckIn = async (value: CheckInData) => {
Expand All @@ -66,8 +82,8 @@ export class OutbreakService implements OutbreakService {
};

init = async () => {
const outbreakStatus = (await AsyncStorage.getItem(Key.OutbreakStatus)) || JSON.stringify(initialOutbreakStatus);
this.outbreakStatus.set(JSON.parse(outbreakStatus));
const outbreakHistory = (await AsyncStorage.getItem(Key.OutbreakHistory)) || '[]';
this.outbreakHistory.set(JSON.parse(outbreakHistory));

const checkInHistory = (await AsyncStorage.getItem(Key.CheckInHistory)) || '[]';
this.checkInHistory.set(JSON.parse(checkInHistory));
Expand All @@ -91,14 +107,24 @@ export class OutbreakService implements OutbreakService {

getOutbreaksFromServer = async () => {
const outbreakEvents = await getOutbreakEvents();
const newOutbreakStatusType = getNewOutbreakStatus(this.checkInHistory.get(), outbreakEvents);
this.setOutbreakStatus(newOutbreakStatusType);
this.processOutbreakNotification(newOutbreakStatusType);
const detectedOutbreakExposures = getNewOutbreakHistoryItems(this.checkInHistory.get(), outbreakEvents);
this.markOutbreaksLastCheckedDateTime(getCurrentDate());
log.debug({payload: {detectedOutbreakExposures}});
if (detectedOutbreakExposures.length === 0) {
return;
}
const newOutbreakExposures = getNewOutbreakExposures(detectedOutbreakExposures, this.outbreakHistory.get());
if (newOutbreakExposures.length === 0) {
return;
}
await this.addToOutbreakHistory(newOutbreakExposures);
const outbreakHistory = this.outbreakHistory.get();
log.debug({payload: {outbreakHistory}});
this.processOutbreakNotification(outbreakHistory);
};

processOutbreakNotification = (status: OutbreakStatus) => {
if (status.type === 'exposed') {
processOutbreakNotification = (outbreakHistory: OutbreakHistoryItem[]) => {
if (isExposedToOutbreak(outbreakHistory)) {
PushNotification.presentLocalNotification({
alertTitle: this.i18n.translate('Notification.OutbreakMessageTitle'),
alertBody: this.i18n.translate('Notification.OutbreakMessageBody'),
Expand Down Expand Up @@ -148,10 +174,9 @@ export const OutbreakProvider = ({children}: OutbreakProviderProps) => {

export const useOutbreakService = () => {
const outbreakService = useContext(OutbreakContext)!.outbreakService!;
const [outbreakStatus, setOutbreakStatusInternal] = useState(outbreakService.outbreakStatus.get());
const [checkInHistory, addCheckInInternal] = useState(outbreakService.checkInHistory.get());
const [outbreakHistory, setOutbreakHistoryInternal] = useState(outbreakService.outbreakHistory.get());

const setOutbreakStatus = useMemo(() => outbreakService.setOutbreakStatus, [outbreakService.setOutbreakStatus]);
const checkForOutbreaks = useMemo(() => outbreakService.checkForOutbreaks, [outbreakService.checkForOutbreaks]);
const addCheckIn = useMemo(
() => (newCheckIn: CheckInData) => {
Expand All @@ -166,18 +191,28 @@ export const useOutbreakService = () => {
},
[outbreakService],
);
useEffect(() => outbreakService.outbreakStatus.observe(setOutbreakStatusInternal), [outbreakService.outbreakStatus]);

const clearOutbreakHistory = useMemo(
() => () => {
outbreakService.clearOutbreakHistory();
},
[outbreakService],
);

useEffect(() => outbreakService.checkInHistory.observe(addCheckInInternal), [outbreakService.checkInHistory]);
useEffect(() => outbreakService.outbreakHistory.observe(setOutbreakHistoryInternal), [
outbreakService.outbreakHistory,
]);

return useMemo(
() => ({
outbreakStatus,
setOutbreakStatus,
outbreakHistory,
clearOutbreakHistory,
checkForOutbreaks,
addCheckIn,
removeCheckIn,
checkInHistory,
}),
[outbreakStatus, setOutbreakStatus, checkForOutbreaks, addCheckIn, removeCheckIn, checkInHistory],
[outbreakHistory, clearOutbreakHistory, checkForOutbreaks, addCheckIn, removeCheckIn, checkInHistory],
);
};
36 changes: 28 additions & 8 deletions src/shared/qr.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CheckInData, doTimeWindowsOverlap, getNewOutbreakStatus, OutbreakStatusType, TimeWindow} from './qr';
import {CheckInData, doTimeWindowsOverlap, getNewOutbreakHistoryItems, isExposedToOutbreak, TimeWindow} from './qr';

describe('doTimeWindowsOverlap', () => {
const dateStr = '2021-01-05';
Expand Down Expand Up @@ -27,7 +27,7 @@ describe('doTimeWindowsOverlap', () => {
});
});

describe('getNewOutbreakStatus', () => {
describe('getNewOutbreakHistoryItems', () => {
const t1100 = new Date('2021-02-01T11:00Z').getTime();
const t1200 = new Date('2021-02-01T12:00Z').getTime();
const t1300 = new Date('2021-02-01T13:00Z').getTime();
Expand All @@ -41,23 +41,43 @@ describe('getNewOutbreakStatus', () => {
{id: '1', timestamp: t1200, address: '', name: ''},
{id: '3', timestamp: t1200, address: '', name: ''},
];
const newStatus = getNewOutbreakStatus(checkInHistory, outbreakEvents);
expect(newStatus.type).toStrictEqual(OutbreakStatusType.Exposed);
const newHistory = getNewOutbreakHistoryItems(checkInHistory, outbreakEvents);
expect(isExposedToOutbreak(newHistory)).toStrictEqual(true);
});
it('returns monitoring if there is no match', () => {
const checkInHistory: CheckInData[] = [
{id: '3', timestamp: t1200, address: '', name: ''},
{id: '4', timestamp: t1200, address: '', name: ''},
];
const newStatus = getNewOutbreakStatus(checkInHistory, outbreakEvents);
expect(newStatus.type).toStrictEqual(OutbreakStatusType.Monitoring);
const newHistory = getNewOutbreakHistoryItems(checkInHistory, outbreakEvents);
expect(isExposedToOutbreak(newHistory)).toStrictEqual(false);
});
it('returns monitoring if id matches but time does not', () => {
const checkInHistory: CheckInData[] = [
{id: '1', timestamp: t1400, address: '', name: ''},
{id: '2', timestamp: t1400, address: '', name: ''},
];
const newStatus = getNewOutbreakStatus(checkInHistory, outbreakEvents);
expect(newStatus.type).toStrictEqual(OutbreakStatusType.Monitoring);
const newHistory = getNewOutbreakHistoryItems(checkInHistory, outbreakEvents);
expect(isExposedToOutbreak(newHistory)).toStrictEqual(false);
});
});

describe('outbreakHistory functions', () => {
describe('expireHistoryItems', () => {
it('expires items older than 14 days', () => {
expect(true).toStrictEqual(true);
});
it('does not expire items newer than 14 days', () => {
expect(true).toStrictEqual(true);
});
});

describe('ignoreHistoryItems', () => {
it('ignores items with ids that are passed in', () => {
expect(true).toStrictEqual(true);
});
it('does not ignore items with ids not passed in', () => {
expect(true).toStrictEqual(true);
});
});
});
Loading

0 comments on commit 92d3490

Please sign in to comment.