From dcd0ea9f7716c12116a4158ff7378c39f60bba5e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 5 Feb 2024 12:26:55 +0100 Subject: [PATCH 1/3] feat(react): Add `browserTracingIntegration` for react router v6 & v6.4 feat(react): Add browserTracingIntegrations for router v4 & v5 This adds a new `browserTracingReactRouterV6Integration()` exports deprecating the old routing instrumentation. I opted to leave as much as possible as-is for now, except for streamlining the attributes/tags we use for the instrumentation. --- packages/react/src/index.ts | 2 + packages/react/src/reactrouterv6.tsx | 160 ++- packages/react/test/reactrouterv6.4.test.tsx | 662 +++++++++++- packages/react/test/reactrouterv6.test.tsx | 996 +++++++++++++++++-- 4 files changed, 1689 insertions(+), 131 deletions(-) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index ad66d1e77801..170496cb7e7d 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -8,7 +8,9 @@ export { createReduxEnhancer } from './redux'; export { reactRouterV3Instrumentation } from './reactrouterv3'; export { reactRouterV4Instrumentation, reactRouterV5Instrumentation, withSentryRouting } from './reactrouter'; export { + // eslint-disable-next-line deprecation/deprecation reactRouterV6Instrumentation, + browserTracingReactRouterV6Integration, withSentryReactRouterV6Routing, wrapUseRoutes, wrapCreateBrowserRouter, diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index c2dc56687571..2cc258a78640 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -1,9 +1,29 @@ +/* eslint-disable max-lines */ // Inspired from Donnie McNeal's solution: // https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 -import { WINDOW } from '@sentry/browser'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import type { Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import { + WINDOW, + browserTracingIntegration, + startBrowserTracingNavigationSpan, + startBrowserTracingPageLoadSpan, +} from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getRootSpan, + spanToJSON, +} from '@sentry/core'; +import type { + Integration, + Span, + StartSpanOptions, + Transaction, + TransactionContext, + TransactionSource, +} from '@sentry/types'; import { getNumberOfUrlSegments, logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; @@ -37,10 +57,77 @@ let _customStartTransaction: (context: TransactionContext) => Transaction | unde let _startTransactionOnLocationChange: boolean; let _stripBasename: boolean = false; -const SENTRY_TAGS = { - 'routing.instrumentation': 'react-router-v6', -}; +interface ReactRouterOptions { + useEffect: UseEffect; + useLocation: UseLocation; + useNavigationType: UseNavigationType; + createRoutesFromChildren: CreateRoutesFromChildren; + matchRoutes: MatchRoutes; + stripBasename?: boolean; +} + +/** + * A browser tracing integration that uses React Router v3 to instrument navigations. + * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options. + */ +export function browserTracingReactRouterV6Integration( + options: Parameters[0] & ReactRouterOptions, +): Integration { + const integration = browserTracingIntegration({ + ...options, + instrumentPageLoad: false, + instrumentNavigation: false, + }); + + const { + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + stripBasename, + instrumentPageLoad = true, + instrumentNavigation = true, + } = options; + + return { + ...integration, + afterAllSetup(client) { + integration.afterAllSetup(client); + + const startNavigationCallback = (startSpanOptions: StartSpanOptions): undefined => { + startBrowserTracingNavigationSpan(client, startSpanOptions); + return undefined; + }; + + const initPathName = WINDOW && WINDOW.location && WINDOW.location.pathname; + if (instrumentPageLoad && initPathName) { + startBrowserTracingPageLoadSpan(client, { + name: initPathName, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, + }); + } + _useEffect = useEffect; + _useLocation = useLocation; + _useNavigationType = useNavigationType; + _matchRoutes = matchRoutes; + _createRoutesFromChildren = createRoutesFromChildren; + _stripBasename = stripBasename || false; + + _customStartTransaction = startNavigationCallback; + _startTransactionOnLocationChange = instrumentNavigation; + }, + }; +} + +/** + * @deprecated Use `browserTracingReactRouterV6Integration()` instead. + */ export function reactRouterV6Instrumentation( useEffect: UseEffect, useLocation: UseLocation, @@ -58,11 +145,10 @@ export function reactRouterV6Instrumentation( if (startTransactionOnPageLoad && initPathName) { activeTransaction = customStartTransaction({ name: initPathName, - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: SENTRY_TAGS, - metadata: { - source: 'url', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', }, }); } @@ -155,6 +241,7 @@ function getNormalizedName( } function updatePageloadTransaction( + activeRootSpan: Span | undefined, location: Location, routes: RouteObject[], matches?: AgnosticDataRouteMatch, @@ -164,10 +251,10 @@ function updatePageloadTransaction( ? matches : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]); - if (activeTransaction && branches) { + if (activeRootSpan && branches) { const [name, source] = getNormalizedName(routes, location, branches, basename); - activeTransaction.updateName(name); - activeTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); + activeRootSpan.updateName(name); + activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); } } @@ -188,11 +275,10 @@ function handleNavigation( const [name, source] = getNormalizedName(routes, location, branches, basename); activeTransaction = _customStartTransaction({ name, - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: SENTRY_TAGS, - metadata: { - source, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', }, }); } @@ -227,7 +313,7 @@ export function withSentryReactRouterV6Routing

, R const routes = _createRoutesFromChildren(props.children) as RouteObject[]; if (isMountRenderPass) { - updatePageloadTransaction(location, routes); + updatePageloadTransaction(getActiveRootSpan(), location, routes); isMountRenderPass = false; } else { handleNavigation(location, routes, navigationType); @@ -285,7 +371,7 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes { typeof stableLocationParam === 'string' ? { pathname: stableLocationParam } : stableLocationParam; if (isMountRenderPass) { - updatePageloadTransaction(normalizedLocation, routes); + updatePageloadTransaction(getActiveRootSpan(), normalizedLocation, routes); isMountRenderPass = false; } else { handleNavigation(normalizedLocation, routes, navigationType); @@ -312,21 +398,18 @@ export function wrapCreateBrowserRouter< const router = createRouterFunction(routes, opts); const basename = opts && opts.basename; + const activeRootSpan = getActiveRootSpan(); + // The initial load ends when `createBrowserRouter` is called. // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. - if (router.state.historyAction === 'POP' && activeTransaction) { - updatePageloadTransaction(router.state.location, routes, undefined, basename); + if (router.state.historyAction === 'POP' && activeRootSpan) { + updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename); } router.subscribe((state: RouterState) => { const location = state.location; - - if ( - _startTransactionOnLocationChange && - (state.historyAction === 'PUSH' || state.historyAction === 'POP') && - activeTransaction - ) { + if (_startTransactionOnLocationChange && (state.historyAction === 'PUSH' || state.historyAction === 'POP')) { handleNavigation(location, routes, state.historyAction, undefined, basename); } }); @@ -334,3 +417,22 @@ export function wrapCreateBrowserRouter< return router; }; } + +function getActiveRootSpan(): Span | undefined { + // Legacy behavior for "old" react router instrumentation + if (activeTransaction) { + return activeTransaction; + } + + const span = getActiveSpan(); + const rootSpan = span ? getRootSpan(span) : undefined; + + if (!rootSpan) { + return undefined; + } + + const op = spanToJSON(rootSpan).op; + + // Only use this root span if it is a pageload or navigation span + return op === 'navigation' || op === 'pageload' ? rootSpan : undefined; +} diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index 29fe612f7e97..3255c21036b7 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -1,4 +1,11 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + createTransport, + getCurrentScope, + setCurrentClient, +} from '@sentry/core'; import { render } from '@testing-library/react'; import { Request } from 'node-fetch'; import * as React from 'react'; @@ -13,7 +20,8 @@ import { useNavigationType, } from 'react-router-6.4'; -import { reactRouterV6Instrumentation, wrapCreateBrowserRouter } from '../src'; +import { BrowserClient, reactRouterV6Instrumentation, wrapCreateBrowserRouter } from '../src'; +import { browserTracingReactRouterV6Integration } from '../src/reactrouterv6'; import type { CreateRouterFunction } from '../src/types'; beforeAll(() => { @@ -22,7 +30,7 @@ beforeAll(() => { global.Request = Request; }); -describe('React Router v6.4', () => { +describe('reactRouterV6Instrumentation (v6.4)', () => { function createInstrumentation(_opts?: { startTransactionOnPageLoad?: boolean; startTransactionOnLocationChange?: boolean; @@ -41,6 +49,7 @@ describe('React Router v6.4', () => { .fn() .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); + // eslint-disable-next-line deprecation/deprecation reactRouterV6Instrumentation( React.useEffect, useLocation, @@ -75,13 +84,10 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockStartTransaction).toHaveBeenCalledWith({ name: '/', - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: { - 'routing.instrumentation': 'react-router-v6', - }, - metadata: { - source: 'url', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', }, }); }); @@ -112,10 +118,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -151,10 +158,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -190,10 +198,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -241,10 +250,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -311,10 +321,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/app/about/us', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -355,10 +366,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/admin/:orgId/users/:userId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -401,10 +413,11 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/:orgId/users/:userId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -443,10 +456,575 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + }); +}); + +const mockStartBrowserTracingPageLoadSpan = jest.fn(); +const mockStartBrowserTracingNavigationSpan = jest.fn(); + +const mockRootSpan = { + updateName: jest.fn(), + setAttribute: jest.fn(), + getSpanJSON() { + return { op: 'pageload' }; + }, +}; + +jest.mock('@sentry/browser', () => { + const actual = jest.requireActual('@sentry/browser'); + return { + ...actual, + startBrowserTracingNavigationSpan: (...args: unknown[]) => { + mockStartBrowserTracingNavigationSpan(...args); + return actual.startBrowserTracingNavigationSpan(...args); + }, + startBrowserTracingPageLoadSpan: (...args: unknown[]) => { + mockStartBrowserTracingPageLoadSpan(...args); + return actual.startBrowserTracingPageLoadSpan(...args); + }, + }; +}); + +jest.mock('@sentry/core', () => { + const actual = jest.requireActual('@sentry/core'); + return { + ...actual, + getRootSpan: () => { + return mockRootSpan; + }, + }; +}); + +describe('browserTracingReactRouterV6Integration (v6.4)', () => { + function createMockBrowserClient(): BrowserClient { + return new BrowserClient({ + integrations: [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), + stackParser: () => [], + debug: true, + }); + } + + beforeEach(() => { + jest.clearAllMocks(); + getCurrentScope().setClient(undefined); + }); + + describe('wrapCreateBrowserRouter', () => { + it('starts a pageload transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element:

TEST
, + }, + ], + { + initialEntries: ['/'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, + }); + }); + + it('starts a navigation transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + }, + ], + { + initialEntries: ['/'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + children: [ + { + path: 'us', + element:
Us
, + }, + ], + }, + ], + { + initialEntries: ['/'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/us', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with parameterized paths', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + children: [ + { + path: ':page', + element:
Page
, + }, + ], + }, + ], + { + initialEntries: ['/'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/:page', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with paths with multiple parameters', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'stores', + element:
Stores
, + children: [ + { + path: ':storeId', + element:
Store
, + children: [ + { + path: 'products', + element:
Products
, + children: [ + { + path: ':productId', + element:
Product
, + }, + ], + }, + ], + }, + ], + }, + ], + { + initialEntries: ['/'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/stores/:storeId/products/:productId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('updates pageload transaction to a parameterized route', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: 'about', + element:
About
, + children: [ + { + path: ':page', + element:
page
, + }, + ], + }, + ], + { + initialEntries: ['/about/us'], + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/about/:page'); + expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + }); + + it('works with `basename` option', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + children: [ + { + path: 'us', + element:
Us
, + }, + ], + }, + ], + { + initialEntries: ['/app'], + basename: '/app', + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/app/about/us', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with parameterized paths and `basename`', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: ':orgId', + children: [ + { + path: 'users', + children: [ + { + path: ':userId', + element:
User
, + }, + ], + }, + ], + }, + ], + { + initialEntries: ['/admin'], + basename: '/admin', + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/admin/:orgId/users/:userId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('strips `basename` from transaction names of parameterized paths', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + stripBasename: true, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: ':orgId', + children: [ + { + path: 'users', + children: [ + { + path: ':userId', + element:
User
, + }, + ], + }, + ], + }, + ], + { + initialEntries: ['/admin'], + basename: '/admin', + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/:orgId/users/:userId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('strips `basename` from transaction names of non-parameterized paths', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + stripBasename: true, + }), + ); + const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); + + const router = sentryCreateBrowserRouter( + [ + { + path: '/', + element: , + }, + { + path: 'about', + element:
About
, + children: [ + { + path: 'us', + element:
Us
, + }, + ], + }, + ], + { + initialEntries: ['/app'], + basename: '/app', + }, + ); + + // @ts-expect-error router is fine + render(); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/us', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); }); diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index df30c4596dbf..16e30e75e649 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -1,4 +1,11 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + createTransport, + getCurrentScope, + setCurrentClient, +} from '@sentry/core'; import { render } from '@testing-library/react'; import * as React from 'react'; import { @@ -15,10 +22,14 @@ import { useRoutes, } from 'react-router-6'; -import { reactRouterV6Instrumentation } from '../src'; -import { withSentryReactRouterV6Routing, wrapUseRoutes } from '../src/reactrouterv6'; +import { BrowserClient, reactRouterV6Instrumentation } from '../src'; +import { + browserTracingReactRouterV6Integration, + withSentryReactRouterV6Routing, + wrapUseRoutes, +} from '../src/reactrouterv6'; -describe('React Router v6', () => { +describe('reactRouterV6Instrumentation', () => { function createInstrumentation(_opts?: { startTransactionOnPageLoad?: boolean; startTransactionOnLocationChange?: boolean; @@ -36,6 +47,7 @@ describe('React Router v6', () => { .fn() .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); + // eslint-disable-next-line deprecation/deprecation reactRouterV6Instrumentation( React.useEffect, useLocation, @@ -62,10 +74,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, }); }); @@ -100,10 +113,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, }); }); @@ -123,10 +137,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -148,10 +163,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -173,10 +189,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -200,10 +217,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -235,10 +253,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/projects/:projectId/views/:viewId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); }); @@ -265,10 +284,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, }); }); @@ -318,10 +338,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(1); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', - op: 'pageload', - origin: 'auto.pageload.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, }); }); @@ -350,10 +371,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -388,10 +410,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -426,10 +449,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -470,10 +494,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -538,10 +563,11 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenCalledTimes(2); expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/projects/:projectId/views/:viewId', - op: 'navigation', - origin: 'auto.navigation.react.reactrouterv6', - tags: { 'routing.instrumentation': 'react-router-v6' }, - metadata: { source: 'route' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, }); }); @@ -639,3 +665,853 @@ describe('React Router v6', () => { }); }); }); + +const mockStartBrowserTracingPageLoadSpan = jest.fn(); +const mockStartBrowserTracingNavigationSpan = jest.fn(); + +const mockRootSpan = { + updateName: jest.fn(), + setAttribute: jest.fn(), + getSpanJSON() { + return { op: 'pageload' }; + }, +}; + +jest.mock('@sentry/browser', () => { + const actual = jest.requireActual('@sentry/browser'); + return { + ...actual, + startBrowserTracingNavigationSpan: (...args: unknown[]) => { + mockStartBrowserTracingNavigationSpan(...args); + return actual.startBrowserTracingNavigationSpan(...args); + }, + startBrowserTracingPageLoadSpan: (...args: unknown[]) => { + mockStartBrowserTracingPageLoadSpan(...args); + return actual.startBrowserTracingPageLoadSpan(...args); + }, + }; +}); + +jest.mock('@sentry/core', () => { + const actual = jest.requireActual('@sentry/core'); + return { + ...actual, + getRootSpan: () => { + return mockRootSpan; + }, + }; +}); + +describe('browserTracingReactRouterV6Integration', () => { + function createMockBrowserClient(): BrowserClient { + return new BrowserClient({ + integrations: [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), + stackParser: () => [], + debug: true, + }); + } + + beforeEach(() => { + jest.clearAllMocks(); + getCurrentScope().setClient(undefined); + }); + + describe('withSentryReactRouterV6Routing', () => { + it('starts a pageload transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + Home} /> + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, + }); + }); + + it('skips pageload transaction with `instrumentPageLoad: false`', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + instrumentPageLoad: false, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + Home} /> + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(0); + }); + + it('skips navigation transaction, with `instrumentNavigation: false`', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + instrumentNavigation: false, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + About} /> + } /> + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(0); + }); + + it('starts a navigation transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + About} /> + } /> + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + About}> + us} /> + + } /> + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/us', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with paramaterized paths', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + About}> + page} /> + + } /> + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/:page', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with paths with multiple parameters', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + Stores}> + Store}> + Product} /> + + + } /> + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/stores/:storeId/products/:productId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested paths with parameters', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + } /> + Account Page} /> + + Project Index} /> + Project Page}> + Project Page Root} /> + Editor}> + View Canvas} /> + Space Canvas} /> + + + + + No Match Page} /> + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + }); + + describe('wrapUseRoutes', () => { + it('starts a pageload transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element:
Home
, + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', + }, + }); + }); + + it('skips pageload transaction with `instrumentPageLoad: false`', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + instrumentPageLoad: false, + }), + ); + + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element:
Home
, + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(0); + }); + + it('skips navigation transaction, with `instrumentNavigation: false`', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + instrumentNavigation: false, + }), + ); + + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: , + }, + { + path: '/about', + element:
About
, + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(0); + }); + + it('starts a navigation transaction', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: , + }, + { + path: '/about', + element:
About
, + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: , + }, + { + path: '/about', + element:
About
, + children: [ + { + path: '/about/us', + element:
us
, + }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/us', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with paramaterized paths', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: , + }, + { + path: '/about', + element:
About
, + children: [ + { + path: '/about/:page', + element:
page
, + }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/about/:page', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with paths with multiple parameters', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: , + }, + { + path: '/stores', + element:
Stores
, + children: [ + { + path: '/stores/:storeId', + element:
Store
, + children: [ + { + path: '/stores/:storeId/products/:productId', + element:
Product
, + }, + ], + }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/stores/:storeId/products/:productId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested paths with parameters', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + index: true, + element: , + }, + { + path: 'account', + element:
Account Page
, + }, + { + path: 'projects', + children: [ + { + index: true, + element:
Project Index
, + }, + { + path: ':projectId', + element:
Project Page
, + children: [ + { + index: true, + element:
Project Page Root
, + }, + { + element:
Editor
, + children: [ + { + path: 'views/:viewId', + element:
View Canvas
, + }, + { + path: 'spaces/:spaceId', + element:
Space Canvas
, + }, + ], + }, + ], + }, + ], + }, + { + path: '*', + element:
No Match Page
, + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('does not add double slashes to URLS', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: ( +
+ +
+ ), + children: [ + { + path: 'tests', + children: [ + { index: true, element:
Main Test
}, + { path: ':testId/*', element:
Test Component
}, + ], + }, + { path: '/', element: }, + { path: '*', element: }, + ], + }, + { + path: '/', + element:
, + children: [ + { path: '404', element:
Error
}, + { path: '*', element: }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + // should be /tests not //tests + expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/tests'); + expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + }); + + it('handles wildcard routes properly', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + path: '/', + element: ( +
+ +
+ ), + children: [ + { + path: 'tests', + children: [ + { index: true, element:
Main Test
}, + { path: ':testId/*', element:
Test Component
}, + ], + }, + { path: '/', element: }, + { path: '*', element: }, + ], + }, + { + path: '/', + element:
, + children: [ + { path: '404', element:
Error
}, + { path: '*', element: }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingPageLoadSpan).toHaveBeenCalledTimes(1); + expect(mockRootSpan.updateName).toHaveBeenLastCalledWith('/tests/:testId/*'); + expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + }); + }); +}); From a37f6785a28903113d6f43997692f24ca11a03aa Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 5 Feb 2024 13:53:35 +0100 Subject: [PATCH 2/3] use for e2e tests --- .../react-create-hash-router/src/index.tsx | 14 ++++++-------- .../react-router-6-use-routes/src/index.tsx | 14 ++++++-------- .../standard-frontend-react/src/index.tsx | 14 ++++++-------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx index 35db65cf3160..055304c1b109 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx @@ -18,14 +18,12 @@ Sentry.init({ // environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - ), + Sentry.browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, }), replay, ], diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx index 2f8587db9859..f0bcbb3e0800 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx @@ -18,14 +18,12 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - ), + Sentry.browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, }), replay, ], diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx b/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx index 660c3827f583..0bdfdce29938 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx @@ -19,14 +19,12 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - ), + Sentry.browserTracingReactRouterV6Integration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, }), replay, ], From 268177cc79fd96f0ea191e8f3eb1ce818b668aa3 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 5 Feb 2024 14:01:14 +0100 Subject: [PATCH 3/3] rename to reactRouterV6BrowserTracingIntegration --- .../react-create-hash-router/src/index.tsx | 2 +- .../react-router-6-use-routes/src/index.tsx | 2 +- .../standard-frontend-react/src/index.tsx | 2 +- packages/react/src/index.ts | 2 +- packages/react/src/reactrouterv6.tsx | 4 +- packages/react/test/reactrouterv6.4.test.tsx | 24 +++++------ packages/react/test/reactrouterv6.test.tsx | 40 +++++++++---------- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx index 055304c1b109..73c5e024539f 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/src/index.tsx @@ -18,7 +18,7 @@ Sentry.init({ // environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - Sentry.browserTracingReactRouterV6Integration({ + Sentry.reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx index f0bcbb3e0800..b8a036fc5340 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx @@ -18,7 +18,7 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - Sentry.browserTracingReactRouterV6Integration({ + Sentry.reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx b/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx index 0bdfdce29938..8cf0e8462e16 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx @@ -19,7 +19,7 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - Sentry.browserTracingReactRouterV6Integration({ + Sentry.reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 170496cb7e7d..230fc7769829 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -10,7 +10,7 @@ export { reactRouterV4Instrumentation, reactRouterV5Instrumentation, withSentryR export { // eslint-disable-next-line deprecation/deprecation reactRouterV6Instrumentation, - browserTracingReactRouterV6Integration, + reactRouterV6BrowserTracingIntegration, withSentryReactRouterV6Routing, wrapUseRoutes, wrapCreateBrowserRouter, diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 2cc258a78640..73196bcfcc2a 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -70,7 +70,7 @@ interface ReactRouterOptions { * A browser tracing integration that uses React Router v3 to instrument navigations. * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options. */ -export function browserTracingReactRouterV6Integration( +export function reactRouterV6BrowserTracingIntegration( options: Parameters[0] & ReactRouterOptions, ): Integration { const integration = browserTracingIntegration({ @@ -126,7 +126,7 @@ export function browserTracingReactRouterV6Integration( } /** - * @deprecated Use `browserTracingReactRouterV6Integration()` instead. + * @deprecated Use `reactRouterV6BrowserTracingIntegration()` instead. */ export function reactRouterV6Instrumentation( useEffect: UseEffect, diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index 3255c21036b7..f534d02f97e2 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -21,7 +21,7 @@ import { } from 'react-router-6.4'; import { BrowserClient, reactRouterV6Instrumentation, wrapCreateBrowserRouter } from '../src'; -import { browserTracingReactRouterV6Integration } from '../src/reactrouterv6'; +import { reactRouterV6BrowserTracingIntegration } from '../src/reactrouterv6'; import type { CreateRouterFunction } from '../src/types'; beforeAll(() => { @@ -502,7 +502,7 @@ jest.mock('@sentry/core', () => { }; }); -describe('browserTracingReactRouterV6Integration (v6.4)', () => { +describe('reactRouterV6BrowserTracingIntegration (v6.4)', () => { function createMockBrowserClient(): BrowserClient { return new BrowserClient({ integrations: [], @@ -523,7 +523,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -564,7 +564,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -609,7 +609,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -660,7 +660,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -711,7 +711,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -774,7 +774,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -815,7 +815,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -867,7 +867,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -923,7 +923,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -980,7 +980,7 @@ describe('browserTracingReactRouterV6Integration (v6.4)', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index 16e30e75e649..f2ec3fb3a4b9 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -24,7 +24,7 @@ import { import { BrowserClient, reactRouterV6Instrumentation } from '../src'; import { - browserTracingReactRouterV6Integration, + reactRouterV6BrowserTracingIntegration, withSentryReactRouterV6Routing, wrapUseRoutes, } from '../src/reactrouterv6'; @@ -702,7 +702,7 @@ jest.mock('@sentry/core', () => { }; }); -describe('browserTracingReactRouterV6Integration', () => { +describe('reactRouterV6BrowserTracingIntegration', () => { function createMockBrowserClient(): BrowserClient { return new BrowserClient({ integrations: [], @@ -723,7 +723,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -757,7 +757,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -784,7 +784,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -813,7 +813,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -848,7 +848,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -885,7 +885,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -922,7 +922,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -961,7 +961,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1010,7 +1010,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1051,7 +1051,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1085,7 +1085,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1124,7 +1124,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1168,7 +1168,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1218,7 +1218,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1268,7 +1268,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1324,7 +1324,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1404,7 +1404,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType, @@ -1462,7 +1462,7 @@ describe('browserTracingReactRouterV6Integration', () => { setCurrentClient(client); client.addIntegration( - browserTracingReactRouterV6Integration({ + reactRouterV6BrowserTracingIntegration({ useEffect: React.useEffect, useLocation, useNavigationType,