Skip to content

Commit 4fdf8d0

Browse files
committed
Clean up / cover raw to parameterized span ugrades
1 parent a65f1ef commit 4fdf8d0

File tree

2 files changed

+26
-56
lines changed

2 files changed

+26
-56
lines changed

dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/timeout-behavior.test.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { waitForTransaction } from '@sentry-internal/test-utils';
33

44
test('lazyRouteTimeout: Routes load within timeout window', async ({ page }) => {
55
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
6-
console.log('[WAIT] Transaction Event:', JSON.stringify(transactionEvent, null, 2));
76
return (
87
!!transactionEvent?.transaction &&
98
transactionEvent.contexts?.trace?.op === 'navigation' &&
@@ -21,11 +20,6 @@ test('lazyRouteTimeout: Routes load within timeout window', async ({ page }) =>
2120

2221
const event = await transactionPromise;
2322

24-
console.log('[TEST] Routes load within timeout:');
25-
console.log(' Transaction:', event.transaction);
26-
console.log(' Source:', event.contexts?.trace?.data?.['sentry.source']);
27-
console.log(' Finish reason:', event.contexts?.trace?.data?.['sentry.idle_span_finish_reason']);
28-
2923
// Should get full parameterized route
3024
expect(event.transaction).toBe('/deep/level2/level3/:id');
3125
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
@@ -34,7 +28,6 @@ test('lazyRouteTimeout: Routes load within timeout window', async ({ page }) =>
3428

3529
test('lazyRouteTimeout: Infinity timeout always waits for routes', async ({ page }) => {
3630
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
37-
console.log('[WAIT] Transaction Event:', JSON.stringify(transactionEvent, null, 2));
3831
return (
3932
!!transactionEvent?.transaction &&
4033
transactionEvent.contexts?.trace?.op === 'navigation' &&
@@ -51,10 +44,6 @@ test('lazyRouteTimeout: Infinity timeout always waits for routes', async ({ page
5144

5245
const event = await transactionPromise;
5346

54-
console.log('[TEST] Infinity timeout:');
55-
console.log(' Transaction:', event.transaction);
56-
console.log(' Source:', event.contexts?.trace?.data?.['sentry.source']);
57-
5847
// Should wait indefinitely and get full route
5948
expect(event.transaction).toBe('/deep/level2/level3/:id');
6049
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
@@ -63,7 +52,6 @@ test('lazyRouteTimeout: Infinity timeout always waits for routes', async ({ page
6352

6453
test('idleTimeout: Captures all activity with increased timeout', async ({ page }) => {
6554
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
66-
console.log('[WAIT] Transaction Event:', JSON.stringify(transactionEvent, null, 2));
6755
return (
6856
!!transactionEvent?.transaction &&
6957
transactionEvent.contexts?.trace?.op === 'navigation' &&
@@ -80,10 +68,6 @@ test('idleTimeout: Captures all activity with increased timeout', async ({ page
8068

8169
const event = await transactionPromise;
8270

83-
console.log('[TEST] Increased idleTimeout:');
84-
console.log(' Transaction:', event.transaction);
85-
console.log(' Duration:', event.timestamp! - event.start_timestamp);
86-
8771
expect(event.transaction).toBe('/deep/level2/level3/:id');
8872
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
8973
expect(event.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout');
@@ -96,7 +80,6 @@ test('idleTimeout: Captures all activity with increased timeout', async ({ page
9680

9781
test('idleTimeout: Finishes prematurely with low timeout', async ({ page }) => {
9882
const transactionPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
99-
console.log('[WAIT] Transaction Event:', JSON.stringify(transactionEvent, null, 2));
10083
return (
10184
!!transactionEvent?.transaction &&
10285
transactionEvent.contexts?.trace?.op === 'navigation' &&
@@ -114,10 +97,6 @@ test('idleTimeout: Finishes prematurely with low timeout', async ({ page }) => {
11497

11598
const event = await transactionPromise;
11699

117-
console.log('[TEST] Low idleTimeout:');
118-
console.log(' Transaction:', event.transaction);
119-
console.log(' Duration:', event.timestamp! - event.start_timestamp);
120-
121100
expect(event.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout');
122101
expect(event.transaction).toBe('/deep/level2/level3/:id');
123102
expect(event.contexts?.trace?.data?.['sentry.source']).toBe('route');
@@ -129,7 +108,6 @@ test('idleTimeout: Finishes prematurely with low timeout', async ({ page }) => {
129108

130109
test('idleTimeout: Pageload on deeply nested route', async ({ page }) => {
131110
const pageloadPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => {
132-
console.log('[WAIT] Transaction Event:', JSON.stringify(transactionEvent, null, 2));
133111
return (
134112
!!transactionEvent?.transaction &&
135113
transactionEvent.contexts?.trace?.op === 'pageload' &&
@@ -142,10 +120,6 @@ test('idleTimeout: Pageload on deeply nested route', async ({ page }) => {
142120

143121
const pageloadEvent = await pageloadPromise;
144122

145-
console.log('[TEST] Pageload on nested route:');
146-
console.log(' Transaction:', pageloadEvent.transaction);
147-
console.log(' Op:', pageloadEvent.contexts?.trace?.op);
148-
149123
expect(pageloadEvent.transaction).toBe('/deep/level2/level3/:id');
150124
expect(pageloadEvent.contexts?.trace?.data?.['sentry.source']).toBe('route');
151125
expect(pageloadEvent.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout');

packages/react/src/reactrouter-compat-utils/instrumentation.tsx

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ export function computeLocationKey(location: Location): string {
9696
return `${location.pathname}${location.search || ''}${location.hash || ''}`;
9797
}
9898

99+
/**
100+
* Checks if a route name is parameterized (contains route parameters like :id or wildcards like *)
101+
* vs a raw URL path.
102+
*/
103+
function isParameterizedRoute(routeName: string): boolean {
104+
return routeName.includes(':') || routeName.includes('*');
105+
}
106+
99107
/**
100108
* Determines if a navigation should be skipped as a duplicate, and if an existing span should be updated.
101109
* Exported for testing.
@@ -116,48 +124,34 @@ export function shouldSkipNavigation(
116124
return { skip: false, shouldUpdate: false };
117125
}
118126

119-
// If it's a placeholder for the same location, check if we should update when it becomes real
120-
if (trackedNav.isPlaceholder && trackedNav.locationKey === locationKey) {
121-
// Even though it's a placeholder, check if the proposed name is better
122-
// This allows cross-usage scenarios where the second wrapper has more complete route info
127+
// Check if this is a duplicate navigation (same location)
128+
// 1. If it's a placeholder, it's always a duplicate (we're waiting for the real one)
129+
// 2. If it's a real span, it's a duplicate only if it hasn't ended yet
130+
const isDuplicate = trackedNav.locationKey === locationKey && (trackedNav.isPlaceholder || !spanHasEnded);
131+
132+
if (isDuplicate) {
133+
// Check if we should update the span name with a better route
134+
// Allow updates if:
135+
// 1. Current has wildcard and new doesn't (wildcard → parameterized upgrade)
136+
// 2. Current is raw path and new is parameterized (raw → parameterized upgrade)
137+
// 3. New name is different and more specific (longer, indicating nested routes resolved)
123138
const currentHasWildcard = !!trackedNav.routeName && transactionNameHasWildcard(trackedNav.routeName);
124139
const proposedHasWildcard = transactionNameHasWildcard(proposedName);
140+
const currentIsParameterized = !!trackedNav.routeName && isParameterizedRoute(trackedNav.routeName);
141+
const proposedIsParameterized = isParameterizedRoute(proposedName);
125142

126143
const isWildcardUpgrade = currentHasWildcard && !proposedHasWildcard;
144+
const isRawToParameterized = !currentIsParameterized && proposedIsParameterized;
127145
const isMoreSpecific =
128146
proposedName !== trackedNav.routeName &&
129147
proposedName.length > (trackedNav.routeName?.length || 0) &&
130148
!proposedHasWildcard;
131149

132-
const shouldUpdate = !!(trackedNav.routeName && (isWildcardUpgrade || isMoreSpecific));
150+
const shouldUpdate = !!(trackedNav.routeName && (isWildcardUpgrade || isRawToParameterized || isMoreSpecific));
133151

134152
return { skip: true, shouldUpdate };
135153
}
136154

137-
// For real spans (not placeholders), check if duplicate by location and end status
138-
if (!trackedNav.isPlaceholder) {
139-
// If tracked span is for the same location and hasn't ended yet, this is a duplicate
140-
if (trackedNav.locationKey === locationKey && !spanHasEnded) {
141-
// Check if we should update the span name with a better route
142-
// Allow updates if:
143-
// 1. Current has wildcard and new doesn't (wildcard → parameterized upgrade)
144-
// 2. New name is different and more specific (longer, indicating nested routes resolved)
145-
const currentHasWildcard = !!trackedNav.routeName && transactionNameHasWildcard(trackedNav.routeName);
146-
const proposedHasWildcard = transactionNameHasWildcard(proposedName);
147-
148-
const isWildcardUpgrade = currentHasWildcard && !proposedHasWildcard;
149-
const isMoreSpecific =
150-
proposedName !== trackedNav.routeName &&
151-
proposedName.length > (trackedNav.routeName?.length || 0) &&
152-
!proposedHasWildcard;
153-
154-
const shouldUpdate = !!(trackedNav.routeName && (isWildcardUpgrade || isMoreSpecific));
155-
156-
return { skip: true, shouldUpdate };
157-
}
158-
}
159-
160-
// Location is different or span has ended - allow creating new span
161155
return { skip: false, shouldUpdate: false };
162156
}
163157

@@ -802,7 +796,9 @@ export function handleNavigation(opts: {
802796
// Update placeholder's route name - the real span will be created with this name
803797
trackedNav.routeName = name;
804798
DEBUG_BUILD &&
805-
debug.log(`[Tracing] Updated placeholder navigation name from "${oldName}" to "${name}" (will apply to real span)`);
799+
debug.log(
800+
`[Tracing] Updated placeholder navigation name from "${oldName}" to "${name}" (will apply to real span)`,
801+
);
806802
} else {
807803
// Update existing real span from wildcard to parameterized route name
808804
trackedNav.span.updateName(name);

0 commit comments

Comments
 (0)