Routing: Update connections to react router 6 (#94461)

* Update routes

* Update test

* Remove the useDataSourcesRoutes hook

* Remove DataSourcesRoutesContext

* Fix imports

* Fix more imports

* Add default

* Betterer
pull/94730/head
Alex Khomenko 9 months ago committed by GitHub
parent ba3629c01c
commit e5795c7b6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 22
      public/app/features/connections/Connections.test.tsx
  3. 68
      public/app/features/connections/Connections.tsx
  4. 4
      public/app/features/connections/hooks/useDataSourceSettingsNav.ts
  5. 4
      public/app/features/connections/pages/DataSourceDashboardsPage.tsx
  6. 4
      public/app/features/connections/pages/DataSourceDetailsPage.tsx
  7. 4
      public/app/features/connections/pages/EditDataSourcePage.tsx
  8. 4
      public/app/features/connections/routes.tsx
  9. 7
      public/app/features/connections/tabs/ConnectData/ConnectData.tsx
  10. 6
      public/app/features/datasources/components/DataSourceAddButton.tsx
  11. 6
      public/app/features/datasources/components/DataSourcesList.tsx
  12. 5
      public/app/features/datasources/components/DataSourcesListCard.tsx
  13. 10
      public/app/features/datasources/components/NewDataSource.tsx
  14. 8
      public/app/features/datasources/state/contexts.ts
  15. 14
      public/app/features/datasources/state/hooks.ts
  16. 1
      public/app/features/datasources/state/index.ts
  17. 8
      public/app/features/plugins/admin/components/GetStartedWithPlugin/GetStartedWithDataSource.tsx

@ -3513,8 +3513,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "2"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "3"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "4"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"],
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "6"]
[0, 0, 0, "Do not use export all (\`export * from ...\`)", "5"]
],
"public/app/features/datasources/state/navModel.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],

@ -1,7 +1,7 @@
import { render, RenderResult, screen } from '@testing-library/react';
import { TestProvider } from 'test/helpers/TestProvider';
import { RenderResult, screen } from '@testing-library/react';
import { Route, Routes } from 'react-router-dom-v5-compat';
import { render } from 'test/test-utils';
import { locationService } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { getMockDataSources } from 'app/features/datasources/__mocks__';
import * as api from 'app/features/datasources/api';
@ -11,7 +11,7 @@ import { getPluginsStateMock } from '../plugins/admin/__mocks__';
import Connections from './Connections';
import { navIndex } from './__mocks__/store.navIndex.mock';
import { ROUTE_BASE_ID, ROUTES } from './constants';
import { ROUTES } from './constants';
jest.mock('app/core/services/context_srv');
jest.mock('app/features/datasources/api');
@ -21,15 +21,17 @@ jest.mock('@grafana/runtime', () => ({
}));
const renderPage = (
path = `/${ROUTE_BASE_ID}`,
path: string = ROUTES.Base,
store = configureStore({ navIndex, plugins: getPluginsStateMock([]) })
): RenderResult => {
locationService.push(path);
return render(
<TestProvider store={store}>
<Connections />
</TestProvider>
<Routes>
<Route path={`${ROUTES.Base}/*`} element={<Connections />} />
</Routes>,
{
store,
historyOptions: { initialEntries: [path] },
}
);
};

