From 40a301161df12da7eb06934aff9a0ed79aea4a8b Mon Sep 17 00:00:00 2001 From: Mark DeLaVernge Date: Mon, 27 Nov 2023 09:19:15 -0500 Subject: [PATCH] Display tracker errors and introduce a >24h error --- .../TrackTile/PillarsTile/PillarsTile.tsx | 2 + .../TrackerDetails/to-procedure-resource.ts | 18 +++++ .../TrackTile/common/DefaultOnError.test.tsx | 22 ++++++ .../TrackTile/common/DefaultOnError.tsx | 15 ++++ .../TrackTile/services/TrackTileService.ts | 4 ++ .../TrackTileAdvancedDetailsScreen.tsx | 2 + src/screens/TrackTileTrackerScreen.test.tsx | 71 +++++++++++++++++++ src/screens/TrackTileTrackerScreen.tsx | 2 + 8 files changed, 136 insertions(+) create mode 100644 src/components/TrackTile/common/DefaultOnError.test.tsx create mode 100644 src/components/TrackTile/common/DefaultOnError.tsx create mode 100644 src/screens/TrackTileTrackerScreen.test.tsx diff --git a/src/components/TrackTile/PillarsTile/PillarsTile.tsx b/src/components/TrackTile/PillarsTile/PillarsTile.tsx index 83b22c70..6d1fb9b3 100644 --- a/src/components/TrackTile/PillarsTile/PillarsTile.tsx +++ b/src/components/TrackTile/PillarsTile/PillarsTile.tsx @@ -14,6 +14,7 @@ import { Pillar } from './Pillar'; import { Card } from 'react-native-paper'; import { useStyles } from '../../../hooks/useStyles'; import { createStyles } from '../../BrandConfigProvider'; +import { DefaultOnError } from '../common/DefaultOnError'; export type PillarsTileProps = { onOpenDetails: ( @@ -70,6 +71,7 @@ export const PillarsTile = ({ trackerValues={trackerDayValues} tracker={tracker} valuesContext={valuesContext} + onError={DefaultOnError} onOpenDetails={() => onOpenDetails(tracker, valuesContext)} onSaveNewValueOverride={ onSaveNewValueOverride diff --git a/src/components/TrackTile/TrackerDetails/to-procedure-resource.ts b/src/components/TrackTile/TrackerDetails/to-procedure-resource.ts index 9408929b..baf9e10b 100644 --- a/src/components/TrackTile/TrackerDetails/to-procedure-resource.ts +++ b/src/components/TrackTile/TrackerDetails/to-procedure-resource.ts @@ -1,13 +1,25 @@ import { TrackerResource, TRACKER_CODE_SYSTEM, + TrackerDetailsError, } from '../services/TrackTileService'; import { format, addSeconds, startOfDay } from 'date-fns'; import { Code, ResourceSettings } from './to-fhir-resource'; import { convertToStoreUnit } from '../util/convert-value'; +import { t } from 'i18next'; export const TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"; +export class OneDayLimitError extends TrackerDetailsError { + constructor(message?: string | undefined) { + super(message); + this.userErrorMessage = t( + 'duration-lt-24h', + 'Duration cannot be greater than or equal to 24 hours.', + ); + } +} + const toFhirProcedureResource = ( from: ResourceSettings, code?: Code, @@ -22,6 +34,12 @@ const toFhirProcedureResource = ( // NOTE: All Procedure units MUST use system http://unitsofmeasure.org const seconds = convertToStoreUnit(value, tracker); + + // Procedures in the trackers/pillars space should never be >= 24 hours. + if (seconds >= 60 * 60 * 24) { + throw new OneDayLimitError(); + } + const endTime = addSeconds(startDate, seconds); return { diff --git a/src/components/TrackTile/common/DefaultOnError.test.tsx b/src/components/TrackTile/common/DefaultOnError.test.tsx new file mode 100644 index 00000000..7bc513a1 --- /dev/null +++ b/src/components/TrackTile/common/DefaultOnError.test.tsx @@ -0,0 +1,22 @@ +import { Alert } from 'react-native'; +import { DefaultOnError } from './DefaultOnError'; +import { TrackerDetailsError } from '../services/TrackTileService'; + +const alertSpy = jest.spyOn(Alert, 'alert'); + +describe('DefaultOnError', () => { + it('should display user error message if provided', () => { + const error = new TrackerDetailsError(); + error.userErrorMessage = 'unit test'; + DefaultOnError(error); + expect(alertSpy).toHaveBeenCalledWith(error.userErrorMessage); + }); + + it('should display generic error message if userMessage not provided', () => { + const error = new Error(); + DefaultOnError(error); + expect(alertSpy).toHaveBeenCalledWith( + 'Error saving tracker details. Please try again.', + ); + }); +}); diff --git a/src/components/TrackTile/common/DefaultOnError.tsx b/src/components/TrackTile/common/DefaultOnError.tsx new file mode 100644 index 00000000..a33c9c82 --- /dev/null +++ b/src/components/TrackTile/common/DefaultOnError.tsx @@ -0,0 +1,15 @@ +import { Alert } from 'react-native'; +import { t } from '../../../../lib/i18n'; +import { TrackerDetailsError } from '../services/TrackTileService'; + +export function DefaultOnError(error: any) { + console.warn('Error in tracker details', error); + let message = t( + 'generic-tracker-error-message', + 'Error saving tracker details. Please try again.', + ); + if (error instanceof TrackerDetailsError && error.userErrorMessage) { + message = error.userErrorMessage; + } + Alert.alert(message); +} diff --git a/src/components/TrackTile/services/TrackTileService.ts b/src/components/TrackTile/services/TrackTileService.ts index 11f3c962..710a8b85 100644 --- a/src/components/TrackTile/services/TrackTileService.ts +++ b/src/components/TrackTile/services/TrackTileService.ts @@ -131,6 +131,10 @@ export type TrackerValuesContext = { shouldUseOntology?: boolean; }; +export class TrackerDetailsError extends Error { + userErrorMessage?: string; +} + export const TRACK_TILE_CAPABILITIES_VERSION = 3; export const TRACK_TILE_CAPABILITIES_VERSION_HEADER = 'LifeOmic-TrackTile-Capabilities-Version'; diff --git a/src/screens/TrackTileAdvancedDetailsScreen.tsx b/src/screens/TrackTileAdvancedDetailsScreen.tsx index 83cfea32..3e82a2a1 100644 --- a/src/screens/TrackTileAdvancedDetailsScreen.tsx +++ b/src/screens/TrackTileAdvancedDetailsScreen.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { AdvancedTrackerDetails } from '../components/TrackTile/TrackerDetails/AdvancedTrackerDetails/AdvancedTrackerDetails'; import { HomeStackScreenProps } from '../navigators/types'; +import { DefaultOnError } from '../components/TrackTile/common/DefaultOnError'; export const AdvancedTrackerDetailsScreen = ({ navigation, @@ -15,6 +16,7 @@ export const AdvancedTrackerDetailsScreen = ({ tracker={tracker} valuesContext={valuesContext} referenceDate={referenceDate} + onError={DefaultOnError} onEditValue={(trackerValue) => navigation.push('Home/AdvancedTrackerEditor', { tracker, diff --git a/src/screens/TrackTileTrackerScreen.test.tsx b/src/screens/TrackTileTrackerScreen.test.tsx new file mode 100644 index 00000000..37fddcb8 --- /dev/null +++ b/src/screens/TrackTileTrackerScreen.test.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; +import TrackTileTrackerScreen from './TrackTileTrackerScreen'; +import { + TRACKER_CODE_SYSTEM, + TrackTileServiceProvider, +} from '../components/TrackTile/main'; +import { TrackerDetails } from '../components/TrackTile/TrackerDetails/TrackerDetails'; + +jest.mock('../components/TrackTile/TrackerDetails/TrackerDetails', () => ({ + TrackerDetails: jest.fn(() => null), +})); + +const TrackerDetailsMock = TrackerDetails as any as jest.Mock; +const navSetOptions = jest.fn(); +const tracker = { + account: 'account-id', + description: 'desc', + lifePoints: 0, + installed: true, + system: TRACKER_CODE_SYSTEM, + id: 'activity', + metricId: 'activity', + name: 'activity', + icon: 'running', + color: '#CD335E', + resourceType: 'Procedure', + code: 'activity', + order: 1, + units: [ + { + code: 'activity', + default: true, + display: 'min', + system: 'http://lifeomic.com/fhir/track-tile-pillar-value/activity', + target: 30, + unit: 'min', + }, + ], +}; + +const valuesContext = {}; +const referenceDate = {}; +const navigation: any = { setOptions: navSetOptions }; +const route: any = { + params: { + tracker, + valuesContext, + referenceDate, + }, +}; + +const renderInContext = () => { + return render( + , + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); +}; + +describe('TrackTileTrackerScreen', () => { + it('should render', () => { + renderInContext(); + expect(TrackerDetailsMock).toHaveBeenCalled(); + }); +}); diff --git a/src/screens/TrackTileTrackerScreen.tsx b/src/screens/TrackTileTrackerScreen.tsx index 27a6c773..34495568 100644 --- a/src/screens/TrackTileTrackerScreen.tsx +++ b/src/screens/TrackTileTrackerScreen.tsx @@ -6,6 +6,7 @@ import { HomeStackParamList } from '../navigators/types'; import { t } from '../../lib/i18n'; import { createStyles } from '../components/BrandConfigProvider'; import { useStyles } from '../hooks'; +import { DefaultOnError } from '../components/TrackTile/common/DefaultOnError'; type Props = NativeStackScreenProps; @@ -29,6 +30,7 @@ export const TrackTileTrackerScreen = ({ valuesContext={valuesContext} referenceDate={referenceDate} canEditUnit={true} + onError={DefaultOnError} /> );