diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 400028a28789..f16f1dba0ff9 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -18,7 +18,7 @@ import { spanToJSON, } from '@sentry/core'; import type { Client, Integration, Span, TransactionSource } from '@sentry/types'; -import { getNumberOfUrlSegments, logger } from '@sentry/utils'; +import { logger } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; @@ -141,6 +141,14 @@ function stripBasenameFromPathname(pathname: string, basename: string): string { return pathname.slice(startIndex) || '/'; } +function sendIndexPath(pathBuilder: string, pathname: string, basename: string): void | string[] { + const p1 = pathBuilder || stripBasenameFromPathname(pathname, basename); + const p2 = pathBuilder || pathname; + const p3 = _stripBasename ? p1 : p2; + const formattedPath = p3[p3.length - 1] === '/' ? p3.slice(0, -1) : p3[p3.length - 1] === '/*' ? p3.slice(0, -1) : p3; + if (formattedPath) return [formattedPath, 'route']; +} + function getNormalizedName( routes: RouteObject[], location: Location, @@ -157,27 +165,22 @@ function getNormalizedName( const route = branch.route; if (route) { // Early return if index route - if (route.index) { - return [_stripBasename ? stripBasenameFromPathname(branch.pathname, basename) : branch.pathname, 'route']; - } + if (route.index) sendIndexPath(pathBuilder, branch.pathname, basename); const path = route.path; if (path) { - const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`; - pathBuilder += newPath; - - if (basename + branch.pathname === location.pathname) { - if ( - // If the route defined on the element is something like - // Product} /> - // We should check against the branch.pathname for the number of / seperators - getNumberOfUrlSegments(pathBuilder) !== getNumberOfUrlSegments(branch.pathname) && - // We should not count wildcard operators in the url segments calculation - pathBuilder.slice(-2) !== '/*' - ) { - return [(_stripBasename ? '' : basename) + newPath, 'route']; + // If path is not a wildcard and has no child routes, append the path + if (!(path === '*' && branch.route.children && branch.route.children.length > 0)) { + const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`; + pathBuilder += newPath; + if (basename + branch.pathname === location.pathname) { + // if the last character of the pathbuilder is a wilcard and there are children, remove the wildcard + if (pathBuilder[pathBuilder.length - 1] === '*' && branch.route.children && branch.route.children.length > 0) { + pathBuilder = pathBuilder.slice(0, -1); + } else { + return [(_stripBasename ? '' : basename) + pathBuilder, 'route']; + } } - return [(_stripBasename ? '' : basename) + pathBuilder, 'route']; } } } diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index d584bcfa22c1..c1cbd0ca04d3 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -250,7 +250,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => { About}> - us} /> + us} /> } /> @@ -287,7 +287,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => { About}> - page} /> + page} /> } /> @@ -324,8 +324,8 @@ describe('reactRouterV6BrowserTracingIntegration', () => { Stores}> - Store}> - Product} /> + Store}> + Product} /> } /> @@ -391,6 +391,55 @@ describe('reactRouterV6BrowserTracingIntegration', () => { }); }); + it('works with wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + }> + } /> + Account Page} /> + + }> + 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', + }, + }); + }); + it("updates the scope's `transactionName` on a navigation", () => { const client = createMockBrowserClient(); setCurrentClient(client); @@ -410,7 +459,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => { About}> - page} /> + page} /> } /> @@ -639,7 +688,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => { element:
About
, children: [ { - path: '/about/us', + path: 'us', element:
us
, }, ], @@ -689,7 +738,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => { element:
About
, children: [ { - path: '/about/:page', + path: ':page', element:
page
, }, ], @@ -739,11 +788,11 @@ describe('reactRouterV6BrowserTracingIntegration', () => { element:
Stores
, children: [ { - path: '/stores/:storeId', + path: ':storeId', element:
Store
, children: [ { - path: '/stores/:storeId/products/:productId', + path: 'products/:productId', element:
Product
, }, ],