@ -1,7 +1,5 @@
import { Route, Switch, useLocation } from 'react-router-dom';
import { Navigate } from 'react-router-dom-v5-compat';
import { Navigate, Routes, Route, useLocation } from 'react-router-dom-v5-compat';
import { DataSourcesRoutesContext } from 'app/features/datasources/state';
import { StoreState, useSelector } from 'app/types';
import { ROUTES } from './constants';
@ -32,39 +30,43 @@ export default function Connections() {
const isAddNewConnectionPageOverridden = Boolean(navIndex['standalone-plugin-page-/connections/add-new-connection']);
return (
<DataSourcesRoutesContext.Provider
value={{
New: ROUTES.DataSourcesNew,
List: ROUTES.DataSources,
Edit: ROUTES.DataSourcesEdit,
Dashboards: ROUTES.DataSourcesDashboards,
}}
>
<Switch>
{/* Redirect to "Add new connection" by default */}
<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.DataSourcesNew} component={NewDataSourcePage} />
<Route exact sensitive path={ROUTES.DataSourcesDetails} component={DataSourceDetailsPage} />
<Route exact sensitive path={ROUTES.DataSourcesEdit} component={EditDataSourcePage} />
<Route exact sensitive path={ROUTES.DataSourcesDashboards} component={DataSourceDashboardsPage} />
{/* "Add new connection" page - we don't register a route in case a plugin already registers a standalone page for it */}
{!isAddNewConnectionPageOverridden && (
<Route exact sensitive path={ROUTES.AddNewConnection} component={AddNewConnectionPage} />
)}
<Routes>
{/* Redirect to "Add new connection" by default */}
<Route caseSensitive path={'/'} element={<Navigate replace to={ROUTES.AddNewConnection} />} />
{/* The route paths need to be relative to the parent path (ROUTES.Base), so we need to remove that part */}
<Route caseSensitive path={ROUTES.DataSources.replace(ROUTES.Base, '')} element={<DataSourcesListPage />} />
<Route caseSensitive path={ROUTES.DataSourcesNew.replace(ROUTES.Base, '')} element={<NewDataSourcePage />} />
<Route
caseSensitive
path={ROUTES.DataSourcesDetails.replace(ROUTES.Base, '')}
element={<DataSourceDetailsPage />}
/>
<Route caseSensitive path={ROUTES.DataSourcesEdit.replace(ROUTES.Base, '')} element={<EditDataSourcePage />} />
<Route
caseSensitive
path={ROUTES.DataSourcesDashboards.replace(ROUTES.Base, '')}
element={<DataSourceDashboardsPage />}
/>
{/* Redirect from earlier routes to updated routes */}
<Route exact path={ROUTES.ConnectDataOutdated} component={RedirectToAddNewConnection} />
{/* "Add new connection" page - we don't register a route in case a plugin already registers a standalone page for it */}
{!isAddNewConnectionPageOverridden && (
<Route
path={`${ROUTES.Base}/your-connections/:page`}
component={() => <Navigate replace to={`${ROUTES.Base}/:page`} />}
caseSensitive
path={ROUTES.AddNewConnection.replace(ROUTES.Base, '')}
element={<AddNewConnectionPage />}
/>
<Route path={ROUTES.YourConnectionsOutdated} component={() => <Navigate replace to={ROUTES.DataSources} />} />
)}
{/* Redirect from earlier routes to updated routes */}
<Route path={ROUTES.ConnectDataOutdated.replace(ROUTES.Base, '')} element={<RedirectToAddNewConnection />} />
<Route path={`/your-connections/:page`} element={<Navigate replace to={`${ROUTES.Base}/:page`} />} />
<Route
path={ROUTES.YourConnectionsOutdated.replace(ROUTES.Base, '')}
element={<Navigate replace to={ROUTES.DataSources} />}
/>
{/* Not found */}
<Route component={() => <Navigate replace to="/notfound" />} />
</Switch>
</DataSourcesRoutesContext.Provider>
{/* Not found */}
<Route element={<Navigate replace to="/notfound" />} />
</Routes>
);
}

