Skip to content

Commit 3e7a3ec

Browse files
authored
ref(react): Add transaction source for react router v4/v5 (#5384)
1 parent f1b29ef commit 3e7a3ec

File tree

3 files changed

+66
-22
lines changed

3 files changed

+66
-22
lines changed

packages/react/src/reactrouter.tsx

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Transaction } from '@sentry/types';
1+
import { Transaction, TransactionSource } from '@sentry/types';
22
import { getGlobalObject } from '@sentry/utils';
33
import hoistNonReactStatics from 'hoist-non-react-statics';
44
import * as React from 'react';
@@ -64,30 +64,42 @@ function createReactRouterInstrumentation(
6464
return undefined;
6565
}
6666

67-
function getTransactionName(pathname: string): string {
67+
/**
68+
* Normalizes a transaction name. Returns the new name as well as the
69+
* source of the transaction.
70+
*
71+
* @param pathname The initial pathname we normalize
72+
*/
73+
function normalizeTransactionName(pathname: string): [string, TransactionSource] {
6874
if (allRoutes.length === 0 || !matchPath) {
69-
return pathname;
75+
return [pathname, 'url'];
7076
}
7177

7278
const branches = matchRoutes(allRoutes, pathname, matchPath);
7379
// eslint-disable-next-line @typescript-eslint/prefer-for-of
7480
for (let x = 0; x < branches.length; x++) {
7581
if (branches[x].match.isExact) {
76-
return branches[x].match.path;
82+
return [branches[x].match.path, 'route'];
7783
}
7884
}
7985

80-
return pathname;
86+
return [pathname, 'url'];
8187
}
8288

89+
const tags = {
90+
'routing.instrumentation': name,
91+
};
92+
8393
return (customStartTransaction, startTransactionOnPageLoad = true, startTransactionOnLocationChange = true): void => {
8494
const initPathName = getInitPathName();
8595
if (startTransactionOnPageLoad && initPathName) {
96+
const [name, source] = normalizeTransactionName(initPathName);
8697
activeTransaction = customStartTransaction({
87-
name: getTransactionName(initPathName),
98+
name,
8899
op: 'pageload',
89-
tags: {
90-
'routing.instrumentation': name,
100+
tags,
101+
metadata: {
102+
source,
91103
},
92104
});
93105
}
@@ -98,14 +110,15 @@ function createReactRouterInstrumentation(
98110
if (activeTransaction) {
99111
activeTransaction.finish();
100112
}
101-
const tags = {
102-
'routing.instrumentation': name,
103-
};
104113

114+
const [name, source] = normalizeTransactionName(location.pathname);
105115
activeTransaction = customStartTransaction({
106-
name: getTransactionName(location.pathname),
116+
name,
107117
op: 'navigation',
108118
tags,
119+
metadata: {
120+
source,
121+
},
109122
});
110123
}
111124
});
@@ -155,6 +168,7 @@ export function withSentryRouting<P extends Record<string, any>, R extends React
155168
const WrappedRoute: React.FC<P> = (props: P) => {
156169
if (activeTransaction && props && props.computedMatch && props.computedMatch.isExact) {
157170
activeTransaction.setName(props.computedMatch.path);
171+
activeTransaction.setMetadata({ source: 'route' });
158172
}
159173

160174
// @ts-ignore Setting more specific React Component typing for `R` generic above

packages/react/test/reactrouterv4.test.tsx

+20-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('React Router v4', () => {
1111
startTransactionOnPageLoad?: boolean;
1212
startTransactionOnLocationChange?: boolean;
1313
routes?: RouteConfig[];
14-
}): [jest.Mock, any, { mockSetName: jest.Mock; mockFinish: jest.Mock }] {
14+
}): [jest.Mock, any, { mockSetName: jest.Mock; mockFinish: jest.Mock; mockSetMetadata: jest.Mock }] {
1515
const options = {
1616
matchPath: _opts && _opts.routes !== undefined ? matchPath : undefined,
1717
routes: undefined,
@@ -22,13 +22,16 @@ describe('React Router v4', () => {
2222
const history = createMemoryHistory();
2323
const mockFinish = jest.fn();
2424
const mockSetName = jest.fn();
25-
const mockStartTransaction = jest.fn().mockReturnValue({ setName: mockSetName, finish: mockFinish });
25+
const mockSetMetadata = jest.fn();
26+
const mockStartTransaction = jest
27+
.fn()
28+
.mockReturnValue({ setName: mockSetName, finish: mockFinish, setMetadata: mockSetMetadata });
2629
reactRouterV4Instrumentation(history, options.routes, options.matchPath)(
2730
mockStartTransaction,
2831
options.startTransactionOnPageLoad,
2932
options.startTransactionOnLocationChange,
3033
);
31-
return [mockStartTransaction, history, { mockSetName, mockFinish }];
34+
return [mockStartTransaction, history, { mockSetName, mockFinish, mockSetMetadata }];
3235
}
3336

3437
it('starts a pageload transaction when instrumentation is started', () => {
@@ -38,6 +41,7 @@ describe('React Router v4', () => {
3841
name: '/',
3942
op: 'pageload',
4043
tags: { 'routing.instrumentation': 'react-router-v4' },
44+
metadata: { source: 'url' },
4145
});
4246
});
4347

@@ -66,6 +70,7 @@ describe('React Router v4', () => {
6670
name: '/about',
6771
op: 'navigation',
6872
tags: { 'routing.instrumentation': 'react-router-v4' },
73+
metadata: { source: 'url' },
6974
});
7075

7176
act(() => {
@@ -76,6 +81,7 @@ describe('React Router v4', () => {
7681
name: '/features',
7782
op: 'navigation',
7883
tags: { 'routing.instrumentation': 'react-router-v4' },
84+
metadata: { source: 'url' },
7985
});
8086
});
8187

@@ -153,11 +159,12 @@ describe('React Router v4', () => {
153159
name: '/users/123',
154160
op: 'navigation',
155161
tags: { 'routing.instrumentation': 'react-router-v4' },
162+
metadata: { source: 'url' },
156163
});
157164
});
158165

159166
it('normalizes transaction name with custom Route', () => {
160-
const [mockStartTransaction, history, { mockSetName }] = createInstrumentation();
167+
const [mockStartTransaction, history, { mockSetName, mockSetMetadata }] = createInstrumentation();
161168
const SentryRoute = withSentryRouting(Route);
162169
const { getByText } = render(
163170
<Router history={history}>
@@ -179,13 +186,15 @@ describe('React Router v4', () => {
179186
name: '/users/123',
180187
op: 'navigation',
181188
tags: { 'routing.instrumentation': 'react-router-v4' },
189+
metadata: { source: 'url' },
182190
});
183191
expect(mockSetName).toHaveBeenCalledTimes(2);
184192
expect(mockSetName).toHaveBeenLastCalledWith('/users/:userid');
193+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
185194
});
186195

187196
it('normalizes nested transaction names with custom Route', () => {
188-
const [mockStartTransaction, history, { mockSetName }] = createInstrumentation();
197+
const [mockStartTransaction, history, { mockSetName, mockSetMetadata }] = createInstrumentation();
189198
const SentryRoute = withSentryRouting(Route);
190199
const { getByText } = render(
191200
<Router history={history}>
@@ -207,9 +216,11 @@ describe('React Router v4', () => {
207216
name: '/organizations/1234/v1/758',
208217
op: 'navigation',
209218
tags: { 'routing.instrumentation': 'react-router-v4' },
219+
metadata: { source: 'url' },
210220
});
211221
expect(mockSetName).toHaveBeenCalledTimes(2);
212222
expect(mockSetName).toHaveBeenLastCalledWith('/organizations/:orgid/v1/:teamid');
223+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
213224

214225
act(() => {
215226
history.push('/organizations/543');
@@ -221,9 +232,11 @@ describe('React Router v4', () => {
221232
name: '/organizations/543',
222233
op: 'navigation',
223234
tags: { 'routing.instrumentation': 'react-router-v4' },
235+
metadata: { source: 'url' },
224236
});
225237
expect(mockSetName).toHaveBeenCalledTimes(3);
226238
expect(mockSetName).toHaveBeenLastCalledWith('/organizations/:orgid');
239+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
227240
});
228241

229242
it('matches with route object', () => {
@@ -253,6 +266,7 @@ describe('React Router v4', () => {
253266
name: '/organizations/:orgid/v1/:teamid',
254267
op: 'navigation',
255268
tags: { 'routing.instrumentation': 'react-router-v4' },
269+
metadata: { source: 'route' },
256270
});
257271

258272
act(() => {
@@ -263,6 +277,7 @@ describe('React Router v4', () => {
263277
name: '/organizations/:orgid',
264278
op: 'navigation',
265279
tags: { 'routing.instrumentation': 'react-router-v4' },
280+
metadata: { source: 'route' },
266281
});
267282
});
268283
});

packages/react/test/reactrouterv5.test.tsx

+20-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('React Router v5', () => {
1111
startTransactionOnPageLoad?: boolean;
1212
startTransactionOnLocationChange?: boolean;
1313
routes?: RouteConfig[];
14-
}): [jest.Mock, any, { mockSetName: jest.Mock; mockFinish: jest.Mock }] {
14+
}): [jest.Mock, any, { mockSetName: jest.Mock; mockFinish: jest.Mock; mockSetMetadata: jest.Mock }] {
1515
const options = {
1616
matchPath: _opts && _opts.routes !== undefined ? matchPath : undefined,
1717
routes: undefined,
@@ -22,13 +22,16 @@ describe('React Router v5', () => {
2222
const history = createMemoryHistory();
2323
const mockFinish = jest.fn();
2424
const mockSetName = jest.fn();
25-
const mockStartTransaction = jest.fn().mockReturnValue({ setName: mockSetName, finish: mockFinish });
25+
const mockSetMetadata = jest.fn();
26+
const mockStartTransaction = jest
27+
.fn()
28+
.mockReturnValue({ setName: mockSetName, finish: mockFinish, setMetadata: mockSetMetadata });
2629
reactRouterV5Instrumentation(history, options.routes, options.matchPath)(
2730
mockStartTransaction,
2831
options.startTransactionOnPageLoad,
2932
options.startTransactionOnLocationChange,
3033
);
31-
return [mockStartTransaction, history, { mockSetName, mockFinish }];
34+
return [mockStartTransaction, history, { mockSetName, mockFinish, mockSetMetadata }];
3235
}
3336

3437
it('starts a pageload transaction when instrumentation is started', () => {
@@ -38,6 +41,7 @@ describe('React Router v5', () => {
3841
name: '/',
3942
op: 'pageload',
4043
tags: { 'routing.instrumentation': 'react-router-v5' },
44+
metadata: { source: 'url' },
4145
});
4246
});
4347

@@ -66,6 +70,7 @@ describe('React Router v5', () => {
6670
name: '/about',
6771
op: 'navigation',
6872
tags: { 'routing.instrumentation': 'react-router-v5' },
73+
metadata: { source: 'url' },
6974
});
7075

7176
act(() => {
@@ -76,6 +81,7 @@ describe('React Router v5', () => {
7681
name: '/features',
7782
op: 'navigation',
7883
tags: { 'routing.instrumentation': 'react-router-v5' },
84+
metadata: { source: 'url' },
7985
});
8086
});
8187

@@ -153,11 +159,12 @@ describe('React Router v5', () => {
153159
name: '/users/123',
154160
op: 'navigation',
155161
tags: { 'routing.instrumentation': 'react-router-v5' },
162+
metadata: { source: 'url' },
156163
});
157164
});
158165

159166
it('normalizes transaction name with custom Route', () => {
160-
const [mockStartTransaction, history, { mockSetName }] = createInstrumentation();
167+
const [mockStartTransaction, history, { mockSetName, mockSetMetadata }] = createInstrumentation();
161168
const SentryRoute = withSentryRouting(Route);
162169

163170
const { getByText } = render(
@@ -179,13 +186,15 @@ describe('React Router v5', () => {
179186
name: '/users/123',
180187
op: 'navigation',
181188
tags: { 'routing.instrumentation': 'react-router-v5' },
189+
metadata: { source: 'url' },
182190
});
183191
expect(mockSetName).toHaveBeenCalledTimes(2);
184192
expect(mockSetName).toHaveBeenLastCalledWith('/users/:userid');
193+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
185194
});
186195

187196
it('normalizes nested transaction names with custom Route', () => {
188-
const [mockStartTransaction, history, { mockSetName }] = createInstrumentation();
197+
const [mockStartTransaction, history, { mockSetName, mockSetMetadata }] = createInstrumentation();
189198
const SentryRoute = withSentryRouting(Route);
190199

191200
const { getByText } = render(
@@ -208,9 +217,11 @@ describe('React Router v5', () => {
208217
name: '/organizations/1234/v1/758',
209218
op: 'navigation',
210219
tags: { 'routing.instrumentation': 'react-router-v5' },
220+
metadata: { source: 'url' },
211221
});
212222
expect(mockSetName).toHaveBeenCalledTimes(2);
213223
expect(mockSetName).toHaveBeenLastCalledWith('/organizations/:orgid/v1/:teamid');
224+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
214225

215226
act(() => {
216227
history.push('/organizations/543');
@@ -222,9 +233,11 @@ describe('React Router v5', () => {
222233
name: '/organizations/543',
223234
op: 'navigation',
224235
tags: { 'routing.instrumentation': 'react-router-v5' },
236+
metadata: { source: 'url' },
225237
});
226238
expect(mockSetName).toHaveBeenCalledTimes(3);
227239
expect(mockSetName).toHaveBeenLastCalledWith('/organizations/:orgid');
240+
expect(mockSetMetadata).toHaveBeenLastCalledWith({ source: 'route' });
228241
});
229242

230243
it('matches with route object', () => {
@@ -254,6 +267,7 @@ describe('React Router v5', () => {
254267
name: '/organizations/:orgid/v1/:teamid',
255268
op: 'navigation',
256269
tags: { 'routing.instrumentation': 'react-router-v5' },
270+
metadata: { source: 'route' },
257271
});
258272

259273
act(() => {
@@ -264,6 +278,7 @@ describe('React Router v5', () => {
264278
name: '/organizations/:orgid',
265279
op: 'navigation',
266280
tags: { 'routing.instrumentation': 'react-router-v5' },
281+
metadata: { source: 'route' },
267282
});
268283
});
269284
});

0 commit comments

Comments
 (0)