Skip to content

Commit 3a36e1a

Browse files
committed
add test, some refactoring and fixes
1 parent abf9b5f commit 3a36e1a

File tree

5 files changed

+98
-35
lines changed

5 files changed

+98
-35
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
import Layout from '../../layouts/Layout.astro';
3+
---
4+
5+
<Layout>
6+
<h1>User Settings</h1>
7+
</Layout>

dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,55 @@ test.describe('nested SSR routes (client, server, server request)', () => {
312312
});
313313
});
314314
});
315+
316+
// Case for `user-page/[id]` vs. `user-page/settings` static routes
317+
test.describe('parametrized vs static paths', () => {
318+
test('should use static route name for static route in parametrized path', async ({ page }) => {
319+
const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
320+
return txnEvent?.transaction?.startsWith('/user-page/') ?? false;
321+
});
322+
323+
const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
324+
return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false;
325+
});
326+
327+
await page.goto('/user-page/settings');
328+
329+
const clientPageloadTxn = await clientPageloadTxnPromise;
330+
const serverPageRequestTxn = await serverPageRequestTxnPromise;
331+
332+
expect(clientPageloadTxn).toMatchObject({
333+
transaction: '/user-page/settings', // todo: parametrize to '/catchAll/[...path]'
334+
transaction_info: { source: 'url' },
335+
contexts: {
336+
trace: {
337+
op: 'pageload',
338+
origin: 'auto.pageload.browser',
339+
data: {
340+
'sentry.op': 'pageload',
341+
'sentry.origin': 'auto.pageload.browser',
342+
'sentry.source': 'url',
343+
},
344+
},
345+
},
346+
});
347+
348+
expect(serverPageRequestTxn).toMatchObject({
349+
transaction: 'GET /user-page/settings',
350+
transaction_info: { source: 'route' },
351+
contexts: {
352+
trace: {
353+
op: 'http.server',
354+
origin: 'auto.http.astro',
355+
data: {
356+
'sentry.op': 'http.server',
357+
'sentry.origin': 'auto.http.astro',
358+
'sentry.source': 'route',
359+
url: expect.stringContaining('/user-page/settings'),
360+
},
361+
},
362+
},
363+
request: { url: expect.stringContaining('/user-page/settings') },
364+
});
365+
});
366+
});

packages/astro/src/integration/index.ts

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const PKG_NAME = '@sentry/astro';
1212
export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
1313
let sentryServerInitPath: string | undefined;
1414

15+
let didSaveRouteData = false;
16+
1517
return {
1618
name: PKG_NAME,
1719
hooks: {
@@ -171,13 +173,34 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
171173
}
172174
},
173175

174-
// @ts-expect-error - This hook is available in Astro 5
176+
// @ts-expect-error - This hook is available in Astro 5+
175177
'astro:routes:resolved': ({ routes }: { routes: IntegrationResolvedRoute[] }) => {
176-
if (!sentryServerInitPath) {
178+
if (!sentryServerInitPath || didSaveRouteData) {
177179
return;
178180
}
179181

180-
includeRouteDataToConfigFile(sentryServerInitPath, routes);
182+
try {
183+
const serverInitContent = readFileSync(sentryServerInitPath, 'utf8');
184+
185+
const updatedServerInitContent = `${serverInitContent}\nglobalThis["__sentryRouteInfo"] = ${JSON.stringify(
186+
routes.map(route => {
187+
return {
188+
...route,
189+
patternCaseSensitive: joinRouteSegments(route.segments), // Store parametrized routes with correct casing on `globalThis` to be able to use them on the server during runtime
190+
patternRegex: route.patternRegex.source, // using `source` to be able to serialize the regex
191+
};
192+
}),
193+
null,
194+
2,
195+
)};`;
196+
197+
writeFileSync(sentryServerInitPath, updatedServerInitContent, 'utf8');
198+
199+
didSaveRouteData = true; // Prevents writing the file multiple times during runtime
200+
debug.log('Successfully added route pattern information to Sentry config file:', sentryServerInitPath);
201+
} catch (error) {
202+
debug.warn(`Failed to write to Sentry config file at ${sentryServerInitPath}:`, error);
203+
}
181204
},
182205
},
183206
};
@@ -300,27 +323,3 @@ function joinRouteSegments(segments: RoutePart[][]): string {
300323

301324
return `/${parthArray.join('/')}`;
302325
}
303-
304-
function includeRouteDataToConfigFile(sentryInitPath: string, routes: IntegrationResolvedRoute[]): void {
305-
try {
306-
const serverInitContent = readFileSync(sentryInitPath, 'utf8');
307-
308-
const updatedServerInitContent = `${serverInitContent}\nglobalThis["__sentryRouteInfo"] = ${JSON.stringify(
309-
routes.map(route => {
310-
return {
311-
...route,
312-
patternCaseSensitive: joinRouteSegments(route.segments), // Store parametrized routes with correct casing on `globalThis` to be able to use them on the server during runtime
313-
patternRegex: route.patternRegex.source, // using `source` to be able to serialize the regex
314-
};
315-
}),
316-
null,
317-
2,
318-
)};`;
319-
320-
writeFileSync(sentryInitPath, updatedServerInitContent, 'utf8');
321-
322-
debug.log('Successfully added route pattern information to Sentry config file:', sentryInitPath);
323-
} catch (error) {
324-
debug.warn(`Failed to write to Sentry config file at ${sentryInitPath}:`, error);
325-
}
326-
}

