Skip to content

Commit 61c1213

Browse files
authored
Merge pull request #2065 from digidem/refactor/wire-sentry-backend
refactor: wire sentry backend
2 parents 6dbe44c + 792040d commit 61c1213

13 files changed

Lines changed: 155 additions & 291 deletions

app.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ module.exports = ({config}) => {
9595
// Trace only outside production (matches the app's prior env-gated
9696
// tracesSampleRate). The full runtime consent model migrates later.
9797
tracesSampleRate: sentryEnvironment === 'production' ? 0 : 1.0,
98+
diagnosticsEnabledDefault: true,
99+
applicationUsageDataDefault: sentryEnvironment !== 'production',
98100
},
99101
},
100102
]);

jest.setup.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@ jest.mock('@maplibre/maplibre-react-native', () => ({
130130
LineCap: {Round: 'round', Butt: 'butt', Square: 'square'},
131131
}));
132132

133+
// `ComapeoCoreModule.ts` calls `requireNativeModule("ComapeoCore")` at
134+
// import time, which throws in tests since there's no native module and
135+
// no `mocks/ComapeoCore.js` for jest-expo to pick up.
136+
jest.mock('@comapeo/core-react-native/sentry', () => ({
137+
sentryConfig: {},
138+
getDiagnosticsEnabled: jest.fn(() => false),
139+
setDiagnosticsEnabled: jest.fn(() => Promise.resolve()),
140+
getApplicationUsageData: jest.fn(() => false),
141+
setApplicationUsageData: jest.fn(() => Promise.resolve()),
142+
getDebugEnabled: jest.fn(() => false),
143+
setDebugEnabled: jest.fn(() => Promise.resolve()),
144+
getRootUserId: jest.fn(() => 'TEST-TEST-TEST'),
145+
initSentry: jest.fn(),
146+
}));
147+
133148
jest.mock('react-native-vision-camera', () => ({
134149
Camera: 'Camera',
135150
useCameraDevice: jest.fn(() => undefined),

src/frontend/App.tsx

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as React from 'react';
22
import {Logger, setConnected} from '@maplibre/maplibre-react-native';
3-
import {getLocales} from 'expo-localization';
43

54
// Maplibre logs when tile requests are cancelled, which is often.
65
// this turns off the unneccessary noise in the console logs
@@ -39,17 +38,19 @@ import * as SplashScreen from 'expo-splash-screen';
3938
import * as Sentry from '@sentry/react-native';
4039
import * as TaskManager from 'expo-task-manager';
4140
import {LOCATION_TASK_NAME, LocationCallbackInfo} from './sharedTypes/location';
42-
import {initSentry} from '@comapeo/core-react-native/sentry';
43-
import {AppDiagnosticMetrics} from './metrics/AppDiagnosticMetrics';
44-
import {DeviceDiagnosticMetrics} from './metrics/DeviceDiagnosticMetrics';
41+
import {
42+
initSentry,
43+
setApplicationUsageData,
44+
getDiagnosticsEnabled,
45+
getApplicationUsageData,
46+
} from '@comapeo/core-react-native/sentry';
4547
import {createDraftObservationStore} from './contexts/PersistedStores/DraftObservationStore';
4648
import {createTrackStore} from './contexts/TrackStoreContext';
4749
import {createSecurityStore} from './contexts/SecurityStoreContext';
4850
import {createCoordinateFormatStore} from './contexts/CoordinateFormatStoreContext';
4951
import {createUnitSystemStore} from './contexts/UnitSystemStoreContext';
5052
import {createManualEntryCoordinateFormatStore} from './contexts/ManualEntryCoordinateFormatStoreContext';
5153
import {createActiveProjectIdStore} from './contexts/ActiveProjectIdStoreContext';
52-
import {createMetricsDiagnosticsStore} from './contexts/MetricsDiagnosticsStoreContext';
5354
import {createLocaleStore, LocaleContext} from './contexts/LocaleStoreContext';
5455
import {IntlProvider} from './contexts/IntlContext';
5556
import {ServerLoading} from './ServerLoading';
@@ -63,47 +64,32 @@ import {createQADeviceNameStore} from './contexts/QADeviceNameStoreContext.tsx';
6364
import {FatalError} from './screens/FatalError.tsx';
6465
import {FatalErrorUntranslated} from './screens/FatalErrorUntranslated.tsx';
6566
import {postHog} from './lib/posthog.ts';
66-
import {APP_VARIANT} from './lib/appVariant.ts';
67+
import {getLocales} from 'expo-localization';
68+
import {AppUsageData} from './metrics/AppUsageData.ts';
69+
import {DeviceDiagnostics} from './metrics/DeviceDiagnosticMetrics.ts';
6770

68-
// DSN / environment / tracesSampleRate are baked into the native config by the
69-
// @comapeo/core-react-native plugin (app.config.js) and locked by initSentry,
70-
// which owns the Sentry.init call across the RN, Node, and Android-FGS hubs —
71-
// we pass only the allowlisted extensions. Tracing stays env-gated via the
72-
// plugin's tracesSampleRate; the full runtime consent model (and a stable user
73-
// id) migrates later with an updated core-react-native.
74-
const appMetricsOptIn = APP_VARIANT !== 'production';
7571
let navigationIntegration:
7672
ReturnType<(typeof Sentry)['reactNavigationIntegration']> | undefined =
7773
undefined;
7874

75+
const backendAppUsageDataEnabled = getApplicationUsageData();
76+
7977
initSentry({
8078
integrations: defaults => {
81-
if (!appMetricsOptIn) return defaults;
79+
if (!backendAppUsageDataEnabled) return defaults;
8280
navigationIntegration = Sentry.reactNavigationIntegration({
8381
enableTimeToInitialDisplay: true,
8482
ignoreEmptyBackNavigationTransactions: false,
8583
});
8684
return [...defaults, navigationIntegration];
8785
},
88-
tags: appMetricsOptIn ? {appMetricsOptIn: 'true'} : undefined,
86+
tags: backendAppUsageDataEnabled ? {appMetricsOptIn: 'true'} : undefined,
8987
});
9088

9189
const persistedLocaleStore = createLocaleStore({
9290
persist: true,
9391
});
9492

95-
const appDiagnosticMetrics = new AppDiagnosticMetrics({
96-
getLocaleInfo: () => {
97-
const systemLocales = getLocales();
98-
const {languageTag} = persistedLocaleStore.instance.getState();
99-
100-
return {
101-
appLanguageTag: languageTag,
102-
deviceLanguageTag: systemLocales[0]!.languageTag,
103-
};
104-
},
105-
});
106-
const deviceDiagnosticMetrics = new DeviceDiagnosticMetrics();
10793
const mapServerApi = {
10894
async getBaseUrl() {
10995
return new URL(await comapeoServicesClient.mapServer.getBaseUrl());
@@ -142,31 +128,13 @@ const persistedActiveProjectIdStore = createActiveProjectIdStore({
142128
persist: true,
143129
});
144130

145-
const persistedMetricsDiagnosticsStore = createMetricsDiagnosticsStore({
146-
persist: true,
147-
});
148-
149131
const qaDeviceNameStore = createQADeviceNameStore({persist: true});
150132

151133
const savedLocationStore = createSavedLocationStore({persist: true});
152134
const lowStorageBannerStore = createLowStorageBannerStore();
153135
const earlyAccessStore = createEarlyAccessStore({persist: true});
154136
const persistedUnitSystemStore = createUnitSystemStore({persist: true});
155137

156-
// Ensure that these metrics instances are initially in sync with initial state of relevant store
157-
const metricsIsEnabled =
158-
persistedMetricsDiagnosticsStore.instance.getState().isEnabled;
159-
appDiagnosticMetrics.setEnabled(metricsIsEnabled);
160-
deviceDiagnosticMetrics.setEnabled(metricsIsEnabled);
161-
162-
// Sync metrics instances with subsequent changes in relevant store state
163-
persistedMetricsDiagnosticsStore.instance.subscribe((current, previous) => {
164-
if (previous.isEnabled !== current.isEnabled) {
165-
appDiagnosticMetrics.setEnabled(current.isEnabled);
166-
deviceDiagnosticMetrics.setEnabled(current.isEnabled);
167-
}
168-
});
169-
170138
// Defines task that handles background location updates for tracks feature
171139
TaskManager.defineTask(
172140
LOCATION_TASK_NAME,
@@ -187,16 +155,45 @@ TaskManager.defineTask(
187155
},
188156
);
189157

158+
const appUsageData = new AppUsageData({
159+
getLocaleInfo: () => {
160+
const systemLocales = getLocales();
161+
const {languageTag} = persistedLocaleStore.instance.getState();
162+
163+
return {
164+
appLanguageTag: languageTag,
165+
deviceLanguageTag: systemLocales[0]!.languageTag,
166+
};
167+
},
168+
});
169+
170+
// App must be restart for the diagnostics to be turned on/off in the backend so this keeps it in sync
171+
appUsageData.setEnabled(backendAppUsageDataEnabled);
172+
190173
const appUsagePromptStore = createAppUsageStatsStore({
191174
persist: true,
192175
appUsageMetricsOptIn: () => {
193176
postHog.optIn();
177+
appUsageData.setEnabled(true);
178+
// Restart-to-activate: takes effect next launch, not this session.
179+
setApplicationUsageData(true).catch(err => {
180+
Sentry.captureException(err);
181+
});
194182
},
195183
appUsageMetricsOptOut: () => {
196184
postHog.optOut();
185+
appUsageData.setEnabled(false);
186+
setApplicationUsageData(false).catch(err => {
187+
Sentry.captureException(err);
188+
});
197189
},
198190
});
199191

192+
const deviceDiagnostics = new DeviceDiagnostics();
193+
const backendDiagnosticsEnabled = getDiagnosticsEnabled();
194+
// App must be restart for the diagnostics to be turned on/off in the backend so this keeps it in sync
195+
deviceDiagnostics.setEnabled(backendDiagnosticsEnabled);
196+
200197
const queryClient = new QueryClient();
201198

202199
AppState.addEventListener('change', status => {
@@ -255,7 +252,6 @@ const App = () => {
255252
}
256253
savedLocationStore={savedLocationStore}
257254
activeProjectIdStore={persistedActiveProjectIdStore}
258-
metricsDiagnosticsStore={persistedMetricsDiagnosticsStore}
259255
appUsageStatsStore={appUsagePromptStore}
260256
lowStorageBannerStore={lowStorageBannerStore}
261257
earlyAccessStore={earlyAccessStore}

src/frontend/contexts/AppProviders.tsx

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ import {
3131
type ManualEntryCoordinateFormatStore,
3232
ManualEntryCoordinateFormatStoreProvider,
3333
} from './ManualEntryCoordinateFormatStoreContext';
34-
import {
35-
type MetricsDiagnosticsStore,
36-
MetricsDiagnosticsStoreProvider,
37-
} from './MetricsDiagnosticsStoreContext';
3834
import {LocationProvider} from './LocationContext';
3935
import {
4036
SavedLocationStoreProvider,
@@ -68,7 +64,6 @@ type AppProvidersProps = {
6864
coordinateFormatStore: CoordinateFormatStore;
6965
manualEntryCoordinateFormatStore: ManualEntryCoordinateFormatStore;
7066
activeProjectIdStore: ActiveProjectIdStore;
71-
metricsDiagnosticsStore: MetricsDiagnosticsStore;
7267
savedLocationStore: SavedLocationStore;
7368
queryClient: QueryClient;
7469
lowStorageBannerStore: LowStorageBannerStore;
@@ -89,7 +84,6 @@ export const AppProviders = ({
8984
coordinateFormatStore,
9085
manualEntryCoordinateFormatStore,
9186
activeProjectIdStore,
92-
metricsDiagnosticsStore,
9387
savedLocationStore,
9488
queryClient,
9589
lowStorageBannerStore,
@@ -100,53 +94,51 @@ export const AppProviders = ({
10094
}: AppProvidersProps) => {
10195
return (
10296
<UnitSystemStoreContext value={unitSystemStore}>
103-
<MetricsDiagnosticsStoreProvider value={metricsDiagnosticsStore}>
104-
<AppUsageStatsProvider value={appUsageStatsStore}>
105-
<SecurityStoreProvider value={securityStore}>
106-
<CoordinateFormatStoreProvider value={coordinateFormatStore}>
107-
<ManualEntryCoordinateFormatStoreProvider
108-
value={manualEntryCoordinateFormatStore}>
109-
<TrackStoreProvider value={trackStore}>
110-
<LowStorageBannerStoreProvider value={lowStorageBannerStore}>
111-
<SafeAreaProvider>
112-
<GestureHandlerRootView style={styles.flex}>
113-
<SavedLocationStoreProvider value={savedLocationStore}>
114-
<LocationProvider>
115-
<LocalDiscoveryProvider
116-
value={localDiscoveryController}>
117-
<ComapeoCoreProvider
118-
clientApi={mapeoApi}
119-
getMapServerBaseUrl={mapServerApi.getBaseUrl}
120-
fetch={fetch}
121-
queryClient={queryClient}>
122-
<ActiveProjectIdStoreProvider
123-
store={activeProjectIdStore}>
124-
<DraftObservationProvider
125-
draftObservationStore={
126-
persistedDrafObservationStore
127-
}>
128-
<EarlyAccessStoreProvider
129-
value={earlyAccessStore}>
130-
<QADeviceNameStoreContext
131-
value={qaDeviceNameStore}>
132-
<AuthProvider>{children}</AuthProvider>
133-
</QADeviceNameStoreContext>
134-
</EarlyAccessStoreProvider>
135-
</DraftObservationProvider>
136-
</ActiveProjectIdStoreProvider>
137-
</ComapeoCoreProvider>
138-
</LocalDiscoveryProvider>
139-
</LocationProvider>
140-
</SavedLocationStoreProvider>
141-
</GestureHandlerRootView>
142-
</SafeAreaProvider>
143-
</LowStorageBannerStoreProvider>
144-
</TrackStoreProvider>
145-
</ManualEntryCoordinateFormatStoreProvider>
146-
</CoordinateFormatStoreProvider>
147-
</SecurityStoreProvider>
148-
</AppUsageStatsProvider>
149-
</MetricsDiagnosticsStoreProvider>
97+
<AppUsageStatsProvider value={appUsageStatsStore}>
98+
<SecurityStoreProvider value={securityStore}>
99+
<CoordinateFormatStoreProvider value={coordinateFormatStore}>
100+
<ManualEntryCoordinateFormatStoreProvider
101+
value={manualEntryCoordinateFormatStore}>
102+
<TrackStoreProvider value={trackStore}>
103+
<LowStorageBannerStoreProvider value={lowStorageBannerStore}>
104+
<SafeAreaProvider>
105+
<GestureHandlerRootView style={styles.flex}>
106+
<SavedLocationStoreProvider value={savedLocationStore}>
107+
<LocationProvider>
108+
<LocalDiscoveryProvider
109+
value={localDiscoveryController}>
110+
<ComapeoCoreProvider
111+
clientApi={mapeoApi}
112+
getMapServerBaseUrl={mapServerApi.getBaseUrl}
113+
fetch={fetch}
114+
queryClient={queryClient}>
115+
<ActiveProjectIdStoreProvider
116+
store={activeProjectIdStore}>
117+
<DraftObservationProvider
118+
draftObservationStore={
119+
persistedDrafObservationStore
120+
}>
121+
<EarlyAccessStoreProvider
122+
value={earlyAccessStore}>
123+
<QADeviceNameStoreContext
124+
value={qaDeviceNameStore}>
125+
<AuthProvider>{children}</AuthProvider>
126+
</QADeviceNameStoreContext>
127+
</EarlyAccessStoreProvider>
128+
</DraftObservationProvider>
129+
</ActiveProjectIdStoreProvider>
130+
</ComapeoCoreProvider>
131+
</LocalDiscoveryProvider>
132+
</LocationProvider>
133+
</SavedLocationStoreProvider>
134+
</GestureHandlerRootView>
135+
</SafeAreaProvider>
136+
</LowStorageBannerStoreProvider>
137+
</TrackStoreProvider>
138+
</ManualEntryCoordinateFormatStoreProvider>
139+
</CoordinateFormatStoreProvider>
140+
</SecurityStoreProvider>
141+
</AppUsageStatsProvider>
150142
</UnitSystemStoreContext>
151143
);
152144
};

src/frontend/contexts/MetricsDiagnosticsStoreContext.test.tsx

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)