diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts index 57e3e764d6a8..24e27e89539e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/navigation.client.test.ts @@ -22,7 +22,7 @@ test.describe('client - navigation performance', () => { data: { 'sentry.origin': 'auto.navigation.react-router', 'sentry.op': 'navigation', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'navigation', origin: 'auto.navigation.react-router', @@ -33,7 +33,7 @@ test.describe('client - navigation performance', () => { timestamp: expect.any(Number), transaction: '/performance/ssr', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, platform: 'javascript', request: { url: expect.stringContaining('/performance/ssr'), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts index b18ae44e0e71..465d000dcd31 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/pageload.client.test.ts @@ -5,7 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('client - pageload performance', () => { test('should send pageload transaction', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/'; + return transactionEvent.transaction === '/performance'; }); await page.goto(`/performance`); @@ -18,20 +18,20 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: '/performance/', + transaction: '/performance', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, measurements: expect.any(Object), platform: 'javascript', request: { @@ -68,12 +68,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), @@ -105,7 +105,7 @@ test.describe('client - pageload performance', () => { test('should send pageload transaction for prerendered pages', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/static/'; + return transactionEvent.transaction === '/performance/static'; }); await page.goto(`/performance/static`); @@ -113,18 +113,18 @@ test.describe('client - pageload performance', () => { const transaction = await txPromise; expect(transaction).toMatchObject({ - transaction: '/performance/static/', + transaction: '/performance/static', contexts: { trace: { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, }); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/navigation.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/navigation.client.test.ts index 57e3e764d6a8..24e27e89539e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/navigation.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/navigation.client.test.ts @@ -22,7 +22,7 @@ test.describe('client - navigation performance', () => { data: { 'sentry.origin': 'auto.navigation.react-router', 'sentry.op': 'navigation', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'navigation', origin: 'auto.navigation.react-router', @@ -33,7 +33,7 @@ test.describe('client - navigation performance', () => { timestamp: expect.any(Number), transaction: '/performance/ssr', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, platform: 'javascript', request: { url: expect.stringContaining('/performance/ssr'), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/pageload.client.test.ts index b18ae44e0e71..465d000dcd31 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/pageload.client.test.ts @@ -5,7 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('client - pageload performance', () => { test('should send pageload transaction', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/'; + return transactionEvent.transaction === '/performance'; }); await page.goto(`/performance`); @@ -18,20 +18,20 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: '/performance/', + transaction: '/performance', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, measurements: expect.any(Object), platform: 'javascript', request: { @@ -68,12 +68,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), @@ -105,7 +105,7 @@ test.describe('client - pageload performance', () => { test('should send pageload transaction for prerendered pages', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/static/'; + return transactionEvent.transaction === '/performance/static'; }); await page.goto(`/performance/static`); @@ -113,18 +113,18 @@ test.describe('client - pageload performance', () => { const transaction = await txPromise; expect(transaction).toMatchObject({ - transaction: '/performance/static/', + transaction: '/performance/static', contexts: { trace: { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, }); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa-node-20-18/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa-node-20-18/tests/performance/pageload.client.test.ts index a02942693a79..1118bde2669c 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa-node-20-18/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa-node-20-18/tests/performance/pageload.client.test.ts @@ -18,12 +18,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), @@ -31,7 +31,7 @@ test.describe('client - pageload performance', () => { timestamp: expect.any(Number), transaction: '/performance', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, measurements: expect.any(Object), platform: 'javascript', request: { @@ -68,12 +68,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/tests/performance/pageload.client.test.ts index a02942693a79..1118bde2669c 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/tests/performance/pageload.client.test.ts @@ -18,12 +18,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), @@ -31,7 +31,7 @@ test.describe('client - pageload performance', () => { timestamp: expect.any(Number), transaction: '/performance', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, measurements: expect.any(Object), platform: 'javascript', request: { @@ -68,12 +68,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/navigation.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/navigation.client.test.ts index 57e3e764d6a8..24e27e89539e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/navigation.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/navigation.client.test.ts @@ -22,7 +22,7 @@ test.describe('client - navigation performance', () => { data: { 'sentry.origin': 'auto.navigation.react-router', 'sentry.op': 'navigation', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'navigation', origin: 'auto.navigation.react-router', @@ -33,7 +33,7 @@ test.describe('client - navigation performance', () => { timestamp: expect.any(Number), transaction: '/performance/ssr', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, platform: 'javascript', request: { url: expect.stringContaining('/performance/ssr'), diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts index b18ae44e0e71..465d000dcd31 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts @@ -5,7 +5,7 @@ import { APP_NAME } from '../constants'; test.describe('client - pageload performance', () => { test('should send pageload transaction', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/'; + return transactionEvent.transaction === '/performance'; }); await page.goto(`/performance`); @@ -18,20 +18,20 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: '/performance/', + transaction: '/performance', type: 'transaction', - transaction_info: { source: 'url' }, + transaction_info: { source: 'route' }, measurements: expect.any(Object), platform: 'javascript', request: { @@ -68,12 +68,12 @@ test.describe('client - pageload performance', () => { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, spans: expect.any(Array), @@ -105,7 +105,7 @@ test.describe('client - pageload performance', () => { test('should send pageload transaction for prerendered pages', async ({ page }) => { const txPromise = waitForTransaction(APP_NAME, async transactionEvent => { - return transactionEvent.transaction === '/performance/static/'; + return transactionEvent.transaction === '/performance/static'; }); await page.goto(`/performance/static`); @@ -113,18 +113,18 @@ test.describe('client - pageload performance', () => { const transaction = await txPromise; expect(transaction).toMatchObject({ - transaction: '/performance/static/', + transaction: '/performance/static', contexts: { trace: { span_id: expect.any(String), trace_id: expect.any(String), data: { - 'sentry.origin': 'auto.pageload.browser', + 'sentry.origin': 'auto.pageload.react-router', 'sentry.op': 'pageload', - 'sentry.source': 'url', + 'sentry.source': 'route', }, op: 'pageload', - origin: 'auto.pageload.browser', + origin: 'auto.pageload.react-router', }, }, }); diff --git a/packages/react-router/src/client/hydratedRouter.ts b/packages/react-router/src/client/hydratedRouter.ts index e5ec2d65d5ef..7cb53d87487b 100644 --- a/packages/react-router/src/client/hydratedRouter.ts +++ b/packages/react-router/src/client/hydratedRouter.ts @@ -36,43 +36,56 @@ export function instrumentHydratedRouter(): void { // The first time we hit the router, we try to update the pageload transaction // todo: update pageload tx here const pageloadSpan = getActiveRootSpan(); - const pageloadName = pageloadSpan ? spanToJSON(pageloadSpan).description : undefined; - const parameterizePageloadRoute = getParameterizedRoute(router.state); - if ( - pageloadName && - normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName) && // this event is for the currently active pageload - normalizePathname(parameterizePageloadRoute) !== normalizePathname(pageloadName) // route is not parameterized yet - ) { - pageloadSpan?.updateName(parameterizePageloadRoute); - pageloadSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - } - // Patching navigate for creating accurate navigation transactions - if (typeof router.navigate === 'function') { - const originalNav = router.navigate.bind(router); - router.navigate = function sentryPatchedNavigate(...args) { - maybeCreateNavigationTransaction( - String(args[0]) || '', // will be updated anyway - 'url', // this also will be updated once we have the parameterized route - ); - return originalNav(...args); - }; + if (pageloadSpan) { + const pageloadName = spanToJSON(pageloadSpan).description; + const parameterizePageloadRoute = getParameterizedRoute(router.state); + if ( + pageloadName && + // this event is for the currently active pageload + normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName) + ) { + pageloadSpan.updateName(parameterizePageloadRoute); + pageloadSpan.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react-router', + }); + } + + // Patching navigate for creating accurate navigation transactions + if (typeof router.navigate === 'function') { + const originalNav = router.navigate.bind(router); + router.navigate = function sentryPatchedNavigate(...args) { + maybeCreateNavigationTransaction( + String(args[0]) || '', // will be updated anyway + 'url', // this also will be updated once we have the parameterized route + ); + return originalNav(...args); + }; + } } // Subscribe to router state changes to update navigation transactions with parameterized routes router.subscribe(newState => { const navigationSpan = getActiveRootSpan(); - const navigationSpanName = navigationSpan ? spanToJSON(navigationSpan).description : undefined; + + if (!navigationSpan) { + return; + } + + const navigationSpanName = spanToJSON(navigationSpan).description; const parameterizedNavRoute = getParameterizedRoute(newState); if ( - navigationSpanName && // we have an active pageload tx + navigationSpanName && newState.navigation.state === 'idle' && // navigation has completed - normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) && // this event is for the currently active navigation - normalizePathname(parameterizedNavRoute) !== normalizePathname(navigationSpanName) // route is not parameterized yet + normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName) // this event is for the currently active navigation ) { - navigationSpan?.updateName(parameterizedNavRoute); - navigationSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + navigationSpan.updateName(parameterizedNavRoute); + navigationSpan.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react-router', + }); } }); return true; diff --git a/packages/react-router/test/client/hydratedRouter.test.ts b/packages/react-router/test/client/hydratedRouter.test.ts index 98ed1a241d93..3e798e829566 100644 --- a/packages/react-router/test/client/hydratedRouter.test.ts +++ b/packages/react-router/test/client/hydratedRouter.test.ts @@ -39,8 +39,8 @@ describe('instrumentHydratedRouter', () => { }; (globalThis as any).__reactRouterDataRouter = mockRouter; - mockPageloadSpan = { updateName: vi.fn(), setAttribute: vi.fn() }; - mockNavigationSpan = { updateName: vi.fn(), setAttribute: vi.fn() }; + mockPageloadSpan = { updateName: vi.fn(), setAttributes: vi.fn() }; + mockNavigationSpan = { updateName: vi.fn(), setAttributes: vi.fn() }; (core.getActiveSpan as any).mockReturnValue(mockPageloadSpan); (core.getRootSpan as any).mockImplementation((span: any) => span); @@ -66,7 +66,7 @@ describe('instrumentHydratedRouter', () => { it('updates pageload transaction name if needed', () => { instrumentHydratedRouter(); expect(mockPageloadSpan.updateName).toHaveBeenCalled(); - expect(mockPageloadSpan.setAttribute).toHaveBeenCalled(); + expect(mockPageloadSpan.setAttributes).toHaveBeenCalled(); }); it('creates navigation transaction on navigate', () => { @@ -89,7 +89,7 @@ describe('instrumentHydratedRouter', () => { (core.getActiveSpan as any).mockReturnValue(mockNavigationSpan); callback(newState); expect(mockNavigationSpan.updateName).toHaveBeenCalled(); - expect(mockNavigationSpan.setAttribute).toHaveBeenCalled(); + expect(mockNavigationSpan.setAttributes).toHaveBeenCalled(); }); it('does not update navigation transaction on state change to loading', () => { @@ -106,6 +106,6 @@ describe('instrumentHydratedRouter', () => { (core.getActiveSpan as any).mockReturnValue(mockNavigationSpan); callback(newState); expect(mockNavigationSpan.updateName).not.toHaveBeenCalled(); - expect(mockNavigationSpan.setAttribute).not.toHaveBeenCalled(); + expect(mockNavigationSpan.setAttributes).not.toHaveBeenCalled(); }); });