diff --git a/packages/jaeger-ui/package.json b/packages/jaeger-ui/package.json index 509dd24f7f..a48b6e24bb 100644 --- a/packages/jaeger-ui/package.json +++ b/packages/jaeger-ui/package.json @@ -48,7 +48,7 @@ "@types/lodash": "^4.14.123", "@types/object-hash": "^3.0.2", "@types/react-helmet": "^6.1.5", - "@types/react-router-dom": "^4.3.1", + "@types/react-router-dom": "^5.1.0", "@types/redux-actions": "2.2.1", "antd": "4.24.13", "chance": "^1.0.10", diff --git a/packages/jaeger-ui/src/components/App/Page.tsx b/packages/jaeger-ui/src/components/App/Page.tsx index 53a8b56848..810bcabc61 100644 --- a/packages/jaeger-ui/src/components/App/Page.tsx +++ b/packages/jaeger-ui/src/components/App/Page.tsx @@ -17,7 +17,6 @@ import { Layout } from 'antd'; import cx from 'classnames'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; import TopNav from './TopNav'; import { ReduxState } from '../../types'; @@ -25,8 +24,9 @@ import { EmbeddedState } from '../../types/embedded'; import { trackPageView } from '../../utils/tracking'; import './Page.css'; +import withRouteProps from '../../utils/withRouteProps'; -type TProps = RouteComponentProps & { +type TProps = { children: React.ReactNode; embedded: EmbeddedState; pathname: string; @@ -76,4 +76,4 @@ export function mapStateToProps(state: ReduxState) { return { embedded, pathname, search }; } -export default withRouter(connect(mapStateToProps)(PageImpl)); +export default connect(mapStateToProps)(withRouteProps(PageImpl)); diff --git a/packages/jaeger-ui/src/components/App/TopNav.tsx b/packages/jaeger-ui/src/components/App/TopNav.tsx index 196ce1f26f..394748bf47 100644 --- a/packages/jaeger-ui/src/components/App/TopNav.tsx +++ b/packages/jaeger-ui/src/components/App/TopNav.tsx @@ -17,7 +17,7 @@ import { Dropdown, Menu } from 'antd'; import { IoChevronDown } from 'react-icons/io5'; import _has from 'lodash/has'; import { connect } from 'react-redux'; -import { RouteComponentProps, Link, withRouter } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import TraceIDSearchInput from './TraceIDSearchInput'; import * as dependencyGraph from '../DependencyGraph/url'; @@ -32,8 +32,9 @@ import { getConfigValue } from '../../utils/config/get-config'; import prefixUrl from '../../utils/prefix-url'; import './TopNav.css'; +import withRouteProps from '../../utils/withRouteProps'; -type Props = RouteComponentProps & ReduxState; +type Props = ReduxState; const NAV_LINKS = [ { @@ -155,4 +156,4 @@ export function mapStateToProps(state: ReduxState) { return state; } -export default withRouter(connect(mapStateToProps)(TopNavImpl)); +export default connect(mapStateToProps)(withRouteProps(TopNavImpl)); diff --git a/packages/jaeger-ui/src/components/App/TraceIDSearchInput.test.js b/packages/jaeger-ui/src/components/App/TraceIDSearchInput.test.js index 210e583e26..8763d0ffe2 100644 --- a/packages/jaeger-ui/src/components/App/TraceIDSearchInput.test.js +++ b/packages/jaeger-ui/src/components/App/TraceIDSearchInput.test.js @@ -16,10 +16,11 @@ import React from 'react'; import { createMemoryHistory } from 'history'; -import { Router } from 'react-router-dom'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; +import { Router } from 'react-router-dom'; import TraceIDSearchInput from './TraceIDSearchInput'; +import { HistoryProvider } from '../../utils/useHistory'; describe('', () => { let history; @@ -27,9 +28,11 @@ describe('', () => { beforeEach(() => { history = createMemoryHistory(); render( - - - + + + + + ); }); diff --git a/packages/jaeger-ui/src/components/App/TraceIDSearchInput.tsx b/packages/jaeger-ui/src/components/App/TraceIDSearchInput.tsx index c42ec6c804..88deab9dda 100644 --- a/packages/jaeger-ui/src/components/App/TraceIDSearchInput.tsx +++ b/packages/jaeger-ui/src/components/App/TraceIDSearchInput.tsx @@ -18,14 +18,15 @@ import { Form } from '@ant-design/compatible'; import '@ant-design/compatible/assets/index.css'; import { Input } from 'antd'; import { IoSearch } from 'react-icons/io5'; -import { RouteComponentProps, Router as RouterHistory, withRouter } from 'react-router-dom'; +import { History } from 'history'; import { getUrl } from '../TracePage/url'; import './TraceIDSearchInput.css'; +import withRouteProps from '../../utils/withRouteProps'; -type Props = RouteComponentProps & { - history: RouterHistory; +type Props = { + history: History; }; class TraceIDSearchInput extends React.PureComponent { @@ -57,4 +58,4 @@ class TraceIDSearchInput extends React.PureComponent { } } -export default withRouter(TraceIDSearchInput); +export default withRouteProps(TraceIDSearchInput); diff --git a/packages/jaeger-ui/src/components/App/__snapshots__/index.test.js.snap b/packages/jaeger-ui/src/components/App/__snapshots__/index.test.js.snap index 3eed611789..2526466610 100644 --- a/packages/jaeger-ui/src/components/App/__snapshots__/index.test.js.snap +++ b/packages/jaeger-ui/src/components/App/__snapshots__/index.test.js.snap @@ -12,7 +12,7 @@ exports[`JaegerUIApp does not explode 1`] = ` } } > - - - - + + + - + - + - + - + - + - - - - - - - - + path="/quality-metrics" + /> + + + + + + + + + `; diff --git a/packages/jaeger-ui/src/components/App/index.jsx b/packages/jaeger-ui/src/components/App/index.jsx index 2c7580d5b8..91dbe3bf5b 100644 --- a/packages/jaeger-ui/src/components/App/index.jsx +++ b/packages/jaeger-ui/src/components/App/index.jsx @@ -40,6 +40,7 @@ import '../common/vars.css'; import '../common/utils.css'; import './index.css'; import { history, store } from '../../utils/configure-store'; +import { HistoryProvider } from '../../utils/useHistory'; export default class JaegerUIApp extends Component { constructor(props) { @@ -51,25 +52,27 @@ export default class JaegerUIApp extends Component { render() { return ( - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - + + + + + ); } diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/__snapshots__/index.test.js.snap b/packages/jaeger-ui/src/components/DeepDependencies/Header/__snapshots__/index.test.js.snap index 1b6b5af12f..4671f631f1 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/__snapshots__/index.test.js.snap +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/__snapshots__/index.test.js.snap @@ -36,7 +36,7 @@ exports[`
renders the hops selector if distanceToPathElems is provided 1 - renders the operation selector iff a service is selected 1`] = - renders the operation selector iff a service is selected 2`] = - renders with minimal props 1`] = ` - void; @@ -98,7 +99,7 @@ export function createBlob(rawTraces: TraceData[]) { return new Blob([`{"data":${JSON.stringify(rawTraces)}}`], { type: 'application/json' }); } -export class UnconnectedSearchResults extends React.PureComponent { +export class UnconnectedSearchResults extends React.PureComponent { static defaultProps = { skipMessage: false, spanLinks: undefined, queryOfResults: undefined }; toggleComparison = (traceID: string, remove?: boolean) => { @@ -245,4 +246,4 @@ export class UnconnectedSearchResults extends React.PureComponent - void; }; +type RouteProps = { + location: Location; + history: History; +}; + type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TDispatchProps & TExtractUiFindFromStateReturn & TTraceTimeline & - RouteComponentProps; + RouteProps; // export for tests export const DEFAULT_HEIGHTS = { @@ -496,14 +502,12 @@ function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { return bindActionCreators(actions, dispatch) as any as TDispatchProps; } -export default withRouter( - connect< - TTraceTimeline & TExtractUiFindFromStateReturn, - TDispatchProps, - TVirtualizedTraceViewOwnProps, - ReduxState - >( - mapStateToProps, - mapDispatchToProps - )(VirtualizedTraceViewImpl) -); +export default connect< + TTraceTimeline & TExtractUiFindFromStateReturn, + TDispatchProps, + TVirtualizedTraceViewOwnProps, + ReduxState +>( + mapStateToProps, + mapDispatchToProps +)(withRouteProps(VirtualizedTraceViewImpl)); diff --git a/packages/jaeger-ui/src/components/common/UiFindInput.tsx b/packages/jaeger-ui/src/components/common/UiFindInput.tsx index a261a5d6d1..462f7c4511 100644 --- a/packages/jaeger-ui/src/components/common/UiFindInput.tsx +++ b/packages/jaeger-ui/src/components/common/UiFindInput.tsx @@ -19,13 +19,13 @@ import { History as RouterHistory, Location } from 'history'; import _debounce from 'lodash/debounce'; import _isString from 'lodash/isString'; import { connect } from 'react-redux'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; import updateUiFind from '../../utils/update-ui-find'; import { TNil, ReduxState } from '../../types/index'; import parseQuery from '../../utils/parseQuery'; +import withRouteProps from '../../utils/withRouteProps'; -type TOwnProps = RouteComponentProps & { +type TOwnProps = { allowClear?: boolean; forwardedRef?: React.Ref; inputProps: Record; @@ -115,4 +115,4 @@ export function extractUiFindFromState(state: ReduxState): TExtractUiFindFromSta return { uiFind }; } -export default withRouter(connect(extractUiFindFromState)(UnconnectedUiFindInput) as any); +export default connect(extractUiFindFromState)(withRouteProps(UnconnectedUiFindInput)) as any; diff --git a/packages/jaeger-ui/src/utils/useHistory.test.js b/packages/jaeger-ui/src/utils/useHistory.test.js new file mode 100644 index 0000000000..86ccd888bf --- /dev/null +++ b/packages/jaeger-ui/src/utils/useHistory.test.js @@ -0,0 +1,33 @@ +// Copyright (c) 2023 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { render } from '@testing-library/react'; +import { useHistory, HistoryProvider } from './useHistory'; + +describe('useHistory', () => { + it('should return the history object from the context', () => { + const history = { push: jest.fn() }; + const TestComponent = () => { + const historyFromContext = useHistory(); + expect(historyFromContext).toEqual(history); + return null; + }; + render( + + + + ); + }); +}); diff --git a/packages/jaeger-ui/src/utils/useHistory.tsx b/packages/jaeger-ui/src/utils/useHistory.tsx new file mode 100644 index 0000000000..9944c59754 --- /dev/null +++ b/packages/jaeger-ui/src/utils/useHistory.tsx @@ -0,0 +1,30 @@ +// Copyright (c) 2023 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React, { ReactNode, createContext, useContext, FC } from 'react'; +import { History } from 'history'; + +const HistoryContext = createContext(undefined); +interface IHistoryProviderProps { + children: ReactNode; + history: History; +} + +export const useHistory = (): History | undefined => { + return useContext(HistoryContext); +}; + +export const HistoryProvider: FC = ({ children, history }) => { + return {children}; +}; diff --git a/packages/jaeger-ui/src/utils/withRouteProps.test.js b/packages/jaeger-ui/src/utils/withRouteProps.test.js new file mode 100644 index 0000000000..c9dfdad0f0 --- /dev/null +++ b/packages/jaeger-ui/src/utils/withRouteProps.test.js @@ -0,0 +1,62 @@ +// Copyright (c) 2023 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { render } from '@testing-library/react'; +import { MemoryRouter, Route } from 'react-router-dom'; +import withRouteProps from './withRouteProps'; +import { useHistory, HistoryProvider } from './useHistory'; + +jest.mock('./useHistory', () => ({ + useHistory: jest.fn(), + HistoryProvider: ({ children }) =>
{children}
, +})); + +describe('withRouteProps', () => { + test('passes route props to WrappedComponent', () => { + const mockHistory = { + push: jest.fn(), + replace: jest.fn(), + location: { pathname: '/test', search: '?param=value' }, + }; + + useHistory.mockReturnValue(mockHistory); + + const WrappedComponent = jest.fn(() => null); + const ComponentWithRouteProps = withRouteProps(WrappedComponent); + render( + + + + + + + + ); + + expect(WrappedComponent).toHaveBeenCalledWith( + expect.objectContaining({ + location: expect.objectContaining({ + pathname: '/test', + search: '?param=value', + }), + pathname: '/test', + search: '?param=value', + params: {}, + history: mockHistory, + }), + {} + ); + }); +}); diff --git a/packages/jaeger-ui/src/utils/withRouteProps.tsx b/packages/jaeger-ui/src/utils/withRouteProps.tsx new file mode 100644 index 0000000000..d6ee8fa763 --- /dev/null +++ b/packages/jaeger-ui/src/utils/withRouteProps.tsx @@ -0,0 +1,46 @@ +// Copyright (c) 2023 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { useLocation, useParams } from 'react-router-dom'; +import { History, Location } from 'history'; +import { useHistory } from './useHistory'; + +export type IWithRouteProps = { + location: Location; + pathname: string; + search: string; + params: object; + history: History; +}; + +export default function withRouteProps(WrappedComponent: React.ElementType) { + return function WithRouteProps(props: IWithRouteProps | object) { + const location = useLocation(); + const params = useParams(); + const { pathname, search } = location; + const history = useHistory(); + + return ( + + ); + }; +} diff --git a/yarn.lock b/yarn.lock index 5460c1d1cb..c0fbd5d320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2616,12 +2616,19 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@16.9.18", "@types/react-dom@18.2.5", "@types/react-dom@^18.0.0": - version "16.9.18" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.18.tgz#1fda8b84370b1339d639a797a84c16d5a195b419" - integrity sha512-lmNARUX3+rNF/nmoAFqasG0jAA7q6MeGZK/fdeLwY3kAA4NPgHHrG5bNQe2B5xmD4B+x6Z6h0rEJQ7MEEgQxsw== +"@types/react-dom@18.2.5": + version "18.2.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.5.tgz#5c5f13548bda23cd98f50ca4a59107238bfe18f3" + integrity sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ== dependencies: - "@types/react" "^16" + "@types/react" "*" + +"@types/react-dom@^18.0.0": + version "18.2.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.8.tgz#338f1b0a646c9f10e0a97208c1d26b9f473dffd6" + integrity sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw== + dependencies: + "@types/react" "*" "@types/react-helmet@^6.1.5": version "6.1.5" @@ -2630,10 +2637,10 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^4.3.1": - version "4.3.5" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f" - integrity sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA== +"@types/react-router-dom@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.0.tgz#8baa84a7fa8c8e7797fb3650ca51f93038cb4caf" + integrity sha512-YCh8r71pL5p8qDwQf59IU13hFy/41fDQG/GeOI3y+xmD4o0w3vEPxE8uBe+dvOgMoDl0W1WUZsWH0pxc1mcZyQ== dependencies: "@types/history" "*" "@types/react" "*" @@ -2653,7 +2660,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.14.35", "@types/react@16.8.7", "@types/react@^16": +"@types/react@*", "@types/react@16.14.35": version "16.14.35" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.35.tgz#9d3cf047d85aca8006c4776693124a5be90ee429" integrity sha512-NUEiwmSS1XXtmBcsm1NyRRPYjoZF2YTE89/5QiLt5mlGffYK9FQqOKuOLuXNrjPQV04oQgaZG+Yq02ZfHoFyyg== @@ -2662,6 +2669,14 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@16.8.7": + version "16.8.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.7.tgz#7b1c0223dd5494f9b4501ad2a69aa6acb350a29b" + integrity sha512-0xbkIyrDNKUn4IJVf8JaCn+ucao/cq6ZB8O6kSzhrJub1cVSqgTArtG0qCfdERWKMEIvUbrwLXeQMqWEsyr9dA== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/redux-actions@2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.2.1.tgz#c1f4a7283ecd3cd696291550361e441bf9389370" @@ -4526,6 +4541,11 @@ cssstyle@^3.0.0: dependencies: rrweb-cssom "^0.6.0" +csstype@^2.2.0: + version "2.6.21" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" + integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== + csstype@^3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" @@ -8344,7 +8364,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@4.17.21, lodash@^4.16.5, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.16.5, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==