Refactor breadcrumbs params detection (#1394)
Co-authored-by: Oleg Shur <workshur@gmail.com>
This commit is contained in:
parent
38c4cf7dd9
commit
e569e46a8a
15 changed files with 264 additions and 154 deletions
|
@ -3,7 +3,6 @@ import { Switch, Route, useLocation } from 'react-router-dom';
|
||||||
import { GIT_TAG, GIT_COMMIT } from 'lib/constants';
|
import { GIT_TAG, GIT_COMMIT } from 'lib/constants';
|
||||||
import Nav from 'components/Nav/Nav';
|
import Nav from 'components/Nav/Nav';
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
|
||||||
import Dashboard from 'components/Dashboard/Dashboard';
|
import Dashboard from 'components/Dashboard/Dashboard';
|
||||||
import ClusterPage from 'components/Cluster/Cluster';
|
import ClusterPage from 'components/Cluster/Cluster';
|
||||||
import Version from 'components/Version/Version';
|
import Version from 'components/Version/Version';
|
||||||
|
@ -82,20 +81,14 @@ const App: React.FC = () => {
|
||||||
aria-label="Overlay"
|
aria-label="Overlay"
|
||||||
/>
|
/>
|
||||||
{areClustersFulfilled ? (
|
{areClustersFulfilled ? (
|
||||||
<>
|
<Switch>
|
||||||
<Breadcrumb />
|
<Route
|
||||||
<Switch>
|
exact
|
||||||
<Route
|
path={['/', '/ui', '/ui/clusters']}
|
||||||
exact
|
component={Dashboard}
|
||||||
path={['/', '/ui', '/ui/clusters']}
|
/>
|
||||||
component={Dashboard}
|
<Route path="/ui/clusters/:clusterName" component={ClusterPage} />
|
||||||
/>
|
</Switch>
|
||||||
<Route
|
|
||||||
path="/ui/clusters/:clusterName"
|
|
||||||
component={ClusterPage}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<PageLoader />
|
<PageLoader />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Switch, Route, Redirect, useParams } from 'react-router-dom';
|
import { Switch, Redirect, useParams } from 'react-router-dom';
|
||||||
import { ClusterFeaturesEnum } from 'generated-sources';
|
import { ClusterFeaturesEnum } from 'generated-sources';
|
||||||
import {
|
import {
|
||||||
getClustersFeatures,
|
getClustersFeatures,
|
||||||
|
@ -22,6 +22,9 @@ import ClusterContext from 'components/contexts/ClusterContext';
|
||||||
import Brokers from 'components/Brokers/Brokers';
|
import Brokers from 'components/Brokers/Brokers';
|
||||||
import ConsumersGroups from 'components/ConsumerGroups/ConsumerGroups';
|
import ConsumersGroups from 'components/ConsumerGroups/ConsumerGroups';
|
||||||
import KsqlDb from 'components/KsqlDb/KsqlDb';
|
import KsqlDb from 'components/KsqlDb/KsqlDb';
|
||||||
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
import { BreadcrumbProvider } from 'components/common/Breadcrumb/Breadcrumb.provider';
|
||||||
|
|
||||||
const Cluster: React.FC = () => {
|
const Cluster: React.FC = () => {
|
||||||
const { clusterName } = useParams<{ clusterName: string }>();
|
const { clusterName } = useParams<{ clusterName: string }>();
|
||||||
|
@ -50,41 +53,53 @@ const Cluster: React.FC = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClusterContext.Provider value={contextValue}>
|
<BreadcrumbProvider>
|
||||||
<Switch>
|
<Breadcrumb />
|
||||||
<Route path={clusterBrokersPath(':clusterName')} component={Brokers} />
|
<ClusterContext.Provider value={contextValue}>
|
||||||
<Route path={clusterTopicsPath(':clusterName')} component={Topics} />
|
<Switch>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
path={clusterConsumerGroupsPath(':clusterName')}
|
path={clusterBrokersPath(':clusterName')}
|
||||||
component={ConsumersGroups}
|
component={Brokers}
|
||||||
/>
|
|
||||||
{hasSchemaRegistryConfigured && (
|
|
||||||
<Route
|
|
||||||
path={clusterSchemasPath(':clusterName')}
|
|
||||||
component={Schemas}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<BreadcrumbRoute
|
||||||
{hasKafkaConnectConfigured && (
|
path={clusterTopicsPath(':clusterName')}
|
||||||
<Route
|
component={Topics}
|
||||||
path={clusterConnectsPath(':clusterName')}
|
|
||||||
component={Connect}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<BreadcrumbRoute
|
||||||
{hasKafkaConnectConfigured && (
|
path={clusterConsumerGroupsPath(':clusterName')}
|
||||||
<Route
|
component={ConsumersGroups}
|
||||||
path={clusterConnectorsPath(':clusterName')}
|
|
||||||
component={Connect}
|
|
||||||
/>
|
/>
|
||||||
)}
|
{hasSchemaRegistryConfigured && (
|
||||||
{hasKsqlDbConfigured && (
|
<BreadcrumbRoute
|
||||||
<Route path={clusterKsqlDbPath(':clusterName')} component={KsqlDb} />
|
path={clusterSchemasPath(':clusterName')}
|
||||||
)}
|
component={Schemas}
|
||||||
<Redirect
|
/>
|
||||||
from="/ui/clusters/:clusterName"
|
)}
|
||||||
to="/ui/clusters/:clusterName/brokers"
|
{hasKafkaConnectConfigured && (
|
||||||
/>
|
<BreadcrumbRoute
|
||||||
</Switch>
|
path={clusterConnectsPath(':clusterName')}
|
||||||
</ClusterContext.Provider>
|
component={Connect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{hasKafkaConnectConfigured && (
|
||||||
|
<BreadcrumbRoute
|
||||||
|
path={clusterConnectorsPath(':clusterName')}
|
||||||
|
component={Connect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{hasKsqlDbConfigured && (
|
||||||
|
<BreadcrumbRoute
|
||||||
|
path={clusterKsqlDbPath(':clusterName')}
|
||||||
|
component={KsqlDb}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Redirect
|
||||||
|
from="/ui/clusters/:clusterName"
|
||||||
|
to="/ui/clusters/:clusterName/brokers"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</ClusterContext.Provider>
|
||||||
|
</BreadcrumbProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
clusterConnectorsPath,
|
clusterConnectorsPath,
|
||||||
clusterConnectorNewPath,
|
clusterConnectorNewPath,
|
||||||
clusterConnectConnectorPath,
|
clusterConnectConnectorPath,
|
||||||
clusterConnectConnectorEditPath,
|
clusterConnectConnectorEditPath,
|
||||||
} from 'lib/paths';
|
} from 'lib/paths';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
|
||||||
import ListContainer from './List/ListContainer';
|
import ListContainer from './List/ListContainer';
|
||||||
import NewContainer from './New/NewContainer';
|
import NewContainer from './New/NewContainer';
|
||||||
|
@ -15,17 +16,17 @@ import EditContainer from './Edit/EditContainer';
|
||||||
const Connect: React.FC = () => (
|
const Connect: React.FC = () => (
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterConnectorsPath(':clusterName')}
|
path={clusterConnectorsPath(':clusterName')}
|
||||||
component={ListContainer}
|
component={ListContainer}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterConnectorNewPath(':clusterName')}
|
path={clusterConnectorNewPath(':clusterName')}
|
||||||
component={NewContainer}
|
component={NewContainer}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterConnectConnectorEditPath(
|
path={clusterConnectConnectorEditPath(
|
||||||
':clusterName',
|
':clusterName',
|
||||||
|
@ -34,7 +35,7 @@ const Connect: React.FC = () => (
|
||||||
)}
|
)}
|
||||||
component={EditContainer}
|
component={EditContainer}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
path={clusterConnectConnectorPath(
|
path={clusterConnectConnectorPath(
|
||||||
':clusterName',
|
':clusterName',
|
||||||
':connectName',
|
':connectName',
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`Connect matches snapshot 1`] = `
|
exports[`Connect matches snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
component={
|
component={
|
||||||
Object {
|
Object {
|
||||||
"$$typeof": Symbol(react.memo),
|
"$$typeof": Symbol(react.memo),
|
||||||
|
@ -15,17 +15,17 @@ exports[`Connect matches snapshot 1`] = `
|
||||||
exact={true}
|
exact={true}
|
||||||
path="/ui/clusters/:clusterName/connectors"
|
path="/ui/clusters/:clusterName/connectors"
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
component={[Function]}
|
component={[Function]}
|
||||||
exact={true}
|
exact={true}
|
||||||
path="/ui/clusters/:clusterName/connectors/create-new"
|
path="/ui/clusters/:clusterName/connectors/create-new"
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
component={[Function]}
|
component={[Function]}
|
||||||
exact={true}
|
exact={true}
|
||||||
path="/ui/clusters/:clusterName/connects/:connectName/connectors/:connectorName/edit"
|
path="/ui/clusters/:clusterName/connects/:connectName/connectors/:connectorName/edit"
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
component={[Function]}
|
component={[Function]}
|
||||||
path="/ui/clusters/:clusterName/connects/:connectName/connectors/:connectorName"
|
path="/ui/clusters/:clusterName/connects/:connectName/connectors/:connectorName"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ClusterName } from 'redux/interfaces';
|
import { ClusterName } from 'redux/interfaces';
|
||||||
import { Switch, Route, useParams } from 'react-router-dom';
|
import { Switch, useParams } from 'react-router-dom';
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import Details from 'components/ConsumerGroups/Details/Details';
|
import Details from 'components/ConsumerGroups/Details/Details';
|
||||||
import List from 'components/ConsumerGroups/List/List';
|
import List from 'components/ConsumerGroups/List/List';
|
||||||
|
@ -10,6 +10,7 @@ import {
|
||||||
fetchConsumerGroups,
|
fetchConsumerGroups,
|
||||||
getAreConsumerGroupsFulfilled,
|
getAreConsumerGroupsFulfilled,
|
||||||
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
|
||||||
const ConsumerGroups: React.FC = () => {
|
const ConsumerGroups: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -22,17 +23,17 @@ const ConsumerGroups: React.FC = () => {
|
||||||
if (isFetched) {
|
if (isFetched) {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path="/ui/clusters/:clusterName/consumer-groups"
|
path="/ui/clusters/:clusterName/consumer-groups"
|
||||||
component={List}
|
component={List}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID"
|
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID"
|
||||||
component={Details}
|
component={Details}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID/reset-offsets"
|
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID/reset-offsets"
|
||||||
component={ResetOffsets}
|
component={ResetOffsets}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||||
|
|
||||||
import ClustersWidgetContainer from './ClustersWidget/ClustersWidgetContainer';
|
import ClustersWidgetContainer from './ClustersWidget/ClustersWidgetContainer';
|
||||||
|
|
||||||
const Dashboard: React.FC = () => (
|
const Dashboard: React.FC = () => (
|
||||||
<div>
|
<>
|
||||||
|
<PageHeading text="Dashboard" />
|
||||||
<ClustersWidgetContainer />
|
<ClustersWidgetContainer />
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch } from 'react-router-dom';
|
||||||
import { clusterKsqlDbPath, clusterKsqlDbQueryPath } from 'lib/paths';
|
import { clusterKsqlDbPath, clusterKsqlDbQueryPath } from 'lib/paths';
|
||||||
import List from 'components/KsqlDb/List/List';
|
import List from 'components/KsqlDb/List/List';
|
||||||
import Query from 'components/KsqlDb/Query/Query';
|
import Query from 'components/KsqlDb/Query/Query';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
|
||||||
const KsqlDb: React.FC = () => {
|
const KsqlDb: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path={clusterKsqlDbPath()} component={List} />
|
<BreadcrumbRoute exact path={clusterKsqlDbPath()} component={List} />
|
||||||
<Route exact path={clusterKsqlDbQueryPath()} component={Query} />
|
<BreadcrumbRoute
|
||||||
|
exact
|
||||||
|
path={clusterKsqlDbQueryPath()}
|
||||||
|
component={Query}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route, useParams } from 'react-router-dom';
|
import { Switch, useParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
clusterSchemaNewPath,
|
clusterSchemaNewPath,
|
||||||
clusterSchemaPath,
|
clusterSchemaPath,
|
||||||
|
@ -16,6 +16,7 @@ import List from 'components/Schemas/List/List';
|
||||||
import Details from 'components/Schemas/Details/Details';
|
import Details from 'components/Schemas/Details/Details';
|
||||||
import New from 'components/Schemas/New/New';
|
import New from 'components/Schemas/New/New';
|
||||||
import Edit from 'components/Schemas/Edit/Edit';
|
import Edit from 'components/Schemas/Edit/Edit';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
|
||||||
const Schemas: React.FC = () => {
|
const Schemas: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -32,18 +33,22 @@ const Schemas: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path={clusterSchemasPath(':clusterName')} component={List} />
|
<BreadcrumbRoute
|
||||||
<Route
|
exact
|
||||||
|
path={clusterSchemasPath(':clusterName')}
|
||||||
|
component={List}
|
||||||
|
/>
|
||||||
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterSchemaNewPath(':clusterName')}
|
path={clusterSchemaNewPath(':clusterName')}
|
||||||
component={New}
|
component={New}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterSchemaPath(':clusterName', ':subject')}
|
path={clusterSchemaPath(':clusterName', ':subject')}
|
||||||
component={Details}
|
component={Details}
|
||||||
/>
|
/>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterSchemaEditPath(':clusterName', ':subject')}
|
path={clusterSchemaEditPath(':clusterName', ':subject')}
|
||||||
component={Edit}
|
component={Edit}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
clusterTopicNewPath,
|
clusterTopicNewPath,
|
||||||
clusterTopicPath,
|
clusterTopicPath,
|
||||||
clusterTopicsPath,
|
clusterTopicsPath,
|
||||||
} from 'lib/paths';
|
} from 'lib/paths';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
|
|
||||||
import ListContainer from './List/ListContainer';
|
import ListContainer from './List/ListContainer';
|
||||||
import TopicContainer from './Topic/TopicContainer';
|
import TopicContainer from './Topic/TopicContainer';
|
||||||
|
@ -12,13 +13,17 @@ import New from './New/New';
|
||||||
|
|
||||||
const Topics: React.FC = () => (
|
const Topics: React.FC = () => (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<BreadcrumbRoute
|
||||||
exact
|
exact
|
||||||
path={clusterTopicsPath(':clusterName')}
|
path={clusterTopicsPath(':clusterName')}
|
||||||
component={ListContainer}
|
component={ListContainer}
|
||||||
/>
|
/>
|
||||||
<Route exact path={clusterTopicNewPath(':clusterName')} component={New} />
|
<BreadcrumbRoute
|
||||||
<Route
|
exact
|
||||||
|
path={clusterTopicNewPath(':clusterName')}
|
||||||
|
component={New}
|
||||||
|
/>
|
||||||
|
<BreadcrumbRoute
|
||||||
path={clusterTopicPath(':clusterName', ':topicName')}
|
path={clusterTopicPath(':clusterName', ':topicName')}
|
||||||
component={TopicContainer}
|
component={TopicContainer}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
|
||||||
import { clusterTopicsPath, clusterTopicPath } from 'lib/paths';
|
|
||||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
clusterName: ClusterName;
|
|
||||||
topicName?: TopicName;
|
|
||||||
current: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormBreadcrumbs: React.FC<Props> = ({
|
|
||||||
clusterName,
|
|
||||||
topicName,
|
|
||||||
current,
|
|
||||||
}) => {
|
|
||||||
const allTopicsLink = {
|
|
||||||
href: clusterTopicsPath(clusterName),
|
|
||||||
label: 'All Topics',
|
|
||||||
};
|
|
||||||
const links = topicName
|
|
||||||
? [
|
|
||||||
allTopicsLink,
|
|
||||||
{ href: clusterTopicPath(clusterName, topicName), label: topicName },
|
|
||||||
]
|
|
||||||
: [allTopicsLink];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="level-item level-left">
|
|
||||||
<Breadcrumb links={links}>{current}</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FormBreadcrumbs;
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export interface BreadcrumbEntry {
|
||||||
|
link: string;
|
||||||
|
path: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BreadcrumbContextInterface extends BreadcrumbEntry {
|
||||||
|
handleRouteChange: (match: { url: string; path: string }) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BreadcrumbContext = createContext<BreadcrumbContextInterface>({
|
||||||
|
link: '',
|
||||||
|
path: [],
|
||||||
|
handleRouteChange: () => {},
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import capitalize from 'lodash/capitalize';
|
||||||
|
|
||||||
|
import { BreadcrumbContext, BreadcrumbEntry } from './Breadcrumb.context';
|
||||||
|
|
||||||
|
const mapLocationToPath = (
|
||||||
|
splittedLocation: string[],
|
||||||
|
splittedRoutePath: string[]
|
||||||
|
) =>
|
||||||
|
splittedLocation.map((item, index) =>
|
||||||
|
splittedRoutePath[index]?.charAt(0) !== ':'
|
||||||
|
? item.split('-').map(capitalize).join(' ')
|
||||||
|
: item
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BreadcrumbProvider: React.FC = ({ children }) => {
|
||||||
|
const [state, setState] = useState<BreadcrumbEntry>({
|
||||||
|
link: '',
|
||||||
|
path: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRouteChange = (params: { url: string; path: string }) => {
|
||||||
|
setState((prevState) => {
|
||||||
|
const newState = { ...prevState };
|
||||||
|
const splittedRoutePath = params.path.split('/');
|
||||||
|
const splittedLocation = params.url.split('/');
|
||||||
|
|
||||||
|
if (prevState.link !== params.url) {
|
||||||
|
newState.link = params.url;
|
||||||
|
newState.path = mapLocationToPath(splittedLocation, splittedRoutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevState.path.length < params.path.split('/').length) {
|
||||||
|
newState.path = mapLocationToPath(splittedLocation, splittedRoutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BreadcrumbContext.Provider
|
||||||
|
value={{
|
||||||
|
...state,
|
||||||
|
handleRouteChange,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</BreadcrumbContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { useContext, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Route,
|
||||||
|
RouteProps,
|
||||||
|
useLocation,
|
||||||
|
useRouteMatch,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import { BreadcrumbContext } from './Breadcrumb.context';
|
||||||
|
|
||||||
|
const BreadcrumbRouteInternal: React.FC = () => {
|
||||||
|
const match = useRouteMatch();
|
||||||
|
const location = useLocation();
|
||||||
|
const context = useContext(BreadcrumbContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
context.handleRouteChange({ ...match, url: location.pathname });
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BreadcrumbRoute: React.FC<RouteProps> = ({
|
||||||
|
children,
|
||||||
|
render,
|
||||||
|
component,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...props}
|
||||||
|
render={(routeParams) => {
|
||||||
|
if (component) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{React.createElement(component)}
|
||||||
|
<BreadcrumbRouteInternal />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (render) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{render(routeParams)}
|
||||||
|
<BreadcrumbRouteInternal />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<BreadcrumbRouteInternal />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,53 +1,34 @@
|
||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { clusterPath } from 'lib/paths';
|
import { clusterPath } from 'lib/paths';
|
||||||
import { capitalize } from 'lodash';
|
|
||||||
|
|
||||||
import { BreadcrumbWrapper } from './Breadcrumb.styled';
|
import { BreadcrumbWrapper } from './Breadcrumb.styled';
|
||||||
|
import { BreadcrumbContext } from './Breadcrumb.context';
|
||||||
export interface BreadcrumbItem {
|
|
||||||
label: string;
|
|
||||||
href: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
links?: BreadcrumbItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const basePathEntriesLength = clusterPath(':clusterName').split('/').length;
|
const basePathEntriesLength = clusterPath(':clusterName').split('/').length;
|
||||||
|
|
||||||
const Breadcrumb: React.FC<Props> = () => {
|
const Breadcrumb: React.FC = () => {
|
||||||
const location = useLocation();
|
const breadcrumbContext = useContext(BreadcrumbContext);
|
||||||
const params = useParams();
|
|
||||||
const pathParams = React.useMemo(() => Object.values(params), [params]);
|
|
||||||
|
|
||||||
const paths = location.pathname.split('/');
|
|
||||||
const links = React.useMemo(
|
const links = React.useMemo(
|
||||||
() =>
|
() => breadcrumbContext.path.slice(basePathEntriesLength),
|
||||||
paths.slice(basePathEntriesLength).map((path, index) => {
|
[breadcrumbContext.link]
|
||||||
return !pathParams.includes(paths[basePathEntriesLength + index])
|
|
||||||
? path.split('-').map(capitalize).join(' ')
|
|
||||||
: path;
|
|
||||||
}),
|
|
||||||
[paths]
|
|
||||||
);
|
);
|
||||||
const currentLink = React.useMemo(() => {
|
|
||||||
if (paths.length < basePathEntriesLength) {
|
|
||||||
return 'Dashboard';
|
|
||||||
}
|
|
||||||
return links[links.length - 1];
|
|
||||||
}, [links]);
|
|
||||||
|
|
||||||
const getPathPredicate = React.useCallback(
|
const getPathPredicate = React.useCallback(
|
||||||
(index: number) =>
|
(index: number) =>
|
||||||
`${paths.slice(0, basePathEntriesLength + index + 1).join('/')}`,
|
`${breadcrumbContext.link
|
||||||
[paths]
|
.split('/')
|
||||||
|
.slice(0, basePathEntriesLength + index + 1)
|
||||||
|
.join('/')}`,
|
||||||
|
[breadcrumbContext.link]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (links.length < 2) {
|
if (links.length < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BreadcrumbWrapper role="list">
|
<BreadcrumbWrapper role="list">
|
||||||
{links.slice(0, links.length - 1).map((link, index) => (
|
{links.slice(0, links.length - 1).map((link, index) => (
|
||||||
|
@ -60,7 +41,7 @@ const Breadcrumb: React.FC<Props> = () => {
|
||||||
'is-size-4 has-text-weight-medium is-capitalized': links.length < 2,
|
'is-size-4 has-text-weight-medium is-capitalized': links.length < 2,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span>{currentLink}</span>
|
<span>{links[links.length - 1]}</span>
|
||||||
</li>
|
</li>
|
||||||
</BreadcrumbWrapper>
|
</BreadcrumbWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
|
||||||
import { screen } from '@testing-library/react';
|
import { BreadcrumbProvider } from 'components/common/Breadcrumb/Breadcrumb.provider';
|
||||||
|
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||||
import { render } from 'lib/testHelpers';
|
import { render } from 'lib/testHelpers';
|
||||||
|
|
||||||
const brokersPath = '/ui/clusters/local/brokers';
|
|
||||||
const createTopicPath = '/ui/clusters/local/topics/create-new';
|
const createTopicPath = '/ui/clusters/local/topics/create-new';
|
||||||
|
const createTopicRoutePath = '/ui/clusters/:clusterName/topics/create-new';
|
||||||
|
|
||||||
|
const topicPath = '/ui/clusters/secondLocal/topics/topic-name';
|
||||||
|
const topicRoutePath = '/ui/clusters/:clusterName/topics/:topicName';
|
||||||
|
|
||||||
describe('Breadcrumb component', () => {
|
describe('Breadcrumb component', () => {
|
||||||
const setupComponent = (pathname: string) =>
|
const setupComponent = (pathname: string, routePath: string) =>
|
||||||
render(<Breadcrumb />, { pathname });
|
render(
|
||||||
|
<BreadcrumbProvider>
|
||||||
|
<Breadcrumb />
|
||||||
|
<BreadcrumbRoute path={routePath} />
|
||||||
|
</BreadcrumbProvider>,
|
||||||
|
{ pathname }
|
||||||
|
);
|
||||||
|
|
||||||
it('renders the name of brokers path', () => {
|
it('renders the list of links', async () => {
|
||||||
setupComponent(brokersPath);
|
const { getByText } = setupComponent(createTopicPath, createTopicRoutePath);
|
||||||
expect(screen.queryByText('Brokers')).not.toBeInTheDocument();
|
expect(getByText('Topics')).toBeInTheDocument();
|
||||||
|
expect(getByText('Create New')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
it('renders the list of links', () => {
|
it('renders the topic overview', async () => {
|
||||||
setupComponent(createTopicPath);
|
const { getByText } = setupComponent(topicPath, topicRoutePath);
|
||||||
expect(screen.getByText('Topics')).toBeInTheDocument();
|
expect(getByText('Topics')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Create New')).toBeInTheDocument();
|
expect(getByText('topic-name')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue