Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react): improve handling of routes nested under path="/" #14821

Merged
merged 3 commits into from
Dec 23, 2024

Conversation

mjq
Copy link
Member

@mjq mjq commented Dec 20, 2024

We noticed this in Sentry's /issues/:groupId/ route, which uses SDK v8.43.0. The full route tree is complex, but the relevant parts are:

<Route path="/" element={<div>root</div>}>
  <Route path="/issues/:groupId/" element={<div>issues group</div>}>
    <Route index element={<div>index</div>} />
  </Route>
</Route>

If you navigate to e.g. /issues/123 (no trailing slash), the recorded transaction name is //issues/:groupId/ (notice the double slash). This looks messy but doesn't have too much of a consequence.

The worse issue is when you navigate to e.g. /issues/123/ (with trailing slash), the transaction name is /issues/123/ - it is not parameterized. This breaks transaction grouping. On the master and develop branch of the SDK, the transaction name is recorded as /. This causes the transactions to be grouped incorrectly with the root, as well as any other routes nested under a route with path="/".

(Thanks @JoshFerge for noticing the bad data in Sentry! 🙌)


This commit fixes both of these issues and passes both the new tests (which reproduce the above issues) and all existing tests, but I don't trust my changes.

I effectively revert a change from #14304 where

if (basename + branch.pathname === location.pathname) {

became

if (location.pathname.endsWith(basename + branch.pathname)) {

This is the change that caused the difference in results between SDK versions. This will always match when basename is empty, branch.pathname is /, and location.pathname ends with / - in other words, if you have a parent route with path="/", every request ending in a slash will match it instead of the more specific child route. (Depending on how often this kind of Route nesting happens in the wild, this could be a wide regression in transaction names.) But, no tests failed from the removal of endsWith, which makes me worried that there's some necessary behaviour that isn't covered and would be broken by this fix. @onurtemizkan can you give some context into that change? Thanks!

mjq added 2 commits December 20, 2024 16:05
We noticed this in Sentry's `/issues/:groupId/` route, which uses SDK v8.43.0. The full route tree is complex, but the relevent parts are:

```js
<Route path="/" element={<div>root</div>}>
  <Route path="/issues/:groupId/" element={<div>issues group</div>}>
    <Route index element={<div>index</div>} />
  </Route>
</Route>
```

If you navigate to e.g. `/issues/123`, the recorded transaction name is
`//issues/:groupId/` (notice the double slash). This is messy but doesn't have
too much of a consequence.

The worse issue is when you navigate to e.g.  `/issues/123/` (note the trailing
slash), the transaction name is `/issues/123/` - it is not parameterized. This
breaks transaction grouping. On the `master` and `develop` branch of the SDK,
the transaction name is recorded as `/`. This causes the transactions to be
grouped incorrectly with the root, as well as other routes with this structure
(nested under a route with path `/`).

This commit fixes both of these issues and passes both the new tests (which reproduce the above issues) and all existing tests, but I still don't trust the change.

First, `newPath` now always has a leading slash, and I don't know how that
interacts with the other path concatenations inside `getNormalizedName`. It
feels like dealing with path concatenation should be handled in a more
systematic way.

Second, I revert a change from 3473447 where

```js
if (basename + branch.pathname === location.pathname) {
```

became

```
if (location.pathname.endsWith(basename + branch.pathname)) {
```

This is the change that difference in results between SDK versions. This will
always match when `basename` is empty, `branch.pathname` is `/`, and
`location.pathname` ends with `/` - in other words, if you have a parent route
with `path="/"`, every request ending in a slash will match it instead of the
more specific child route. This seems likely to be a wide regression in
transaction names. But, no tests failed from this change, which makes me
worried that there's some necessary behaviour that isn't covered.
Comment on lines 429 to +430
const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
pathBuilder += newPath;
pathBuilder = trimSlash(pathBuilder) + prefixWithSlash(newPath);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I thought to change these lines to

const newPath = prefixWithSlash(path);
pathBuilder = trimSlash(pathBuilder) + newPath;

but newPath is used in a different path concatenation further down the method and, even though no tests fail, I didn't want collateral damage. (Tests probably should have failed though - I think the path concatenation in this method could be better handled, and is under-tested.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make a refactor/cleanup once we get the tests in a better state.

@mjq mjq marked this pull request as ready for review December 20, 2024 21:59
Copy link
Collaborator

@onurtemizkan onurtemizkan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix @mjq! I think it's safe to merge, and I agree that we need to improve tests to cover as many route declaration patterns as possible.


// If the path matches the current location, return the path
if (location.pathname.endsWith(basename + branch.pathname)) {
if (trimSlash(location.pathname) === trimSlash(basename + branch.pathname)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect this to break descendant routes. But in the final implementation, descendant routes don't end up here. So, I believe it's safe to revert.

Comment on lines 429 to +430
const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
pathBuilder += newPath;
pathBuilder = trimSlash(pathBuilder) + prefixWithSlash(newPath);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can make a refactor/cleanup once we get the tests in a better state.

@mjq
Copy link
Member Author

mjq commented Dec 23, 2024

Thanks @onurtemizkan! Going to merge in that case 👍

@mjq mjq merged commit 17f7bcd into develop Dec 23, 2024
151 checks passed
@mjq mjq deleted the mjq-rr6-txn-names branch December 23, 2024 19:35
onurtemizkan pushed a commit that referenced this pull request Jan 3, 2025
We noticed this in Sentry's `/issues/:groupId/` route, which uses SDK
v8.43.0. The full route tree is complex, but the relevant parts are:

```js
<Route path="/" element={<div>root</div>}>
  <Route path="/issues/:groupId/" element={<div>issues group</div>}>
    <Route index element={<div>index</div>} />
  </Route>
</Route>
```

If you navigate to e.g. `/issues/123` (no trailing slash), the recorded
transaction name is `//issues/:groupId/` (notice the double slash). This
looks messy but doesn't have too much of a consequence.

The worse issue is when you navigate to e.g. `/issues/123/` (with
trailing slash), the transaction name is `/issues/123/` - it is not
parameterized. This breaks transaction grouping. On the `master` and
`develop` branch of the SDK, the transaction name is recorded as `/`.
This causes the transactions to be grouped incorrectly with the root, as
well as any other routes nested under a route with `path="/"`.

(Thanks @JoshFerge for noticing the bad data in Sentry! 🙌)

---

Note that this commit effectively reverts a change from #14304 where

```js
if (basename + branch.pathname === location.pathname) {
```

became

```js
if (location.pathname.endsWith(basename + branch.pathname)) {
```

This is the change that caused the difference in results between SDK
versions. This will always match when `basename` is empty,
`branch.pathname` is `/`, and `location.pathname` ends with `/` - in
other words, if you have a parent route with `path="/"`, every request
ending in a slash will match it instead of the more specific child
route. (Depending on how often this kind of `Route` nesting happens in
the wild, this could be a wide regression in transaction names.) But, no
tests failed from the removal of `endsWith`. @onurtemizkan, who wrote
the change in question:

> I'd expect this to break descendant routes. But in the final
> implementation, descendant routes don't end up here. So, I
> believe it's safe to revert.
onurtemizkan pushed a commit that referenced this pull request Jan 3, 2025
We noticed this in Sentry's `/issues/:groupId/` route, which uses SDK
v8.43.0. The full route tree is complex, but the relevant parts are:

```js
<Route path="/" element={<div>root</div>}>
  <Route path="/issues/:groupId/" element={<div>issues group</div>}>
    <Route index element={<div>index</div>} />
  </Route>
</Route>
```

If you navigate to e.g. `/issues/123` (no trailing slash), the recorded
transaction name is `//issues/:groupId/` (notice the double slash). This
looks messy but doesn't have too much of a consequence.

The worse issue is when you navigate to e.g. `/issues/123/` (with
trailing slash), the transaction name is `/issues/123/` - it is not
parameterized. This breaks transaction grouping. On the `master` and
`develop` branch of the SDK, the transaction name is recorded as `/`.
This causes the transactions to be grouped incorrectly with the root, as
well as any other routes nested under a route with `path="/"`.

(Thanks @JoshFerge for noticing the bad data in Sentry! 🙌)

---

Note that this commit effectively reverts a change from #14304 where

```js
if (basename + branch.pathname === location.pathname) {
```

became

```js
if (location.pathname.endsWith(basename + branch.pathname)) {
```

This is the change that caused the difference in results between SDK
versions. This will always match when `basename` is empty,
`branch.pathname` is `/`, and `location.pathname` ends with `/` - in
other words, if you have a parent route with `path="/"`, every request
ending in a slash will match it instead of the more specific child
route. (Depending on how often this kind of `Route` nesting happens in
the wild, this could be a wide regression in transaction names.) But, no
tests failed from the removal of `endsWith`. @onurtemizkan, who wrote
the change in question:

> I'd expect this to break descendant routes. But in the final
> implementation, descendant routes don't end up here. So, I
> believe it's safe to revert.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants