Routing: Replace Redirect component with Navigate (#94072)

* Routing: Replace Redirect with Navigate

* Use replace state

* Update routes.tsx

* Fix test
pull/94136/head
Alex Khomenko 10 months ago committed by GitHub
parent 28d9cc7310
commit f55f7f2634
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      public/app/AppWrapper.tsx
  2. 5
      public/app/core/components/FormPrompt/FormPrompt.tsx
  3. 5
      public/app/features/alerting/unified/MuteTimings.tsx
  4. 6
      public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx
  5. 6
      public/app/features/alerting/unified/RedirectToRuleViewer.tsx
  6. 5
      public/app/features/alerting/unified/components/rules/CloneRule.tsx
  7. 17
      public/app/features/connections/Connections.tsx
  8. 4
      public/app/features/trails/DataTrailsHome.tsx
  9. 36
      public/app/routes/routes.tsx

@ -1,8 +1,8 @@
import { Action, KBarProvider } from 'kbar'; import { Action, KBarProvider } from 'kbar';
import { Component, ComponentType } from 'react'; import { Component, ComponentType } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Switch, RouteComponentProps } from 'react-router-dom';
import { CompatRoute } from 'react-router-dom-v5-compat'; import { CompatRoute, Navigate } from 'react-router-dom-v5-compat';
import { config, navigationLogger, reportInteraction } from '@grafana/runtime'; import { config, navigationLogger, reportInteraction } from '@grafana/runtime';
import { ErrorBoundaryAlert, GlobalStyles, PortalContainer } from '@grafana/ui'; import { ErrorBoundaryAlert, GlobalStyles, PortalContainer } from '@grafana/ui';
@ -67,7 +67,7 @@ export class AppWrapper extends Component<AppWrapperProps, AppWrapperState> {
// TODO[Router]: test this logic // TODO[Router]: test this logic
if (roles?.length) { if (roles?.length) {
if (!roles.some((r: string) => contextSrv.hasRole(r))) { if (!roles.some((r: string) => contextSrv.hasRole(r))) {
return <Redirect to="/" />; return <Navigate replace to="/" />;
} }
} }

@ -1,7 +1,8 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import history from 'history'; import history from 'history';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Prompt, Redirect } from 'react-router-dom'; import { Prompt } from 'react-router-dom';
import { Navigate } from 'react-router-dom-v5-compat';
import { Button, Modal } from '@grafana/ui'; import { Button, Modal } from '@grafana/ui';
@ -80,7 +81,7 @@ export const FormPrompt = ({ confirmRedirect, onDiscard, onLocationChange }: Pro
return ( return (
<> <>
<Prompt when={true} message={handleRedirect} /> <Prompt when={true} message={handleRedirect} />
{blockedLocation && changesDiscarded && <Redirect to={blockedLocation} />} {blockedLocation && changesDiscarded && <Navigate replace to={blockedLocation} />}
<UnsavedChangesModal isOpen={modalIsOpen} onDiscard={onDiscardChanges} onBackToForm={onBackToForm} /> <UnsavedChangesModal isOpen={modalIsOpen} onDiscard={onDiscardChanges} onBackToForm={onBackToForm} />
</> </>
); );

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; import { Route, Switch, useRouteMatch } from 'react-router-dom';
import { Navigate } from 'react-router-dom-v5-compat';
import { NavModelItem } from '@grafana/data'; import { NavModelItem } from '@grafana/data';
import { useGetMuteTiming } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings'; import { useGetMuteTiming } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
@ -23,7 +24,7 @@ const EditTimingRoute = () => {
}); });
if (!name) { if (!name) {
return <Redirect to="/alerting/routes" />; return <Navigate replace to="/alerting/routes" />;
} }
return ( return (

@ -12,9 +12,9 @@ import { getRulesSourceByName } from './utils/datasource';
jest.mock('./hooks/useCombinedRule'); jest.mock('./hooks/useCombinedRule');
jest.mock('./utils/datasource'); jest.mock('./utils/datasource');
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom'), ...jest.requireActual('react-router-dom-v5-compat'),
Redirect: jest.fn(({}) => `Redirected`), Navigate: jest.fn(({}) => `Redirected`),
})); }));
jest.mock('react-use', () => ({ jest.mock('react-use', () => ({

@ -1,6 +1,6 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Redirect } from 'react-router-dom'; import { Navigate } from 'react-router-dom-v5-compat';
import { useLocation } from 'react-use'; import { useLocation } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
@ -53,7 +53,7 @@ export function RedirectToRuleViewer(): JSX.Element | null {
} = useCloudCombinedRulesMatching(name, sourceName, { namespace, groupName: group }); } = useCloudCombinedRulesMatching(name, sourceName, { namespace, groupName: group });
if (!name || !sourceName) { if (!name || !sourceName) {
return <Redirect to="/notfound" />; return <Navigate replace to="/notfound" />;
} }
if (error) { if (error) {
@ -95,7 +95,7 @@ export function RedirectToRuleViewer(): JSX.Element | null {
if (rules.length === 1) { if (rules.length === 1) {
const [rule] = rules; const [rule] = rules;
const to = createViewLink(rulesSource, rule, '/alerting/list').replace(subUrl, ''); const to = createViewLink(rulesSource, rule, '/alerting/list').replace(subUrl, '');
return <Redirect to={to} />; return <Navigate replace to={to} />;
} }
if (rules.length === 0) { if (rules.length === 0) {

@ -1,5 +1,6 @@
import { forwardRef, useState } from 'react'; import { forwardRef, useState } from 'react';
import { Redirect, useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Navigate } from 'react-router-dom-v5-compat';
import { Button, ConfirmModal } from '@grafana/ui'; import { Button, ConfirmModal } from '@grafana/ui';
import { RuleIdentifier } from 'app/types/unified-alerting'; import { RuleIdentifier } from 'app/types/unified-alerting';
@ -33,7 +34,7 @@ export function RedirectToCloneRule({
returnTo: redirectTo ? returnTo : '', returnTo: redirectTo ? returnTo : '',
}); });
return <Redirect to={`/alerting/new?` + queryParams.toString()} push />; return <Navigate to={`/alerting/new?` + queryParams.toString()} replace={false} />;
} }
return ( return (

@ -1,4 +1,5 @@
import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; import { Route, Switch, useLocation } from 'react-router-dom';
import { Navigate } from 'react-router-dom-v5-compat';
import { DataSourcesRoutesContext } from 'app/features/datasources/state'; import { DataSourcesRoutesContext } from 'app/features/datasources/state';
import { StoreState, useSelector } from 'app/types'; import { StoreState, useSelector } from 'app/types';
@ -16,7 +17,8 @@ import {
function RedirectToAddNewConnection() { function RedirectToAddNewConnection() {
const { search } = useLocation(); const { search } = useLocation();
return ( return (
<Redirect <Navigate
replace
to={{ to={{
pathname: ROUTES.AddNewConnection, pathname: ROUTES.AddNewConnection,
search, search,
@ -40,7 +42,7 @@ export default function Connections() {
> >
<Switch> <Switch>
{/* Redirect to "Add new connection" by default */} {/* Redirect to "Add new connection" by default */}
<Route exact sensitive path={ROUTES.Base} component={() => <Redirect to={ROUTES.AddNewConnection} />} /> <Route exact sensitive path={ROUTES.Base} component={() => <Navigate replace to={ROUTES.AddNewConnection} />} />
<Route exact sensitive path={ROUTES.DataSources} component={DataSourcesListPage} /> <Route exact sensitive path={ROUTES.DataSources} component={DataSourcesListPage} />
<Route exact sensitive path={ROUTES.DataSourcesNew} component={NewDataSourcePage} /> <Route exact sensitive path={ROUTES.DataSourcesNew} component={NewDataSourcePage} />
<Route exact sensitive path={ROUTES.DataSourcesDetails} component={DataSourceDetailsPage} /> <Route exact sensitive path={ROUTES.DataSourcesDetails} component={DataSourceDetailsPage} />
@ -54,11 +56,14 @@ export default function Connections() {
{/* Redirect from earlier routes to updated routes */} {/* Redirect from earlier routes to updated routes */}
<Route exact path={ROUTES.ConnectDataOutdated} component={RedirectToAddNewConnection} /> <Route exact path={ROUTES.ConnectDataOutdated} component={RedirectToAddNewConnection} />
<Redirect from={`${ROUTES.Base}/your-connections/:page`} to={`${ROUTES.Base}/:page`} /> <Route
<Redirect from={ROUTES.YourConnectionsOutdated} to={ROUTES.DataSources} /> path={`${ROUTES.Base}/your-connections/:page`}
component={() => <Navigate replace to={`${ROUTES.Base}/:page`} />}
/>
<Route path={ROUTES.YourConnectionsOutdated} component={() => <Navigate replace to={ROUTES.DataSources} />} />
{/* Not found */} {/* Not found */}
<Route component={() => <Redirect to="/notfound" />} /> <Route component={() => <Navigate replace to="/notfound" />} />
</Switch> </Switch>
</DataSourcesRoutesContext.Provider> </DataSourcesRoutesContext.Provider>
); );

@ -1,6 +1,6 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useState } from 'react'; import { useState } from 'react';
import { Redirect } from 'react-router-dom'; import { Navigate } from 'react-router-dom-v5-compat';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
@ -57,7 +57,7 @@ export class DataTrailsHome extends SceneObjectBase<DataTrailsHomeState> {
// If there are no recent trails, don't show home page and create a new trail // If there are no recent trails, don't show home page and create a new trail
if (!getTrailStore().recent.length) { if (!getTrailStore().recent.length) {
const trail = newMetricsTrail(getDatasourceForNewTrail()); const trail = newMetricsTrail(getDatasourceForNewTrail());
return <Redirect to={getUrlForTrail(trail)} />; return <Navigate replace to={getUrlForTrail(trail)} />;
} }
return ( return (

@ -1,4 +1,4 @@
import { Redirect, RouteComponentProps } from 'react-router-dom'; import { Navigate, useParams } from 'react-router-dom-v5-compat';
import { isTruthy } from '@grafana/data'; import { isTruthy } from '@grafana/data';
import { NavLandingPage } from 'app/core/components/NavLandingPage/NavLandingPage'; import { NavLandingPage } from 'app/core/components/NavLandingPage/NavLandingPage';
@ -107,23 +107,19 @@ export function getAppRoutes(): RouteDescriptor[] {
}, },
{ {
path: DATASOURCES_ROUTES.List, path: DATASOURCES_ROUTES.List,
component: () => <Redirect to={CONNECTIONS_ROUTES.DataSources} />, component: () => <Navigate replace to={CONNECTIONS_ROUTES.DataSources} />,
}, },
{ {
path: DATASOURCES_ROUTES.Edit, path: DATASOURCES_ROUTES.Edit,
component: (props: RouteComponentProps<{ uid: string }>) => ( component: DataSourceEditRoute,
<Redirect to={CONNECTIONS_ROUTES.DataSourcesEdit.replace(':uid', props.match.params.uid)} />
),
}, },
{ {
path: DATASOURCES_ROUTES.Dashboards, path: DATASOURCES_ROUTES.Dashboards,
component: (props: RouteComponentProps<{ uid: string }>) => ( component: DataSourceDashboardRoute,
<Redirect to={CONNECTIONS_ROUTES.DataSourcesDashboards.replace(':uid', props.match.params.uid)} />
),
}, },
{ {
path: DATASOURCES_ROUTES.New, path: DATASOURCES_ROUTES.New,
component: () => <Redirect to={CONNECTIONS_ROUTES.DataSourcesNew} />, component: () => <Navigate replace to={CONNECTIONS_ROUTES.DataSourcesNew} />,
}, },
{ {
path: '/datasources/correlations', path: '/datasources/correlations',
@ -219,7 +215,7 @@ export function getAppRoutes(): RouteDescriptor[] {
{ {
path: '/org/users', path: '/org/users',
// Org users page has been combined with admin users // Org users page has been combined with admin users
component: () => <Redirect to={'/admin/users'} />, component: () => <Navigate replace to={'/admin/users'} />,
}, },
{ {
path: '/org/users/invite', path: '/org/users/invite',
@ -292,7 +288,7 @@ export function getAppRoutes(): RouteDescriptor[] {
() => () =>
import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/AuthProvidersListPage') import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/AuthProvidersListPage')
) )
: () => <Redirect to="/admin" />, : () => <Navigate replace to="/admin" />,
}, },
{ {
path: '/admin/authentication/ldap', path: '/admin/authentication/ldap',
@ -309,7 +305,7 @@ export function getAppRoutes(): RouteDescriptor[] {
? SafeDynamicImport( ? SafeDynamicImport(
() => import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/ProviderConfigPage') () => import(/* webpackChunkName: "AdminAuthentication" */ '../features/auth-config/ProviderConfigPage')
) )
: () => <Redirect to="/admin" />, : () => <Navigate replace to="/admin" />,
}, },
{ {
path: '/admin/settings', path: '/admin/settings',
@ -357,7 +353,7 @@ export function getAppRoutes(): RouteDescriptor[] {
? SafeDynamicImport( ? SafeDynamicImport(
() => import(/* webpackChunkName: "AdminFeatureTogglesPage" */ 'app/features/admin/AdminFeatureTogglesPage') () => import(/* webpackChunkName: "AdminFeatureTogglesPage" */ 'app/features/admin/AdminFeatureTogglesPage')
) )
: () => <Redirect to="/admin" />, : () => <Navigate replace to="/admin" />,
}, },
{ {
path: '/admin/storage/:path*', path: '/admin/storage/:path*',
@ -398,7 +394,7 @@ export function getAppRoutes(): RouteDescriptor[] {
{ {
path: '/verify', path: '/verify',
component: !config.verifyEmailEnabled component: !config.verifyEmailEnabled
? () => <Redirect to="/signup" /> ? () => <Navigate replace to="/signup" />
: SafeDynamicImport( : SafeDynamicImport(
() => import(/* webpackChunkName "VerifyEmailPage"*/ 'app/core/components/Signup/VerifyEmailPage') () => import(/* webpackChunkName "VerifyEmailPage"*/ 'app/core/components/Signup/VerifyEmailPage')
), ),
@ -408,7 +404,7 @@ export function getAppRoutes(): RouteDescriptor[] {
{ {
path: '/signup', path: '/signup',
component: config.disableUserSignUp component: config.disableUserSignUp
? () => <Redirect to="/login" /> ? () => <Navigate replace to="/login" />
: SafeDynamicImport(() => import(/* webpackChunkName "SignupPage"*/ 'app/core/components/Signup/SignupPage')), : SafeDynamicImport(() => import(/* webpackChunkName "SignupPage"*/ 'app/core/components/Signup/SignupPage')),
pageClass: 'login-page', pageClass: 'login-page',
chromeless: true, chromeless: true,
@ -556,3 +552,13 @@ export function getSupportBundleRoutes(cfg = config): RouteDescriptor[] {
}, },
]; ];
} }
function DataSourceDashboardRoute() {
const { uid = '' } = useParams();
return <Navigate replace to={CONNECTIONS_ROUTES.DataSourcesDashboards.replace(':uid', uid)} />;
}
function DataSourceEditRoute() {
const { uid = '' } = useParams();
return <Navigate replace to={CONNECTIONS_ROUTES.DataSourcesEdit.replace(':uid', uid)} />;
}

Loading…
Cancel
Save