Skip to content

fix: Normalize uppercase trace ids to lowercase in URL#3485

Merged
yurishkuro merged 12 commits intojaegertracing:mainfrom
samar-703:fix/uppercase-traceId-normalization
Mar 9, 2026
Merged

fix: Normalize uppercase trace ids to lowercase in URL#3485
yurishkuro merged 12 commits intojaegertracing:mainfrom
samar-703:fix/uppercase-traceId-normalization

Conversation

@samar-703
Copy link
Copy Markdown
Contributor

Which problem is this PR solving?

Description of the changes

  • Replaced broken class component normalization with useEffect hook
  • Uses useNavigate with replace option for URL normalization
  • Preserves query parameters during normalization
  • Updated test to match new implementation

How was this change tested?

  • All existing unit tests pass (87/87 tests in TracePage suite)
  • Updated the "forces lowercase id" test to verify new implementation behavior
  • Manually tested with uppercase trace ID: http://localhost:5173/trace/B36DE06C5972AB071AC119C504CA07DC successfully auto-normalizes to lowercase
  • Verified already-lowercase URLs don't trigger unnecessary redirects
  • TypeScript compilation successful

Checklist

Copilot AI review requested due to automatic review settings January 31, 2026 11:45
@samar-703 samar-703 requested a review from a team as a code owner January 31, 2026 11:45
@github-actions github-actions bot added the pr-quota-reached PR is on hold due to quota limits for new contributors label Jan 31, 2026
@github-actions
Copy link
Copy Markdown

Hi @samar-703, thanks for your contribution! To ensure quality reviews, we limit how many concurrent open PRs new contributors can open.

This PR is currently on hold (Status: 2/1 open). We will automatically move this into the review queue once your existing PRs are merged or closed.

Please see our Contributing Guidelines for details on our tiered quota policy.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors how trace ID URL normalization is handled for the Trace page so that uppercase trace IDs are normalized to lowercase at the routing layer, aiming to fix #3477 and to preserve query parameters.

Changes:

  • Moves trace ID normalization out of TracePageImpl.ensureTraceFetched into a useEffect hook in the functional TracePage wrapper that uses useNavigate from react-router-dom-v5-compat.
  • Uses useLocation to preserve the existing query string (location.search) during normalization redirects.
  • Updates the "forces lowercase id" test in index.test.js to stop asserting on history.replace and instead perform a minimal render assertion with an uppercase ID.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/jaeger-ui/src/components/TracePage/index.tsx Adds a hook-based URL-normalization step in the functional TracePage wrapper and removes the old history.replace(getLocation(...)) logic from TracePageImpl.ensureTraceFetched.
packages/jaeger-ui/src/components/TracePage/index.test.js Adjusts the "forces lowercase id" test to no longer rely on history.replace and to render TracePageImpl with an uppercase ID, but without asserting on the new navigation behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member

@Parship12 Parship12 left a comment

Choose a reason for hiding this comment

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

LGTM 👍

we could do this with a simpler fix (by reordering logic in ensureTraceFetched), but since we are planning to convert this component to functional component with hooks, this approach makes sense as it moves us in that direction. I think we may need to reorganize the logic, but we can handle that during the full migration.

@Parship12
Copy link
Copy Markdown
Member

Screen.Recording.2026-01-31.194943.mp4

Copilot AI review requested due to automatic review settings February 1, 2026 07:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

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

Please respond to copilot comments

Copilot AI review requested due to automatic review settings February 1, 2026 19:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@samar-703
Copy link
Copy Markdown
Contributor Author

Please respond to copilot comments

I've made the required changes to address the Copilot feedback:

  1. removed the ineffective mock in the test file as suggested

  2. fixed the navigation approach. I initially tried adding location.state to dependencies (which caused loops) and then using relative path ../ as you suggested, however, in React Router v5, relative paths resolve from the route match rather than URL segments, causing ../ to navigate to root /, which redirects to /search.

The solution is using the existing getUrl() utility function, which properly handles deployment URL prefixes via prefixUrl().

tested locally - uppercase trace IDs now normalize to lowercase without redirecting.

Note: Copilot is now suggesting adding tests for the wrapper component's normalization behavior. I can add those if needed, though it would require setting up proper mocking for the default export with router context. Let me know if you'd like me to add wrapper tests or if the current test coverage is sufficient for this fix.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings February 5, 2026 15:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@samar-703 samar-703 requested a review from yurishkuro February 6, 2026 07:31
@samar-703 samar-703 force-pushed the fix/uppercase-traceId-normalization branch from 662a70f to 606a720 Compare February 6, 2026 15:07
Copilot AI review requested due to automatic review settings February 8, 2026 09:59
@samar-703 samar-703 force-pushed the fix/uppercase-traceId-normalization branch from 606a720 to 0b2240d Compare February 8, 2026 09:59
…ce ID normalization