@ -1,4 +1,4 @@
import { useLocation, useParams } from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom-v5-compat';
import { NavModel, NavModelItem } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
@ -9,7 +9,7 @@ import { useGetSingle } from 'app/features/plugins/admin/state/hooks';
import { useSelector } from 'app/types';
export function useDataSourceSettingsNav(pageIdParam?: string) {
const { uid } = useParams<{ uid: string }>();
const { uid = '' } = useParams<{ uid: string }>();
const location = useLocation();
const datasource = useDataSource(uid);
const dataSourceMeta = useDataSourceMeta(datasource.type);

@ -1,4 +1,4 @@
import { useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom-v5-compat';
import { Page } from 'app/core/components/Page/Page';
import { DataSourceDashboards } from 'app/features/datasources/components/DataSourceDashboards';
@ -6,7 +6,7 @@ import { DataSourceDashboards } from 'app/features/datasources/components/DataSo
import { useDataSourceSettingsNav } from '../hooks/useDataSourceSettingsNav';
export function DataSourceDashboardsPage() {
const { uid } = useParams<{ uid: string }>();
const { uid = '' } = useParams<{ uid: string }>();
const { navId, pageNav } = useDataSourceSettingsNav('dashboards');
return (

@ -1,4 +1,4 @@
import { useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom-v5-compat';
import { Alert, Badge } from '@grafana/ui';
import { PluginDetailsPage } from 'app/features/plugins/admin/components/PluginDetailsPage';
@ -8,7 +8,7 @@ import { ROUTES } from '../constants';
export function DataSourceDetailsPage() {
const overrideNavId = 'standalone-plugin-page-/connections/add-new-connection';
const { id } = useParams<{ id: string }>();
const { id = '' } = useParams<{ id: string }>();
const navIndex = useSelector((state: StoreState) => state.navIndex);
const isConnectDataPageOverriden = Boolean(navIndex[overrideNavId]);
const navId = isConnectDataPageOverriden ? overrideNavId : 'connections-add-new-connection'; // The nav id changes (gets a prefix) if it is overriden by a plugin

@ -1,9 +1,9 @@
import { useLocation, useParams } from 'react-router-dom';
import { useLocation, useParams } from 'react-router-dom-v5-compat';
import DataSourceTabPage from 'app/features/datasources/components/DataSourceTabPage';
export function EditDataSourcePage() {
const { uid } = useParams<{ uid: string }>();
const { uid = '' } = useParams<{ uid: string }>();
const location = useLocation();
const params = new URLSearchParams(location.search);
const pageId = params.get('page');

@ -1,12 +1,12 @@
import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport';
import { RouteDescriptor } from 'app/core/navigation/types';
import { ROUTE_BASE_ID } from './constants';
import { ROUTES } from './constants';
export function getRoutes(): RouteDescriptor[] {
return [
{
path: `/${ROUTE_BASE_ID}`,
path: ROUTES.Base,
exact: false,
component: SafeDynamicImport(
() => import(/* webpackChunkName: "Connections"*/ 'app/features/connections/Connections')

@ -1,6 +1,5 @@
import { css } from '@emotion/css';
import { useMemo, useState } from 'react';
import * as React from 'react';
import { useMemo, useState, FormEvent, MouseEvent } from 'react';
import { GrafanaTheme2, PluginType } from '@grafana/data';
import { useStyles2, LoadingPlaceholder, EmptyState } from '@grafana/ui';
@ -38,7 +37,7 @@ export function AddNewConnection() {
const styles = useStyles2(getStyles);
const canCreateDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
const handleSearchChange = (e: FormEvent<HTMLInputElement>) => {
setQueryParams({
search: e.currentTarget.value.toLowerCase(),
});
@ -62,7 +61,7 @@ export function AddNewConnection() {
[plugins]
);
const onClickCardGridItem = (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => {
const onClickCardGridItem = (e: MouseEvent<HTMLElement>, item: CardGridItem) => {
if (!canCreateDataSources) {
e.preventDefault();
e.stopPropagation();

@ -2,16 +2,14 @@ import { config } from '@grafana/runtime';
import { LinkButton } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { Trans } from 'app/core/internationalization';
import { ROUTES } from 'app/features/connections/constants';
import { AccessControlAction } from 'app/types';
import { useDataSourcesRoutes } from '../state';
export function DataSourceAddButton(): JSX.Element | null {
const canCreateDataSource = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
const dataSourcesRoutes = useDataSourcesRoutes();
return canCreateDataSource ? (
<LinkButton icon="plus" href={config.appSubUrl + dataSourcesRoutes.New}>
<LinkButton icon="plus" href={config.appSubUrl + ROUTES.DataSourcesNew}>
<Trans i18nKey="data-sources.datasource-add-button.label">Add new data source</Trans>
</LinkButton>
) : null;

@ -9,7 +9,8 @@ import { contextSrv } from 'app/core/core';
import { Trans, t } from 'app/core/internationalization';
import { StoreState, AccessControlAction, useSelector } from 'app/types';
import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state';
import { ROUTES } from '../../connections/constants';
import { getDataSources, getDataSourcesCount, useLoadDataSources } from '../state';
import { trackDataSourcesListViewed } from '../tracking';
import { DataSourcesListCard } from './DataSourcesListCard';
@ -54,7 +55,6 @@ export function DataSourcesListView({
hasExploreRights,
}: ViewProps) {
const styles = useStyles2(getStyles);
const dataSourcesRoutes = useDataSourcesRoutes();
const location = useLocation();
useEffect(() => {
@ -69,7 +69,7 @@ export function DataSourcesListView({
<EmptyState
variant="call-to-action"
button={
<LinkButton disabled={!hasCreateRights} href={dataSourcesRoutes.New} icon="database" size="lg">
<LinkButton disabled={!hasCreateRights} href={ROUTES.DataSourcesNew} icon="database" size="lg">
<Trans i18nKey="data-source-list.empty-state.button-title">Add data source</Trans>
</LinkButton>
}

@ -5,7 +5,7 @@ import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Card, LinkButton, Stack, Tag, useStyles2 } from '@grafana/ui';
import { useDataSourcesRoutes } from '../state';
import { ROUTES } from '../../connections/constants';
import { trackCreateDashboardClicked, trackExploreClicked } from '../tracking';
import { constructDataSourceExploreUrl } from '../utils';
@ -16,8 +16,7 @@ export interface Props {
}
export function DataSourcesListCard({ dataSource, hasWriteRights, hasExploreRights }: Props) {
const dataSourcesRoutes = useDataSourcesRoutes();
const dsLink = config.appSubUrl + dataSourcesRoutes.Edit.replace(/:uid/gi, dataSource.uid);
const dsLink = config.appSubUrl + ROUTES.DataSourcesEdit.replace(/:uid/gi, dataSource.uid);
const styles = useStyles2(getStyles);
return (

@ -1,4 +1,4 @@
import { AnyAction } from 'redux';
import { Action } from 'redux';
import { DataSourcePluginMeta, PluginType } from '@grafana/data';
import { LinkButton, FilterInput } from '@grafana/ui';
@ -6,6 +6,7 @@ import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { PluginsErrorsInfo } from 'app/features/plugins/components/PluginsErrorsInfo';
import { DataSourcePluginCategory, StoreState, useDispatch, useSelector } from 'app/types';
import { ROUTES } from '../../connections/constants';
import { DataSourceCategories } from '../components/DataSourceCategories';
import { DataSourceTypeCardList } from '../components/DataSourceTypeCardList';
import {
@ -13,7 +14,6 @@ import {
useLoadDataSourcePlugins,
getFilteredDataSourcePlugins,
setDataSourceTypeSearchQuery,
useDataSourcesRoutes,
} from '../state';
export function NewDataSource() {
@ -45,7 +45,7 @@ export type ViewProps = {
searchQuery: string;
isLoading: boolean;
onAddDataSource: (dataSource: DataSourcePluginMeta) => void;
onSetSearchQuery: (q: string) => AnyAction;
onSetSearchQuery: (q: string) => Action;
};
export function NewDataSourceView({
@ -56,8 +56,6 @@ export function NewDataSourceView({
onAddDataSource,
onSetSearchQuery,
}: ViewProps) {
const dataSourcesRoutes = useDataSourcesRoutes();
if (isLoading) {
return <PageLoader />;
}
@ -68,7 +66,7 @@ export function NewDataSourceView({
<div className="page-action-bar">
<FilterInput value={searchQuery} onChange={onSetSearchQuery} placeholder="Filter by name or type" />
<div className="page-action-bar__spacer" />
<LinkButton href={dataSourcesRoutes.List} fill="outline" variant="secondary" icon="arrow-left">
<LinkButton href={ROUTES.DataSources} fill="outline" variant="secondary" icon="arrow-left">
Cancel
</LinkButton>
</div>

@ -1,8 +0,0 @@
import { createContext } from 'react';
import { DATASOURCES_ROUTES } from '../constants';
import { DataSourcesRoutes } from '../types';
// The purpose of this context is to be able to override the data-sources routes (used for links for example) used under
// the app/features/datasources modules, so we can reuse them more easily in different parts of the application (e.g. under Connections)
export const DataSourcesRoutesContext = createContext<DataSourcesRoutes>(DATASOURCES_ROUTES);

@ -1,4 +1,4 @@
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
import { cleanUpAction } from 'app/core/actions/cleanUp';
@ -7,6 +7,7 @@ import { contextSrv } from 'app/core/core';
import { AccessControlAction, useDispatch, useSelector } from 'app/types';
import { ShowConfirmModalEvent } from 'app/types/events';
import { ROUTES } from '../../connections/constants';
import { DataSourceRights } from '../types';
import { constructDataSourceExploreUrl } from '../utils';
@ -20,7 +21,6 @@ import {
updateDataSource,
deleteLoadedDataSource,
} from './actions';
import { DataSourcesRoutesContext } from './contexts';
import { initialDataSourceSettingsState } from './reducers';
import { getDataSource, getDataSourceMeta } from './selectors';
@ -42,9 +42,8 @@ export const useInitDataSourceSettings = (uid: string) => {
export const useTestDataSource = (uid: string) => {
const dispatch = useDispatch();
const dataSourcesRoutes = useDataSourcesRoutes();
return () => dispatch(testDataSource(uid, dataSourcesRoutes.Edit));
return () => dispatch(testDataSource(uid, ROUTES.DataSourcesEdit));
};
export const useLoadDataSources = () => {
@ -77,10 +76,9 @@ export const useLoadDataSourcePlugins = () => {
export const useAddDatasource = () => {
const dispatch = useDispatch();
const dataSourcesRoutes = useDataSourcesRoutes();
return (plugin: DataSourcePluginMeta) => {
dispatch(addDataSource(plugin, dataSourcesRoutes.Edit));
dispatch(addDataSource(plugin, ROUTES.DataSourcesEdit));
};
};
@ -136,7 +134,3 @@ export const useDataSourceRights = (uid: string): DataSourceRights => {
hasDeleteRights,
};
};
export const useDataSourcesRoutes = () => {
return useContext(DataSourcesRoutesContext);
};

@ -1,6 +1,5 @@
export * from './actions';
export * from './buildCategories';
export * from './contexts';
export * from './hooks';
export * from './navModel';
export * from './reducers';

@ -4,7 +4,8 @@ import * as React from 'react';
import { DataSourcePluginMeta } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Button } from '@grafana/ui';
import { useDataSourcesRoutes, addDataSource } from 'app/features/datasources/state';
import { ROUTES } from 'app/features/connections/constants';
import { addDataSource } from 'app/features/datasources/state';
import { useDispatch } from 'app/types';
import { isDataSourceEditor } from '../../permissions';
@ -16,15 +17,14 @@ type Props = {
export function GetStartedWithDataSource({ plugin }: Props): React.ReactElement | null {
const dispatch = useDispatch();
const dataSourcesRoutes = useDataSourcesRoutes();
const onAddDataSource = useCallback(() => {
const meta = {
name: plugin.name,
id: plugin.id,
} as DataSourcePluginMeta;
dispatch(addDataSource(meta, dataSourcesRoutes.Edit));
}, [dispatch, plugin, dataSourcesRoutes]);
dispatch(addDataSource(meta, ROUTES.DataSourcesEdit));
}, [dispatch, plugin]);
if (!isDataSourceEditor()) {
return null;

Loading…
Cancel
Save