Skip to content

Commit 963c3de

Browse files
authored
feat(react): Update scope's transactionName in React Router instrumentations (#11048)
Update the current scope's `transactionName` value in our React Router instrumentations (i.e. `reactRoutervXbrowserTracingIntegration`s).
1 parent 34d7e41 commit 963c3de

7 files changed

+348
-7
lines changed

packages/react/src/reactrouter.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
1010
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
1111
getActiveSpan,
12+
getCurrentScope,
1213
getRootSpan,
1314
spanToJSON,
1415
} from '@sentry/core';
@@ -226,9 +227,13 @@ export function withSentryRouting<P extends Record<string, any>, R extends React
226227
const activeRootSpan = getActiveRootSpan();
227228

228229
const WrappedRoute: React.FC<P> = (props: P) => {
229-
if (activeRootSpan && props && props.computedMatch && props.computedMatch.isExact) {
230-
activeRootSpan.updateName(props.computedMatch.path);
231-
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
230+
if (props && props.computedMatch && props.computedMatch.isExact) {
231+
getCurrentScope().setTransactionName(props.computedMatch.path);
232+
233+
if (activeRootSpan) {
234+
activeRootSpan.updateName(props.computedMatch.path);
235+
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
236+
}
232237
}
233238

234239
// @ts-expect-error Setting more specific React Component typing for `R` generic above

packages/react/src/reactrouterv6.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
1414
getActiveSpan,
1515
getClient,
16+
getCurrentScope,
1617
getRootSpan,
1718
spanToJSON,
1819
} from '@sentry/core';
@@ -198,10 +199,15 @@ function updatePageloadTransaction(
198199
? matches
199200
: (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]);
200201

201-
if (activeRootSpan && branches) {
202+
if (branches) {
202203
const [name, source] = getNormalizedName(routes, location, branches, basename);
203-
activeRootSpan.updateName(name);
204-
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
204+
205+
getCurrentScope().setTransactionName(name);
206+
207+
if (activeRootSpan) {
208+
activeRootSpan.updateName(name);
209+
activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
210+
}
205211
}
206212
}
207213

packages/react/test/reactrouterv3.test.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const mockStartBrowserTracingPageLoadSpan = jest.fn();
2929
const mockStartBrowserTracingNavigationSpan = jest.fn();
3030

3131
const mockRootSpan = {
32-
updateName: jest.fn(),
3332
setAttribute: jest.fn(),
3433
getSpanJSON() {
3534
return { op: 'pageload' };
@@ -115,6 +114,18 @@ describe('browserTracingReactRouterV3', () => {
115114
});
116115
});
117116

117+
it("updates the scope's `transactionName` on pageload", () => {
118+
const client = createMockBrowserClient();
119+
setCurrentClient(client);
120+
121+
client.addIntegration(reactRouterV3BrowserTracingIntegration({ history, routes: instrumentationRoutes, match }));
122+
123+
client.init();
124+
render(<Router history={history}>{routes}</Router>);
125+
126+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
127+
});
128+
118129
it('starts a navigation transaction', () => {
119130
const client = createMockBrowserClient();
120131
setCurrentClient(client);
@@ -192,4 +203,23 @@ describe('browserTracingReactRouterV3', () => {
192203
},
193204
});
194205
});
206+
207+
it("updates the scope's `transactionName` on a navigation", () => {
208+
const client = createMockBrowserClient();
209+
210+
const history = createMemoryHistory();
211+
client.addIntegration(reactRouterV3BrowserTracingIntegration({ history, routes: instrumentationRoutes, match }));
212+
213+
client.init();
214+
const { container } = render(<Router history={history}>{routes}</Router>);
215+
216+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
217+
218+
act(() => {
219+
history.push('/users/123');
220+
});
221+
expect(container.innerHTML).toContain('123');
222+
223+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/users/:userid');
224+
});
195225
});

packages/react/test/reactrouterv4.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ describe('browserTracingReactRouterV4', () => {
8686
});
8787
});
8888

89+
it("updates the scope's `transactionName` on pageload", () => {
90+
const client = createMockBrowserClient();
91+
setCurrentClient(client);
92+
93+
const history = createMemoryHistory();
94+
client.addIntegration(reactRouterV4BrowserTracingIntegration({ history }));
95+
96+
client.init();
97+
98+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
99+
});
100+
89101
it('starts a navigation transaction', () => {
90102
const client = createMockBrowserClient();
91103
setCurrentClient(client);
@@ -341,4 +353,45 @@ describe('browserTracingReactRouterV4', () => {
341353
},
342354
});
343355
});
356+
357+
it("updates the scope's `transactionName` on a route change", () => {
358+
const routes: RouteConfig[] = [
359+
{
360+
path: '/organizations/:orgid/v1/:teamid',
361+
},
362+
{ path: '/organizations/:orgid' },
363+
{ path: '/' },
364+
];
365+
const client = createMockBrowserClient();
366+
setCurrentClient(client);
367+
368+
const history = createMemoryHistory();
369+
client.addIntegration(reactRouterV4BrowserTracingIntegration({ history, routes, matchPath }));
370+
371+
client.init();
372+
373+
const SentryRoute = withSentryRouting(Route);
374+
375+
render(
376+
<Router history={history as any}>
377+
<Switch>
378+
<SentryRoute path="/organizations/:orgid/v1/:teamid" component={() => <div>Team</div>} />
379+
<SentryRoute path="/organizations/:orgid" component={() => <div>OrgId</div>} />
380+
<SentryRoute path="/" component={() => <div>Home</div>} />
381+
</Switch>
382+
</Router>,
383+
);
384+
385+
act(() => {
386+
history.push('/organizations/1234/v1/758');
387+
});
388+
389+
expect(getCurrentScope().getScopeData().transactionName).toEqual('/organizations/:orgid/v1/:teamid');
390+
391+
act(() => {
392+
history.push('/organizations/1234');
393+
});
394+
395+
expect(getCurrentScope().getScopeData().transactionName).toEqual('/organizations/:orgid');
396+
});
344397
});

