Skip to content

Commit f9afda8

Browse files
committed
fix(integrations): Fix support for wildcard paths in react-router-dom v6 integration (getsentry#5997)
1 parent eb17d62 commit f9afda8

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

packages/react/src/reactrouterv6.tsx

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
spanToJSON,
1919
} from '@sentry/core';
2020
import type { Client, Integration, Span, TransactionSource } from '@sentry/types';
21-
import { getNumberOfUrlSegments, logger } from '@sentry/utils';
21+
import { logger } from '@sentry/utils';
2222
import hoistNonReactStatics from 'hoist-non-react-statics';
2323
import * as React from 'react';
2424

@@ -141,6 +141,14 @@ function stripBasenameFromPathname(pathname: string, basename: string): string {
141141
return pathname.slice(startIndex) || '/';
142142
}
143143

144+
function sendIndexPath(pathBuilder: string, pathname: string, basename: string): void | string[] {
145+
const p1 = pathBuilder || stripBasenameFromPathname(pathname, basename);
146+
const p2 = pathBuilder || pathname;
147+
const p3 = _stripBasename ? p1 : p2;
148+
const formattedPath = p3[p3.length - 1] === '/' ? p3.slice(0, -1) : p3[p3.length - 1] === '/*' ? p3.slice(0, -1) : p3;
149+
if (formattedPath) return [formattedPath, 'route'];
150+
}
151+
144152
function getNormalizedName(
145153
routes: RouteObject[],
146154
location: Location,
@@ -157,27 +165,22 @@ function getNormalizedName(
157165
const route = branch.route;
158166
if (route) {
159167
// Early return if index route
160-
if (route.index) {
161-
return [_stripBasename ? stripBasenameFromPathname(branch.pathname, basename) : branch.pathname, 'route'];
162-
}
168+
if (route.index) sendIndexPath(pathBuilder, branch.pathname, basename);
163169

164170
const path = route.path;
165171
if (path) {
166-
const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
167-
pathBuilder += newPath;
168-
169-
if (basename + branch.pathname === location.pathname) {
170-
if (
171-
// If the route defined on the element is something like
172-
// <Route path="/stores/:storeId/products/:productId" element={<div>Product</div>} />
173-
// We should check against the branch.pathname for the number of / seperators
174-
getNumberOfUrlSegments(pathBuilder) !== getNumberOfUrlSegments(branch.pathname) &&
175-
// We should not count wildcard operators in the url segments calculation
176-
pathBuilder.slice(-2) !== '/*'
177-
) {
178-
return [(_stripBasename ? '' : basename) + newPath, 'route'];
172+
// If path is not a wildcard and has no child routes, append the path
173+
if (!(path === '*' && branch.route.children && branch.route.children.length > 0)) {
174+
const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
175+
pathBuilder += newPath;
176+
if (basename + branch.pathname === location.pathname) {
177+
// if the last character of the pathbuilder is a wilcard and there are children, remove the wildcard
178+
if (pathBuilder[pathBuilder.length - 1] === '*' && branch.route.children && branch.route.children.length > 0) {
179+
pathBuilder = pathBuilder.slice(0, -1);
180+
} else {
181+
return [(_stripBasename ? '' : basename) + pathBuilder, 'route'];
182+
}
179183
}
180-
return [(_stripBasename ? '' : basename) + pathBuilder, 'route'];
181184
}
182185
}
183186
}

packages/react/test/reactrouterv6.test.tsx

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
250250
<MemoryRouter initialEntries={['/']}>
251251
<SentryRoutes>
252252
<Route path="/about" element={<div>About</div>}>
253-
<Route path="/about/us" element={<div>us</div>} />
253+
<Route path="us" element={<div>us</div>} />
254254
</Route>
255255
<Route path="/" element={<Navigate to="/about/us" />} />
256256
</SentryRoutes>
@@ -287,7 +287,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
287287
<MemoryRouter initialEntries={['/']}>
288288
<SentryRoutes>
289289
<Route path="/about" element={<div>About</div>}>
290-
<Route path="/about/:page" element={<div>page</div>} />
290+
<Route path=":page" element={<div>page</div>} />
291291
</Route>
292292
<Route path="/" element={<Navigate to="/about/us" />} />
293293
</SentryRoutes>
@@ -324,8 +324,8 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
324324
<MemoryRouter initialEntries={['/']}>
325325
<SentryRoutes>
326326
<Route path="/stores" element={<div>Stores</div>}>
327-
<Route path="/stores/:storeId" element={<div>Store</div>}>
328-
<Route path="/stores/:storeId/products/:productId" element={<div>Product</div>} />
327+
<Route path=":storeId" element={<div>Store</div>}>
328+
<Route path="products/:productId" element={<div>Product</div>} />
329329
</Route>
330330
</Route>
331331
<Route path="/" element={<Navigate to="/stores/foo/products/234" />} />
@@ -391,6 +391,55 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
391391
});
392392
});
393393