Signed-off-by: Samar <hello.samar7@gmail.com>
- normalize trace ID to lowercase before passing to component
- preserve query parameters and location state during redirect
- added integration tests for normalization and query param preservation.

Signed-off-by: Samar <hello.samar7@gmail.com>
Signed-off-by: Samar <hello.samar7@gmail.com>
- Extract uppercase-to-lowercase normalization into useNormalizeTraceId hook
- Hook handles redirect and preserves query params and location state
- Replace integration test with focused unit tests for the hook
- Simplifies TracePage component by removing inline navigation logic

Fixes double-fetch caused by uppercase trace IDs in Redux store.

Signed-off-by: Samar <hello.samar7@gmail.com>
Signed-off-by: Samar <hello.samar7@gmail.com>
Copilot AI review requested due to automatic review settings March 7, 2026 05:31
@samar-703 samar-703 force-pushed the fix/uppercase-traceId-normalization branch from 4d28534 to 9097997 Compare March 7, 2026 05:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 7, 2026

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.15%. Comparing base (4713873) to head (bc356ee).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...kages/jaeger-ui/src/components/TracePage/index.tsx 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3485      +/-   ##
==========================================
+ Coverage   89.04%   89.15%   +0.10%     
==========================================
  Files         302      304       +2     
  Lines        9613     9719     +106     
  Branches     2547     2587      +40     
==========================================
+ Hits         8560     8665     +105     
- Misses       1050     1051       +1     
  Partials        3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jkowall jkowall requested a review from Copilot March 7, 2026 19:53
