Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nextjs|sveltekit/v7): Ensure we can pass browserTracingIntegration #11765

Merged
merged 2 commits into from
Apr 24, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ Sentry.init({
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1.0,
sendDefaultPii: true,

// Ensure it also works with passing the integration
integrations: [Sentry.browserTracingIntegration()],
});
16 changes: 12 additions & 4 deletions packages/nextjs/src/client/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
} from '@sentry/react';
import type { Integration, StartSpanOptions } from '@sentry/types';
import type { StartSpanOptions } from '@sentry/types';
import { nextRouterInstrumentation } from '../index.client';

/**
Expand Down Expand Up @@ -42,7 +42,7 @@ export class BrowserTracing extends OriginalBrowserTracing {
*/
export function browserTracingIntegration(
options?: Parameters<typeof originalBrowserTracingIntegration>[0],
): Integration {
): ReturnType<typeof originalBrowserTracingIntegration> {
const browserTracingIntegrationInstance = originalBrowserTracingIntegration({
// eslint-disable-next-line deprecation/deprecation
tracingOrigins:
Expand All @@ -61,8 +61,16 @@ export function browserTracingIntegration(
instrumentPageLoad: false,
});

const fullOptions = {
...browserTracingIntegrationInstance.options,
instrumentPageLoad: true,
instrumentNavigation: true,
...options,
};

return {
...browserTracingIntegrationInstance,
options: fullOptions,
afterAllSetup(client) {
const startPageloadCallback = (startSpanOptions: StartSpanOptions): void => {
startBrowserTracingPageLoadSpan(client, startSpanOptions);
Expand All @@ -80,7 +88,7 @@ export function browserTracingIntegration(
nextRouterInstrumentation(
() => undefined,
false,
options?.instrumentNavigation,
fullOptions.instrumentNavigation,
startPageloadCallback,
startNavigationCallback,
);
Expand All @@ -90,7 +98,7 @@ export function browserTracingIntegration(
// eslint-disable-next-line deprecation/deprecation
nextRouterInstrumentation(
() => undefined,
options?.instrumentPageLoad,
fullOptions.instrumentPageLoad,
false,
startPageloadCallback,
startNavigationCallback,
Expand Down
82 changes: 75 additions & 7 deletions packages/nextjs/test/clientSdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { BaseClient, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, spanToJSON } from '@sentry/core';
import {
BaseClient,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
getActiveSpan,
getIsolationScope,
spanToJSON,
} from '@sentry/core';
import * as SentryReact from '@sentry/react';
import type { BrowserClient } from '@sentry/react';
import { browserTracingIntegration } from '@sentry/react';
Expand All @@ -7,7 +13,13 @@ import type { Integration } from '@sentry/types';
import { logger } from '@sentry/utils';
import { JSDOM } from 'jsdom';

import { BrowserTracing, breadcrumbsIntegration, init, nextRouterInstrumentation } from '../src/client';
import {
BrowserTracing,
breadcrumbsIntegration,
browserTracingIntegration as nextjsBrowserTracingIntegration,
init,
nextRouterInstrumentation,
} from '../src/client';

const reactInit = jest.spyOn(SentryReact, 'init');
const captureEvent = jest.spyOn(BaseClient.prototype, 'captureEvent');
Expand Down Expand Up @@ -35,6 +47,11 @@ function findIntegrationByName(integrations: Integration[] = [], name: string):
const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337';

describe('Client init()', () => {
beforeEach(() => {
getCurrentScope().clear();
getIsolationScope().clear();
});

afterEach(() => {
jest.clearAllMocks();
WINDOW.__SENTRY__.hub = undefined;
Expand Down Expand Up @@ -141,9 +158,16 @@ describe('Client init()', () => {
});

it('forces correct router instrumentation if user provides `browserTracingIntegration`', () => {
const beforeStartSpan = jest.fn(options => options);
init({
dsn: TEST_DSN,
integrations: [browserTracingIntegration({ finalTimeout: 10 })],
integrations: [
browserTracingIntegration({
finalTimeout: 10,
instrumentNavigation: false,
beforeStartSpan,
}),
],
enableTracing: true,
});

Expand All @@ -156,14 +180,58 @@ describe('Client init()', () => {
// It is a "new" browser tracing integration
expect(typeof integration?.afterAllSetup).toBe('function');

// the hooks is correctly invoked
expect(beforeStartSpan).toHaveBeenCalledTimes(1);
expect(beforeStartSpan).toHaveBeenCalledWith(
expect.objectContaining({
name: '/',
op: 'pageload',
}),
);

// it correctly starts the page load span
expect(getActiveSpan()).toBeDefined();
expect(spanToJSON(getActiveSpan()!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toEqual(
'auto.pageload.nextjs.app_router_instrumentation',
);

// This shows that the user-configured options are still here
expect(integration?.options?.finalTimeout).toEqual(10);
expect(integration?.options.finalTimeout).toEqual(10);
expect(integration?.options.instrumentNavigation).toBe(false);
expect(integration?.options.instrumentPageLoad).toBe(true);
});

// it is the svelte kit variety
it('forces correct router instrumentation if user provides Next.js `browserTracingIntegration` ', () => {
init({
dsn: TEST_DSN,
integrations: [
nextjsBrowserTracingIntegration({
finalTimeout: 10,
instrumentNavigation: false,
}),
],
enableTracing: true,
});

const client = getClient<BrowserClient>()!;
// eslint-disable-next-line deprecation/deprecation
const integration = client.getIntegrationByName<ReturnType<typeof browserTracingIntegration>>('BrowserTracing');

expect(integration).toBeDefined();

// It is a "new" browser tracing integration
expect(typeof integration?.afterAllSetup).toBe('function');

// it correctly starts the pageload span
expect(getActiveSpan()).toBeDefined();
expect(spanToJSON(getActiveSpan()!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toEqual(
'auto.pageload.nextjs.app_router_instrumentation',
);

// This shows that the user-configured options are still here
expect(integration?.options.finalTimeout).toEqual(10);
expect(integration?.options.instrumentNavigation).toBe(false);
expect(integration?.options.instrumentPageLoad).toBe(true);
});

it('forces correct router instrumentation if user provides `BrowserTracing` in a function', () => {
Expand All @@ -187,10 +255,10 @@ describe('Client init()', () => {

expect(browserTracingIntegration?.options).toEqual(
expect.objectContaining({
startTransactionOnPageLoad: true,
startTransactionOnLocationChange: false,
// eslint-disable-next-line deprecation/deprecation
routingInstrumentation: nextRouterInstrumentation,
// This proves it's still the user's copy
startTransactionOnLocationChange: false,
}),
);
});
Expand Down
16 changes: 12 additions & 4 deletions packages/sveltekit/src/client/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
startBrowserTracingPageLoadSpan,
startInactiveSpan,
} from '@sentry/svelte';
import type { Client, Integration, Span } from '@sentry/types';
import type { Client, Span } from '@sentry/types';
import { svelteKitRoutingInstrumentation } from './router';

/**
Expand All @@ -36,7 +36,7 @@ export class BrowserTracing extends OriginalBrowserTracing {
*/
export function browserTracingIntegration(
options: Parameters<typeof originalBrowserTracingIntegration>[0] = {},
): Integration {
): ReturnType<typeof originalBrowserTracingIntegration> {
const integration = {
...originalBrowserTracingIntegration({
...options,
Expand All @@ -45,16 +45,24 @@ export function browserTracingIntegration(
}),
};

const fullOptions = {
...integration.options,
instrumentPageLoad: true,
instrumentNavigation: true,
...options,
};

return {
...integration,
options: fullOptions,
afterAllSetup: client => {
integration.afterAllSetup(client);

if (options.instrumentPageLoad !== false) {
if (fullOptions.instrumentPageLoad) {
_instrumentPageload(client);
}

if (options.instrumentNavigation !== false) {
if (fullOptions.instrumentNavigation) {
_instrumentNavigations(client);
}
},
Expand Down
61 changes: 54 additions & 7 deletions packages/sveltekit/test/client/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, getClient, getCurrentScope, spanToJSON } from '@sentry/core';
import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
getActiveSpan,
getClient,
getCurrentScope,
getIsolationScope,
spanToJSON,
} from '@sentry/core';
import type { BrowserClient } from '@sentry/svelte';
import * as SentrySvelte from '@sentry/svelte';
import { SDK_VERSION, WINDOW, browserTracingIntegration } from '@sentry/svelte';
import { vi } from 'vitest';

import { BrowserTracing, init } from '../../src/client';
import {
BrowserTracing,
browserTracingIntegration as sveltekitBrowserTracingIntegration,
init,
} from '../../src/client';
import { svelteKitRoutingInstrumentation } from '../../src/client/router';

const svelteInit = vi.spyOn(SentrySvelte, 'init');
Expand All @@ -16,6 +27,11 @@ describe('Sentry client SDK', () => {
WINDOW.__SENTRY__.hub = undefined;
});

beforeEach(() => {
getCurrentScope().clear();
getIsolationScope().clear();
});

it('adds SvelteKit metadata to the SDK options', () => {
expect(svelteInit).not.toHaveBeenCalled();

Expand Down Expand Up @@ -99,7 +115,7 @@ describe('Sentry client SDK', () => {
init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
// eslint-disable-next-line deprecation/deprecation
integrations: [new BrowserTracing({ finalTimeout: 10 })],
integrations: [new BrowserTracing({ finalTimeout: 10, startTransactionOnLocationChange: false })],
enableTracing: true,
});

Expand All @@ -118,34 +134,65 @@ describe('Sentry client SDK', () => {
// But we force the routing instrumentation to be ours
// eslint-disable-next-line deprecation/deprecation
expect(options.routingInstrumentation).toEqual(svelteKitRoutingInstrumentation);
expect(options.startTransactionOnPageLoad).toEqual(true);
expect(options.startTransactionOnLocationChange).toEqual(false);
});

it('Merges a user-provided browserTracingIntegration with the automatically added one', () => {
init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [browserTracingIntegration({ finalTimeout: 10 })],
integrations: [browserTracingIntegration({ finalTimeout: 10, instrumentNavigation: false })],
enableTracing: true,
});

const browserTracing =
getClient<BrowserClient>()?.getIntegrationByName<ReturnType<typeof browserTracingIntegration>>(
'BrowserTracing',
);
const options = browserTracing?.options;

expect(browserTracing).toBeDefined();

// It is a "new" browser tracing integration
expect(typeof browserTracing?.afterAllSetup).toBe('function');

// it is the svelte kit variety
expect(getActiveSpan()).toBeDefined();
expect(spanToJSON(getActiveSpan()!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toEqual(
'auto.pageload.sveltekit',
);

// This shows that the user-configured options are still here
expect(options?.finalTimeout).toEqual(10);
expect(browserTracing?.options.finalTimeout).toEqual(10);
expect(browserTracing?.options.instrumentPageLoad).toEqual(true);
expect(browserTracing?.options.instrumentNavigation).toEqual(false);
});

// it is the svelte kit variety
it('forces correct router instrumentation if user provides Sveltekit `browserTracingIntegration` ', () => {
init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [sveltekitBrowserTracingIntegration({ finalTimeout: 10, instrumentNavigation: false })],
enableTracing: true,
});

const client = getClient<BrowserClient>()!;
// eslint-disable-next-line deprecation/deprecation
const integration = client.getIntegrationByName<ReturnType<typeof browserTracingIntegration>>('BrowserTracing');

expect(integration).toBeDefined();

// It is a "new" browser tracing integration
expect(typeof integration?.afterAllSetup).toBe('function');

// it correctly starts the pageload span
expect(getActiveSpan()).toBeDefined();
expect(spanToJSON(getActiveSpan()!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]).toEqual(
'auto.pageload.sveltekit',
);

// This shows that the user-configured options are still here
expect(integration?.options.finalTimeout).toEqual(10);
expect(integration?.options.instrumentNavigation).toBe(false);
expect(integration?.options.instrumentPageLoad).toBe(true);
});
});
});
Expand Down