Skip to content
Merged
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
16 changes: 5 additions & 11 deletions packages/jaeger-ui/src/components/TracePage/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,21 +347,15 @@ describe('<TracePage>', () => {
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
});

it('forces lowercase id', () => {
const replaceMock = jest.fn();
it('renders without error when given uppercase id', () => {
// URL normalization is handled by the useNormalizeTraceId hook in the wrapper.
// This test verifies TracePageImpl renders successfully with uppercase IDs.
const props = {
...defaultProps,
id: trace.traceID.toUpperCase(),
history: {
replace: replaceMock,
},
};
render(<TracePage {...props} />);
expect(replaceMock).toHaveBeenCalledWith(
expect.objectContaining({
pathname: expect.stringContaining(trace.traceID),
})
);
expect(() => render(<TracePage {...props} />)).not.toThrow();
expect(document.querySelector('.Tracepage--headerSection')).toBeInTheDocument();
});

it('focuses on search bar when there is a search bar and focusOnSearchBar is called', () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/jaeger-ui/src/components/TracePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import * as React from 'react';
import { InputRef } from 'antd';
import { useNormalizeTraceId } from './useNormalizeTraceId';
import { Location, History as RouterHistory } from 'history';
import _clamp from 'lodash/clamp';
import _get from 'lodash/get';
Expand Down Expand Up @@ -31,7 +32,7 @@ import TracePageHeader from './TracePageHeader';
import TraceTimelineViewer from './TraceTimelineViewer';
import { actions as timelineActions } from './TraceTimelineViewer/duck';
import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate, ETraceViewType } from './types';
import { getLocation, getUrl } from './url';
import { getUrl } from './url';
import ErrorMessage from '../common/ErrorMessage';
import LoadingIndicator from '../common/LoadingIndicator';
import { extractUiFindFromState } from '../common/UiFindInput';
Expand Down Expand Up @@ -291,14 +292,9 @@ export class TracePageImpl extends React.PureComponent<TProps, TState> {
};

ensureTraceFetched() {
const { fetchTrace, location, trace, id } = this.props;
const { fetchTrace, trace, id } = this.props;
if (!trace) {
fetchTrace(id);
return;
}
const { history } = this.props;
if (id && id !== id.toLowerCase()) {
history.replace(getLocation(id.toLowerCase(), location.state));
}
}

Expand Down Expand Up @@ -524,9 +520,13 @@ type TracePageProps = {

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)}
Comment on lines 521 to 530
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.
enableSidePanel={Boolean(config.traceTimeline?.enableSidePanel)}
storageCapabilities={config.storageCapabilities}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2026 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

const mockNavigate = jest.fn();
let mockLocation: { search: string; state: unknown } = { search: '', state: null };

jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
useNavigate: () => mockNavigate,
useLocation: () => mockLocation,
}));

// Simplified mock sufficient for testing normalization logic;
// URL prefix handling is tested separately in url/index.test.js
jest.mock('./url', () => ({
getUrl: (id: string) => `/trace/${id}`,
}));

import { renderHook } from '@testing-library/react';

import { useNormalizeTraceId } from './useNormalizeTraceId';

describe('useNormalizeTraceId', () => {
beforeEach(() => {
mockNavigate.mockClear();
mockLocation = { search: '', state: null };
});

it('normalizes uppercase trace IDs to lowercase in the URL', () => {
const uppercaseTraceId = 'ABC123DEF456';
const lowercaseTraceId = uppercaseTraceId.toLowerCase();

const { result } = renderHook(() => useNormalizeTraceId(uppercaseTraceId));

expect(result.current).toBe(lowercaseTraceId);
expect(mockNavigate).toHaveBeenCalledWith(`/trace/${lowercaseTraceId}`, {
replace: true,
state: null,
});
});

it('preserves query parameters during trace ID normalization', () => {
const uppercaseTraceId = 'ABC123DEF456';
const lowercaseTraceId = uppercaseTraceId.toLowerCase();
const searchParams = '?uiFind=foo&x=1';

mockLocation = { search: searchParams, state: null };

const { result } = renderHook(() => useNormalizeTraceId(uppercaseTraceId));

expect(result.current).toBe(lowercaseTraceId);
expect(mockNavigate).toHaveBeenCalledWith(`/trace/${lowercaseTraceId}${searchParams}`, {
replace: true,
state: null,
});
});

it('does not redirect when trace ID is already lowercase', () => {
const lowercaseTraceId = 'abc123def456';

const { result } = renderHook(() => useNormalizeTraceId(lowercaseTraceId));

expect(result.current).toBe(lowercaseTraceId);
expect(mockNavigate).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2026 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

import * as React from 'react';
import { useNavigate, useLocation } from 'react-router-dom-v5-compat';

import { getUrl } from './url';

// Custom hook that normalizes a trace ID to lowercase in the URL
export function useNormalizeTraceId(traceID: string): string {
const normalizedTraceID = traceID.toLowerCase();
const navigate = useNavigate();
const location = useLocation();

React.useEffect(() => {
if (traceID && traceID !== normalizedTraceID) {
const url = getUrl(normalizedTraceID);
navigate(`${url}${location.search}`, { replace: true, state: location.state });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [traceID, normalizedTraceID]);

return normalizedTraceID;
}