394+
it('works with wildcard routes', () => {
395+
const client = createMockBrowserClient();
396+
setCurrentClient(client);
397+
398+
client.addIntegration(
399+
reactRouterV6BrowserTracingIntegration({
400+
useEffect: React.useEffect,
401+
useLocation,
402+
useNavigationType,
403+
createRoutesFromChildren,
404+
matchRoutes,
405+
}),
406+
);
407+
const SentryRoutes = withSentryReactRouterV6Routing(Routes);
408+
409+
render(
410+
<MemoryRouter initialEntries={['/']}>
411+
<SentryRoutes>
412+
<Route path="*" element={<Outlet />}>
413+
<Route index element={<Navigate to="/projects/123/views/234" />} />
414+
<Route path="account" element={<div>Account Page</div>} />
415+
<Route path="projects">
416+
<Route path="*" element={<Outlet />}>
417+
<Route path=":projectId" element={<div>Project Page</div>}>
418+
<Route index element={<div>Project Page Root</div>} />
419+
<Route element={<div>Editor</div>}>
420+
<Route path="views/:viewId" element={<div>View Canvas</div>} />
421+
<Route path="spaces/:spaceId" element={<div>Space Canvas</div>} />
422+
</Route>
423+
</Route>
424+
</Route>
425+
</Route>
426+
<Route path="*" element={<div>No Match Page</div>} />
427+
</Route>
428+
</SentryRoutes>
429+
</MemoryRouter>,
430+
);
431+
432+
expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1);
433+
expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), {
434+
name: '/projects/:projectId/views/:viewId',
435+
attributes: {
436+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
437+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
438+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6',
439+
},
440+
});
441+
});
442+
394443
it("updates the scope's `transactionName` on a navigation", () => {
395444
const client = createMockBrowserClient();
396445
setCurrentClient(client);
@@ -410,7 +459,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
410459
<MemoryRouter initialEntries={['/']}>
411460
<SentryRoutes>
412461
<Route path="/about" element={<div>About</div>}>
413-
<Route path="/about/:page" element={<div>page</div>} />
462+
<Route path=":page" element={<div>page</div>} />
414463
</Route>
415464
<Route path="/" element={<Navigate to="/about/us" />} />
416465
</SentryRoutes>
@@ -639,7 +688,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
639688
element: <div>About</div>,
640689
children: [
641690
{
642-
path: '/about/us',
691+
path: 'us',
643692
element: <div>us</div>,
644693
},
645694
],
@@ -689,7 +738,7 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
689738
element: <div>About</div>,
690739
children: [
691740
{
692-
path: '/about/:page',
741+
path: ':page',
693742
element: <div>page</div>,
694743
},
695744
],
@@ -739,11 +788,11 @@ describe('reactRouterV6BrowserTracingIntegration', () => {
739788
element: <div>Stores</div>,
740789
children: [
741790
{
742-
path: '/stores/:storeId',
791+
path: ':storeId',
743792
element: <div>Store</div>,
744793
children: [
745794
{
746-
path: '/stores/:storeId/products/:productId',
795+
path: 'products/:productId',
747796
element: <div>Product</div>,
748797
},
749798
],

0 commit comments

Comments
 (0)