@jkowall jkowall added the changelog:bugfix-or-minor-feature 🐞 Bug fixes, Minor Improvements label Mar 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 521 to 530
const TracePage = (props: TracePageProps) => {
const config = useConfig();
const traceID = props.params.id;
const normalizedTraceID = useNormalizeTraceId(traceID);

return (
<ConnectedTracePage
{...props}
params={{ ...props.params, id: normalizedTraceID }}
archiveEnabled={Boolean(config.archiveEnabled)}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

The URL-normalization behavior is implemented in the functional TracePage wrapper (hook call + passing a normalized params.id), but the existing index.test.js suite only renders TracePageImpl directly. The new unit tests cover useNormalizeTraceId(), but they don’t verify that the real TracePage route actually invokes the hook and performs navigation when params.id is uppercase. Adding a small integration test that renders the default export (withRouteProps wrapper) under a router and asserts useNavigate(..., { replace: true }) is called would prevent regressions where the hook is accidentally removed or the normalized params.id is no longer passed down.

Copilot uses AI. Check for mistakes.
@jkowall
Copy link
Copy Markdown
Contributor

jkowall commented Mar 7, 2026

@samar-703 Update the copyright year to 2026 in the files to fix the CI issue. Also, is there a way to fix the drop in code coverage?

@github-actions github-actions bot added the waiting-for-author PR is waiting for author to respond to maintainer's comments label Mar 7, 2026
Copy link
Copy Markdown
Contributor

@jkowall jkowall left a comment

Choose a reason for hiding this comment

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

Review

Summary

This PR extracts trace ID case normalization from the TracePageImpl class component's ensureTraceFetched method into a new useNormalizeTraceId custom hook used by the functional TracePage wrapper. This fixes #3477 where uppercase trace IDs in URLs caused issues.

+98 / -17 across 4 files (2 new, 2 modified)


What's Good

  • Clean separation of concerns: Extracting normalization into a dedicated hook is the right pattern, especially since the project is migrating toward functional components.
  • Query params preserved: The hook correctly appends location.search to the redirect URL.
  • Replace navigation: Using { replace: true } avoids polluting browser history.
  • Tests are well-structured: The hook tests cover the three key scenarios (uppercase -> redirect, query param preservation, already-lowercase -> no-op).

Issues & Suggestions

1. Bug: location in useEffect dependency causes infinite loop risk

useNormalizeTraceId.ts:20location.state is in the dependency array but location is a new object on every render in React Router. While location.search is a primitive string (safe), location.state is an object reference that can change identity without changing value, potentially causing unnecessary re-runs.

Suggestion: Remove location.state from the dependency array, or capture it with a ref. The redirect only needs to fire when traceID changes.

React.useEffect(() => {
  if (traceID && traceID !== normalizedTraceID) {
    const url = getUrl(normalizedTraceID);
    navigate(`${url}${location.search}`, { replace: true, state: location.state });
  }
}, [traceID, normalizedTraceID]); // navigate and location accessed via closure

2. Bug: getUrl mock in tests doesn't match real signature

useNormalizeTraceId.test.ts:15-17 — The mock getUrl: (id: string) => /trace/${id} ignores the optional uiFind parameter and more importantly, doesn't use prefixUrl(). This means tests won't catch issues with URL prefix handling. Consider using the real getUrl or at least documenting why the simplification is intentional.

3. Potential double-fetch still exists

The PR description mentions fixing "double-fetch caused by uppercase trace IDs in Redux store," but look at the flow:

  1. TracePage renders with uppercase traceID
  2. useNormalizeTraceId returns the lowercase ID immediately (before the effect fires)
  3. ConnectedTracePage receives the lowercase ID and passes it to TracePageImpl
  4. ensureTraceFetched calls fetchTrace(id) with the lowercase ID
  5. The useEffect fires and triggers a navigation, which re-renders

This means fetchTrace is called with the correct lowercase ID on first render, but the navigation in step 5 causes a second render cycle. If React Router re-mounts the component on navigation, fetchTrace could be called again. Worth verifying this doesn't cause a double fetch.

4. ensureTraceFetched lost the early return

index.tsx:292-296 — The original code had:

if (!trace) {
  fetchTrace(id);
  return;  // <-- this early return was removed
}

The return was there to skip the normalization check below it. Now that the normalization check is gone, removing return is fine functionally. But note that the location destructuring was also removed from ensureTraceFetched, while location is still in the props type — just confirming that's intentional and location isn't used elsewhere in this method.

5. Test weakened for TracePageImpl

index.test.js:348-361 — The old test verified that history.replace was called with the correct lowercase path. The new test only checks expect(() => render(...)).not.toThrow(), which doesn't actually verify any behavior. Since normalization now lives in the wrapper, this is somewhat acceptable, but the test name "forces lowercase id" is now misleading — it should be renamed to something like "renders without error when given uppercase id".

6. Minor: getLocation is now unused

The diff removes the import of getLocation from index.tsx, but getLocation is still exported from url/index.ts. Verify no other consumers use it; if not, it should be removed to avoid dead code.

7. Copyright year

useNormalizeTraceId.ts:1 and useNormalizeTraceId.test.ts:1 — Both files have Copyright (c) 2025 but the current year is 2026.


Verdict

The overall approach is sound and aligns with the project's direction toward hooks. The main concern is the location.state in the useEffect dependency array which could cause re-render loops in certain scenarios. The test coverage for the hook is good but the existing TracePageImpl test was weakened without a meaningful replacement assertion.

I'd suggest addressing issues #1 and #5 before merging.

Copy link
Copy Markdown
Contributor

@jkowall jkowall left a comment

Choose a reason for hiding this comment

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

Review

Summary

This PR extracts trace ID case normalization from the TracePageImpl class component's ensureTraceFetched method into a new useNormalizeTraceId custom hook used by the functional TracePage wrapper. This fixes #3477 where uppercase trace IDs in URLs caused issues.

+98 / -17 across 4 files (2 new, 2 modified)


What's Good

  • Clean separation of concerns: Extracting normalization into a dedicated hook is the right pattern, especially since the project is migrating toward functional components.
  • Query params preserved: The hook correctly appends location.search to the redirect URL.
  • Replace navigation: Using { replace: true } avoids polluting browser history.
  • Tests are well-structured: The hook tests cover the three key scenarios (uppercase -> redirect, query param preservation, already-lowercase -> no-op).

Issues & Suggestions

1. Bug: location in useEffect dependency causes infinite loop risk

useNormalizeTraceId.ts:20location.state is in the dependency array but location is a new object on every render in React Router. While location.search is a primitive string (safe), location.state is an object reference that can change identity without changing value, potentially causing unnecessary re-runs.

Suggestion: Remove location.state from the dependency array, or capture it with a ref. The redirect only needs to fire when traceID changes.

React.useEffect(() => {
  if (traceID && traceID !== normalizedTraceID) {
    const url = getUrl(normalizedTraceID);
    navigate(`${url}${location.search}`, { replace: true, state: location.state });
  }
}, [traceID, normalizedTraceID]); // navigate and location accessed via closure

2. Bug: getUrl mock in tests doesn't match real signature

useNormalizeTraceId.test.ts:15-17 — The mock getUrl: (id: string) => /trace/${id} ignores the optional uiFind parameter and more importantly, doesn't use prefixUrl(). This means tests won't catch issues with URL prefix handling. Consider using the real getUrl or at least documenting why the simplification is intentional.

3. Potential double-fetch still exists

The PR description mentions fixing "double-fetch caused by uppercase trace IDs in Redux store," but look at the flow:

  1. TracePage renders with uppercase traceID
  2. useNormalizeTraceId returns the lowercase ID immediately (before the effect fires)
  3. ConnectedTracePage receives the lowercase ID and passes it to TracePageImpl
  4. ensureTraceFetched calls fetchTrace(id) with the lowercase ID
  5. The useEffect fires and triggers a navigation, which re-renders

This means fetchTrace is called with the correct lowercase ID on first render, but the navigation in step 5 causes a second render cycle. If React Router re-mounts the component on navigation, fetchTrace could be called again. Worth verifying this doesn't cause a double fetch.

4. ensureTraceFetched lost the early return

index.tsx:292-296 — The original code had:

if (!trace) {
  fetchTrace(id);
  return;  // <-- this early return was removed
}

The return was there to skip the normalization check below it. Now that the normalization check is gone, removing return is fine functionally. But note that the location destructuring was also removed from ensureTraceFetched, while location is still in the props type — just confirming that's intentional and location isn't used elsewhere in this method.

5. Test weakened for TracePageImpl

index.test.js:348-361 — The old test verified that history.replace was called with the correct lowercase path. The new test only checks expect(() => render(...)).not.toThrow(), which doesn't actually verify any behavior. Since normalization now lives in the wrapper, this is somewhat acceptable, but the test name "forces lowercase id" is now misleading — it should be renamed to something like "renders without error when given uppercase id".

6. Minor: getLocation is now unused

The diff removes the import of getLocation from index.tsx, but getLocation is still exported from url/index.ts. Verify no other consumers use it; if not, it should be removed to avoid dead code.

7. Copyright year

useNormalizeTraceId.ts:1 and useNormalizeTraceId.test.ts:1 — Both files have Copyright (c) 2025 but the current year is 2026.


Verdict

The overall approach is sound and aligns with the project's direction toward hooks. The main concern is the location.state in the useEffect dependency array which could cause re-render loops in certain scenarios. The test coverage for the hook is good but the existing TracePageImpl test was weakened without a meaningful replacement assertion.

I'd suggest addressing issues #1 and #5 before merging.

@github-actions github-actions bot removed the pr-quota-reached PR is on hold due to quota limits for new contributors label Mar 7, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 7, 2026

PR quota unlocked!

@samar-703, this PR has been moved out of the waiting room and into the active review queue:

  • Open: 1
  • Limit: 2

Thank you for your patience.

- Fix copyright year to 2026 in new files
- Remove location.state from useEffect deps to prevent re-render loops;
  navigate and location accessed via closure instead
- Rename misleading test 'forces lowercase id' to accurately reflect
  that normalization now lives in the wrapper hook
- Document simplified getUrl mock intent in hook unit tests

Signed-off-by: Samar <hello.samar7@gmail.com>
@github-actions github-actions bot removed the waiting-for-author PR is waiting for author to respond to maintainer's comments label Mar 8, 2026
@samar-703
Copy link
Copy Markdown
Contributor Author

@samar-703 Update the copyright year to 2026 in the files to fix the CI issue. Also, is there a way to fix the drop in code coverage?

done! updated copyright year to 2026 in both new files and added a DOM
assertion in index.test.js to recover the coverage drop on index.tsx.
Just pushed the fixes, CI should be passing shortly

@samar-703 samar-703 requested a review from jkowall March 8, 2026 12:53
Copy link
Copy Markdown
Contributor

@jkowall jkowall left a comment

Choose a reason for hiding this comment

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

Looks good thanks for the contribution

@jkowall jkowall added this pull request to the merge queue Mar 8, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Mar 8, 2026
@yurishkuro yurishkuro changed the title fix: normalize uppercase trace ids to lowercase in URL fix: Normalize uppercase trace ids to lowercase in URL Mar 9, 2026
@yurishkuro yurishkuro merged commit 863cbe8 into jaegertracing:main Mar 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:bugfix-or-minor-feature 🐞 Bug fixes, Minor Improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Uppercase trace IDs not normalizing to lowercase

5 participants