packages/react/test/reactrouterv5.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ describe('browserTracingReactRouterV5', () => {
8686
});
8787
});
8888

89+
it("updates the scope's `transactionName` on pageload", () => {
90+
const client = createMockBrowserClient();
91+
setCurrentClient(client);
92+
93+
const history = createMemoryHistory();
94+
client.addIntegration(reactRouterV5BrowserTracingIntegration({ history }));
95+
96+
client.init();
97+
98+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
99+
});
100+
89101
it('starts a navigation transaction', () => {
90102
const client = createMockBrowserClient();
91103
setCurrentClient(client);
@@ -341,4 +353,45 @@ describe('browserTracingReactRouterV5', () => {
341353
},
342354
});
343355
});
356+
357+
it("updates the scope's `transactionName` on a route change", () => {
358+
const routes: RouteConfig[] = [
359+
{
360+
path: '/organizations/:orgid/v1/:teamid',
361+
},
362+
{ path: '/organizations/:orgid' },
363+
{ path: '/' },
364+
];
365+
const client = createMockBrowserClient();
366+
setCurrentClient(client);
367+
368+
const history = createMemoryHistory();
369+
client.addIntegration(reactRouterV5BrowserTracingIntegration({ history, routes, matchPath }));
370+
371+
client.init();
372+
373+
const SentryRoute = withSentryRouting(Route);
374+
375+
render(
376+
<Router history={history as any}>
377+
<Switch>
378+
<SentryRoute path="/organizations/:orgid/v1/:teamid" component={() => <div>Team</div>} />
379+
<SentryRoute path="/organizations/:orgid" component={() => <div>OrgId</div>} />
380+
<SentryRoute path="/" component={() => <div>Home</div>} />
381+
</Switch>
382+
</Router>,
383+
);
384+
385+
act(() => {
386+
history.push('/organizations/1234/v1/758');
387+
});
388+
389+
expect(getCurrentScope().getScopeData().transactionName).toBe('/organizations/:orgid/v1/:teamid');
390+
391+
act(() => {
392+
history.push('/organizations/1234');
393+
});
394+
395+
expect(getCurrentScope().getScopeData().transactionName).toBe('/organizations/:orgid');
396+
});
344397
});

packages/react/test/reactrouterv6.4.test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,39 @@ describe('reactRouterV6BrowserTracingIntegration (v6.4)', () => {
122122
});
123123
});
124124

125+
it("updates the scope's `transactionName` on a pageload", () => {
126+
const client = createMockBrowserClient();
127+
setCurrentClient(client);
128+
129+
client.addIntegration(
130+
reactRouterV6BrowserTracingIntegration({
131+
useEffect: React.useEffect,
132+
useLocation,
133+
useNavigationType,
134+
createRoutesFromChildren,
135+
matchRoutes,
136+
}),
137+
);
138+
const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
139+
140+
const router = sentryCreateBrowserRouter(
141+
[
142+
{
143+
path: '/',
144+
element: <div>TEST</div>,
145+
},
146+
],
147+
{
148+
initialEntries: ['/'],
149+
},
150+
);
151+
152+
// @ts-expect-error router is fine
153+
render(<RouterProvider router={router} />);
154+
155+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/');
156+
});
157+
125158
it('starts a navigation transaction', () => {
126159
const client = createMockBrowserClient();
127160
setCurrentClient(client);
@@ -590,5 +623,42 @@ describe('reactRouterV6BrowserTracingIntegration (v6.4)', () => {
590623
},
591624
});
592625
});
626+
627+
it("updates the scope's `transactionName` on a navigation", () => {
628+
const client = createMockBrowserClient();
629+
setCurrentClient(client);
630+
631+
client.addIntegration(
632+
reactRouterV6BrowserTracingIntegration({
633+
useEffect: React.useEffect,
634+
useLocation,
635+
useNavigationType,
636+
createRoutesFromChildren,
637+
matchRoutes,
638+
}),
639+
);
640+
const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
641+
642+
const router = sentryCreateBrowserRouter(
643+
[
644+
{
645+
path: '/',
646+
element: <Navigate to="/about" />,
647+
},
648+
{
649+
path: 'about',
650+
element: <div>About</div>,
651+
},
652+
],
653+
{
654+
initialEntries: ['/'],
655+
},
656+
);
657+
658+
// @ts-expect-error router is fine
659+
render(<RouterProvider router={router} />);
660+
661+
expect(getCurrentScope().getScopeData()?.transactionName).toEqual('/about');
662+
});
593663
});
594664
});

0 commit comments

Comments
 (0)