Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function instrumentEmberAppInstanceForPerformance(
// Somehow the router service etc. may not be fully ready/initialized yet at this point
// Probably because we are running this before the Ember setup is necessarily completed
// So in order to accomodate this, we fall back to starting the pageload span with the current URL and update it later
const routeInfo = url ? routerService.recognize(url) : undefined;
const routeInfo = url ? _recognizeURL(routerService, url) : undefined;

activeRootSpan = startBrowserTracingPageLoadSpan(client, {
// With span streaming, span names have to be low cardinality, so we can't fall back to the URL.
Expand Down Expand Up @@ -129,7 +129,7 @@ export function instrumentEmberAppInstanceForPerformance(
const location = getRouterMain(appInstance).location;
const url = _getLocationURL(location);
if (url) {
const routeInfo = routerService.recognize(url);
const routeInfo = _recognizeURL(routerService, url);
activeRootSpan.updateName(`route:${toRoute}`);
activeRootSpan.setAttributes({
[SENTRY_SEGMENT_NAME_SOURCE]: 'route',
Expand Down Expand Up @@ -164,7 +164,7 @@ export function instrumentEmberAppInstanceForPerformance(

const url = routerService.currentURL ?? _getLocationURL(location);
if (url) {
const routeInfo = routerService.recognize(url);
const routeInfo = _recognizeURL(routerService, url);
// `currentURL` is the normalized route path and never includes the hash fragment, so we source
// `url.full` from the location URL (which preserves `#/...` for hash-location apps) when available.
const fullUrl = _getLocationURL(location) || url;
Expand Down Expand Up @@ -258,6 +258,23 @@ function _getRouteUrlAttributes(
};
}

// Only exported for testing
export function _recognizeURL(
routerService: RouterService,
url: string,
): ReturnType<RouterService['recognize']> | undefined {
// `recognize()` throws for URLs the router cannot resolve. Most notably it asserts
// "You must pass a url that begins with the application's rootURL" whenever the URL is not
// prefixed with the app's `rootURL`, which is the case under Ember's `none` location (used by
// `@ember/test-helpers`). Every call site already treats a missing `routeInfo` as "fall back
// to the URL", so degrade to that instead of throwing out of the integration's setup.
try {
return routerService.recognize(url);
} catch {
return undefined;
}
}

// Only exported for testing
export function _getLocationURL(location: EmberRouterMain['location']): string {
if (!location?.getURL || !location?.formatURL) {
Expand Down
35 changes: 34 additions & 1 deletion packages/ember/tests/instrument-router-location.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { _getLocationURL } from '../src/utils/instrumentEmberAppInstanceForPerformance.ts';
import { _getLocationURL, _recognizeURL } from '../src/utils/instrumentEmberAppInstanceForPerformance.ts';

interface Location {
formatURL?: (url: string) => string;
Expand Down Expand Up @@ -80,3 +80,36 @@ describe('_getLocationURL', () => {
expect(_getLocationURL(mockLocation)).toBe('');
});
});

type RouterServiceArg = Parameters<typeof _recognizeURL>[0];

function mockRouterService(recognize: (url: string) => unknown): RouterServiceArg {
return { recognize } as unknown as RouterServiceArg;
}

describe('_recognizeURL', () => {
it('returns the route info when the router recognizes the URL', () => {
const routeInfo = { name: 'my.route', params: { id: '1' } };
const routerService = mockRouterService(() => routeInfo);

expect(_recognizeURL(routerService, '/my/route/1')).toBe(routeInfo);
});

it('returns undefined when the URL is not prefixed with the rootURL', () => {
// Ember's `none` location (used by `@ember/test-helpers`) yields URLs that `recognize()`
// rejects with this assertion, which previously threw out of the integration's setup.
const routerService = mockRouterService(() => {
throw new Error('Assertion Failed: You must pass a url that begins with the application\'s rootURL "/"');
});

expect(_recognizeURL(routerService, 'not-root-url-prefixed')).toBeUndefined();
});

it('returns undefined for any other recognize() failure', () => {
const routerService = mockRouterService(() => {
throw new Error('nope');
});

expect(_recognizeURL(routerService, '/unknown')).toBeUndefined();
});
});