Skip to content

Commit

Permalink
fix(tracing): ReactNativeTracing and initial navigation spans have to…
Browse files Browse the repository at this point in the history
… be created after integrations setup (#4000)
  • Loading branch information
krystofwoldrich authored Aug 14, 2024
1 parent d838577 commit eeb83da
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 12 deletions.
1 change: 0 additions & 1 deletion src/js/tracing/integrations/nativeFrames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export const nativeFramesIntegration = (): Integration => {

NATIVE.enableNativeFramesTracking();

// TODO: Ensure other integrations like ReactNativeTracing and ReactNavigation create spans after all integration are setup.
client.on('spanStart', _onSpanStart);
client.on('spanEnd', _onSpanFinish);
logger.log('[ReactNativeTracing] Native frames instrumentation initialized.');
Expand Down
21 changes: 12 additions & 9 deletions src/js/tracing/reactnativetracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ export const reactNativeTracingIntegration = (
};

const setup = (client: Client): void => {
addDefaultOpForSpanFrom(client);

instrumentOutgoingRequests({
traceFetch: finalOptions.traceFetch,
traceXHR: finalOptions.traceXHR,
shouldCreateSpanForRequest: finalOptions.shouldCreateSpanForRequest,
tracePropagationTargets: client.getOptions().tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS,
});
};

const afterAllSetup = (): void => {
if (finalOptions.routingInstrumentation) {
const idleNavigationSpanOptions = {
finalTimeout: finalOptions.finalTimeoutMs,
Expand Down Expand Up @@ -139,15 +150,6 @@ export const reactNativeTracingIntegration = (
} else {
logger.log(`[${INTEGRATION_NAME}] Not instrumenting route changes as routingInstrumentation has not been set.`);
}

addDefaultOpForSpanFrom(client);

instrumentOutgoingRequests({
traceFetch: finalOptions.traceFetch,
traceXHR: finalOptions.traceXHR,
shouldCreateSpanForRequest: finalOptions.shouldCreateSpanForRequest,
tracePropagationTargets: client.getOptions().tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS,
});
};

const processEvent = (event: Event): Event => {
Expand All @@ -160,6 +162,7 @@ export const reactNativeTracingIntegration = (
return {
name: INTEGRATION_NAME,
setup,
afterAllSetup,
processEvent,
options: finalOptions,
state,
Expand Down
1 change: 1 addition & 0 deletions test/tracing/reactnativetracing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe('ReactNativeTracing', () => {
});

integration.setup(client);
integration.afterAllSetup(client);
// wait for internal promises to resolve, fetch app start data from mocked native
await Promise.resolve();

Expand Down
85 changes: 83 additions & 2 deletions test/tracing/reactnavigation.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable deprecation/deprecation */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { getCurrentScope, getGlobalScope, getIsolationScope, SentrySpan, setCurrentClient } from '@sentry/core';
import type { StartSpanOptions } from '@sentry/types';
import type { Event, Measurements, StartSpanOptions } from '@sentry/types';

import { reactNativeTracingIntegration } from '../../src/js';
import { nativeFramesIntegration, reactNativeTracingIntegration } from '../../src/js';
import { DEFAULT_NAVIGATION_SPAN_NAME } from '../../src/js/tracing/reactnativetracing';
import type { NavigationRoute } from '../../src/js/tracing/reactnavigation';
import { ReactNavigationInstrumentation } from '../../src/js/tracing/reactnavigation';
Expand All @@ -21,13 +21,15 @@ import {
} from '../../src/js/tracing/semanticAttributes';
import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide';
import { getDefaultTestClientOptions, TestClient } from '../mocks/client';
import { NATIVE } from '../mockWrapper';
import { createMockNavigationAndAttachTo } from './reactnavigationutils';

const dummyRoute = {
name: 'Route',
key: '0',
};

jest.mock('../../src/js/wrapper.ts', () => jest.requireActual('../mockWrapper.ts'));
jest.useFakeTimers({ advanceTimers: true });

class MockNavigationContainer {
Expand Down Expand Up @@ -82,6 +84,85 @@ describe('ReactNavigationInstrumentation', () => {
);
});

describe('initial navigation span is created after all integrations are setup', () => {
let rnTracing: ReturnType<typeof reactNativeTracingIntegration>;

beforeEach(() => {
const startFrames = {
totalFrames: 100,
slowFrames: 20,
frozenFrames: 5,
};
const finishFrames = {
totalFrames: 200,
slowFrames: 40,
frozenFrames: 10,
};
NATIVE.fetchNativeFrames.mockResolvedValueOnce(startFrames).mockResolvedValueOnce(finishFrames);

const rNavigation = new ReactNavigationInstrumentation({
routeChangeTimeoutMs: 200,
});
mockNavigation = createMockNavigationAndAttachTo(rNavigation);

rnTracing = reactNativeTracingIntegration({
routingInstrumentation: rNavigation,
});
});

test('initial navigation span contains native frames when nativeFrames integration is after react native tracing', async () => {
const options = getDefaultTestClientOptions({
enableNativeFramesTracking: true,
enableStallTracking: false,
tracesSampleRate: 1.0,
integrations: [rnTracing, nativeFramesIntegration()],
enableAppStartTracking: false,
});
client = new TestClient(options);
setCurrentClient(client);
client.init();

// Flush the init transaction, must be async to allow for the native start frames to be fetched
await jest.runOnlyPendingTimersAsync();
await client.flush();

expectInitNavigationSpanWithNativeFrames(client.event);
});

test('initial navigation span contains native frames when nativeFrames integration is before react native tracing', async () => {
const options = getDefaultTestClientOptions({
enableNativeFramesTracking: true,
enableStallTracking: false,
tracesSampleRate: 1.0,
integrations: [nativeFramesIntegration(), rnTracing],
enableAppStartTracking: false,
});
client = new TestClient(options);
setCurrentClient(client);
client.init();

// Flush the init transaction, must be async to allow for the native start frames to be fetched
await jest.runOnlyPendingTimersAsync();
await client.flush();

expectInitNavigationSpanWithNativeFrames(client.event);
});

function expectInitNavigationSpanWithNativeFrames(event: Event): void {
expect(event).toEqual(
expect.objectContaining<Event>({
type: 'transaction',
transaction: 'Initial Screen',
measurements: expect.objectContaining<Measurements>({
frames_total: expect.toBeObject(),
frames_slow: expect.toBeObject(),
frames_frozen: expect.toBeObject(),
}),
}),
);
}
});

test('transaction sent on navigation', async () => {
setupTestClient();
jest.runOnlyPendingTimers(); // Flush the init transaction
Expand Down

0 comments on commit eeb83da

Please sign in to comment.