Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions src/js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import type {
import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils';
import { Alert } from 'react-native';

import { createIntegration } from './integrations/factory';
import { Screenshot } from './integrations/screenshot';
import { defaultSdkInfo } from './integrations/sdkinfo';
import type { ReactNativeClientOptions } from './options';
import { ReactNativeTracing } from './tracing';
import { createUserFeedbackEnvelope, items } from './utils/envelope';
import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
import { mergeOutcomes } from './utils/outcome';
Expand Down Expand Up @@ -117,6 +119,18 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
this._sendEnvelope(envelope);
}

/**
* Sets up the integrations
*/
public setupIntegrations(): void {
super.setupIntegrations();
const tracing = this.getIntegration(ReactNativeTracing);
const routingName = tracing?.options.routingInstrumentation?.name
if (routingName) {
this.addIntegration(createIntegration(routingName));
}
}

/**
* @inheritdoc
*/
Expand Down
17 changes: 17 additions & 0 deletions src/js/integrations/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {
Integration,
} from '@sentry/types';

/**
* Creates an integration out of the provided name and setup function.
* @hidden
*/
export function createIntegration(
name: Integration['name'],
setupOnce: Integration['setupOnce'] = () => { /* noop */ },
): Integration {
return {
name: name,
setupOnce,
};
}
13 changes: 12 additions & 1 deletion src/js/touchevents.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { addBreadcrumb } from '@sentry/core';
import { addBreadcrumb, getCurrentHub } from '@sentry/core';
import type { SeverityLevel } from '@sentry/types';
import { logger } from '@sentry/utils';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';

import { createIntegration } from './integrations/factory';

