Skip to content

Commit e135fed

Browse files
feat(internal): Add routing instrumentations report in integrations (#2798)
1 parent 9687ca8 commit e135fed

File tree

10 files changed

+103
-1
lines changed

10 files changed

+103
-1
lines changed

src/js/client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import type {
1515
import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils';
1616
import { Alert } from 'react-native';
1717

18+
import { createIntegration } from './integrations/factory';
1819
import { Screenshot } from './integrations/screenshot';
1920
import { defaultSdkInfo } from './integrations/sdkinfo';
2021
import type { ReactNativeClientOptions } from './options';
22+
import { ReactNativeTracing } from './tracing';
2123
import { createUserFeedbackEnvelope, items } from './utils/envelope';
2224
import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
2325
import { mergeOutcomes } from './utils/outcome';
@@ -117,6 +119,18 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
117119
this._sendEnvelope(envelope);
118120
}
119121

122+
/**
123+
* Sets up the integrations
124+
*/
125+
public setupIntegrations(): void {
126+
super.setupIntegrations();
127+
const tracing = this.getIntegration(ReactNativeTracing);
128+
const routingName = tracing?.options.routingInstrumentation?.name
129+
if (routingName) {
130+
this.addIntegration(createIntegration(routingName));
131+
}
132+
}
133+
120134
/**
121135
* @inheritdoc
122136
*/

src/js/integrations/factory.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type {
2+
Integration,
3+
} from '@sentry/types';
4+
5+
/**
6+
* Creates an integration out of the provided name and setup function.
7+
* @hidden
8+
*/
9+
export function createIntegration(
10+
name: Integration['name'],
11+
setupOnce: Integration['setupOnce'] = () => { /* noop */ },
12+
): Integration {
13+
return {
14+
name: name,
15+
setupOnce,
16+
};
17+
}

src/js/touchevents.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { addBreadcrumb } from '@sentry/core';
1+
import { addBreadcrumb, getCurrentHub } from '@sentry/core';
22
import type { SeverityLevel } from '@sentry/types';
33
import { logger } from '@sentry/utils';
44
import * as React from 'react';
55
import { StyleSheet, View } from 'react-native';
66

7+
import { createIntegration } from './integrations/factory';
8+
79
export type TouchEventBoundaryProps = {
810
/**
911
* The category assigned to the breadcrumb that is logged by the touch event.
@@ -70,6 +72,15 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
7072
maxComponentTreeSize: DEFAULT_MAX_COMPONENT_TREE_SIZE,
7173
};
7274

75+
public readonly name: string = 'TouchEventBoundary';
76+
77+
/**
78+
* Registers the TouchEventBoundary as a Sentry Integration.
79+
*/
80+
public componentDidMount(): void {
81+
getCurrentHub().getClient()?.addIntegration?.(createIntegration(this.name));
82+
}
83+
7384
/**
7485
*
7586
*/

src/js/tracing/reactnativenavigation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export interface NavigationDelegate {
7070
export class ReactNativeNavigationInstrumentation extends InternalRoutingInstrumentation {
7171
public static instrumentationName: string = 'react-native-navigation';
7272

73+
public readonly name: string = ReactNativeNavigationInstrumentation.instrumentationName;
74+
7375
private _navigation: NavigationDelegate;
7476
private _options: ReactNativeNavigationOptions;
7577

src/js/tracing/reactnativeprofiler.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { getCurrentHub, Profiler } from '@sentry/react';
22

3+
import { createIntegration } from '../integrations/factory';
34
import { ReactNativeTracing } from './reactnativetracing';
45

56
/**
67
* Custom profiler for the React Native app root.
78
*/
89
export class ReactNativeProfiler extends Profiler {
10+
public readonly name: string = 'ReactNativeProfiler';
11+
912
/**
1013
* Get the app root mount time.
1114
*/
1215
public componentDidMount(): void {
1316
super.componentDidMount();
17+
getCurrentHub().getClient()?.addIntegration?.(createIntegration(this.name));
1418

1519
const tracingIntegration = getCurrentHub().getIntegration(
1620
ReactNativeTracing

src/js/tracing/reactnavigation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const defaultOptions: ReactNavigationOptions = {
5454
export class ReactNavigationInstrumentation extends InternalRoutingInstrumentation {
5555
public static instrumentationName: string = 'react-navigation-v5';
5656

57+
public readonly name: string = ReactNavigationInstrumentation.instrumentationName;
58+
5759
private _navigationContainer: NavigationContainer | null = null;
5860

5961
private readonly _maxRecentRouteLen: number = 200;

src/js/tracing/reactnavigationv4.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ const defaultOptions: ReactNavigationV4Options = {
6666
class ReactNavigationV4Instrumentation extends InternalRoutingInstrumentation {
6767
public static instrumentationName: string = 'react-navigation-v4';
6868

69+
public readonly name: string = ReactNavigationV4Instrumentation.instrumentationName;
70+
6971
private _appContainer: AppContainerInstance | null = null;
7072

7173
private readonly _maxRecentRouteLen: number = 200;

src/js/tracing/routingInstrumentation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export type TransactionCreator = (
1010
export type OnConfirmRoute = (context: TransactionContext) => void;
1111

1212
export interface RoutingInstrumentationInstance {
13+
/**
14+
* Name of the routing instrumentation
15+
*/
16+
readonly name: string;
1317
/**
1418
* Registers a listener that's called on every route change with a `TransactionContext`.
1519
*
@@ -40,6 +44,8 @@ export interface RoutingInstrumentationInstance {
4044
export class RoutingInstrumentation implements RoutingInstrumentationInstance {
4145
public static instrumentationName: string = 'base-routing-instrumentation';
4246

47+
public readonly name: string = RoutingInstrumentation.instrumentationName;
48+
4349
protected _getCurrentHub?: () => Hub;
4450
protected _beforeNavigate?: BeforeNavigate;
4551
protected _onConfirmRoute?: OnConfirmRoute;

test/client.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import * as RN from 'react-native';
55

66
import { ReactNativeClient } from '../src/js/client';
77
import type { ReactNativeClientOptions } from '../src/js/options';
8+
import type { RoutingInstrumentationInstance } from '../src/js/tracing';
9+
import { ReactNativeTracing } from '../src/js/tracing';
810
import { NativeTransport } from '../src/js/transports/native';
911
import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../src/js/version';
1012
import { NATIVE } from '../src/js/wrapper';
@@ -29,6 +31,8 @@ interface MockedReactNative {
2931
crash: jest.Mock;
3032
captureEnvelope: jest.Mock;
3133
captureScreenshot: jest.Mock;
34+
fetchNativeAppStart: jest.Mock;
35+
enableNativeFramesTracking: jest.Mock;
3236
};
3337
};
3438
Platform: {
@@ -54,6 +58,8 @@ jest.mock(
5458
crash: jest.fn(),
5559
captureEnvelope: jest.fn(),
5660
captureScreenshot: jest.fn().mockResolvedValue(null),
61+
fetchNativeAppStart: jest.fn(),
62+
enableNativeFramesTracking: jest.fn(),
5763
},
5864
},
5965
Platform: {
@@ -530,6 +536,27 @@ describe('Tests ReactNativeClient', () => {
530536
client.recordDroppedEvent('before_send', 'error');
531537
}
532538
});
539+
540+
describe('register enabled instrumentation as integrations', () => {
541+
test('register routing instrumentation', () => {
542+
const mockRoutingInstrumentation: RoutingInstrumentationInstance = {
543+
registerRoutingInstrumentation: jest.fn(),
544+
onRouteWillChange: jest.fn(),
545+
name: 'MockRoutingInstrumentation',
546+
}
547+
const client = new ReactNativeClient(mockedOptions({
548+
dsn: EXAMPLE_DSN,
549+
integrations: [
550+
new ReactNativeTracing({
551+
routingInstrumentation: mockRoutingInstrumentation,
552+
}),
553+
],
554+
}));
555+
client.setupIntegrations();
556+
557+
expect(client.getIntegrationById('MockRoutingInstrumentation')).toBeTruthy();
558+
});
559+
});
533560
});
534561

535562
function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeClientOptions {

test/touchevents.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { SeverityLevel } from '@sentry/types';
66

77
import { TouchEventBoundary } from '../src/js/touchevents';
88

9+
jest.mock('@sentry/core');
10+
911
describe('TouchEventBoundary._onTouchStart', () => {
1012
let addBreadcrumb: jest.SpyInstance;
1113

@@ -14,6 +16,21 @@ describe('TouchEventBoundary._onTouchStart', () => {
1416
addBreadcrumb = jest.spyOn(core, 'addBreadcrumb');
1517
});
1618

19+
it('register itself as integration', () => {
20+
const mockAddIntegration = jest.fn();
21+
(core.getCurrentHub as jest.Mock).mockReturnValue({
22+
getClient: jest.fn().mockReturnValue({
23+
addIntegration: mockAddIntegration,
24+
}),
25+
});
26+
const { defaultProps } = TouchEventBoundary;
27+
const boundary = new TouchEventBoundary(defaultProps);
28+
29+
boundary.componentDidMount();
30+
31+
expect(mockAddIntegration).toBeCalledWith(expect.objectContaining({ name: 'TouchEventBoundary' }));
32+
});
33+
1734
it('tree without displayName or label is not logged', () => {
1835
const { defaultProps } = TouchEventBoundary;
1936
const boundary = new TouchEventBoundary(defaultProps);

0 commit comments

Comments
 (0)