packages/astro/src/integration/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
2-
import type { InjectedRoute, RouteData } from 'astro';
2+
import type { RouteData } from 'astro';
33

44
type SdkInitPaths = {
55
/**
@@ -227,12 +227,16 @@ export type SentryOptions = SdkInitPaths &
227227
} & DeprecatedRuntimeOptions;
228228

229229
/**
230-
* Inline type for official `IntegrationResolvedRoute` (only available after Astro v5)
230+
* Routes inside 'astro:routes:resolved' hook (Astro v5+)
231+
*
232+
* Inline type for official `IntegrationResolvedRoute`.
231233
* The type includes more properties, but we only need some of them.
232234
*
233235
* @see https://github.com/withastro/astro/blob/04e60119afee668264a2ff6665c19a32150f4c91/packages/astro/src/types/public/integrations.ts#L287
234236
*/
235-
export type IntegrationResolvedRoute = InjectedRoute & {
237+
export type IntegrationResolvedRoute = {
238+
isPrerendered: RouteData['prerender'];
239+
pattern: RouteData['route'];
236240
patternRegex: RouteData['pattern'];
237241
segments: RouteData['segments'];
238242
};

packages/astro/src/server/middleware.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,15 @@ async function instrumentRequest(
132132
}
133133

134134
try {
135+
// `routePattern` is available after Astro 5
135136
const contextWithRoutePattern = ctx as Parameters<MiddlewareResponseHandler>[0] & { routePattern?: string };
136137
const rawRoutePattern = contextWithRoutePattern.routePattern;
137-
138138
const foundRoute = storedBuildTimeRoutes?.find(route => route.pattern === rawRoutePattern);
139139

140-
const interpolatedRoute =
140+
const parametrizedRoute =
141141
foundRoute?.patternCaseSensitive || interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params);
142-
const source = interpolatedRoute ? 'route' : 'url';
142+
143+
const source = parametrizedRoute ? 'route' : 'url';
143144
// storing res in a variable instead of directly returning is necessary to
144145
// invoke the catch block if next() throws
145146

@@ -158,12 +159,12 @@ async function instrumentRequest(
158159
attributes['http.fragment'] = ctx.url.hash;
159160
}
160161

161-
isolationScope?.setTransactionName(`${method} ${interpolatedRoute || ctx.url.pathname}`);
162+
isolationScope?.setTransactionName(`${method} ${parametrizedRoute || ctx.url.pathname}`);
162163

163164
const res = await startSpan(
164165
{
165166
attributes,
166-
name: `${method} ${interpolatedRoute || ctx.url.pathname}`,
167+
name: `${method} ${parametrizedRoute || ctx.url.pathname}`,
167168
op: 'http.server',
168169
},
169170
async span => {

0 commit comments

Comments
 (0)