export type TouchEventBoundaryProps = {
/**
* The category assigned to the breadcrumb that is logged by the touch event.
Expand Down Expand Up @@ -70,6 +72,15 @@ class TouchEventBoundary extends React.Component<TouchEventBoundaryProps> {
maxComponentTreeSize: DEFAULT_MAX_COMPONENT_TREE_SIZE,
};

public readonly name: string = 'TouchEventBoundary';

/**
* Registers the TouchEventBoundary as a Sentry Integration.
*/
public componentDidMount(): void {
getCurrentHub().getClient()?.addIntegration?.(createIntegration(this.name));
}

/**
*
*/
Expand Down
2 changes: 2 additions & 0 deletions src/js/tracing/reactnativenavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export interface NavigationDelegate {
export class ReactNativeNavigationInstrumentation extends InternalRoutingInstrumentation {
public static instrumentationName: string = 'react-native-navigation';

public readonly name: string = ReactNativeNavigationInstrumentation.instrumentationName;

private _navigation: NavigationDelegate;
private _options: ReactNativeNavigationOptions;

Expand Down
4 changes: 4 additions & 0 deletions src/js/tracing/reactnativeprofiler.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { getCurrentHub, Profiler } from '@sentry/react';

import { createIntegration } from '../integrations/factory';
import { ReactNativeTracing } from './reactnativetracing';

/**
* Custom profiler for the React Native app root.
*/
export class ReactNativeProfiler extends Profiler {
public readonly name: string = 'ReactNativeProfiler';

/**
* Get the app root mount time.
*/
public componentDidMount(): void {
super.componentDidMount();
getCurrentHub().getClient()?.addIntegration?.(createIntegration(this.name));

const tracingIntegration = getCurrentHub().getIntegration(
ReactNativeTracing
Expand Down
2 changes: 2 additions & 0 deletions src/js/tracing/reactnavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const defaultOptions: ReactNavigationOptions = {
export class ReactNavigationInstrumentation extends InternalRoutingInstrumentation {
public static instrumentationName: string = 'react-navigation-v5';

public readonly name: string = ReactNavigationInstrumentation.instrumentationName;

private _navigationContainer: NavigationContainer | null = null;

private readonly _maxRecentRouteLen: number = 200;
Expand Down
2 changes: 2 additions & 0 deletions src/js/tracing/reactnavigationv4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const defaultOptions: ReactNavigationV4Options = {
class ReactNavigationV4Instrumentation extends InternalRoutingInstrumentation {
public static instrumentationName: string = 'react-navigation-v4';

public readonly name: string = ReactNavigationV4Instrumentation.instrumentationName;

private _appContainer: AppContainerInstance | null = null;

private readonly _maxRecentRouteLen: number = 200;
Expand Down
6 changes: 6 additions & 0 deletions src/js/tracing/routingInstrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export type TransactionCreator = (
export type OnConfirmRoute = (context: TransactionContext) => void;

export interface RoutingInstrumentationInstance {
/**
* Name of the routing instrumentation
*/
readonly name: string;
/**
* Registers a listener that's called on every route change with a `TransactionContext`.
*
Expand Down Expand Up @@ -40,6 +44,8 @@ export interface RoutingInstrumentationInstance {
export class RoutingInstrumentation implements RoutingInstrumentationInstance {
public static instrumentationName: string = 'base-routing-instrumentation';

public readonly name: string = RoutingInstrumentation.instrumentationName;

protected _getCurrentHub?: () => Hub;
protected _beforeNavigate?: BeforeNavigate;
protected _onConfirmRoute?: OnConfirmRoute;
Expand Down
27 changes: 27 additions & 0 deletions test/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as RN from 'react-native';

import { ReactNativeClient } from '../src/js/client';
import type { ReactNativeClientOptions } from '../src/js/options';
import type { RoutingInstrumentationInstance } from '../src/js/tracing';
import { ReactNativeTracing } from '../src/js/tracing';
import { NativeTransport } from '../src/js/transports/native';
import { SDK_NAME, SDK_PACKAGE_NAME, SDK_VERSION } from '../src/js/version';
import { NATIVE } from '../src/js/wrapper';
Expand All @@ -29,6 +31,8 @@ interface MockedReactNative {
crash: jest.Mock;
captureEnvelope: jest.Mock;
captureScreenshot: jest.Mock;
fetchNativeAppStart: jest.Mock;
enableNativeFramesTracking: jest.Mock;
};
};
Platform: {
Expand All @@ -54,6 +58,8 @@ jest.mock(
crash: jest.fn(),
captureEnvelope: jest.fn(),
captureScreenshot: jest.fn().mockResolvedValue(null),
fetchNativeAppStart: jest.fn(),
enableNativeFramesTracking: jest.fn(),
},
},
Platform: {
Expand Down Expand Up @@ -530,6 +536,27 @@ describe('Tests ReactNativeClient', () => {
client.recordDroppedEvent('before_send', 'error');
}
});

describe('register enabled instrumentation as integrations', () => {
test('register routing instrumentation', () => {
const mockRoutingInstrumentation: RoutingInstrumentationInstance = {
registerRoutingInstrumentation: jest.fn(),
onRouteWillChange: jest.fn(),
name: 'MockRoutingInstrumentation',
}
const client = new ReactNativeClient(mockedOptions({
dsn: EXAMPLE_DSN,
integrations: [
new ReactNativeTracing({
routingInstrumentation: mockRoutingInstrumentation,
}),
],
}));
client.setupIntegrations();

expect(client.getIntegrationById('MockRoutingInstrumentation')).toBeTruthy();
});
});
});

function mockedOptions(options: Partial<ReactNativeClientOptions>): ReactNativeClientOptions {
Expand Down
17 changes: 17 additions & 0 deletions test/touchevents.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type { SeverityLevel } from '@sentry/types';

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

jest.mock('@sentry/core');

describe('TouchEventBoundary._onTouchStart', () => {
let addBreadcrumb: jest.SpyInstance;

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

it('register itself as integration', () => {
const mockAddIntegration = jest.fn();
(core.getCurrentHub as jest.Mock).mockReturnValue({
getClient: jest.fn().mockReturnValue({
addIntegration: mockAddIntegration,
}),
});
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);

boundary.componentDidMount();

expect(mockAddIntegration).toBeCalledWith(expect.objectContaining({ name: 'TouchEventBoundary' }));
});

it('tree without displayName or label is not logged', () => {
const { defaultProps } = TouchEventBoundary;
const boundary = new TouchEventBoundary(defaultProps);
Expand Down