react router migration (#2045)
* remove withRouter HOC from FiltersContainer * remove withRouter HOC from Topics DetailsContainer * remove withRouter HOC from Topics TopicsConsumerGroupsContainer * withRouter HOC from Topics TopicsConsumerGroupsContainer * minor code refactor in the Details spec * Routes code modifications to refactor strings representation to functions * Settings and TopicsConsumer removal of HOC with Router * Remove withRouter HOC from Overview file * Remove withRouter HOC from Edit file * replace Router path with functions instead of strings * delete CustomParamsContainer and use the simple component in the TopicForm * remove HOC from DangerZone container * Remove withRouter HOC from Connect pages like Config , Overview , Tasks * Remove withRouter HOC from Connect pages like Actions, Details, Edit, New * Refactor Kafka Connect Codes * Refactor Topics pages * Remove HOC from Diff component and minor code refactor * Route component migration into children instead of renderProps or component param in App Component * Route component migration into children instead of renderProps or component param in Cluster Component * Route component migration into children instead of renderProps or component param in Topics Component * Route component migration into children instead of renderProps or component param in Topic Component * Route component migration into children instead of renderProps or component param in Topic Component * minor bug fix in the Overview selector spread * change Router from component Render to child render in ConsumerGroups page * change Router from component Render to child render in Schemas page * change Router from component Render to child render in KsqlDb page * change Router from component Render to child render in Connect page * change Router from component Render to child render in Connect Details page * Overview Details styling code modifications * All written path to paths with functions * Route Parameters code fix with functions and params with variables * Updating BreadCrumb Route * Refactor Redirects * WIP React Router v6 migration * Remove unused imports from the file * Make KsqlDb pages work with relative Routes * WIP Make Connect pages work and fix the Schema page testing problem * transforming consumer groups into relative path router * Transform Topics pages into relative routes * Transform Topic pages into relative routes * Minor changes in Connect and KsqlDb test suites relative routes * Minor changes in Connect and KsqlDb test suites relative routes * change the Details into relative Routes * Topics List naviagtion and caching issue fixed in tests suites * Topic New Naviagation issue fix + tests suites * Details navigate migrating into relative paths * Send Message Submit Naviagttion with tests suites * Topic Edit pages with working routes navigation * Topic Details and ResetOffsets Pages tests suites and navigations * Messages Table Tests suites * BreadCrumbs Routes fixes * ClusterMenu and Links styling minor code modifications * ClusterMenu and Links styling minor code modifications * Minor Code modifications * Fix Lintter Problems * fix Code Smells * create custom useParams hook * Adding Path tests * minor code refactors * Fix the Button Component redundant Props + transforming routes to relative * Fix linter issues
This commit is contained in:
parent
2a51f0ee14
commit
71ac16357b
122 changed files with 2084 additions and 1977 deletions
115
kafka-ui-react-app/package-lock.json
generated
115
kafka-ui-react-app/package-lock.json
generated
|
@ -36,7 +36,7 @@
|
|||
"react-multi-select-component": "^4.0.6",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.3.1",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"redux": "^4.1.1",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"sass": "^1.43.4",
|
||||
|
@ -16158,7 +16158,6 @@
|
|||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.6"
|
||||
}
|
||||
|
@ -24856,11 +24855,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
|
||||
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
|
@ -24876,53 +24875,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.1.tgz",
|
||||
"integrity": "sha512-f0pj/gMAbv9e8gahTmCEY20oFhxhrmHwYeIwH5EO5xu0qme+wXtsdB8YfUOAZzUz4VaXmb58m3ceiLtjMhqYmQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.3.1",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"history": "^5.2.0",
|
||||
"react-router": "6.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom/node_modules/history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom/node_modules/react-router": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.1.tgz",
|
||||
"integrity": "sha512-v+zwjqb7bakqgF+wMVKlAPTca/cEmPOvQ9zt7gpSNyPXau1+0qvuYZ5BWzzNDP1y6s15zDwgb9rPN63+SIniRQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.4.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"history": "^5.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15"
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/node_modules/history": {
|
||||
|
@ -40798,7 +40771,6 @@
|
|||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.6"
|
||||
}
|
||||
|
@ -47310,11 +47282,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
|
||||
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
|
@ -47342,47 +47314,20 @@
|
|||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.1.tgz",
|
||||
"integrity": "sha512-f0pj/gMAbv9e8gahTmCEY20oFhxhrmHwYeIwH5EO5xu0qme+wXtsdB8YfUOAZzUz4VaXmb58m3ceiLtjMhqYmQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-router": "5.3.1",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"history": "^5.2.0",
|
||||
"react-router": "6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"history": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^3.0.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.1.tgz",
|
||||
"integrity": "sha512-v+zwjqb7bakqgF+wMVKlAPTca/cEmPOvQ9zt7gpSNyPXau1+0qvuYZ5BWzzNDP1y6s15zDwgb9rPN63+SIniRQ==",
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.13",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.1.0",
|
||||
"loose-envify": "^1.3.1",
|
||||
"mini-create-react-context": "^0.4.0",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-is": "^16.6.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0"
|
||||
"history": "^5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"react-multi-select-component": "^4.0.6",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.3.1",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"redux": "^4.1.1",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"sass": "^1.43.4",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Switch, Route, useLocation } from 'react-router-dom';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { GIT_TAG, GIT_COMMIT } from 'lib/constants';
|
||||
import { clusterPath, getNonExactPath } from 'lib/paths';
|
||||
import Nav from 'components/Nav/Nav';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import Dashboard from 'components/Dashboard/Dashboard';
|
||||
|
@ -81,14 +82,19 @@ const App: React.FC = () => {
|
|||
aria-label="Overlay"
|
||||
/>
|
||||
{areClustersFulfilled ? (
|
||||
<Switch>
|
||||
<Routes>
|
||||
{['/', '/ui', '/ui/clusters'].map((path) => (
|
||||
<Route
|
||||
key="Home" // optional: avoid full re-renders on route changes
|
||||
path={path}
|
||||
element={<Dashboard />}
|
||||
/>
|
||||
))}
|
||||
<Route
|
||||
exact
|
||||
path={['/', '/ui', '/ui/clusters']}
|
||||
component={Dashboard}
|
||||
path={getNonExactPath(clusterPath())}
|
||||
element={<ClusterPage />}
|
||||
/>
|
||||
<Route path="/ui/clusters/:clusterName" component={ClusterPage} />
|
||||
</Switch>
|
||||
</Routes>
|
||||
) : (
|
||||
<PageLoader />
|
||||
)}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import React from 'react';
|
||||
import { ClusterName } from 'redux/interfaces';
|
||||
import useInterval from 'lib/hooks/useInterval';
|
||||
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import * as Metrics from 'components/common/Metrics';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
import {
|
||||
fetchBrokers,
|
||||
fetchClusterStats,
|
||||
selectStats,
|
||||
} from 'redux/reducers/brokers/brokersSlice';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
const Brokers: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { clusterName } = useParams<{ clusterName: ClusterName }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const {
|
||||
brokerCount,
|
||||
activeControllers,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import Brokers from 'components/Brokers/Brokers';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen, waitFor } from '@testing-library/dom';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { clusterBrokersPath } from 'lib/paths';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { clusterStatsPayload } from 'redux/reducers/brokers/__test__/fixtures';
|
||||
|
@ -18,11 +17,11 @@ describe('Brokers Component', () => {
|
|||
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<Route path={clusterBrokersPath(':clusterName')}>
|
||||
<WithRoute path={clusterBrokersPath()}>
|
||||
<Brokers />
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterBrokersPath(clusterName),
|
||||
initialEntries: [clusterBrokersPath(clusterName)],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Switch, Redirect, useParams } from 'react-router-dom';
|
||||
import { Routes, Navigate, Route, Outlet } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { ClusterFeaturesEnum } from 'generated-sources';
|
||||
import {
|
||||
getClustersFeatures,
|
||||
getClustersReadonlyStatus,
|
||||
} from 'redux/reducers/clusters/clustersSlice';
|
||||
import {
|
||||
clusterBrokersPath,
|
||||
clusterConnectorsPath,
|
||||
clusterConnectsPath,
|
||||
clusterConsumerGroupsPath,
|
||||
clusterKsqlDbPath,
|
||||
clusterSchemasPath,
|
||||
clusterTopicsPath,
|
||||
clusterBrokerRelativePath,
|
||||
clusterConnectorsRelativePath,
|
||||
clusterConnectsRelativePath,
|
||||
clusterConsumerGroupsRelativePath,
|
||||
clusterKsqlDbRelativePath,
|
||||
ClusterNameRoute,
|
||||
clusterSchemasRelativePath,
|
||||
clusterTopicsRelativePath,
|
||||
getNonExactPath,
|
||||
} from 'lib/paths';
|
||||
import Topics from 'components/Topics/Topics';
|
||||
import Schemas from 'components/Schemas/Schemas';
|
||||
|
@ -27,7 +30,7 @@ import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
|||
import { BreadcrumbProvider } from 'components/common/Breadcrumb/Breadcrumb.provider';
|
||||
|
||||
const Cluster: React.FC = () => {
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const isReadOnly = useSelector(getClustersReadonlyStatus(clusterName));
|
||||
const features = useSelector(getClustersFeatures(clusterName));
|
||||
|
||||
|
@ -61,48 +64,77 @@ const Cluster: React.FC = () => {
|
|||
<BreadcrumbProvider>
|
||||
<Breadcrumb />
|
||||
<ClusterContext.Provider value={contextValue}>
|
||||
<Switch>
|
||||
<BreadcrumbRoute
|
||||
path={clusterBrokersPath(':clusterName')}
|
||||
component={Brokers}
|
||||
<Routes>
|
||||
<Route
|
||||
path={getNonExactPath(clusterBrokerRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Brokers />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
path={clusterTopicsPath(':clusterName')}
|
||||
component={Topics}
|
||||
<Route
|
||||
path={getNonExactPath(clusterTopicsRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Topics />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
path={clusterConsumerGroupsPath(':clusterName')}
|
||||
component={ConsumersGroups}
|
||||
<Route
|
||||
path={getNonExactPath(clusterConsumerGroupsRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<ConsumersGroups />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
{hasSchemaRegistryConfigured && (
|
||||
<BreadcrumbRoute
|
||||
path={clusterSchemasPath(':clusterName')}
|
||||
component={Schemas}
|
||||
<Route
|
||||
path={getNonExactPath(clusterSchemasRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Schemas />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasKafkaConnectConfigured && (
|
||||
<BreadcrumbRoute
|
||||
path={clusterConnectsPath(':clusterName')}
|
||||
component={Connect}
|
||||
<Route
|
||||
path={getNonExactPath(clusterConnectsRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Connect />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasKafkaConnectConfigured && (
|
||||
<BreadcrumbRoute
|
||||
path={clusterConnectorsPath(':clusterName')}
|
||||
component={Connect}
|
||||
<Route
|
||||
path={getNonExactPath(clusterConnectorsRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Connect />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasKsqlDbConfigured && (
|
||||
<BreadcrumbRoute
|
||||
path={clusterKsqlDbPath(':clusterName')}
|
||||
component={KsqlDb}
|
||||
<Route
|
||||
path={getNonExactPath(clusterKsqlDbRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<KsqlDb />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Redirect
|
||||
from="/ui/clusters/:clusterName"
|
||||
to="/ui/clusters/:clusterName/brokers"
|
||||
<Route
|
||||
path="/"
|
||||
element={<Navigate to={clusterBrokerRelativePath} replace />}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
<Outlet />
|
||||
</ClusterContext.Provider>
|
||||
</BreadcrumbProvider>
|
||||
);
|
||||
|
|
|
@ -1,51 +1,71 @@
|
|||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { ClusterFeaturesEnum } from 'generated-sources';
|
||||
import { store } from 'redux/store';
|
||||
import { onlineClusterPayload } from 'redux/reducers/clusters/__test__/fixtures';
|
||||
import Cluster from 'components/Cluster/Cluster';
|
||||
import { fetchClusters } from 'redux/reducers/clusters/clustersSlice';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterBrokersPath,
|
||||
clusterConnectsPath,
|
||||
clusterConsumerGroupsPath,
|
||||
clusterKsqlDbPath,
|
||||
clusterPath,
|
||||
clusterSchemasPath,
|
||||
clusterTopicsPath,
|
||||
} from 'lib/paths';
|
||||
|
||||
jest.mock('components/Topics/Topics', () => () => <div>Topics</div>);
|
||||
jest.mock('components/Schemas/Schemas', () => () => <div>Schemas</div>);
|
||||
jest.mock('components/Connect/Connect', () => () => <div>Connect</div>);
|
||||
jest.mock('components/Connect/Connect', () => () => <div>Connect</div>);
|
||||
jest.mock('components/Brokers/Brokers', () => () => <div>Brokers</div>);
|
||||
jest.mock('components/ConsumerGroups/ConsumerGroups', () => () => (
|
||||
<div>ConsumerGroups</div>
|
||||
const CLusterCompText = {
|
||||
Topics: 'Topics',
|
||||
Schemas: 'Schemas',
|
||||
Connect: 'Connect',
|
||||
Brokers: 'Brokers',
|
||||
ConsumerGroups: 'ConsumerGroups',
|
||||
KsqlDb: 'KsqlDb',
|
||||
};
|
||||
|
||||
jest.mock('components/Topics/Topics', () => () => (
|
||||
<div>{CLusterCompText.Topics}</div>
|
||||
));
|
||||
jest.mock('components/Schemas/Schemas', () => () => (
|
||||
<div>{CLusterCompText.Schemas}</div>
|
||||
));
|
||||
jest.mock('components/Connect/Connect', () => () => (
|
||||
<div>{CLusterCompText.Connect}</div>
|
||||
));
|
||||
jest.mock('components/Brokers/Brokers', () => () => (
|
||||
<div>{CLusterCompText.Brokers}</div>
|
||||
));
|
||||
jest.mock('components/ConsumerGroups/ConsumerGroups', () => () => (
|
||||
<div>{CLusterCompText.ConsumerGroups}</div>
|
||||
));
|
||||
jest.mock('components/KsqlDb/KsqlDb', () => () => (
|
||||
<div>{CLusterCompText.KsqlDb}</div>
|
||||
));
|
||||
jest.mock('components/KsqlDb/KsqlDb', () => () => <div>KsqlDb</div>);
|
||||
|
||||
describe('Cluster', () => {
|
||||
const renderComponent = (pathname: string) =>
|
||||
render(
|
||||
<Route path="/ui/clusters/:clusterName">
|
||||
<WithRoute path={`${clusterPath()}/*`}>
|
||||
<Cluster />
|
||||
</Route>,
|
||||
{ pathname, store }
|
||||
</WithRoute>,
|
||||
{ initialEntries: [pathname], store }
|
||||
);
|
||||
|
||||
it('renders Brokers', () => {
|
||||
renderComponent(clusterBrokersPath('second'));
|
||||
expect(screen.getByText('Brokers')).toBeInTheDocument();
|
||||
expect(screen.getByText(CLusterCompText.Brokers)).toBeInTheDocument();
|
||||
});
|
||||
it('renders Topics', () => {
|
||||
renderComponent(clusterTopicsPath('second'));
|
||||
expect(screen.getByText('Topics')).toBeInTheDocument();
|
||||
expect(screen.getByText(CLusterCompText.Topics)).toBeInTheDocument();
|
||||
});
|
||||
it('renders ConsumerGroups', () => {
|
||||
renderComponent(clusterConsumerGroupsPath('second'));
|
||||
expect(screen.getByText('ConsumerGroups')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(CLusterCompText.ConsumerGroups)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('configured features', () => {
|
||||
|
@ -62,7 +82,9 @@ describe('Cluster', () => {
|
|||
)
|
||||
);
|
||||
renderComponent(clusterSchemasPath('second'));
|
||||
expect(screen.queryByText('Schemas')).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(CLusterCompText.Schemas)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
it('renders Schemas if SCHEMA_REGISTRY is configured', async () => {
|
||||
store.dispatch(
|
||||
|
@ -77,7 +99,7 @@ describe('Cluster', () => {
|
|||
)
|
||||
);
|
||||
renderComponent(clusterSchemasPath(onlineClusterPayload.name));
|
||||
expect(screen.getByText('Schemas')).toBeInTheDocument();
|
||||
expect(screen.getByText(CLusterCompText.Schemas)).toBeInTheDocument();
|
||||
});
|
||||
it('renders Connect if KAFKA_CONNECT is configured', async () => {
|
||||
store.dispatch(
|
||||
|
@ -92,7 +114,7 @@ describe('Cluster', () => {
|
|||
)
|
||||
);
|
||||
renderComponent(clusterConnectsPath(onlineClusterPayload.name));
|
||||
expect(screen.getByText('Connect')).toBeInTheDocument();
|
||||
expect(screen.getByText(CLusterCompText.Connect)).toBeInTheDocument();
|
||||
});
|
||||
it('renders KSQL if KSQL_DB is configured', async () => {
|
||||
store.dispatch(
|
||||
|
@ -107,7 +129,7 @@ describe('Cluster', () => {
|
|||
)
|
||||
);
|
||||
renderComponent(clusterKsqlDbPath(onlineClusterPayload.name));
|
||||
expect(screen.getByText('KsqlDb')).toBeInTheDocument();
|
||||
expect(screen.getByText(CLusterCompText.KsqlDb)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { Switch, Redirect } from 'react-router-dom';
|
||||
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||
import {
|
||||
clusterConnectorsPath,
|
||||
clusterConnectsPath,
|
||||
clusterConnectorNewPath,
|
||||
clusterConnectConnectorPath,
|
||||
clusterConnectConnectorEditPath,
|
||||
clusterConnectConnectorsPath,
|
||||
RouteParams,
|
||||
clusterConnectConnectorEditRelativePath,
|
||||
clusterConnectConnectorRelativePath,
|
||||
clusterConnectConnectorsRelativePath,
|
||||
clusterConnectorNewRelativePath,
|
||||
getNonExactPath,
|
||||
} from 'lib/paths';
|
||||
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||
|
||||
|
@ -16,45 +16,48 @@ import DetailsContainer from './Details/DetailsContainer';
|
|||
import EditContainer from './Edit/EditContainer';
|
||||
|
||||
const Connect: React.FC = () => (
|
||||
<div>
|
||||
<Switch>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterConnectorsPath(':clusterName')}
|
||||
component={ListContainer}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterConnectorNewPath(':clusterName')}
|
||||
component={NewContainer}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterConnectConnectorEditPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
)}
|
||||
component={EditContainer}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
path={clusterConnectConnectorPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
)}
|
||||
component={DetailsContainer}
|
||||
/>
|
||||
<Redirect
|
||||
from={clusterConnectConnectorsPath(':clusterName', ':connectName')}
|
||||
to={clusterConnectorsPath(':clusterName')}
|
||||
/>
|
||||
<Redirect
|
||||
from={`${clusterConnectsPath(':clusterName')}/:connectName`}
|
||||
to={clusterConnectorsPath(':clusterName')}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<ListContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={clusterConnectorNewRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<NewContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={clusterConnectConnectorEditRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<EditContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={getNonExactPath(clusterConnectConnectorRelativePath)}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<DetailsContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={clusterConnectConnectorsRelativePath}
|
||||
element={<Navigate to="/" replace />}
|
||||
/>
|
||||
<Route
|
||||
path={RouteParams.connectName}
|
||||
element={<Navigate to="/" replace />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
export default Connect;
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { ConnectorState, ConnectorAction } from 'generated-sources';
|
||||
import { ClusterName, ConnectName, ConnectorName } from 'redux/interfaces';
|
||||
import {
|
||||
clusterConnectConnectorEditPath,
|
||||
clusterConnectorsPath,
|
||||
RouterParamsClusterConnectConnector,
|
||||
} from 'lib/paths';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import styled from 'styled-components';
|
||||
import { Button } from 'components/common/Button/Button';
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
connectName: ConnectName;
|
||||
connectorName: ConnectorName;
|
||||
}
|
||||
|
||||
const ConnectorActionsWrapperStyled = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
@ -63,9 +59,11 @@ const Actions: React.FC<ActionsProps> = ({
|
|||
resumeConnector,
|
||||
isConnectorActionRunning,
|
||||
}) => {
|
||||
const { clusterName, connectName, connectorName } = useParams<RouterParams>();
|
||||
const { clusterName, connectName, connectorName } =
|
||||
useAppParams<RouterParamsClusterConnectConnector>();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const history = useHistory();
|
||||
const [
|
||||
isDeleteConnectorConfirmationVisible,
|
||||
setIsDeleteConnectorConfirmationVisible,
|
||||
|
@ -74,7 +72,7 @@ const Actions: React.FC<ActionsProps> = ({
|
|||
const deleteConnectorHandler = async () => {
|
||||
try {
|
||||
await deleteConnector({ clusterName, connectName, connectorName });
|
||||
history.push(clusterConnectorsPath(clusterName));
|
||||
navigate(clusterConnectorsPath(clusterName));
|
||||
} catch {
|
||||
// do not redirect
|
||||
}
|
||||
|
@ -175,7 +173,6 @@ const Actions: React.FC<ActionsProps> = ({
|
|||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
isLink
|
||||
disabled={isConnectorActionRunning}
|
||||
to={clusterConnectConnectorEditPath(
|
||||
clusterName,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
deleteConnector,
|
||||
|
@ -30,6 +29,4 @@ const mapDispatchToProps = {
|
|||
resumeConnector,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Actions)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Actions);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterConnectConnectorPath, clusterConnectorsPath } from 'lib/paths';
|
||||
import ActionsContainer from 'components/Connect/Details/Actions/ActionsContainer';
|
||||
import Actions, {
|
||||
|
@ -19,9 +18,7 @@ const cancelMock = jest.fn();
|
|||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useHistory: () => ({
|
||||
push: mockHistoryPush,
|
||||
}),
|
||||
useNavigate: () => mockHistoryPush,
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
|
@ -38,6 +35,12 @@ const expectActionButtonsExists = () => {
|
|||
};
|
||||
|
||||
describe('Actions', () => {
|
||||
afterEach(() => {
|
||||
mockHistoryPush.mockClear();
|
||||
deleteConnector.mockClear();
|
||||
cancelMock.mockClear();
|
||||
});
|
||||
|
||||
const actionsContainer = (props: Partial<ActionsProps> = {}) => (
|
||||
<ActionsContainer>
|
||||
<Actions
|
||||
|
@ -60,17 +63,13 @@ describe('Actions', () => {
|
|||
});
|
||||
|
||||
describe('view', () => {
|
||||
const pathname = clusterConnectConnectorPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const pathname = clusterConnectConnectorPath();
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
||||
const confirmationModal = (props: Partial<ConfirmationModalProps> = {}) => (
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={pathname}>
|
||||
<ConfirmationModal
|
||||
onCancel={cancelMock}
|
||||
onConfirm={() =>
|
||||
|
@ -91,11 +90,11 @@ describe('Actions', () => {
|
|||
Confirm
|
||||
</button>
|
||||
</ConfirmationModal>
|
||||
</Route>
|
||||
</WithRoute>
|
||||
);
|
||||
|
||||
const component = (props: Partial<ActionsProps> = {}) => (
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={pathname}>
|
||||
<Actions
|
||||
deleteConnector={jest.fn()}
|
||||
isConnectorDeleting={false}
|
||||
|
@ -107,16 +106,14 @@ describe('Actions', () => {
|
|||
isConnectorActionRunning={false}
|
||||
{...props}
|
||||
/>
|
||||
</Route>
|
||||
</WithRoute>
|
||||
);
|
||||
|
||||
it('renders buttons when paused', () => {
|
||||
render(component({ connectorStatus: ConnectorState.PAUSED }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
expect(screen.getAllByRole('button').length).toEqual(6);
|
||||
expect(screen.getByText('Resume')).toBeInTheDocument();
|
||||
|
@ -127,11 +124,9 @@ describe('Actions', () => {
|
|||
|
||||
it('renders buttons when failed', () => {
|
||||
render(component({ connectorStatus: ConnectorState.FAILED }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
expect(screen.getAllByRole('button').length).toEqual(5);
|
||||
|
||||
|
@ -143,11 +138,9 @@ describe('Actions', () => {
|
|||
|
||||
it('renders buttons when unassigned', () => {
|
||||
render(component({ connectorStatus: ConnectorState.UNASSIGNED }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
expect(screen.getAllByRole('button').length).toEqual(5);
|
||||
expect(screen.queryByText('Resume')).not.toBeInTheDocument();
|
||||
|
@ -157,11 +150,9 @@ describe('Actions', () => {
|
|||
|
||||
it('renders buttons when running connector action', () => {
|
||||
render(component({ connectorStatus: ConnectorState.RUNNING }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
expect(screen.getAllByRole('button').length).toEqual(6);
|
||||
expect(screen.queryByText('Resume')).not.toBeInTheDocument();
|
||||
|
@ -172,11 +163,9 @@ describe('Actions', () => {
|
|||
|
||||
it('opens confirmation modal when delete button clicked', () => {
|
||||
render(component({ deleteConnector }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
userEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
||||
|
||||
|
@ -187,11 +176,9 @@ describe('Actions', () => {
|
|||
|
||||
it('closes when cancel button clicked', () => {
|
||||
render(confirmationModal({ isOpen: true }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
const cancelBtn = screen.getByRole('button', { name: 'Cancel' });
|
||||
userEvent.click(cancelBtn);
|
||||
|
@ -200,11 +187,9 @@ describe('Actions', () => {
|
|||
|
||||
it('calls deleteConnector when confirm button clicked', () => {
|
||||
render(confirmationModal({ isOpen: true }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
const confirmBtn = screen.getByRole('button', { name: 'Confirm' });
|
||||
userEvent.click(confirmBtn);
|
||||
|
@ -218,11 +203,9 @@ describe('Actions', () => {
|
|||
|
||||
it('redirects after delete', async () => {
|
||||
render(confirmationModal({ isOpen: true }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
const confirmBtn = screen.getByRole('button', { name: 'Confirm' });
|
||||
userEvent.click(confirmBtn);
|
||||
|
@ -235,11 +218,9 @@ describe('Actions', () => {
|
|||
it('calls restartConnector when restart button clicked', () => {
|
||||
const restartConnector = jest.fn();
|
||||
render(component({ restartConnector }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(clusterName, connectName, connectorName),
|
||||
],
|
||||
});
|
||||
userEvent.click(
|
||||
screen.getByRole('button', { name: 'Restart Connector' })
|
||||
|
@ -260,11 +241,13 @@ describe('Actions', () => {
|
|||
pauseConnector,
|
||||
}),
|
||||
{
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
userEvent.click(screen.getByRole('button', { name: 'Pause' }));
|
||||
|
@ -284,11 +267,13 @@ describe('Actions', () => {
|
|||
resumeConnector,
|
||||
}),
|
||||
{
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
userEvent.click(screen.getByRole('button', { name: 'Resume' }));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
ClusterName,
|
||||
ConnectName,
|
||||
|
@ -9,12 +9,7 @@ import {
|
|||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import Editor from 'components/common/Editor/Editor';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
connectName: ConnectName;
|
||||
connectorName: ConnectorName;
|
||||
}
|
||||
import { RouterParamsClusterConnectConnector } from 'lib/paths';
|
||||
|
||||
export interface ConfigProps {
|
||||
fetchConfig(payload: {
|
||||
|
@ -35,7 +30,8 @@ const Config: React.FC<ConfigProps> = ({
|
|||
isConfigFetching,
|
||||
config,
|
||||
}) => {
|
||||
const { clusterName, connectName, connectorName } = useParams<RouterParams>();
|
||||
const { clusterName, connectName, connectorName } =
|
||||
useAppParams<RouterParamsClusterConnectConnector>();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchConfig({ clusterName, connectName, connectorName });
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { fetchConnectorConfig } from 'redux/reducers/connect/connectSlice';
|
||||
import {
|
||||
|
@ -18,4 +17,4 @@ const mapDispatchToProps = {
|
|||
fetchConfig: fetchConnectorConfig,
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Config));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Config);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterConnectConnectorConfigPath } from 'lib/paths';
|
||||
import Config, { ConfigProps } from 'components/Connect/Details/Config/Config';
|
||||
import { connector } from 'redux/reducers/connect/__test__/fixtures';
|
||||
|
@ -9,44 +8,44 @@ import { screen } from '@testing-library/dom';
|
|||
jest.mock('components/common/Editor/Editor', () => 'mock-Editor');
|
||||
|
||||
describe('Config', () => {
|
||||
const pathname = clusterConnectConnectorConfigPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const pathname = clusterConnectConnectorConfigPath();
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
||||
const component = (props: Partial<ConfigProps> = {}) => (
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={pathname}>
|
||||
<Config
|
||||
fetchConfig={jest.fn()}
|
||||
isConfigFetching={false}
|
||||
config={connector.config}
|
||||
{...props}
|
||||
/>
|
||||
</Route>
|
||||
</WithRoute>
|
||||
);
|
||||
|
||||
it('to be in the document when fetching config', () => {
|
||||
render(component({ isConfigFetching: true }), {
|
||||
pathname: clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
});
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('is empty when no config', () => {
|
||||
const { container } = render(component({ config: null }), {
|
||||
pathname: clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
});
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
@ -54,11 +53,13 @@ describe('Config', () => {
|
|||
it('fetches config on mount', () => {
|
||||
const fetchConfig = jest.fn();
|
||||
render(component({ fetchConfig }), {
|
||||
pathname: clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
});
|
||||
expect(fetchConfig).toHaveBeenCalledTimes(1);
|
||||
expect(fetchConfig).toHaveBeenCalledWith({
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import React from 'react';
|
||||
import { NavLink, Route, Switch, useParams } from 'react-router-dom';
|
||||
import { NavLink, Route, Routes } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Connector, Task } from 'generated-sources';
|
||||
import { ClusterName, ConnectName, ConnectorName } from 'redux/interfaces';
|
||||
import {
|
||||
clusterConnectConnectorConfigPath,
|
||||
clusterConnectConnectorConfigRelativePath,
|
||||
clusterConnectConnectorPath,
|
||||
clusterConnectConnectorTasksPath,
|
||||
clusterConnectConnectorTasksRelativePath,
|
||||
RouterParamsClusterConnectConnector,
|
||||
} from 'lib/paths';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import Navbar from 'components/common/Navigation/Navbar.styled';
|
||||
|
@ -16,12 +20,6 @@ import TasksContainer from './Tasks/TasksContainer';
|
|||
import ConfigContainer from './Config/ConfigContainer';
|
||||
import ActionsContainer from './Actions/ActionsContainer';
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
connectName: ConnectName;
|
||||
connectorName: ConnectorName;
|
||||
}
|
||||
|
||||
export interface DetailsProps {
|
||||
fetchConnector(payload: {
|
||||
clusterName: ClusterName;
|
||||
|
@ -46,7 +44,8 @@ const Details: React.FC<DetailsProps> = ({
|
|||
areTasksFetching,
|
||||
connector,
|
||||
}) => {
|
||||
const { clusterName, connectName, connectorName } = useParams<RouterParams>();
|
||||
const { clusterName, connectName, connectorName } =
|
||||
useAppParams<RouterParamsClusterConnectConnector>();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchConnector({ clusterName, connectName, connectorName });
|
||||
|
@ -69,68 +68,47 @@ const Details: React.FC<DetailsProps> = ({
|
|||
</PageHeading>
|
||||
<Navbar role="navigation">
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
)}
|
||||
activeClassName="is-active"
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Overview
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
)}
|
||||
activeClassName="is-active"
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Tasks
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
)}
|
||||
activeClassName="is-active"
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Config
|
||||
</NavLink>
|
||||
</Navbar>
|
||||
<Switch>
|
||||
<Routes>
|
||||
<Route index element={<OverviewContainer />} />
|
||||
<Route
|
||||
exact
|
||||
path={clusterConnectConnectorTasksPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
)}
|
||||
component={TasksContainer}
|
||||
path={clusterConnectConnectorTasksRelativePath}
|
||||
element={<TasksContainer />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={clusterConnectConnectorConfigPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
)}
|
||||
component={ConfigContainer}
|
||||
path={clusterConnectConnectorConfigRelativePath}
|
||||
element={<ConfigContainer />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={clusterConnectConnectorPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
)}
|
||||
component={OverviewContainer}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
fetchConnector,
|
||||
|
@ -26,6 +25,4 @@ const mapDispatchToProps = {
|
|||
fetchTasks: fetchConnectorTasks,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Details)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Details);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
getConnector,
|
||||
|
@ -15,4 +14,4 @@ const mapStateToProps = (state: RootState) => ({
|
|||
failedTasksCount: getConnectorFailedTasksCount(state),
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(Overview));
|
||||
export default connect(mapStateToProps)(Overview);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Task, TaskId } from 'generated-sources';
|
||||
import { ClusterName, ConnectName, ConnectorName } from 'redux/interfaces';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
|
@ -7,12 +7,7 @@ import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
|||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import * as C from 'components/common/Tag/Tag.styled';
|
||||
import getTagColor from 'components/common/Tag/getTagColor';
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
connectName: ConnectName;
|
||||
connectorName: ConnectorName;
|
||||
}
|
||||
import { RouterParamsClusterConnectConnector } from 'lib/paths';
|
||||
|
||||
export interface ListItemProps {
|
||||
task: Task;
|
||||
|
@ -25,7 +20,8 @@ export interface ListItemProps {
|
|||
}
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ task, restartTask }) => {
|
||||
const { clusterName, connectName, connectorName } = useParams<RouterParams>();
|
||||
const { clusterName, connectName, connectorName } =
|
||||
useAppParams<RouterParamsClusterConnectConnector>();
|
||||
|
||||
const restartTaskHandler = async () => {
|
||||
await restartTask({
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { Task } from 'generated-sources';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { restartConnectorTask } from 'redux/reducers/connect/connectSlice';
|
||||
|
||||
import ListItem from './ListItem';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
interface OwnProps {
|
||||
task: Task;
|
||||
}
|
||||
|
||||
|
@ -18,6 +17,4 @@ const mapDispatchToProps = {
|
|||
restartTask: restartConnectorTask,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(ListItem)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ListItem);
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterConnectConnectorTasksPath } from 'lib/paths';
|
||||
import ListItem, {
|
||||
ListItemProps,
|
||||
} from 'components/Connect/Details/Tasks/ListItem/ListItem';
|
||||
import { tasks } from 'redux/reducers/connect/__test__/fixtures';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const pathname = clusterConnectConnectorTasksPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const pathname = clusterConnectConnectorTasksPath();
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
@ -22,19 +17,21 @@ const task = tasks[0];
|
|||
|
||||
const renderComponent = (props: ListItemProps = { task, restartTask }) => {
|
||||
return render(
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={pathname}>
|
||||
<table>
|
||||
<tbody>
|
||||
<ListItem {...props} />
|
||||
</tbody>
|
||||
</table>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { fetchConnectorTasks } from 'redux/reducers/connect/connectSlice';
|
||||
import {
|
||||
|
@ -18,4 +17,4 @@ const mapDispatchToProps = {
|
|||
fetchTasks: fetchConnectorTasks,
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Tasks));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Tasks);
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterConnectConnectorTasksPath } from 'lib/paths';
|
||||
import TasksContainer from 'components/Connect/Details/Tasks/TasksContainer';
|
||||
import Tasks, { TasksProps } from 'components/Connect/Details/Tasks/Tasks';
|
||||
import { tasks } from 'redux/reducers/connect/__test__/fixtures';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { screen } from '@testing-library/dom';
|
||||
|
||||
jest.mock(
|
||||
|
@ -19,28 +18,25 @@ describe('Tasks', () => {
|
|||
});
|
||||
|
||||
describe('view', () => {
|
||||
const pathname = clusterConnectConnectorTasksPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
||||
const setupWrapper = (props: Partial<TasksProps> = {}) => (
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={clusterConnectConnectorTasksPath()}>
|
||||
<Tasks areTasksFetching={false} tasks={tasks} {...props} />
|
||||
</Route>
|
||||
</WithRoute>
|
||||
);
|
||||
|
||||
it('to be in the document when fetching tasks', () => {
|
||||
render(setupWrapper({ areTasksFetching: true }), {
|
||||
pathname: clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
});
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
|
@ -48,11 +44,13 @@ describe('Tasks', () => {
|
|||
|
||||
it('to be in the document when no tasks', () => {
|
||||
render(setupWrapper({ tasks: [] }), {
|
||||
pathname: clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
});
|
||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||
expect(screen.getByText('No tasks found')).toBeInTheDocument();
|
||||
|
|
|
@ -1,100 +1,89 @@
|
|||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { clusterConnectConnectorPath } from 'lib/paths';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterConnectConnectorConfigPath,
|
||||
clusterConnectConnectorPath,
|
||||
clusterConnectConnectorTasksPath,
|
||||
getNonExactPath,
|
||||
} from 'lib/paths';
|
||||
import Details, { DetailsProps } from 'components/Connect/Details/Details';
|
||||
import { connector, tasks } from 'redux/reducers/connect/__test__/fixtures';
|
||||
import { screen } from '@testing-library/dom';
|
||||
|
||||
jest.mock(
|
||||
'components/Connect/Details/Overview/OverviewContainer',
|
||||
() => 'mock-OverviewContainer'
|
||||
);
|
||||
const DetailsCompText = {
|
||||
overview: 'OverviewContainer',
|
||||
tasks: 'TasksContainer',
|
||||
config: 'ConfigContainer',
|
||||
actions: 'ActionsContainer',
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'components/Connect/Details/Tasks/TasksContainer',
|
||||
() => 'mock-TasksContainer'
|
||||
);
|
||||
jest.mock('components/Connect/Details/Overview/OverviewContainer', () => () => (
|
||||
<div>{DetailsCompText.overview}</div>
|
||||
));
|
||||
|
||||
jest.mock(
|
||||
'components/Connect/Details/Config/ConfigContainer',
|
||||
() => 'mock-ConfigContainer'
|
||||
);
|
||||
jest.mock('components/Connect/Details/Tasks/TasksContainer', () => () => (
|
||||
<div>{DetailsCompText.tasks}</div>
|
||||
));
|
||||
|
||||
jest.mock(
|
||||
'components/Connect/Details/Actions/ActionsContainer',
|
||||
() => 'mock-ActionsContainer'
|
||||
);
|
||||
jest.mock('components/Connect/Details/Config/ConfigContainer', () => () => (
|
||||
<div>{DetailsCompText.config}</div>
|
||||
));
|
||||
|
||||
jest.mock('components/Connect/Details/Actions/ActionsContainer', () => () => (
|
||||
<div>{DetailsCompText.actions}</div>
|
||||
));
|
||||
|
||||
describe('Details', () => {
|
||||
const pathname = clusterConnectConnectorPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
||||
const setupWrapper = (props: Partial<DetailsProps> = {}) => (
|
||||
<Route path={pathname}>
|
||||
<Details
|
||||
fetchConnector={jest.fn()}
|
||||
fetchTasks={jest.fn()}
|
||||
isConnectorFetching={false}
|
||||
areTasksFetching={false}
|
||||
connector={connector}
|
||||
tasks={tasks}
|
||||
{...props}
|
||||
/>
|
||||
</Route>
|
||||
const defaultPath = clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
);
|
||||
|
||||
const setupWrapper = (
|
||||
props: Partial<DetailsProps> = {},
|
||||
path: string = defaultPath
|
||||
) =>
|
||||
render(
|
||||
<WithRoute path={getNonExactPath(clusterConnectConnectorPath())}>
|
||||
<Details
|
||||
fetchConnector={jest.fn()}
|
||||
fetchTasks={jest.fn()}
|
||||
isConnectorFetching={false}
|
||||
areTasksFetching={false}
|
||||
connector={connector}
|
||||
tasks={tasks}
|
||||
{...props}
|
||||
/>
|
||||
</WithRoute>,
|
||||
{ initialEntries: [path] }
|
||||
);
|
||||
|
||||
it('renders progressbar when fetching connector', () => {
|
||||
render(setupWrapper({ isConnectorFetching: true }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
});
|
||||
setupWrapper({ isConnectorFetching: true });
|
||||
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders progressbar when fetching tasks', () => {
|
||||
render(setupWrapper({ areTasksFetching: true }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
});
|
||||
setupWrapper({ areTasksFetching: true });
|
||||
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('is empty when no connector', () => {
|
||||
const { container } = render(setupWrapper({ connector: null }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
});
|
||||
const { container } = setupWrapper({ connector: null });
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('fetches connector on mount', () => {
|
||||
const fetchConnector = jest.fn();
|
||||
render(setupWrapper({ fetchConnector }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
});
|
||||
setupWrapper({ fetchConnector });
|
||||
expect(fetchConnector).toHaveBeenCalledTimes(1);
|
||||
expect(fetchConnector).toHaveBeenCalledWith({
|
||||
clusterName,
|
||||
|
@ -105,13 +94,7 @@ describe('Details', () => {
|
|||
|
||||
it('fetches tasks on mount', () => {
|
||||
const fetchTasks = jest.fn();
|
||||
render(setupWrapper({ fetchTasks }), {
|
||||
pathname: clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
});
|
||||
setupWrapper({ fetchTasks });
|
||||
expect(fetchTasks).toHaveBeenCalledTimes(1);
|
||||
expect(fetchTasks).toHaveBeenCalledWith({
|
||||
clusterName,
|
||||
|
@ -119,4 +102,35 @@ describe('Details', () => {
|
|||
connectorName,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Router component tests', () => {
|
||||
it('should test if overview is rendering', () => {
|
||||
setupWrapper({});
|
||||
expect(screen.getByText(DetailsCompText.overview));
|
||||
});
|
||||
|
||||
it('should test if tasks is rendering', () => {
|
||||
setupWrapper(
|
||||
{},
|
||||
clusterConnectConnectorTasksPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
)
|
||||
);
|
||||
expect(screen.getByText(DetailsCompText.tasks));
|
||||
});
|
||||
|
||||
it('should test if list is rendering', () => {
|
||||
setupWrapper(
|
||||
{},
|
||||
clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
)
|
||||
);
|
||||
expect(screen.getByText(DetailsCompText.config));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { ErrorMessage } from '@hookform/error-message';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
|
@ -9,7 +10,10 @@ import {
|
|||
ConnectorConfig,
|
||||
ConnectorName,
|
||||
} from 'redux/interfaces';
|
||||
import { clusterConnectConnectorConfigPath } from 'lib/paths';
|
||||
import {
|
||||
clusterConnectConnectorConfigPath,
|
||||
RouterParamsClusterConnectConnector,
|
||||
} from 'lib/paths';
|
||||
import yup from 'lib/yupExtended';
|
||||
import Editor from 'components/common/Editor/Editor';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
|
@ -24,12 +28,6 @@ const validationSchema = yup.object().shape({
|
|||
config: yup.string().required().isJsonObject(),
|
||||
});
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
connectName: ConnectName;
|
||||
connectorName: ConnectorName;
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
config: string;
|
||||
}
|
||||
|
@ -56,8 +54,9 @@ const Edit: React.FC<EditProps> = ({
|
|||
config,
|
||||
updateConfig,
|
||||
}) => {
|
||||
const { clusterName, connectName, connectorName } = useParams<RouterParams>();
|
||||
const history = useHistory();
|
||||
const { clusterName, connectName, connectorName } =
|
||||
useAppParams<RouterParamsClusterConnectConnector>();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
|
@ -89,7 +88,7 @@ const Edit: React.FC<EditProps> = ({
|
|||
connectorConfig: JSON.parse(values.config.trim()),
|
||||
});
|
||||
if (connector) {
|
||||
history.push(
|
||||
navigate(
|
||||
clusterConnectConnectorConfigPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
fetchConnectorConfig,
|
||||
|
@ -22,4 +21,4 @@ const mapDispatchToProps = {
|
|||
updateConfig: updateConnectorConfig,
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Edit));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Edit);
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterConnectConnectorConfigPath,
|
||||
clusterConnectConnectorEditPath,
|
||||
} from 'lib/paths';
|
||||
import Edit, { EditProps } from 'components/Connect/Edit/Edit';
|
||||
import { connector } from 'redux/reducers/connect/__test__/fixtures';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { act, fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
|
@ -17,24 +16,18 @@ jest.mock('components/common/Editor/Editor', () => 'mock-Editor');
|
|||
const mockHistoryPush = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useHistory: () => ({
|
||||
push: mockHistoryPush,
|
||||
}),
|
||||
useNavigate: () => mockHistoryPush,
|
||||
}));
|
||||
|
||||
describe('Edit', () => {
|
||||
const pathname = clusterConnectConnectorEditPath(
|
||||
':clusterName',
|
||||
':connectName',
|
||||
':connectorName'
|
||||
);
|
||||
const pathname = clusterConnectConnectorEditPath();
|
||||
const clusterName = 'my-cluster';
|
||||
const connectName = 'my-connect';
|
||||
const connectorName = 'my-connector';
|
||||
|
||||
const renderComponent = (props: Partial<EditProps> = {}) =>
|
||||
render(
|
||||
<Route path={pathname}>
|
||||
<WithRoute path={pathname}>
|
||||
<Edit
|
||||
fetchConfig={jest.fn()}
|
||||
isConfigFetching={false}
|
||||
|
@ -42,13 +35,15 @@ describe('Edit', () => {
|
|||
updateConfig={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConnectConnectorEditPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConnectConnectorEditPath(
|
||||
clusterName,
|
||||
connectName,
|
||||
connectorName
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Connect, FullConnectorInfo } from 'generated-sources';
|
||||
import { ClusterName, ConnectorSearch } from 'redux/interfaces';
|
||||
import { clusterConnectorNewPath } from 'lib/paths';
|
||||
import { clusterConnectorNewRelativePath, ClusterNameRoute } from 'lib/paths';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import Search from 'components/common/Search/Search';
|
||||
|
@ -40,7 +40,7 @@ const List: React.FC<ListProps> = ({
|
|||
setConnectorSearch,
|
||||
}) => {
|
||||
const { isReadOnly } = React.useContext(ClusterContext);
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchConnects(clusterName);
|
||||
|
@ -58,10 +58,9 @@ const List: React.FC<ListProps> = ({
|
|||
<PageHeading text="Connectors">
|
||||
{!isReadOnly && (
|
||||
<Button
|
||||
isLink
|
||||
buttonType="primary"
|
||||
buttonSize="M"
|
||||
to={clusterConnectorNewPath(clusterName)}
|
||||
to={clusterConnectorNewRelativePath}
|
||||
>
|
||||
Create Connector
|
||||
</Button>
|
||||
|
|
|
@ -60,10 +60,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
return (
|
||||
<tr>
|
||||
<TableKeyLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterConnectConnectorPath(clusterName, connect, name)}
|
||||
>
|
||||
<NavLink to={clusterConnectConnectorPath(clusterName, connect, name)}>
|
||||
{name}
|
||||
</NavLink>
|
||||
</TableKeyLink>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
import { ErrorMessage } from '@hookform/error-message';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Connect, Connector, NewConnector } from 'generated-sources';
|
||||
import { ClusterName, ConnectName } from 'redux/interfaces';
|
||||
import { clusterConnectConnectorPath } from 'lib/paths';
|
||||
import { clusterConnectConnectorPath, ClusterNameRoute } from 'lib/paths';
|
||||
import yup from 'lib/yupExtended';
|
||||
import Editor from 'components/common/Editor/Editor';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
|
@ -23,10 +24,6 @@ const validationSchema = yup.object().shape({
|
|||
config: yup.string().required().isJsonObject(),
|
||||
});
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
}
|
||||
|
||||
export interface NewProps {
|
||||
fetchConnects(clusterName: ClusterName): unknown;
|
||||
areConnectsFetching: boolean;
|
||||
|
@ -50,8 +47,8 @@ const New: React.FC<NewProps> = ({
|
|||
connects,
|
||||
createConnector,
|
||||
}) => {
|
||||
const { clusterName } = useParams<RouterParams>();
|
||||
const history = useHistory();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const methods = useForm<FormValues>({
|
||||
mode: 'onTouched',
|
||||
|
@ -96,7 +93,7 @@ const New: React.FC<NewProps> = ({
|
|||
});
|
||||
|
||||
if (connector) {
|
||||
history.push(
|
||||
navigate(
|
||||
clusterConnectConnectorPath(
|
||||
clusterName,
|
||||
connector.connect,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import {
|
||||
createConnector,
|
||||
fetchConnects,
|
||||
|
@ -22,4 +21,4 @@ const mapDispatchToProps = {
|
|||
createConnector: createConnector as unknown as NewProps['createConnector'],
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(New));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(New);
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterConnectConnectorPath,
|
||||
clusterConnectorNewPath,
|
||||
} from 'lib/paths';
|
||||
import New, { NewProps } from 'components/Connect/New/New';
|
||||
import { connects, connector } from 'redux/reducers/connect/__test__/fixtures';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { fireEvent, screen, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ControllerRenderProps } from 'react-hook-form';
|
||||
|
@ -22,9 +21,7 @@ jest.mock(
|
|||
const mockHistoryPush = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useHistory: () => ({
|
||||
push: mockHistoryPush,
|
||||
}),
|
||||
useNavigate: () => mockHistoryPush,
|
||||
}));
|
||||
|
||||
describe('New', () => {
|
||||
|
@ -51,7 +48,7 @@ describe('New', () => {
|
|||
|
||||
const renderComponent = (props: Partial<NewProps> = {}) =>
|
||||
render(
|
||||
<Route path={clusterConnectorNewPath(':clusterName')}>
|
||||
<WithRoute path={clusterConnectorNewPath()}>
|
||||
<New
|
||||
fetchConnects={jest.fn()}
|
||||
areConnectsFetching={false}
|
||||
|
@ -59,8 +56,8 @@ describe('New', () => {
|
|||
createConnector={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
</Route>,
|
||||
{ pathname: clusterConnectorNewPath(clusterName) }
|
||||
</WithRoute>,
|
||||
{ initialEntries: [clusterConnectorNewPath(clusterName)] }
|
||||
);
|
||||
|
||||
it('fetches connects on mount', async () => {
|
||||
|
|
|
@ -1,53 +1,68 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import Connect from 'components/Connect/Connect';
|
||||
import { store } from 'redux/store';
|
||||
import { Route } from 'react-router-dom';
|
||||
import {
|
||||
clusterConnectorsPath,
|
||||
clusterConnectorNewPath,
|
||||
clusterConnectConnectorPath,
|
||||
clusterConnectConnectorEditPath,
|
||||
getNonExactPath,
|
||||
clusterConnectsPath,
|
||||
} from 'lib/paths';
|
||||
|
||||
const ConnectCompText = {
|
||||
new: 'NewContainer',
|
||||
list: 'ListContainer',
|
||||
details: 'DetailsContainer',
|
||||
edit: 'EditContainer',
|
||||
};
|
||||
|
||||
jest.mock('components/Connect/New/NewContainer', () => () => (
|
||||
<div>NewContainer</div>
|
||||
<div>{ConnectCompText.new}</div>
|
||||
));
|
||||
jest.mock('components/Connect/List/ListContainer', () => () => (
|
||||
<div>ListContainer</div>
|
||||
<div>{ConnectCompText.list}</div>
|
||||
));
|
||||
jest.mock('components/Connect/Details/DetailsContainer', () => () => (
|
||||
<div>DetailsContainer</div>
|
||||
<div>{ConnectCompText.details}</div>
|
||||
));
|
||||
jest.mock('components/Connect/Edit/EditContainer', () => () => (
|
||||
<div>EditContainer</div>
|
||||
<div>{ConnectCompText.edit}</div>
|
||||
));
|
||||
|
||||
describe('Connect', () => {
|
||||
const renderComponent = (pathname: string) =>
|
||||
const renderComponent = (pathname: string, routePath: string) =>
|
||||
render(
|
||||
<Route path="/ui/clusters/:clusterName">
|
||||
<WithRoute path={getNonExactPath(routePath)}>
|
||||
<Connect />
|
||||
</Route>,
|
||||
{ pathname, store }
|
||||
</WithRoute>,
|
||||
{ initialEntries: [pathname], store }
|
||||
);
|
||||
|
||||
it('renders ListContainer', () => {
|
||||
renderComponent(clusterConnectorsPath('my-cluster'));
|
||||
expect(screen.getByText('ListContainer')).toBeInTheDocument();
|
||||
renderComponent(
|
||||
clusterConnectorsPath('my-cluster'),
|
||||
clusterConnectorsPath()
|
||||
);
|
||||
expect(screen.getByText(ConnectCompText.list)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders NewContainer', () => {
|
||||
renderComponent(clusterConnectorNewPath('my-cluster'));
|
||||
expect(screen.getByText('NewContainer')).toBeInTheDocument();
|
||||
renderComponent(
|
||||
clusterConnectorNewPath('my-cluster'),
|
||||
clusterConnectorsPath()
|
||||
);
|
||||
expect(screen.getByText(ConnectCompText.new)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders DetailsContainer', () => {
|
||||
renderComponent(
|
||||
clusterConnectConnectorPath('my-cluster', 'my-connect', 'my-connector')
|
||||
clusterConnectConnectorPath('my-cluster', 'my-connect', 'my-connector'),
|
||||
clusterConnectsPath()
|
||||
);
|
||||
expect(screen.getByText('DetailsContainer')).toBeInTheDocument();
|
||||
expect(screen.getByText(ConnectCompText.details)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders EditContainer', () => {
|
||||
|
@ -56,8 +71,9 @@ describe('Connect', () => {
|
|||
'my-cluster',
|
||||
'my-connect',
|
||||
'my-connector'
|
||||
)
|
||||
),
|
||||
clusterConnectsPath()
|
||||
);
|
||||
expect(screen.getByText('EditContainer')).toBeInTheDocument();
|
||||
expect(screen.getByText(ConnectCompText.edit)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,28 +1,42 @@
|
|||
import React from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import Details from 'components/ConsumerGroups/Details/Details';
|
||||
import ListContainer from 'components/ConsumerGroups/List/ListContainer';
|
||||
import ResetOffsets from 'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets';
|
||||
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||
import {
|
||||
clusterConsumerGroupResetOffsetsRelativePath,
|
||||
RouteParams,
|
||||
} from 'lib/paths';
|
||||
|
||||
const ConsumerGroups: React.FC = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/consumer-groups"
|
||||
component={ListContainer}
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<ListContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID"
|
||||
component={Details}
|
||||
<Route
|
||||
path={RouteParams.consumerGroupID}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Details />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
path="/ui/clusters/:clusterName/consumer-groups/:consumerGroupID/reset-offsets"
|
||||
component={ResetOffsets}
|
||||
<Route
|
||||
path={clusterConsumerGroupResetOffsetsRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<ResetOffsets />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react';
|
||||
import { ClusterName } from 'redux/interfaces';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
clusterConsumerGroupResetOffsetsPath,
|
||||
clusterConsumerGroupsPath,
|
||||
clusterConsumerGroupResetRelativePath,
|
||||
ClusterGroupParam,
|
||||
} from 'lib/paths';
|
||||
import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
|
@ -31,10 +30,9 @@ import getTagColor from 'components/common/Tag/getTagColor';
|
|||
import ListItem from './ListItem';
|
||||
|
||||
const Details: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const { isReadOnly } = React.useContext(ClusterContext);
|
||||
const { consumerGroupID, clusterName } =
|
||||
useParams<{ consumerGroupID: ConsumerGroupID; clusterName: ClusterName }>();
|
||||
const { consumerGroupID, clusterName } = useAppParams<ClusterGroupParam>();
|
||||
const dispatch = useAppDispatch();
|
||||
const consumerGroup = useAppSelector((state) =>
|
||||
selectById(state, consumerGroupID)
|
||||
|
@ -55,14 +53,12 @@ const Details: React.FC = () => {
|
|||
};
|
||||
React.useEffect(() => {
|
||||
if (isDeleted) {
|
||||
history.push(clusterConsumerGroupsPath(clusterName));
|
||||
navigate('../');
|
||||
}
|
||||
}, [clusterName, history, isDeleted]);
|
||||
}, [clusterName, navigate, isDeleted]);
|
||||
|
||||
const onResetOffsets = () => {
|
||||
history.push(
|
||||
clusterConsumerGroupResetOffsetsPath(clusterName, consumerGroupID)
|
||||
);
|
||||
navigate(clusterConsumerGroupResetRelativePath);
|
||||
};
|
||||
|
||||
if (!isFetched || !consumerGroup) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { ConsumerGroupOffsetsResetType } from 'generated-sources';
|
||||
import { clusterConsumerGroupDetailsPath } from 'lib/paths';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ConsumerGroupOffsetsResetType } from 'generated-sources';
|
||||
import { ClusterGroupParam } from 'lib/paths';
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFieldArray,
|
||||
useForm,
|
||||
} from 'react-hook-form';
|
||||
import { ClusterName, ConsumerGroupID } from 'redux/interfaces';
|
||||
import MultiSelect from 'react-multi-select-component';
|
||||
import { Option } from 'react-multi-select-component/dist/lib/interfaces';
|
||||
import DatePicker from 'react-datepicker';
|
||||
|
@ -15,7 +15,6 @@ import 'react-datepicker/dist/react-datepicker.css';
|
|||
import { groupBy } from 'lodash';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import { ErrorMessage } from '@hookform/error-message';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import Select from 'components/common/Select/Select';
|
||||
import { InputLabel } from 'components/common/Input/InputLabel.styled';
|
||||
import { Button } from 'components/common/Button/Button';
|
||||
|
@ -30,6 +29,7 @@ import {
|
|||
resetConsumerGroupOffsets,
|
||||
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
|
||||
|
||||
import {
|
||||
|
@ -48,8 +48,7 @@ interface FormType {
|
|||
|
||||
const ResetOffsets: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { consumerGroupID, clusterName } =
|
||||
useParams<{ consumerGroupID: ConsumerGroupID; clusterName: ClusterName }>();
|
||||
const { consumerGroupID, clusterName } = useAppParams<ClusterGroupParam>();
|
||||
const consumerGroup = useAppSelector((state) =>
|
||||
selectById(state, consumerGroupID)
|
||||
);
|
||||
|
@ -162,15 +161,13 @@ const ResetOffsets: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
React.useEffect(() => {
|
||||
if (isOffsetReseted) {
|
||||
dispatch(resetLoaderById('consumerGroups/resetConsumerGroupOffsets'));
|
||||
history.push(
|
||||
clusterConsumerGroupDetailsPath(clusterName, consumerGroupID)
|
||||
);
|
||||
navigate('../');
|
||||
}
|
||||
}, [clusterName, consumerGroupID, dispatch, history, isOffsetReseted]);
|
||||
}, [clusterName, consumerGroupID, dispatch, navigate, isOffsetReseted]);
|
||||
|
||||
if (!isFetched || !consumerGroup) {
|
||||
return <PageLoader />;
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterConsumerGroupResetOffsetsPath } from 'lib/paths';
|
||||
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import ResetOffsets from 'components/ConsumerGroups/Details/ResetOffsets/ResetOffsets';
|
||||
|
@ -13,19 +12,16 @@ const { groupId } = consumerGroupPayload;
|
|||
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<Route
|
||||
path={clusterConsumerGroupResetOffsetsPath(
|
||||
':clusterName',
|
||||
':consumerGroupID'
|
||||
)}
|
||||
>
|
||||
<WithRoute path={clusterConsumerGroupResetOffsetsPath()}>
|
||||
<ResetOffsets />
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConsumerGroupResetOffsetsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConsumerGroupResetOffsetsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -3,28 +3,27 @@ import { clusterConsumerGroupDetailsPath } from 'lib/paths';
|
|||
import { screen } from '@testing-library/react';
|
||||
import TopicContents from 'components/ConsumerGroups/Details/TopicContents/TopicContents';
|
||||
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { ConsumerGroupTopicPartition } from 'generated-sources';
|
||||
|
||||
const clusterName = 'cluster1';
|
||||
|
||||
const renderComponent = (consumers: ConsumerGroupTopicPartition[] = []) =>
|
||||
render(
|
||||
<Route
|
||||
path={clusterConsumerGroupDetailsPath(':clusterName', ':consumerGroupID')}
|
||||
>
|
||||
<WithRoute path={clusterConsumerGroupDetailsPath()}>
|
||||
<table>
|
||||
<tbody>
|
||||
<TopicContents consumers={consumers} />
|
||||
</tbody>
|
||||
</table>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConsumerGroupDetailsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConsumerGroupDetailsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import Details from 'components/ConsumerGroups/Details/Details';
|
||||
import React from 'react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterConsumerGroupDetailsPath,
|
||||
clusterConsumerGroupResetOffsetsPath,
|
||||
clusterConsumerGroupsPath,
|
||||
clusterConsumerGroupResetRelativePath,
|
||||
} from 'lib/paths';
|
||||
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import {
|
||||
|
@ -20,26 +17,25 @@ import { act } from '@testing-library/react';
|
|||
|
||||
const clusterName = 'cluster1';
|
||||
const { groupId } = consumerGroupPayload;
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
const renderComponent = () => {
|
||||
history.push(clusterConsumerGroupDetailsPath(clusterName, groupId));
|
||||
render(
|
||||
<Router history={history}>
|
||||
<Route
|
||||
path={clusterConsumerGroupDetailsPath(
|
||||
':clusterName',
|
||||
':consumerGroupID'
|
||||
)}
|
||||
>
|
||||
<Details />
|
||||
</Route>
|
||||
</Router>
|
||||
<WithRoute path={clusterConsumerGroupDetailsPath()}>
|
||||
<Details />
|
||||
</WithRoute>,
|
||||
{ initialEntries: [clusterConsumerGroupDetailsPath(clusterName, groupId)] }
|
||||
);
|
||||
};
|
||||
describe('Details component', () => {
|
||||
afterEach(() => {
|
||||
fetchMock.reset();
|
||||
mockNavigate.mockClear();
|
||||
});
|
||||
|
||||
describe('when consumer groups are NOT fetched', () => {
|
||||
|
@ -76,8 +72,8 @@ describe('Details component', () => {
|
|||
|
||||
it('handles [Reset offset] click', async () => {
|
||||
userEvent.click(screen.getByText('Reset offset'));
|
||||
expect(history.location.pathname).toEqual(
|
||||
clusterConsumerGroupResetOffsetsPath(clusterName, groupId)
|
||||
expect(mockNavigate).toHaveBeenLastCalledWith(
|
||||
clusterConsumerGroupResetRelativePath
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -106,9 +102,7 @@ describe('Details component', () => {
|
|||
});
|
||||
expect(deleteConsumerGroupMock.called()).toBeTruthy();
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||
expect(history.location.pathname).toEqual(
|
||||
clusterConsumerGroupsPath(clusterName)
|
||||
);
|
||||
expect(mockNavigate).toHaveBeenLastCalledWith('../');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,17 +4,14 @@ import { screen } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import ListItem from 'components/ConsumerGroups/Details/ListItem';
|
||||
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { ConsumerGroupTopicPartition } from 'generated-sources';
|
||||
|
||||
const clusterName = 'cluster1';
|
||||
|
||||
const renderComponent = (consumers: ConsumerGroupTopicPartition[] = []) =>
|
||||
render(
|
||||
<Route
|
||||
path={clusterConsumerGroupDetailsPath(':clusterName', ':consumerGroupID')}
|
||||
>
|
||||
<WithRoute path={clusterConsumerGroupDetailsPath()}>
|
||||
<table>
|
||||
<tbody>
|
||||
<ListItem
|
||||
|
@ -24,12 +21,14 @@ const renderComponent = (consumers: ConsumerGroupTopicPartition[] = []) =>
|
|||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConsumerGroupDetailsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
initialEntries: [
|
||||
clusterConsumerGroupDetailsPath(
|
||||
clusterName,
|
||||
consumerGroupPayload.groupId
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export const GroupIDCell: React.FC<TableCellProps<ConsumerGroup, string>> = ({
|
|||
}) => {
|
||||
return (
|
||||
<SmartTableKeyLink>
|
||||
<Link to={`consumer-groups/${groupId}`}>{groupId}</Link>
|
||||
<Link to={groupId}>{groupId}</Link>
|
||||
</SmartTableKeyLink>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import Search from 'components/common/Search/Search';
|
||||
import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
|
||||
|
@ -18,7 +17,8 @@ import {
|
|||
import usePagination from 'lib/hooks/usePagination';
|
||||
import useSearch from 'lib/hooks/useSearch';
|
||||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
import { ClusterName } from 'redux/interfaces';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
import { fetchConsumerGroupsPaged } from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
|
||||
|
@ -42,7 +42,7 @@ const List: React.FC<Props> = ({
|
|||
const { page, perPage } = usePagination();
|
||||
const [searchText, handleSearchText] = useSearch();
|
||||
const dispatch = useAppDispatch();
|
||||
const { clusterName } = useParams<{ clusterName: ClusterName }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(
|
||||
|
|
|
@ -40,10 +40,7 @@ describe('Consumer Groups Table Cells', () => {
|
|||
);
|
||||
const linkElement = screen.getByRole('link');
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
expect(linkElement).toHaveAttribute(
|
||||
'href',
|
||||
`/consumer-groups/${consumerGroup.groupId}`
|
||||
);
|
||||
expect(linkElement).toHaveAttribute('href', `/${consumerGroup.groupId}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { clusterConsumerGroupsPath } from 'lib/paths';
|
||||
import { clusterConsumerGroupsPath, getNonExactPath } from 'lib/paths';
|
||||
import {
|
||||
act,
|
||||
screen,
|
||||
|
@ -11,27 +11,19 @@ import {
|
|||
consumerGroups,
|
||||
noConsumerGroupsResponse,
|
||||
} from 'redux/reducers/consumerGroups/__test__/fixtures';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
const clusterName = 'cluster1';
|
||||
|
||||
const historyMock = createMemoryHistory({
|
||||
initialEntries: [clusterConsumerGroupsPath(clusterName)],
|
||||
});
|
||||
|
||||
const renderComponent = (history = historyMock) =>
|
||||
const renderComponent = (path?: string) =>
|
||||
render(
|
||||
<Router history={history}>
|
||||
<Route path={clusterConsumerGroupsPath(':clusterName')}>
|
||||
<ConsumerGroups />
|
||||
</Route>
|
||||
</Router>,
|
||||
<WithRoute path={getNonExactPath(clusterConsumerGroupsPath())}>
|
||||
<ConsumerGroups />
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterConsumerGroupsPath(clusterName),
|
||||
initialEntries: [path || clusterConsumerGroupsPath(clusterName)],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -123,12 +115,9 @@ describe('ConsumerGroups', () => {
|
|||
}
|
||||
);
|
||||
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [
|
||||
`${clusterConsumerGroupsPath(clusterName)}?q=${searchText}`,
|
||||
],
|
||||
});
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(
|
||||
`${clusterConsumerGroupsPath(clusterName)}?q=${searchText}`
|
||||
);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
||||
await waitFor(() => expect(consumerGroupsMock.called()).toBeTruthy());
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
import React from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
import { clusterKsqlDbPath, clusterKsqlDbQueryPath } from 'lib/paths';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { clusterKsqlDbQueryRelativePath } from 'lib/paths';
|
||||
import List from 'components/KsqlDb/List/List';
|
||||
import Query from 'components/KsqlDb/Query/Query';
|
||||
import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
||||
|
||||
const KsqlDb: React.FC = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<BreadcrumbRoute exact path={clusterKsqlDbPath()} component={List} />
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterKsqlDbQueryPath()}
|
||||
component={Query}
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<List />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
<Route
|
||||
path={clusterKsqlDbQueryRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Query />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, { FC, useEffect } from 'react';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import * as Metrics from 'components/common/Metrics';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import ListItem from 'components/KsqlDb/List/ListItem';
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { fetchKsqlDbTables } from 'redux/reducers/ksqlDb/ksqlDbSlice';
|
||||
import { getKsqlDbTables } from 'redux/reducers/ksqlDb/selectors';
|
||||
import { clusterKsqlDbQueryPath } from 'lib/paths';
|
||||
import { clusterKsqlDbQueryRelativePath, ClusterNameRoute } from 'lib/paths';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
|
@ -25,7 +25,7 @@ const accessors = headers.map((header) => header.accessor);
|
|||
const List: FC = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
|
||||
const { rows, fetching, tablesCount, streamsCount } =
|
||||
useSelector(getKsqlDbTables);
|
||||
|
@ -38,8 +38,7 @@ const List: FC = () => {
|
|||
<>
|
||||
<PageHeading text="KSQL DB">
|
||||
<Button
|
||||
isLink
|
||||
to={clusterKsqlDbQueryPath(clusterName)}
|
||||
to={clusterKsqlDbQueryRelativePath}
|
||||
buttonType="primary"
|
||||
buttonSize="M"
|
||||
>
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
import React from 'react';
|
||||
import List from 'components/KsqlDb/List/List';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { clusterKsqlDbPath } from 'lib/paths';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { screen, waitForElementToBeRemoved } from '@testing-library/dom';
|
||||
|
||||
const history = createMemoryHistory();
|
||||
const clusterName = 'local';
|
||||
|
||||
const renderComponent = () => {
|
||||
history.push(clusterKsqlDbPath(clusterName));
|
||||
render(
|
||||
<Router history={history}>
|
||||
<Route path={clusterKsqlDbPath(':clusterName')}>
|
||||
<List />
|
||||
</Route>
|
||||
</Router>
|
||||
<WithRoute path={clusterKsqlDbPath()}>
|
||||
<List />
|
||||
</WithRoute>,
|
||||
{ initialEntries: [clusterKsqlDbPath(clusterName)] }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { clusterKsqlDbPath } from 'lib/paths';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/dom';
|
||||
import ListItem from 'components/KsqlDb/List/ListItem';
|
||||
|
||||
const history = createMemoryHistory();
|
||||
const clusterName = 'local';
|
||||
|
||||
const renderComponent = ({
|
||||
|
@ -16,13 +13,11 @@ const renderComponent = ({
|
|||
accessors: string[];
|
||||
data: Record<string, string>;
|
||||
}) => {
|
||||
history.push(clusterKsqlDbPath(clusterName));
|
||||
render(
|
||||
<Router history={history}>
|
||||
<Route path={clusterKsqlDbPath(':clusterName')}>
|
||||
<ListItem accessors={accessors} data={data} />
|
||||
</Route>
|
||||
</Router>
|
||||
<WithRoute path={clusterKsqlDbPath()}>
|
||||
<ListItem accessors={accessors} data={data} />
|
||||
</WithRoute>,
|
||||
{ initialEntries: [clusterKsqlDbPath(clusterName)] }
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useEffect, FC, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import TableRenderer from 'components/KsqlDb/Query/renderer/TableRenderer/TableRenderer';
|
||||
import {
|
||||
executeKsql,
|
||||
|
@ -11,6 +11,7 @@ import { BASE_PARAMS } from 'lib/constants';
|
|||
import { KsqlResponse, KsqlTableResponse } from 'generated-sources';
|
||||
import { alertAdded, alertDissmissed } from 'redux/reducers/alerts/alertsSlice';
|
||||
import { now } from 'lodash';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
|
||||
import type { FormValues } from './QueryForm/QueryForm';
|
||||
import * as S from './Query.styled';
|
||||
|
@ -61,7 +62,7 @@ export const getFormattedErrorFromTableData = (
|
|||
};
|
||||
|
||||
const Query: FC = () => {
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
|
||||
const sseRef = React.useRef<{ sse: EventSource | null; isOpen: boolean }>({
|
||||
sse: null,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { render, EventSourceMock } from 'lib/testHelpers';
|
||||
import { render, EventSourceMock, WithRoute } from 'lib/testHelpers';
|
||||
import React from 'react';
|
||||
import Query, {
|
||||
getFormattedErrorFromTableData,
|
||||
|
@ -6,18 +6,17 @@ import Query, {
|
|||
import { screen, within } from '@testing-library/dom';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { clusterKsqlDbQueryPath } from 'lib/paths';
|
||||
import { act } from '@testing-library/react';
|
||||
|
||||
const clusterName = 'testLocal';
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<Route path={clusterKsqlDbQueryPath(':clusterName')}>
|
||||
<WithRoute path={clusterKsqlDbQueryPath()}>
|
||||
<Query />
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterKsqlDbQueryPath(clusterName),
|
||||
initialEntries: [clusterKsqlDbQueryPath(clusterName)],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,15 +1,42 @@
|
|||
import React from 'react';
|
||||
import KsqlDb from 'components/KsqlDb/KsqlDb';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/dom';
|
||||
import { clusterKsqlDbPath } from 'lib/paths';
|
||||
import {
|
||||
clusterKsqlDbPath,
|
||||
clusterKsqlDbQueryPath,
|
||||
getNonExactPath,
|
||||
} from 'lib/paths';
|
||||
|
||||
const KSqLComponentText = {
|
||||
list: 'list',
|
||||
query: 'query',
|
||||
};
|
||||
|
||||
jest.mock('components/KsqlDb/List/List', () => () => (
|
||||
<div>{KSqLComponentText.list}</div>
|
||||
));
|
||||
jest.mock('components/KsqlDb/Query/Query', () => () => (
|
||||
<div>{KSqLComponentText.query}</div>
|
||||
));
|
||||
|
||||
describe('KsqlDb Component', () => {
|
||||
describe('KsqlDb', () => {
|
||||
it('to be in the document', () => {
|
||||
render(<KsqlDb />, { pathname: clusterKsqlDbPath() });
|
||||
expect(screen.getByText('KSQL DB')).toBeInTheDocument();
|
||||
expect(screen.getByText('Execute KSQL Request')).toBeInTheDocument();
|
||||
});
|
||||
const clusterName = 'clusterName';
|
||||
const renderComponent = (path: string) =>
|
||||
render(
|
||||
<WithRoute path={getNonExactPath(clusterKsqlDbPath())}>
|
||||
<KsqlDb />
|
||||
</WithRoute>,
|
||||
{ initialEntries: [path] }
|
||||
);
|
||||
|
||||
it('Renders the List', () => {
|
||||
renderComponent(clusterKsqlDbPath(clusterName));
|
||||
expect(screen.getByText(KSqLComponentText.list)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders the List', () => {
|
||||
renderComponent(clusterKsqlDbQueryPath(clusterName));
|
||||
expect(screen.getByText(KSqLComponentText.query)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
clusterConsumerGroupsPath,
|
||||
clusterSchemasPath,
|
||||
clusterConnectorsPath,
|
||||
clusterConnectsPath,
|
||||
clusterKsqlDbPath,
|
||||
} from 'lib/paths';
|
||||
|
||||
|
@ -54,10 +53,6 @@ const ClusterMenu: React.FC<Props> = ({
|
|||
<ClusterMenuItem
|
||||
to={clusterConnectorsPath(name)}
|
||||
title="Kafka Connect"
|
||||
isActive={(_, location) =>
|
||||
location.pathname.startsWith(clusterConnectsPath(name)) ||
|
||||
location.pathname.startsWith(clusterConnectorsPath(name))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasFeatureConfigured(ClusterFeaturesEnum.KSQL_DB) && (
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
import React, { PropsWithChildren } from 'react';
|
||||
import { NavLinkProps } from 'react-router-dom';
|
||||
|
||||
import * as S from './Nav.styled';
|
||||
|
||||
export interface ClusterMenuItemProps {
|
||||
to: string;
|
||||
title?: string;
|
||||
exact?: boolean;
|
||||
isTopLevel?: boolean;
|
||||
isActive?: NavLinkProps['isActive'];
|
||||
}
|
||||
|
||||
const ClusterMenuItem: React.FC<PropsWithChildren<ClusterMenuItemProps>> = (
|
||||
props
|
||||
) => {
|
||||
const { to, title, children, exact, isTopLevel, isActive } = props;
|
||||
const { to, title, children, isTopLevel } = props;
|
||||
|
||||
if (to) {
|
||||
return (
|
||||
<S.ListItem $isTopLevel={isTopLevel}>
|
||||
<S.Link to={to} title={title} exact={exact} isActive={isActive}>
|
||||
<S.Link to={to} title={title}>
|
||||
{title}
|
||||
</S.Link>
|
||||
{children}
|
||||
|
|
|
@ -14,13 +14,13 @@ export const Divider = styled.hr`
|
|||
height: 1px;
|
||||
`;
|
||||
|
||||
export const Link = styled(NavLink).attrs({ activeClassName: 'is-active' })(
|
||||
({ theme, activeClassName }) => css`
|
||||
export const Link = styled(NavLink)(
|
||||
({ theme }) => css`
|
||||
width: 100%;
|
||||
padding: 0.5em 0.75em;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
margin: 0px 0px;
|
||||
margin: 0 0;
|
||||
background-color: ${theme.menu.backgroundColor.normal};
|
||||
color: ${theme.menu.color.normal};
|
||||
|
||||
|
@ -28,8 +28,7 @@ export const Link = styled(NavLink).attrs({ activeClassName: 'is-active' })(
|
|||
background-color: ${theme.menu.backgroundColor.hover};
|
||||
color: ${theme.menu.color.hover};
|
||||
}
|
||||
|
||||
&.${activeClassName} {
|
||||
&.active {
|
||||
background-color: ${theme.menu.backgroundColor.active};
|
||||
color: ${theme.menu.color.active};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ interface Props {
|
|||
const Nav: React.FC<Props> = ({ areClustersFulfilled, clusters }) => (
|
||||
<aside aria-label="Sidebar Menu">
|
||||
<S.List>
|
||||
<ClusterMenuItem exact to="/" title="Dashboard" isTopLevel />
|
||||
<ClusterMenuItem to="/" title="Dashboard" isTopLevel />
|
||||
</S.List>
|
||||
|
||||
{areClustersFulfilled &&
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Cluster, ClusterFeaturesEnum } from 'generated-sources';
|
|||
import { onlineClusterPayload } from 'redux/reducers/clusters/__test__/fixtures';
|
||||
import ClusterMenu from 'components/Nav/ClusterMenu';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { clusterConnectorsPath, clusterConnectsPath } from 'lib/paths';
|
||||
import { clusterConnectorsPath } from 'lib/paths';
|
||||
import { render } from 'lib/testHelpers';
|
||||
|
||||
describe('ClusterMenu', () => {
|
||||
|
@ -55,7 +55,7 @@ describe('ClusterMenu', () => {
|
|||
});
|
||||
it('renders open cluster menu', () => {
|
||||
render(setupComponent(onlineClusterPayload, true), {
|
||||
pathname: clusterConnectorsPath(onlineClusterPayload.name),
|
||||
initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)],
|
||||
});
|
||||
|
||||
expect(getMenuItems().length).toEqual(4);
|
||||
|
@ -70,28 +70,15 @@ describe('ClusterMenu', () => {
|
|||
...onlineClusterPayload,
|
||||
features: [ClusterFeaturesEnum.KAFKA_CONNECT],
|
||||
}),
|
||||
{ pathname: clusterConnectorsPath(onlineClusterPayload.name) }
|
||||
{ initialEntries: [clusterConnectorsPath(onlineClusterPayload.name)] }
|
||||
);
|
||||
expect(getMenuItems().length).toEqual(1);
|
||||
userEvent.click(getMenuItem());
|
||||
expect(getMenuItems().length).toEqual(5);
|
||||
|
||||
expect(getKafkaConnect()).toBeInTheDocument();
|
||||
expect(getKafkaConnect()).toHaveClass('is-active');
|
||||
});
|
||||
it('makes Kafka Connect link active', () => {
|
||||
render(
|
||||
setupComponent({
|
||||
...onlineClusterPayload,
|
||||
features: [ClusterFeaturesEnum.KAFKA_CONNECT],
|
||||
}),
|
||||
{ pathname: clusterConnectsPath(onlineClusterPayload.name) }
|
||||
);
|
||||
expect(getMenuItems().length).toEqual(1);
|
||||
userEvent.click(getMenuItem());
|
||||
expect(getMenuItems().length).toEqual(5);
|
||||
const kafkaConnect = getKafkaConnect();
|
||||
expect(kafkaConnect).toBeInTheDocument();
|
||||
|
||||
expect(getKafkaConnect()).toBeInTheDocument();
|
||||
expect(getKafkaConnect()).toHaveClass('is-active');
|
||||
expect(getKafkaConnect()).toHaveClass('active');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
clusterSchemasPath,
|
||||
clusterSchemaSchemaDiffPath,
|
||||
clusterSchemaEditPath,
|
||||
ClusterSubjectParam,
|
||||
clusterSchemaEditPageRelativePath,
|
||||
clusterSchemaSchemaDiffRelativePath,
|
||||
} from 'lib/paths';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
|
@ -31,16 +31,16 @@ import { serverErrorAlertAdded } from 'redux/reducers/alerts/alertsSlice';
|
|||
import { getResponse } from 'lib/errorHandling';
|
||||
import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
|
||||
import { TableTitle } from 'components/common/table/TableTitle/TableTitle.styled';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import LatestVersionItem from './LatestVersion/LatestVersionItem';
|
||||
import SchemaVersion from './SchemaVersion/SchemaVersion';
|
||||
|
||||
const Details: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReadOnly } = React.useContext(ClusterContext);
|
||||
const { clusterName, subject } =
|
||||
useParams<{ clusterName: string; subject: string }>();
|
||||
const { clusterName, subject } = useAppParams<ClusterSubjectParam>();
|
||||
const [
|
||||
isDeleteSchemaConfirmationVisible,
|
||||
setDeleteSchemaConfirmationVisible,
|
||||
|
@ -71,7 +71,7 @@ const Details: React.FC = () => {
|
|||
clusterName,
|
||||
subject,
|
||||
});
|
||||
history.push(clusterSchemasPath(clusterName));
|
||||
navigate('../');
|
||||
} catch (e) {
|
||||
const err = await getResponse(e as Response);
|
||||
dispatch(serverErrorAlertAdded(err));
|
||||
|
@ -87,21 +87,19 @@ const Details: React.FC = () => {
|
|||
{!isReadOnly && (
|
||||
<>
|
||||
<Button
|
||||
isLink
|
||||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
to={{
|
||||
pathname: clusterSchemaSchemaDiffPath(clusterName, subject),
|
||||
pathname: clusterSchemaSchemaDiffRelativePath,
|
||||
search: `leftVersion=${versions[0]?.version}&rightVersion=${versions[0]?.version}`,
|
||||
}}
|
||||
>
|
||||
Compare Versions
|
||||
</Button>
|
||||
<Button
|
||||
isLink
|
||||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
to={clusterSchemaEditPath(clusterName, subject)}
|
||||
to={clusterSchemaEditPageRelativePath}
|
||||
>
|
||||
Edit Schema
|
||||
</Button>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import Details from 'components/Schemas/Details/Details';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterSchemaPath } from 'lib/paths';
|
||||
import { screen, waitFor } from '@testing-library/dom';
|
||||
import {
|
||||
|
@ -27,13 +26,13 @@ const renderComponent = (
|
|||
context: ContextProps = contextInitialValue
|
||||
) =>
|
||||
render(
|
||||
<Route path={clusterSchemaPath(':clusterName', ':subject')}>
|
||||
<WithRoute path={clusterSchemaPath()}>
|
||||
<ClusterContext.Provider value={context}>
|
||||
<Details />
|
||||
</ClusterContext.Provider>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterSchemaPath(clusterName, schemaVersion.subject),
|
||||
initialEntries: [clusterSchemaPath(clusterName, schemaVersion.subject)],
|
||||
preloadedState: {
|
||||
schemas: initialState,
|
||||
},
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import { clusterSchemaSchemaDiffPath } from 'lib/paths';
|
||||
import { clusterSchemaSchemaDiffPath, ClusterSubjectParam } from 'lib/paths';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import DiffViewer from 'components/common/DiffViewer/DiffViewer';
|
||||
import { useHistory, useParams, useLocation } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
fetchSchemaVersions,
|
||||
SCHEMAS_VERSIONS_FETCH_ACTION,
|
||||
|
@ -12,31 +12,32 @@ import { useForm, Controller } from 'react-hook-form';
|
|||
import Select from 'components/common/Select/Select';
|
||||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import * as S from './Diff.styled';
|
||||
|
||||
export interface DiffProps {
|
||||
leftVersionInPath?: string;
|
||||
rightVersionInPath?: string;
|
||||
versions: SchemaSubject[];
|
||||
areVersionsFetched: boolean;
|
||||
}
|
||||
|
||||
const Diff: React.FC<DiffProps> = ({
|
||||
leftVersionInPath,
|
||||
rightVersionInPath,
|
||||
versions,
|
||||
areVersionsFetched,
|
||||
}) => {
|
||||
const [leftVersion, setLeftVersion] = React.useState(leftVersionInPath || '');
|
||||
const [rightVersion, setRightVersion] = React.useState(
|
||||
rightVersionInPath || ''
|
||||
);
|
||||
const history = useHistory();
|
||||
const Diff: React.FC<DiffProps> = ({ versions, areVersionsFetched }) => {
|
||||
const { clusterName, subject } = useAppParams<ClusterSubjectParam>();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const { clusterName, subject } =
|
||||
useParams<{ clusterName: string; subject: string }>();
|
||||
const searchParams = React.useMemo(
|
||||
() => new URLSearchParams(location.search),
|
||||
[location]
|
||||
);
|
||||
|
||||
const [leftVersion, setLeftVersion] = React.useState(
|
||||
searchParams.get('leftVersion') || ''
|
||||
);
|
||||
const [rightVersion, setRightVersion] = React.useState(
|
||||
searchParams.get('rightVersion') || ''
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -64,11 +65,6 @@ const Diff: React.FC<DiffProps> = ({
|
|||
control,
|
||||
} = methods;
|
||||
|
||||
const searchParams = React.useMemo(
|
||||
() => new URLSearchParams(location.search),
|
||||
[location]
|
||||
);
|
||||
|
||||
return (
|
||||
<S.Section>
|
||||
{areVersionsFetched ? (
|
||||
|
@ -89,7 +85,7 @@ const Diff: React.FC<DiffProps> = ({
|
|||
leftVersion === '' ? versions[0].version : leftVersion
|
||||
}
|
||||
onChange={(event) => {
|
||||
history.push(
|
||||
navigate(
|
||||
clusterSchemaSchemaDiffPath(clusterName, subject)
|
||||
);
|
||||
searchParams.set('leftVersion', event.toString());
|
||||
|
@ -99,7 +95,7 @@ const Diff: React.FC<DiffProps> = ({
|
|||
? versions[0].version
|
||||
: rightVersion
|
||||
);
|
||||
history.push({
|
||||
navigate({
|
||||
search: `?${searchParams.toString()}`,
|
||||
});
|
||||
setLeftVersion(event.toString());
|
||||
|
@ -130,7 +126,7 @@ const Diff: React.FC<DiffProps> = ({
|
|||
rightVersion === '' ? versions[0].version : rightVersion
|
||||
}
|
||||
onChange={(event) => {
|
||||
history.push(
|
||||
navigate(
|
||||
clusterSchemaSchemaDiffPath(clusterName, subject)
|
||||
);
|
||||
searchParams.set(
|
||||
|
@ -138,7 +134,7 @@ const Diff: React.FC<DiffProps> = ({
|
|||
leftVersion === '' ? versions[0].version : leftVersion
|
||||
);
|
||||
searchParams.set('rightVersion', event.toString());
|
||||
history.push({
|
||||
navigate({
|
||||
search: `?${searchParams.toString()}`,
|
||||
});
|
||||
setRightVersion(event.toString());
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import {
|
||||
getAreSchemaVersionsFulfilled,
|
||||
selectAllSchemaVersions,
|
||||
|
@ -8,25 +7,9 @@ import {
|
|||
|
||||
import Diff from './Diff';
|
||||
|
||||
interface RouteProps {
|
||||
leftVersion?: string;
|
||||
rightVersion?: string;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { leftVersion, rightVersion },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
versions: selectAllSchemaVersions(state),
|
||||
areVersionsFetched: getAreSchemaVersionsFulfilled(state),
|
||||
leftVersionInPath: leftVersion,
|
||||
rightVersionInPath: rightVersion,
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(Diff));
|
||||
export default connect(mapStateToProps)(Diff);
|
||||
|
|
|
@ -1,20 +1,46 @@
|
|||
import React from 'react';
|
||||
import Diff, { DiffProps } from 'components/Schemas/Diff/Diff';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { clusterSchemaSchemaDiffPath } from 'lib/paths';
|
||||
|
||||
import { versions } from './fixtures';
|
||||
|
||||
const defaultClusterName = 'defaultClusterName';
|
||||
const defaultSubject = 'defaultSubject';
|
||||
const defaultPathName = clusterSchemaSchemaDiffPath(
|
||||
defaultClusterName,
|
||||
defaultSubject
|
||||
);
|
||||
|
||||
describe('Diff', () => {
|
||||
const setupComponent = (props: DiffProps) =>
|
||||
render(
|
||||
<Diff
|
||||
versions={props.versions}
|
||||
leftVersionInPath={props.leftVersionInPath}
|
||||
rightVersionInPath={props.rightVersionInPath}
|
||||
areVersionsFetched={props.areVersionsFetched}
|
||||
/>
|
||||
const setupComponent = (
|
||||
props: DiffProps,
|
||||
searchQuery: { rightVersion?: string; leftVersion?: string } = {}
|
||||
) => {
|
||||
let pathname = defaultPathName;
|
||||
const searchParams = new URLSearchParams(pathname);
|
||||
if (searchQuery.rightVersion) {
|
||||
searchParams.set('rightVersion', searchQuery.rightVersion);
|
||||
}
|
||||
if (searchQuery.leftVersion) {
|
||||
searchParams.set('leftVersion', searchQuery.leftVersion);
|
||||
}
|
||||
|
||||
pathname = `${pathname}?${searchParams.toString()}`;
|
||||
|
||||
return render(
|
||||
<WithRoute path={clusterSchemaSchemaDiffPath()}>
|
||||
<Diff
|
||||
versions={props.versions}
|
||||
areVersionsFetched={props.areVersionsFetched}
|
||||
/>
|
||||
</WithRoute>,
|
||||
{
|
||||
initialEntries: [pathname],
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
describe('Container', () => {
|
||||
it('renders view', () => {
|
||||
|
@ -69,12 +95,13 @@ describe('Diff', () => {
|
|||
});
|
||||
describe('when schema versions are loaded and two versions in path', () => {
|
||||
beforeEach(() => {
|
||||
setupComponent({
|
||||
areVersionsFetched: true,
|
||||
versions,
|
||||
leftVersionInPath: '1',
|
||||
rightVersionInPath: '2',
|
||||
});
|
||||
setupComponent(
|
||||
{
|
||||
areVersionsFetched: true,
|
||||
versions,
|
||||
},
|
||||
{ leftVersion: '1', rightVersion: '2' }
|
||||
);
|
||||
});
|
||||
|
||||
it('renders left select with version 1', () => {
|
||||
|
@ -92,11 +119,15 @@ describe('Diff', () => {
|
|||
|
||||
describe('when schema versions are loaded and only one versions in path', () => {
|
||||
beforeEach(() => {
|
||||
setupComponent({
|
||||
areVersionsFetched: true,
|
||||
versions,
|
||||
leftVersionInPath: '1',
|
||||
});
|
||||
setupComponent(
|
||||
{
|
||||
areVersionsFetched: true,
|
||||
versions,
|
||||
},
|
||||
{
|
||||
leftVersion: '1',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('renders left select with version 1', () => {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller, FormProvider } from 'react-hook-form';
|
||||
import {
|
||||
CompatibilityLevelCompatibilityEnum,
|
||||
SchemaType,
|
||||
} from 'generated-sources';
|
||||
import { clusterSchemaPath } from 'lib/paths';
|
||||
import { clusterSchemaPath, ClusterSubjectParam } from 'lib/paths';
|
||||
import { NewSchemaSubjectRaw } from 'redux/interfaces';
|
||||
import Editor from 'components/common/Editor/Editor';
|
||||
import Select from 'components/common/Select/Select';
|
||||
|
@ -13,6 +13,7 @@ import { Button } from 'components/common/Button/Button';
|
|||
import { InputLabel } from 'components/common/Input/InputLabel.styled';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
schemaAdded,
|
||||
schemasApiClient,
|
||||
|
@ -30,11 +31,10 @@ import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
|
|||
import * as S from './Edit.styled';
|
||||
|
||||
const Edit: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { clusterName, subject } =
|
||||
useParams<{ clusterName: string; subject: string }>();
|
||||
const { clusterName, subject } = useAppParams<ClusterSubjectParam>();
|
||||
const methods = useForm<NewSchemaSubjectRaw>({ mode: 'onChange' });
|
||||
const {
|
||||
formState: { isDirty, isSubmitting, dirtyFields },
|
||||
|
@ -90,7 +90,7 @@ const Edit: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
history.push(clusterSchemaPath(clusterName, subject));
|
||||
navigate(clusterSchemaPath(clusterName, subject));
|
||||
} catch (e) {
|
||||
const err = await getResponse(e as Response);
|
||||
dispatch(serverErrorAlertAdded(err));
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import React from 'react';
|
||||
import Edit from 'components/Schemas/Edit/Edit';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterSchemaEditPath } from 'lib/paths';
|
||||
import {
|
||||
schemasInitialState,
|
||||
schemaVersion,
|
||||
} from 'redux/reducers/schemas/__test__/fixtures';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { screen, waitFor } from '@testing-library/dom';
|
||||
import ClusterContext, {
|
||||
ContextProps,
|
||||
|
@ -24,13 +23,15 @@ const renderComponent = (
|
|||
context: ContextProps = contextInitialValue
|
||||
) =>
|
||||
render(
|
||||
<Route path={clusterSchemaEditPath(':clusterName', ':subject')}>
|
||||
<WithRoute path={clusterSchemaEditPath()}>
|
||||
<ClusterContext.Provider value={context}>
|
||||
<Edit />
|
||||
</ClusterContext.Provider>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterSchemaEditPath(clusterName, schemaVersion.subject),
|
||||
initialEntries: [
|
||||
clusterSchemaEditPath(clusterName, schemaVersion.subject),
|
||||
],
|
||||
preloadedState: {
|
||||
schemas: initialState,
|
||||
},
|
||||
|
@ -41,7 +42,7 @@ describe('Edit', () => {
|
|||
afterEach(() => fetchMock.reset());
|
||||
|
||||
describe('fetch failed', () => {
|
||||
it('renders pageloader', async () => {
|
||||
it('renders page loader', async () => {
|
||||
const schemasAPILatestMock = fetchMock.getOnce(schemasAPILatestUrl, 404);
|
||||
await act(() => {
|
||||
renderComponent();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import Select from 'components/common/Select/Select';
|
||||
import { CompatibilityLevelCompatibilityEnum } from 'generated-sources';
|
||||
|
@ -5,18 +6,18 @@ import { getResponse } from 'lib/errorHandling';
|
|||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
import usePagination from 'lib/hooks/usePagination';
|
||||
import useSearch from 'lib/hooks/useSearch';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { serverErrorAlertAdded } from 'redux/reducers/alerts/alertsSlice';
|
||||
import {
|
||||
fetchSchemas,
|
||||
schemasApiClient,
|
||||
} from 'redux/reducers/schemas/schemasSlice';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
|
||||
import * as S from './GlobalSchemaSelector.styled';
|
||||
|
||||
const GlobalSchemaSelector: React.FC = () => {
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const dispatch = useAppDispatch();
|
||||
const [searchText] = useSearch();
|
||||
const { page, perPage } = usePagination();
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import { act, screen, waitFor, within } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { CompatibilityLevelCompatibilityEnum } from 'generated-sources';
|
||||
import GlobalSchemaSelector from 'components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { clusterSchemasPath } from 'lib/paths';
|
||||
import { Route } from 'react-router-dom';
|
||||
import fetchMock from 'fetch-mock';
|
||||
|
||||
const clusterName = 'testClusterName';
|
||||
|
@ -29,11 +28,11 @@ const expectOptionIsSelected = (option: string) => {
|
|||
describe('GlobalSchemaSelector', () => {
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<Route path={clusterSchemasPath(':clusterName')}>
|
||||
<WithRoute path={clusterSchemasPath()}>
|
||||
<GlobalSchemaSelector />
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterSchemasPath(clusterName),
|
||||
initialEntries: [clusterSchemasPath(clusterName)],
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { clusterSchemaNewPath } from 'lib/paths';
|
||||
import { ClusterNameRoute, clusterSchemaNewRelativePath } from 'lib/paths';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import * as C from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import { Button } from 'components/common/Button/Button';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
selectAllSchemas,
|
||||
fetchSchemas,
|
||||
|
@ -27,7 +27,7 @@ import GlobalSchemaSelector from './GlobalSchemaSelector/GlobalSchemaSelector';
|
|||
const List: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReadOnly } = React.useContext(ClusterContext);
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
|
||||
const schemas = useAppSelector(selectAllSchemas);
|
||||
const isFetched = useAppSelector(getAreSchemasFulfilled);
|
||||
|
@ -52,8 +52,7 @@ const List: React.FC = () => {
|
|||
<Button
|
||||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
isLink
|
||||
to={clusterSchemaNewPath(clusterName)}
|
||||
to={clusterSchemaNewRelativePath}
|
||||
>
|
||||
<i className="fas fa-plus" /> Create Schema
|
||||
</Button>
|
||||
|
|
|
@ -13,7 +13,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
return (
|
||||
<tr>
|
||||
<S.TableKeyLink>
|
||||
<NavLink exact to={`schemas/${subject}`} role="link">
|
||||
<NavLink to={subject} role="link">
|
||||
{subject}
|
||||
</NavLink>
|
||||
</S.TableKeyLink>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import List from 'components/Schemas/List/List';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterSchemasPath } from 'lib/paths';
|
||||
import { act, screen } from '@testing-library/react';
|
||||
import {
|
||||
|
@ -27,13 +26,13 @@ const renderComponent = (
|
|||
context: ContextProps = contextInitialValue
|
||||
) =>
|
||||
render(
|
||||
<Route path={clusterSchemasPath(':clusterName')}>
|
||||
<WithRoute path={clusterSchemasPath()}>
|
||||
<ClusterContext.Provider value={context}>
|
||||
<List />
|
||||
</ClusterContext.Provider>
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterSchemasPath(clusterName),
|
||||
initialEntries: [clusterSchemasPath(clusterName)],
|
||||
preloadedState: {
|
||||
schemas: initialState,
|
||||
},
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { NewSchemaSubjectRaw } from 'redux/interfaces';
|
||||
import { FormProvider, useForm, Controller } from 'react-hook-form';
|
||||
import { ErrorMessage } from '@hookform/error-message';
|
||||
import { clusterSchemaPath } from 'lib/paths';
|
||||
import { ClusterNameRoute, clusterSchemaPath } from 'lib/paths';
|
||||
import { SchemaType } from 'generated-sources';
|
||||
import { SCHEMA_NAME_VALIDATION_PATTERN } from 'lib/constants';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { InputLabel } from 'components/common/Input/InputLabel.styled';
|
||||
import Input from 'components/common/Input/Input';
|
||||
import { FormError } from 'components/common/Input/Input.styled';
|
||||
|
@ -18,6 +18,7 @@ import {
|
|||
schemasApiClient,
|
||||
} from 'redux/reducers/schemas/schemasSlice';
|
||||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { serverErrorAlertAdded } from 'redux/reducers/alerts/alertsSlice';
|
||||
import { getResponse } from 'lib/errorHandling';
|
||||
|
||||
|
@ -30,8 +31,8 @@ const SchemaTypeOptions: Array<SelectOption> = [
|
|||
];
|
||||
|
||||
const New: React.FC = () => {
|
||||
const { clusterName } = useParams<{ clusterName: string }>();
|
||||
const history = useHistory();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const methods = useForm<NewSchemaSubjectRaw>();
|
||||
const {
|
||||
|
@ -52,7 +53,7 @@ const New: React.FC = () => {
|
|||
newSchemaSubject: { subject, schema, schemaType },
|
||||
});
|
||||
dispatch(schemaAdded(resp));
|
||||
history.push(clusterSchemaPath(clusterName, subject));
|
||||
navigate(clusterSchemaPath(clusterName, subject));
|
||||
} catch (e) {
|
||||
const err = await getResponse(e as Response);
|
||||
dispatch(serverErrorAlertAdded(err));
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import New from 'components/Schemas/New/New';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterSchemaNewPath } from 'lib/paths';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { screen } from '@testing-library/dom';
|
||||
|
||||
const clusterName = 'local';
|
||||
|
@ -10,11 +9,11 @@ const clusterName = 'local';
|
|||
describe('New Component', () => {
|
||||
beforeEach(() => {
|
||||
render(
|
||||
<Route path={clusterSchemaNewPath(':clusterName')}>
|
||||
<WithRoute path={clusterSchemaNewPath()}>
|
||||
<New />
|
||||
</Route>,
|
||||
</WithRoute>,
|
||||
{
|
||||
pathname: clusterSchemaNewPath(clusterName),
|
||||
initialEntries: [clusterSchemaNewPath(clusterName)],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Switch } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import {
|
||||
clusterSchemaNewPath,
|
||||
clusterSchemaPath,
|
||||
clusterSchemaEditPath,
|
||||
clusterSchemasPath,
|
||||
clusterSchemaSchemaDiffPath,
|
||||
clusterSchemaEditRelativePath,
|
||||
clusterSchemaNewRelativePath,
|
||||
RouteParams,
|
||||
} from 'lib/paths';
|
||||
import List from 'components/Schemas/List/List';
|
||||
import Details from 'components/Schemas/Details/Details';
|
||||
|
@ -16,33 +14,48 @@ import { BreadcrumbRoute } from 'components/common/Breadcrumb/Breadcrumb.route';
|
|||
|
||||
const Schemas: React.FC = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterSchemasPath(':clusterName')}
|
||||
component={List}
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<List />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterSchemaNewPath(':clusterName')}
|
||||
component={New}
|
||||
<Route
|
||||
path={clusterSchemaNewRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<New />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterSchemaPath(':clusterName', ':subject')}
|
||||
component={Details}
|
||||
<Route
|
||||
path={RouteParams.subject}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Details />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterSchemaEditPath(':clusterName', ':subject')}
|
||||
component={Edit}
|
||||
<Route
|
||||
path={clusterSchemaEditRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<Edit />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
<BreadcrumbRoute
|
||||
exact
|
||||
path={clusterSchemaSchemaDiffPath(':clusterName', ':subject')}
|
||||
component={DiffContainer}
|
||||
<Route
|
||||
path={clusterSchemaEditRelativePath}
|
||||
element={
|
||||
<BreadcrumbRoute>
|
||||
<DiffContainer />
|
||||
</BreadcrumbRoute>
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,32 +1,46 @@
|
|||
import React from 'react';
|
||||
import Schemas from 'components/Schemas/Schemas';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterPath,
|
||||
clusterSchemaEditPath,
|
||||
clusterSchemaNewPath,
|
||||
clusterSchemaPath,
|
||||
clusterSchemasPath,
|
||||
getNonExactPath,
|
||||
} from 'lib/paths';
|
||||
import { screen, waitFor } from '@testing-library/dom';
|
||||
import { Route } from 'react-router-dom';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { schemaVersion } from 'redux/reducers/schemas/__test__/fixtures';
|
||||
|
||||
const renderComponent = (pathname: string) =>
|
||||
render(
|
||||
<Route path={clusterPath(':clusterName')}>
|
||||
<WithRoute path={getNonExactPath(clusterSchemasPath())}>
|
||||
<Schemas />
|
||||
</Route>,
|
||||
{ pathname }
|
||||
</WithRoute>,
|
||||
{ initialEntries: [pathname] }
|
||||
);
|
||||
|
||||
const clusterName = 'secondLocal';
|
||||
|
||||
jest.mock('components/Schemas/List/List', () => () => <div>List</div>);
|
||||
jest.mock('components/Schemas/Details/Details', () => () => <div>Details</div>);
|
||||
jest.mock('components/Schemas/New/New', () => () => <div>New</div>);
|
||||
jest.mock('components/Schemas/Edit/Edit', () => () => <div>Edit</div>);
|
||||
const SchemaCompText = {
|
||||
List: 'List',
|
||||
Details: 'Details',
|
||||
New: 'New',
|
||||
Edit: 'Edit',
|
||||
};
|
||||
|
||||
jest.mock('components/Schemas/List/List', () => () => (
|
||||
<div>{SchemaCompText.List}</div>
|
||||
));
|
||||
jest.mock('components/Schemas/Details/Details', () => () => (
|
||||
<div>{SchemaCompText.Details}</div>
|
||||
));
|
||||
jest.mock('components/Schemas/New/New', () => () => (
|
||||
<div>{SchemaCompText.New}</div>
|
||||
));
|
||||
jest.mock('components/Schemas/Edit/Edit', () => () => (
|
||||
<div>{SchemaCompText.Edit}</div>
|
||||
));
|
||||
|
||||
describe('Schemas', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -35,20 +49,26 @@ describe('Schemas', () => {
|
|||
afterEach(() => fetchMock.restore());
|
||||
it('renders List', async () => {
|
||||
renderComponent(clusterSchemasPath(clusterName));
|
||||
await waitFor(() => expect(screen.queryByText('List')).toBeInTheDocument());
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText(SchemaCompText.List)).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
it('renders New', async () => {
|
||||
renderComponent(clusterSchemaNewPath(clusterName));
|
||||
await waitFor(() => expect(screen.queryByText('New')).toBeInTheDocument());
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText(SchemaCompText.New)).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
it('renders Details', async () => {
|
||||
renderComponent(clusterSchemaPath(clusterName, schemaVersion.subject));
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Details')).toBeInTheDocument()
|
||||
expect(screen.queryByText(SchemaCompText.Details)).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
it('renders Edit', async () => {
|
||||
renderComponent(clusterSchemaEditPath(clusterName, schemaVersion.subject));
|
||||
await waitFor(() => expect(screen.queryByText('Edit')).toBeInTheDocument());
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText(SchemaCompText.Edit)).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,10 +2,10 @@ import { Td } from 'components/common/table/TableHeaderCell/TableHeaderCell.styl
|
|||
import { NavLink } from 'react-router-dom';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const Link = styled(NavLink).attrs({ activeClassName: 'is-active' })<{
|
||||
export const Link = styled(NavLink)<{
|
||||
$isInternal?: boolean;
|
||||
}>(
|
||||
({ theme, activeClassName, $isInternal }) => css`
|
||||
({ theme, $isInternal }) => css`
|
||||
color: ${theme.topicsList.color.normal};
|
||||
font-weight: 500;
|
||||
padding-left: ${$isInternal ? '5px' : 0};
|
||||
|
@ -15,7 +15,7 @@ export const Link = styled(NavLink).attrs({ activeClassName: 'is-active' })<{
|
|||
color: ${theme.topicsList.color.hover};
|
||||
}
|
||||
|
||||
&.${activeClassName} {
|
||||
&.active {
|
||||
background-color: ${theme.topicsList.backgroundColor.active};
|
||||
color: ${theme.topicsList.color.active};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
TopicWithDetailedInfo,
|
||||
ClusterName,
|
||||
TopicName,
|
||||
} from 'redux/interfaces';
|
||||
import { clusterTopicCopyPath, clusterTopicNewPath } from 'lib/paths';
|
||||
import {
|
||||
ClusterNameRoute,
|
||||
clusterTopicCopyRelativePath,
|
||||
clusterTopicNewRelativePath,
|
||||
} from 'lib/paths';
|
||||
import usePagination from 'lib/hooks/usePagination';
|
||||
import useModal from 'lib/hooks/useModal';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
|
@ -88,13 +93,15 @@ const List: React.FC<TopicsListProps> = ({
|
|||
}) => {
|
||||
const { isReadOnly, isTopicDeletionAllowed } =
|
||||
React.useContext(ClusterContext);
|
||||
const { clusterName } = useParams<{ clusterName: ClusterName }>();
|
||||
const { page, perPage, pathname } = usePagination();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const { page, perPage } = usePagination();
|
||||
const [showInternal, setShowInternal] = React.useState<boolean>(
|
||||
!localStorage.getItem('hideInternalTopics') && true
|
||||
);
|
||||
const [cachedPage, setCachedPage] = React.useState<number | null>(null);
|
||||
const history = useHistory();
|
||||
const [cachedPage, setCachedPage] = React.useState<number | null>(
|
||||
page || null
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const topicsListParams = React.useMemo(
|
||||
() => ({
|
||||
|
@ -154,7 +161,9 @@ const List: React.FC<TopicsListProps> = ({
|
|||
}
|
||||
|
||||
setShowInternal(!showInternal);
|
||||
history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
|
||||
navigate({
|
||||
search: `?page=1&perPage=${perPage || PER_PAGE}`,
|
||||
});
|
||||
};
|
||||
|
||||
const [confirmationModal, setConfirmationModal] = React.useState<
|
||||
|
@ -176,9 +185,9 @@ const List: React.FC<TopicsListProps> = ({
|
|||
|
||||
const newPageQuery = !searchString && cachedPage ? cachedPage : 1;
|
||||
|
||||
history.push(
|
||||
`${pathname}?page=${newPageQuery}&perPage=${perPage || PER_PAGE}`
|
||||
);
|
||||
navigate({
|
||||
search: `?page=${newPageQuery}&perPage=${perPage || PER_PAGE}`,
|
||||
});
|
||||
};
|
||||
const deleteOrPurgeConfirmationHandler = () => {
|
||||
const selectedIds = Array.from(tableState.selectedIds);
|
||||
|
@ -283,8 +292,7 @@ const List: React.FC<TopicsListProps> = ({
|
|||
<Button
|
||||
buttonType="primary"
|
||||
buttonSize="M"
|
||||
isLink
|
||||
to={clusterTopicNewPath(clusterName)}
|
||||
to={clusterTopicNewRelativePath}
|
||||
>
|
||||
<i className="fas fa-plus" /> Add a Topic
|
||||
</Button>
|
||||
|
@ -331,9 +339,8 @@ const List: React.FC<TopicsListProps> = ({
|
|||
<Button
|
||||
buttonSize="M"
|
||||
buttonType="secondary"
|
||||
isLink
|
||||
to={{
|
||||
pathname: clusterTopicCopyPath(clusterName),
|
||||
pathname: clusterTopicCopyRelativePath,
|
||||
search: `?${getSelectedTopic()}`,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -12,7 +12,7 @@ export const TitleCell: React.FC<
|
|||
return (
|
||||
<>
|
||||
{internal && <Tag color="gray">IN</Tag>}
|
||||
<S.Link exact to={`topics/${name}`} $isInternal={internal}>
|
||||
<S.Link to={name} $isInternal={internal}>
|
||||
{name}
|
||||
</S.Link>
|
||||
</>
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { screen, waitFor, within } from '@testing-library/react';
|
||||
import { Route, Router, StaticRouter } from 'react-router-dom';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { act, screen, waitFor, within } from '@testing-library/react';
|
||||
import ClusterContext, {
|
||||
ContextProps,
|
||||
} from 'components/contexts/ClusterContext';
|
||||
import List, { TopicsListProps } from 'components/Topics/List/List';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { externalTopicPayload } from 'redux/reducers/topics/__test__/fixtures';
|
||||
import { CleanUpPolicy, SortOrder } from 'generated-sources';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { clusterTopicsPath } from 'lib/paths';
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
describe('List', () => {
|
||||
afterEach(() => {
|
||||
mockNavigate.mockClear();
|
||||
});
|
||||
|
||||
const setupComponent = (props: Partial<TopicsListProps> = {}) => (
|
||||
<List
|
||||
areTopicsFetching={false}
|
||||
|
@ -32,15 +41,13 @@ describe('List', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const historyMock = createMemoryHistory();
|
||||
|
||||
const renderComponentWithProviders = (
|
||||
contextProps: Partial<ContextProps> = {},
|
||||
props: Partial<TopicsListProps> = {},
|
||||
history = historyMock
|
||||
queryParams = ''
|
||||
) =>
|
||||
render(
|
||||
<Router history={history}>
|
||||
<WithRoute path={clusterTopicsPath()}>
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
isReadOnly: true,
|
||||
|
@ -52,7 +59,8 @@ describe('List', () => {
|
|||
>
|
||||
{setupComponent(props)}
|
||||
</ClusterContext.Provider>
|
||||
</Router>
|
||||
</WithRoute>,
|
||||
{ initialEntries: [`${clusterTopicsPath('test')}${queryParams}`] }
|
||||
);
|
||||
|
||||
describe('when it has readonly flag', () => {
|
||||
|
@ -112,6 +120,10 @@ describe('List', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(fetchTopicsList).toHaveBeenLastCalledWith({
|
||||
clusterName: 'test',
|
||||
orderBy: undefined,
|
||||
page: undefined,
|
||||
perPage: undefined,
|
||||
search: '',
|
||||
showInternal: value === 'on',
|
||||
sortOrder: SortOrder.ASC,
|
||||
|
@ -119,47 +131,43 @@ describe('List', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should reset page query param on show internal toggle change', () => {
|
||||
const mockedHistory = createMemoryHistory();
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
renderComponentWithProviders(
|
||||
{ isReadOnly: false },
|
||||
{ fetchTopicsList },
|
||||
mockedHistory
|
||||
);
|
||||
it('should reset page query param on show internal toggle change', async () => {
|
||||
renderComponentWithProviders({ isReadOnly: false }, { fetchTopicsList });
|
||||
|
||||
const internalCheckBox: HTMLInputElement = screen.getByRole('checkbox');
|
||||
userEvent.click(internalCheckBox);
|
||||
|
||||
expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
|
||||
expect(mockNavigate).toHaveBeenCalledWith({
|
||||
search: '?page=1&perPage=25',
|
||||
});
|
||||
});
|
||||
|
||||
it('should set cached page query param on show internal toggle change', async () => {
|
||||
const mockedHistory = createMemoryHistory();
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
|
||||
const cachedPage = 5;
|
||||
mockedHistory.push(`/?page=${cachedPage}&perPage=25`);
|
||||
|
||||
renderComponentWithProviders(
|
||||
{ isReadOnly: false },
|
||||
{ fetchTopicsList, totalPages: 10 },
|
||||
mockedHistory
|
||||
`?page=${cachedPage}&perPage=25`
|
||||
);
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search by Topic Name');
|
||||
userEvent.type(searchInput, 'nonEmptyString');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
|
||||
expect(mockNavigate).toHaveBeenCalledWith({
|
||||
search: '?page=1&perPage=25',
|
||||
});
|
||||
});
|
||||
|
||||
userEvent.clear(searchInput);
|
||||
await act(() => {
|
||||
userEvent.clear(searchInput);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedHistory.push).toHaveBeenCalledWith(
|
||||
`/?page=${cachedPage}&perPage=25`
|
||||
);
|
||||
expect(mockNavigate).toHaveBeenLastCalledWith({
|
||||
search: `?page=${cachedPage}&perPage=25`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -173,38 +181,37 @@ describe('List', () => {
|
|||
const fetchTopicsList = jest.fn();
|
||||
|
||||
jest.useFakeTimers();
|
||||
const pathname = '/ui/clusters/local/topics';
|
||||
const pathname = clusterTopicsPath('local');
|
||||
|
||||
beforeEach(() => {
|
||||
render(
|
||||
<StaticRouter location={{ pathname }}>
|
||||
<Route path="/ui/clusters/:clusterName">
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
isReadOnly: false,
|
||||
hasKafkaConnectConfigured: true,
|
||||
hasSchemaRegistryConfigured: true,
|
||||
isTopicDeletionAllowed: true,
|
||||
}}
|
||||
>
|
||||
{setupComponent({
|
||||
topics: [
|
||||
{
|
||||
...externalTopicPayload,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
},
|
||||
{ ...externalTopicPayload, name: 'external.topic2' },
|
||||
],
|
||||
deleteTopics: mockDeleteTopics,
|
||||
clearTopicsMessages: mockClearTopicsMessages,
|
||||
recreateTopic: mockRecreate,
|
||||
deleteTopic: mockDeleteTopic,
|
||||
clearTopicMessages: mockClearTopic,
|
||||
fetchTopicsList,
|
||||
})}
|
||||
</ClusterContext.Provider>
|
||||
</Route>
|
||||
</StaticRouter>
|
||||
<WithRoute path={clusterTopicsPath()}>
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
isReadOnly: false,
|
||||
hasKafkaConnectConfigured: true,
|
||||
hasSchemaRegistryConfigured: true,
|
||||
isTopicDeletionAllowed: true,
|
||||
}}
|
||||
>
|
||||
{setupComponent({
|
||||
topics: [
|
||||
{
|
||||
...externalTopicPayload,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
},
|
||||
{ ...externalTopicPayload, name: 'external.topic2' },
|
||||
],
|
||||
deleteTopics: mockDeleteTopics,
|
||||
clearTopicsMessages: mockClearTopicsMessages,
|
||||
recreateTopic: mockRecreate,
|
||||
deleteTopic: mockDeleteTopic,
|
||||
clearTopicMessages: mockClearTopic,
|
||||
fetchTopicsList,
|
||||
})}
|
||||
</ClusterContext.Provider>
|
||||
</WithRoute>,
|
||||
{ initialEntries: [pathname] }
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import React from 'react';
|
||||
import { ClusterName, TopicFormData } from 'redux/interfaces';
|
||||
import { TopicFormData } from 'redux/interfaces';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { clusterTopicPath } from 'lib/paths';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
import TopicForm from 'components/Topics/shared/Form/TopicForm';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { createTopic } from 'redux/reducers/topics/topicsSlice';
|
||||
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { topicFormValidationSchema } from 'lib/yupExtended';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
|
||||
interface RouterParams {
|
||||
clusterName: ClusterName;
|
||||
}
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
enum Filters {
|
||||
NAME = 'name',
|
||||
|
@ -28,8 +25,9 @@ const New: React.FC = () => {
|
|||
resolver: yupResolver(topicFormValidationSchema),
|
||||
});
|
||||
|
||||
const { clusterName } = useParams<RouterParams>();
|
||||
const history = useHistory();
|
||||
const { clusterName } = useAppParams<ClusterNameRoute>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { search } = useLocation();
|
||||
const dispatch = useAppDispatch();
|
||||
const params = new URLSearchParams(search);
|
||||
|
@ -44,7 +42,7 @@ const New: React.FC = () => {
|
|||
const { meta } = await dispatch(createTopic({ clusterName, data }));
|
||||
|
||||
if (meta.requestStatus === 'fulfilled') {
|
||||
history.push(clusterTopicPath(clusterName, data.name));
|
||||
navigate(`../${data.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import React from 'react';
|
||||
import New from 'components/Topics/New/New';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import * as redux from 'react-redux';
|
||||
import { act, screen, waitFor } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import fetchMock from 'fetch-mock-jest';
|
||||
import {
|
||||
clusterTopicCopyPath,
|
||||
|
@ -24,25 +23,37 @@ const topicName = 'test-topic';
|
|||
|
||||
const initialState: Partial<RootState> = {};
|
||||
const storeMock = mockStore(initialState);
|
||||
const historyMock = createMemoryHistory();
|
||||
|
||||
const renderComponent = (history = historyMock, store = storeMock) =>
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
const renderComponent = (path: string, store = storeMock) =>
|
||||
render(
|
||||
<Router history={history}>
|
||||
<Route path={clusterTopicNewPath(':clusterName')}>
|
||||
<Provider store={store}>
|
||||
<New />
|
||||
</Provider>
|
||||
</Route>
|
||||
<Route path={clusterTopicCopyPath(':clusterName')}>
|
||||
<Provider store={store}>
|
||||
<New />
|
||||
</Provider>
|
||||
</Route>
|
||||
<Route path={clusterTopicPath(':clusterName', ':topicName')}>
|
||||
New topic path
|
||||
</Route>
|
||||
</Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path={clusterTopicNewPath()}
|
||||
element={
|
||||
<Provider store={store}>
|
||||
<New />
|
||||
</Provider>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={clusterTopicCopyPath()}
|
||||
element={
|
||||
<Provider store={store}>
|
||||
<New />
|
||||
</Provider>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path={clusterTopicPath()} element="New topic path" />
|
||||
</Routes>,
|
||||
{ initialEntries: [path] }
|
||||
);
|
||||
|
||||
describe('New', () => {
|
||||
|
@ -50,38 +61,26 @@ describe('New', () => {
|
|||
fetchMock.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockNavigate.mockClear();
|
||||
});
|
||||
|
||||
it('checks header for create new', async () => {
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [clusterTopicNewPath(clusterName)],
|
||||
});
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'Create new Topic' })
|
||||
).toHaveTextContent('Create new Topic');
|
||||
});
|
||||
|
||||
it('checks header for copy', async () => {
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [
|
||||
{
|
||||
pathname: clusterTopicCopyPath(clusterName),
|
||||
search: `?name=test`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(`${clusterTopicCopyPath(clusterName)}?name=test`);
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'Copy Topic' })
|
||||
).toHaveTextContent('Copy Topic');
|
||||
});
|
||||
|
||||
it('validates form', async () => {
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [clusterTopicNewPath(clusterName)],
|
||||
});
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
|
||||
await waitFor(() => {
|
||||
userEvent.click(screen.getByText(/submit/i));
|
||||
|
@ -90,7 +89,7 @@ describe('New', () => {
|
|||
expect(screen.getByText('name is a required field')).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockedHistory.push).toBeCalledTimes(0);
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -101,14 +100,8 @@ describe('New', () => {
|
|||
})) as jest.Mock;
|
||||
useDispatchSpy.mockReturnValue(useDispatchMock);
|
||||
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [clusterTopicNewPath(clusterName)],
|
||||
});
|
||||
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
|
||||
await act(() => {
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -116,14 +109,12 @@ describe('New', () => {
|
|||
userEvent.click(screen.getByText(/submit/i));
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedHistory.location.pathname).toBe(
|
||||
clusterTopicPath(clusterName, topicName)
|
||||
)
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toHaveBeenLastCalledWith(`../${topicName}`);
|
||||
});
|
||||
|
||||
expect(useDispatchMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockedHistory.push).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not redirect page when request is not fulfilled', async () => {
|
||||
|
@ -131,16 +122,11 @@ describe('New', () => {
|
|||
const useDispatchMock = jest.fn(() => ({
|
||||
meta: { requestStatus: 'pending' },
|
||||
})) as jest.Mock;
|
||||
|
||||
useDispatchSpy.mockReturnValue(useDispatchMock);
|
||||
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [clusterTopicNewPath(clusterName)],
|
||||
});
|
||||
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
|
||||
await act(() => {
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -148,24 +134,16 @@ describe('New', () => {
|
|||
userEvent.click(screen.getByText(/submit/i));
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedHistory.location.pathname).toBe(
|
||||
clusterTopicNewPath(clusterName)
|
||||
)
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('submits valid form that result in an error', async () => {
|
||||
const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
|
||||
const useDispatchMock = jest.fn();
|
||||
useDispatchSpy.mockReturnValue(useDispatchMock);
|
||||
|
||||
const mockedHistory = createMemoryHistory({
|
||||
initialEntries: [clusterTopicNewPath(clusterName)],
|
||||
});
|
||||
|
||||
jest.spyOn(mockedHistory, 'push');
|
||||
renderComponent(mockedHistory);
|
||||
renderComponent(clusterTopicNewPath(clusterName));
|
||||
|
||||
await act(() => {
|
||||
userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
|
||||
|
@ -173,6 +151,6 @@ describe('New', () => {
|
|||
});
|
||||
|
||||
expect(useDispatchMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockedHistory.push).toBeCalledTimes(0);
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import React from 'react';
|
||||
import { Topic, TopicDetails, ConsumerGroup } from 'generated-sources';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { clusterConsumerGroupsPath } from 'lib/paths';
|
||||
import { clusterConsumerGroupsPath, RouteParamsClusterTopic } from 'lib/paths';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import getTagColor from 'components/common/Tag/getTagColor';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { getTopicConsumerGroups } from 'redux/reducers/topics/selectors';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
export interface Props extends Topic, TopicDetails {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
consumerGroups: ConsumerGroup[];
|
||||
export interface Props {
|
||||
isFetched: boolean;
|
||||
fetchTopicConsumerGroups(payload: {
|
||||
clusterName: ClusterName;
|
||||
|
@ -22,12 +21,15 @@ export interface Props extends Topic, TopicDetails {
|
|||
}
|
||||
|
||||
const TopicConsumerGroups: React.FC<Props> = ({
|
||||
consumerGroups,
|
||||
fetchTopicConsumerGroups,
|
||||
clusterName,
|
||||
topicName,
|
||||
isFetched,
|
||||
}) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const consumerGroups = useAppSelector((state) =>
|
||||
getTopicConsumerGroups(state, topicName)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchTopicConsumerGroups({ clusterName, topicName });
|
||||
}, [clusterName, fetchTopicConsumerGroups, topicName]);
|
||||
|
|
|
@ -1,31 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState, TopicName, ClusterName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { fetchTopicConsumerGroups } from 'redux/reducers/topics/topicsSlice';
|
||||
import TopicConsumerGroups from 'components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups';
|
||||
import {
|
||||
getTopicConsumerGroups,
|
||||
getTopicsConsumerGroupsFetched,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
import { getTopicsConsumerGroupsFetched } from 'redux/reducers/topics/selectors';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
consumerGroups: getTopicConsumerGroups(state, topicName),
|
||||
topicName,
|
||||
clusterName,
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isFetched: getTopicsConsumerGroupsFetched(state),
|
||||
});
|
||||
|
||||
|
@ -33,6 +12,7 @@ const mapDispatchToProps = {
|
|||
fetchTopicConsumerGroups,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(TopicConsumerGroups)
|
||||
);
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TopicConsumerGroups);
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import ConsumerGroups, {
|
||||
import TopicConsumerGroups, {
|
||||
Props,
|
||||
} from 'components/Topics/Topic/Details/ConsumerGroups/TopicConsumerGroups';
|
||||
import { ConsumerGroupState } from 'generated-sources';
|
||||
import { ConsumerGroup, ConsumerGroupState } from 'generated-sources';
|
||||
import { getTopicStateFixtures } from 'redux/reducers/topics/__test__/fixtures';
|
||||
import { TopicWithDetailedInfo } from 'redux/interfaces';
|
||||
import { clusterTopicConsumerGroupsPath } from 'lib/paths';
|
||||
|
||||
describe('TopicConsumerGroups', () => {
|
||||
const mockClusterName = 'localClusterName';
|
||||
|
@ -32,18 +35,32 @@ describe('TopicConsumerGroups', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const setUpComponent = (props: Partial<Props> = {}) => {
|
||||
const { name, topicName, consumerGroups, isFetched } = props;
|
||||
const setUpComponent = (
|
||||
props: Partial<Props> = {},
|
||||
consumerGroups?: ConsumerGroup[]
|
||||
) => {
|
||||
const topic: TopicWithDetailedInfo = {
|
||||
name: mockTopicName,
|
||||
consumerGroups,
|
||||
};
|
||||
const topicsState = getTopicStateFixtures([topic]);
|
||||
|
||||
return render(
|
||||
<ConsumerGroups
|
||||
clusterName={mockClusterName}
|
||||
consumerGroups={consumerGroups?.length ? consumerGroups : []}
|
||||
name={name || mockTopicName}
|
||||
fetchTopicConsumerGroups={jest.fn()}
|
||||
topicName={topicName || mockTopicName}
|
||||
isFetched={'isFetched' in props ? !!isFetched : false}
|
||||
/>
|
||||
<WithRoute path={clusterTopicConsumerGroupsPath()}>
|
||||
<TopicConsumerGroups
|
||||
fetchTopicConsumerGroups={jest.fn()}
|
||||
isFetched={false}
|
||||
{...props}
|
||||
/>
|
||||
</WithRoute>,
|
||||
{
|
||||
initialEntries: [
|
||||
clusterTopicConsumerGroupsPath(mockClusterName, mockTopicName),
|
||||
],
|
||||
preloadedState: {
|
||||
topics: topicsState,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -62,10 +79,18 @@ describe('TopicConsumerGroups', () => {
|
|||
});
|
||||
|
||||
it('render ConsumerGroups in Topic', () => {
|
||||
setUpComponent({
|
||||
consumerGroups: mockWithConsumerGroup,
|
||||
isFetched: true,
|
||||
});
|
||||
setUpComponent(
|
||||
{
|
||||
isFetched: true,
|
||||
},
|
||||
mockWithConsumerGroup
|
||||
);
|
||||
expect(screen.getAllByRole('rowgroup')).toHaveLength(2);
|
||||
expect(
|
||||
screen.getByText(mockWithConsumerGroup[0].groupId)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(mockWithConsumerGroup[1].groupId)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ export const DropdownExtraMessage = styled.div`
|
|||
`;
|
||||
|
||||
export const ReplicaCell = styled.span.attrs({ 'aria-label': 'replica-info' })<{
|
||||
leader: boolean | undefined;
|
||||
leader?: boolean;
|
||||
}>`
|
||||
${this} ~ ${this}::before {
|
||||
color: black;
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import React from 'react';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { Topic, TopicDetails } from 'generated-sources';
|
||||
import { NavLink, Switch, Route, useHistory } from 'react-router-dom';
|
||||
import { NavLink, Route, Routes, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
clusterTopicSettingsPath,
|
||||
clusterTopicPath,
|
||||
clusterTopicMessagesPath,
|
||||
clusterTopicsPath,
|
||||
clusterTopicConsumerGroupsPath,
|
||||
clusterTopicEditPath,
|
||||
clusterTopicSendMessagePath,
|
||||
RouteParamsClusterTopic,
|
||||
clusterTopicMessagesRelativePath,
|
||||
clusterTopicSettingsRelativePath,
|
||||
clusterTopicConsumerGroupsRelativePath,
|
||||
clusterTopicEditRelativePath,
|
||||
clusterTopicSendMessageRelativePath,
|
||||
} from 'lib/paths';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
|
@ -22,18 +20,20 @@ import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
|||
import styled from 'styled-components';
|
||||
import Navbar from 'components/common/Navigation/Navbar.styled';
|
||||
import * as S from 'components/Topics/Topic/Details/Details.styled';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import {
|
||||
getIsTopicDeletePolicy,
|
||||
getIsTopicInternal,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import OverviewContainer from './Overview/OverviewContainer';
|
||||
import TopicConsumerGroupsContainer from './ConsumerGroups/TopicConsumerGroupsContainer';
|
||||
import SettingsContainer from './Settings/SettingsContainer';
|
||||
import Messages from './Messages/Messages';
|
||||
|
||||
interface Props extends Topic, TopicDetails {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
isInternal: boolean;
|
||||
interface Props {
|
||||
isDeleted: boolean;
|
||||
isDeletePolicy: boolean;
|
||||
deleteTopic: (payload: {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
|
@ -56,16 +56,22 @@ const HeaderControlsWrapper = styled.div`
|
|||
`;
|
||||
|
||||
const Details: React.FC<Props> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
isInternal,
|
||||
isDeleted,
|
||||
isDeletePolicy,
|
||||
deleteTopic,
|
||||
recreateTopic,
|
||||
clearTopicMessages,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const isInternal = useAppSelector((state) =>
|
||||
getIsTopicInternal(state, topicName)
|
||||
);
|
||||
|
||||
const isDeletePolicy = useAppSelector((state) =>
|
||||
getIsTopicDeletePolicy(state, topicName)
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { isReadOnly, isTopicDeletionAllowed } =
|
||||
React.useContext(ClusterContext);
|
||||
|
@ -81,9 +87,9 @@ const Details: React.FC<Props> = ({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (isDeleted) {
|
||||
history.push(clusterTopicsPath(clusterName));
|
||||
navigate('../..');
|
||||
}
|
||||
}, [isDeleted, clusterName, dispatch, history]);
|
||||
}, [isDeleted, clusterName, dispatch, navigate]);
|
||||
|
||||
const clearTopicMessagesHandler = () => {
|
||||
clearTopicMessages({ clusterName, topicName });
|
||||
|
@ -99,58 +105,62 @@ const Details: React.FC<Props> = ({
|
|||
<div>
|
||||
<PageHeading text={topicName}>
|
||||
<HeaderControlsWrapper>
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/topics/:topicName/messages"
|
||||
>
|
||||
<Button
|
||||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
isLink
|
||||
to={clusterTopicSendMessagePath(clusterName, topicName)}
|
||||
>
|
||||
Produce Message
|
||||
</Button>
|
||||
</Route>
|
||||
<Routes>
|
||||
<Route
|
||||
path={clusterTopicMessagesRelativePath}
|
||||
element={
|
||||
<Button
|
||||
buttonSize="M"
|
||||
buttonType="primary"
|
||||
to={`../${clusterTopicSendMessageRelativePath}`}
|
||||
>
|
||||
Produce Message
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
{!isReadOnly && !isInternal && (
|
||||
<Route path="/ui/clusters/:clusterName/topics/:topicName">
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<DropdownItem
|
||||
onClick={() =>
|
||||
history.push(clusterTopicEditPath(clusterName, topicName))
|
||||
}
|
||||
>
|
||||
Edit settings
|
||||
<S.DropdownExtraMessage>
|
||||
Pay attention! This operation has
|
||||
<br />
|
||||
especially important consequences.
|
||||
</S.DropdownExtraMessage>
|
||||
</DropdownItem>
|
||||
{isDeletePolicy && (
|
||||
<DropdownItem
|
||||
onClick={() => setClearTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Clear messages
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem
|
||||
onClick={() => setRecreateTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Recreate Topic
|
||||
</DropdownItem>
|
||||
{isTopicDeletionAllowed && (
|
||||
<DropdownItem
|
||||
onClick={() => setDeleteTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Remove topic
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
</Route>
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
element={
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<DropdownItem
|
||||
onClick={() => navigate(clusterTopicEditRelativePath)}
|
||||
>
|
||||
Edit settings
|
||||
<S.DropdownExtraMessage>
|
||||
Pay attention! This operation has
|
||||
<br />
|
||||
especially important consequences.
|
||||
</S.DropdownExtraMessage>
|
||||
</DropdownItem>
|
||||
{isDeletePolicy && (
|
||||
<DropdownItem
|
||||
onClick={() => setClearTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Clear messages
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem
|
||||
onClick={() => setRecreateTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Recreate Topic
|
||||
</DropdownItem>
|
||||
{isTopicDeletionAllowed && (
|
||||
<DropdownItem
|
||||
onClick={() => setDeleteTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Remove topic
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
)}
|
||||
</HeaderControlsWrapper>
|
||||
</PageHeading>
|
||||
|
@ -177,56 +187,45 @@ const Details: React.FC<Props> = ({
|
|||
</ConfirmationModal>
|
||||
<Navbar role="navigation">
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterTopicPath(clusterName, topicName)}
|
||||
activeClassName="is-active is-primary"
|
||||
to="."
|
||||
className={({ isActive }) => (isActive ? 'is-active is-primary' : '')}
|
||||
>
|
||||
Overview
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterTopicMessagesPath(clusterName, topicName)}
|
||||
activeClassName="is-active"
|
||||
to={clusterTopicMessagesRelativePath}
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Messages
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterTopicConsumerGroupsPath(clusterName, topicName)}
|
||||
activeClassName="is-active"
|
||||
to={clusterTopicConsumerGroupsRelativePath}
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Consumers
|
||||
</NavLink>
|
||||
<NavLink
|
||||
exact
|
||||
to={clusterTopicSettingsPath(clusterName, topicName)}
|
||||
activeClassName="is-active"
|
||||
to={clusterTopicSettingsRelativePath}
|
||||
className={({ isActive }) => (isActive ? 'is-active' : '')}
|
||||
>
|
||||
Settings
|
||||
</NavLink>
|
||||
</Navbar>
|
||||
<Switch>
|
||||
<Routes>
|
||||
<Route index element={<OverviewContainer />} />
|
||||
|
||||
<Route path={clusterTopicMessagesRelativePath} element={<Messages />} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/topics/:topicName/messages"
|
||||
component={Messages}
|
||||
path={clusterTopicSettingsRelativePath}
|
||||
element={<SettingsContainer />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/topics/:topicName/settings"
|
||||
component={SettingsContainer}
|
||||
path={clusterTopicConsumerGroupsRelativePath}
|
||||
element={<TopicConsumerGroupsContainer />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/topics/:topicName"
|
||||
component={OverviewContainer}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/topics/:topicName/consumer-groups"
|
||||
component={TopicConsumerGroupsContainer}
|
||||
/>
|
||||
</Switch>
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,36 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { deleteTopic, recreateTopic } from 'redux/reducers/topics/topicsSlice';
|
||||
import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
|
||||
import {
|
||||
getIsTopicDeleted,
|
||||
getIsTopicDeletePolicy,
|
||||
getIsTopicInternal,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
import { getIsTopicDeleted } from 'redux/reducers/topics/selectors';
|
||||
|
||||
import Details from './Details';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
isInternal: getIsTopicInternal(state, topicName),
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isDeleted: getIsTopicDeleted(state),
|
||||
isDeletePolicy: getIsTopicDeletePolicy(state, topicName),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
@ -39,6 +16,4 @@ const mapDispatchToProps = {
|
|||
clearTopicMessages,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Details)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Details);
|
||||
|
|
|
@ -12,12 +12,11 @@ import {
|
|||
} from 'generated-sources';
|
||||
import React, { useContext } from 'react';
|
||||
import { omitBy } from 'lodash';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import MultiSelect from 'components/common/MultiSelect/MultiSelect.styled';
|
||||
import { Option } from 'react-multi-select-component/dist/lib/interfaces';
|
||||
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { BASE_PARAMS } from 'lib/constants';
|
||||
import Input from 'components/common/Input/Input';
|
||||
import Select from 'components/common/Select/Select';
|
||||
|
@ -29,6 +28,10 @@ import FilterModal, {
|
|||
import { SeekDirectionOptions } from 'components/Topics/Topic/Details/Messages/Messages';
|
||||
import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
|
||||
import useModal from 'lib/hooks/useModal';
|
||||
import { getPartitionsByTopicName } from 'redux/reducers/topics/selectors';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import * as S from './Filters.styled';
|
||||
import {
|
||||
|
@ -41,10 +44,7 @@ import {
|
|||
type Query = Record<string, string | string[] | number>;
|
||||
|
||||
export interface FiltersProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
phaseMessage?: string;
|
||||
partitions: Partition[];
|
||||
meta: TopicMessageConsuming;
|
||||
isFetching: boolean;
|
||||
addMessage(content: { message: TopicMessage; prepend: boolean }): void;
|
||||
|
@ -73,9 +73,6 @@ export const SeekTypeOptions = [
|
|||
];
|
||||
|
||||
const Filters: React.FC<FiltersProps> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
partitions,
|
||||
phaseMessage,
|
||||
meta: { elapsedMs, bytesConsumed, messagesConsumed },
|
||||
isFetching,
|
||||
|
@ -85,8 +82,13 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
updateMeta,
|
||||
setIsFetching,
|
||||
}) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const partitions = useAppSelector((state) =>
|
||||
getPartitionsByTopicName(state, topicName)
|
||||
);
|
||||
|
||||
const { searchParams, seekDirection, isLive, changeSeekDirection } =
|
||||
useContext(TopicMessagesContext);
|
||||
|
@ -212,7 +214,7 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
.map((key) => `${key}=${newProps[key]}`)
|
||||
.join('&');
|
||||
|
||||
history.push({
|
||||
navigate({
|
||||
search: `?${qs}`,
|
||||
});
|
||||
},
|
||||
|
@ -224,6 +226,7 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
timestamp,
|
||||
query,
|
||||
selectedPartitions,
|
||||
navigate,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { ClusterName, RootState, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
addTopicMessage,
|
||||
resetTopicMessages,
|
||||
|
@ -13,29 +12,11 @@ import {
|
|||
getTopicMessgesPhase,
|
||||
getIsTopicMessagesFetching,
|
||||
} from 'redux/reducers/topicMessages/selectors';
|
||||
import { getPartitionsByTopicName } from 'redux/reducers/topics/selectors';
|
||||
|
||||
import Filters from './Filters';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
phaseMessage: getTopicMessgesPhase(state),
|
||||
partitions: getPartitionsByTopicName(state, topicName),
|
||||
meta: getTopicMessgesMeta(state),
|
||||
isFetching: getIsTopicMessagesFetching(state),
|
||||
});
|
||||
|
@ -48,6 +29,4 @@ const mapDispatchToProps = {
|
|||
setIsFetching: setTopicMessagesFetchingStatus,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Filters)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Filters);
|
||||
|
|
|
@ -4,7 +4,7 @@ import Filters, {
|
|||
FiltersProps,
|
||||
SeekTypeOptions,
|
||||
} from 'components/Topics/Topic/Details/Messages/Filters/Filters';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { EventSourceMock, render } from 'lib/testHelpers';
|
||||
import { act, screen, within, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import TopicMessagesContext, {
|
||||
|
@ -26,9 +26,6 @@ const renderComponent = (
|
|||
render(
|
||||
<TopicMessagesContext.Provider value={ctx}>
|
||||
<Filters
|
||||
clusterName="test-cluster"
|
||||
topicName="test-topic"
|
||||
partitions={[{ partition: 0, offsetMin: 0, offsetMax: 100 }]}
|
||||
meta={{}}
|
||||
isFetching={false}
|
||||
addMessage={jest.fn()}
|
||||
|
@ -43,6 +40,10 @@ const renderComponent = (
|
|||
};
|
||||
|
||||
describe('Filters component', () => {
|
||||
Object.defineProperty(window, 'EventSource', {
|
||||
value: EventSourceMock,
|
||||
});
|
||||
|
||||
it('shows cancel button while fetching', () => {
|
||||
renderComponent({ isFetching: true });
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
|
|
@ -5,8 +5,6 @@ import Messages, {
|
|||
SeekDirectionOptions,
|
||||
SeekDirectionOptionsObj,
|
||||
} from 'components/Topics/Topic/Details/Messages/Messages';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { SeekDirection, SeekType } from 'generated-sources';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
|
@ -14,15 +12,9 @@ describe('Messages', () => {
|
|||
const searchParams = `?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}&seekTo=0::9`;
|
||||
|
||||
const setUpComponent = (param: string = searchParams) => {
|
||||
const history = createMemoryHistory();
|
||||
history.push({
|
||||
search: new URLSearchParams(param).toString(),
|
||||
return render(<Messages />, {
|
||||
initialEntries: [`/?${new URLSearchParams(param).toString()}`],
|
||||
});
|
||||
return render(
|
||||
<Router history={history}>
|
||||
<Messages />
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -2,8 +2,6 @@ import React from 'react';
|
|||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import MessagesTable from 'components/Topics/Topic/Details/Messages/MessagesTable';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import { SeekDirection, SeekType, TopicMessage } from 'generated-sources';
|
||||
import TopicMessagesContext, {
|
||||
ContextProps,
|
||||
|
@ -15,6 +13,12 @@ import {
|
|||
|
||||
const mockTopicsMessages: TopicMessage[] = [{ ...topicMessagePayload }];
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
describe('MessagesTable', () => {
|
||||
const seekToResult = '&seekTo=0::9';
|
||||
const searchParamsValue = `?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}${seekToResult}`;
|
||||
|
@ -31,20 +35,15 @@ describe('MessagesTable', () => {
|
|||
ctx: ContextProps = contextValue,
|
||||
messages: TopicMessage[] = [],
|
||||
isFetching?: boolean,
|
||||
customHistory?: MemoryHistory
|
||||
path?: string
|
||||
) => {
|
||||
const history =
|
||||
customHistory ||
|
||||
createMemoryHistory({
|
||||
initialEntries: [params.toString()],
|
||||
});
|
||||
const customPath = path || params.toString();
|
||||
return render(
|
||||
<Router history={history}>
|
||||
<TopicMessagesContext.Provider value={ctx}>
|
||||
<MessagesTable />
|
||||
</TopicMessagesContext.Provider>
|
||||
</Router>,
|
||||
<TopicMessagesContext.Provider value={ctx}>
|
||||
<MessagesTable />
|
||||
</TopicMessagesContext.Provider>,
|
||||
{
|
||||
initialEntries: [customPath],
|
||||
preloadedState: {
|
||||
topicMessages: {
|
||||
messages,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Partition, Replica, Topic, TopicDetails } from 'generated-sources';
|
||||
import { Partition, Replica } from 'generated-sources';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
|
@ -10,11 +10,13 @@ import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeader
|
|||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import * as Metrics from 'components/common/Metrics';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { getTopicByName } from 'redux/reducers/topics/selectors';
|
||||
import { ReplicaCell } from 'components/Topics/Topic/Details/Details.styled';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
export interface Props extends Topic, TopicDetails {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
export interface Props {
|
||||
clearTopicMessages(params: {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
|
@ -22,21 +24,25 @@ export interface Props extends Topic, TopicDetails {
|
|||
}): void;
|
||||
}
|
||||
|
||||
const Overview: React.FC<Props> = ({
|
||||
partitions,
|
||||
underReplicatedPartitions,
|
||||
inSyncReplicas,
|
||||
replicas,
|
||||
partitionCount,
|
||||
internal,
|
||||
replicationFactor,
|
||||
segmentSize,
|
||||
segmentCount,
|
||||
clusterName,
|
||||
topicName,
|
||||
cleanUpPolicy,
|
||||
clearTopicMessages,
|
||||
}) => {
|
||||
const Overview: React.FC<Props> = ({ clearTopicMessages }) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const {
|
||||
partitions,
|
||||
underReplicatedPartitions,
|
||||
inSyncReplicas,
|
||||
replicas,
|
||||
partitionCount,
|
||||
internal,
|
||||
replicationFactor,
|
||||
segmentSize,
|
||||
segmentCount,
|
||||
cleanUpPolicy,
|
||||
} = useAppSelector((state) => {
|
||||
const res = getTopicByName(state, topicName);
|
||||
return res || {};
|
||||
});
|
||||
|
||||
const { isReadOnly } = React.useContext(ClusterContext);
|
||||
|
||||
const messageCount = React.useMemo(
|
||||
|
|
|
@ -1,34 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState, TopicName, ClusterName } from 'redux/interfaces';
|
||||
import { getTopicByName } from 'redux/reducers/topics/selectors';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
|
||||
import Overview from 'components/Topics/Topic/Details/Overview/Overview';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
...getTopicByName(state, topicName),
|
||||
topicName,
|
||||
clusterName,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
clearTopicMessages,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Overview)
|
||||
);
|
||||
export default connect(null, mapDispatchToProps)(Overview);
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import Overview, {
|
||||
Props as OverviewProps,
|
||||
} from 'components/Topics/Topic/Details/Overview/Overview';
|
||||
import theme from 'theme/theme';
|
||||
import { CleanUpPolicy } from 'generated-sources';
|
||||
import { CleanUpPolicy, Topic } from 'generated-sources';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { getTopicStateFixtures } from 'redux/reducers/topics/__test__/fixtures';
|
||||
import { clusterTopicPath } from 'lib/paths';
|
||||
import { ReplicaCell } from 'components/Topics/Topic/Details/Details.styled';
|
||||
|
||||
describe('Overview', () => {
|
||||
const getReplicaCell = () => screen.getByLabelText('replica-info');
|
||||
const mockClusterName = 'local';
|
||||
const mockTopicName = 'topic';
|
||||
const mockClearTopicMessages = jest.fn();
|
||||
const mockTopic = { name: mockTopicName };
|
||||
|
||||
const mockPartitions = [
|
||||
{
|
||||
partition: 1,
|
||||
|
@ -36,67 +38,63 @@ describe('Overview', () => {
|
|||
hasSchemaRegistryConfigured: true,
|
||||
isTopicDeletionAllowed: true,
|
||||
};
|
||||
const defaultProps: OverviewProps = {
|
||||
name: mockTopicName,
|
||||
partitions: [],
|
||||
internal: true,
|
||||
clusterName: mockClusterName,
|
||||
topicName: mockTopicName,
|
||||
clearTopicMessages: mockClearTopicMessages,
|
||||
};
|
||||
|
||||
const setupComponent = (
|
||||
props = defaultProps,
|
||||
contextValues = defaultContextValues,
|
||||
underReplicatedPartitions?: number,
|
||||
inSyncReplicas?: number,
|
||||
replicas?: number
|
||||
props: Partial<OverviewProps> = {},
|
||||
topicState: Topic = mockTopic,
|
||||
contextValues = defaultContextValues
|
||||
) => {
|
||||
const topics = getTopicStateFixtures([topicState]);
|
||||
|
||||
return render(
|
||||
<ClusterContext.Provider value={contextValues}>
|
||||
<Overview
|
||||
underReplicatedPartitions={underReplicatedPartitions}
|
||||
inSyncReplicas={inSyncReplicas}
|
||||
replicas={replicas}
|
||||
{...props}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
<WithRoute path={clusterTopicPath()}>
|
||||
<ClusterContext.Provider value={contextValues}>
|
||||
<Overview clearTopicMessages={jest.fn()} {...props} />
|
||||
</ClusterContext.Provider>
|
||||
</WithRoute>,
|
||||
{
|
||||
initialEntries: [clusterTopicPath(mockClusterName, mockTopicName)],
|
||||
preloadedState: { topics },
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mockClearTopicMessages.mockClear();
|
||||
});
|
||||
|
||||
it('at least one replica was rendered', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
underReplicatedPartitions: 0,
|
||||
inSyncReplicas: 1,
|
||||
replicas: 1,
|
||||
});
|
||||
expect(getReplicaCell()).toBeInTheDocument();
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
expect(screen.getByLabelText('replica-info')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders replica cell with props', () => {
|
||||
render(<ReplicaCell leader />);
|
||||
expect(getReplicaCell()).toBeInTheDocument();
|
||||
expect(getReplicaCell()).toHaveStyleRule('color', 'orange');
|
||||
const element = screen.getByLabelText('replica-info');
|
||||
expect(element).toBeInTheDocument();
|
||||
expect(element).toHaveStyleRule('color', 'orange');
|
||||
});
|
||||
|
||||
describe('when it has internal flag', () => {
|
||||
it('does not render the Action button a Topic', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
});
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
expect(screen.getAllByRole('menu')[0]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render Partitions', () => {
|
||||
setupComponent();
|
||||
setupComponent({}, { ...mockTopic, partitions: [] });
|
||||
|
||||
expect(screen.getByText('No Partitions found')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -110,12 +108,15 @@ describe('Overview', () => {
|
|||
});
|
||||
|
||||
it('should be the appropriate color', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
underReplicatedPartitions: 0,
|
||||
inSyncReplicas: 1,
|
||||
replicas: 2,
|
||||
});
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
underReplicatedPartitions: 0,
|
||||
inSyncReplicas: 1,
|
||||
replicas: 2,
|
||||
}
|
||||
);
|
||||
const circles = screen.getAllByRole('circle');
|
||||
expect(circles[0]).toHaveStyle(
|
||||
`fill: ${theme.circularAlert.color.success}`
|
||||
|
@ -127,24 +128,30 @@ describe('Overview', () => {
|
|||
});
|
||||
|
||||
describe('when Clear Messages is clicked', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
it('should when Clear Messages is clicked', () => {
|
||||
const mockClearTopicMessages = jest.fn();
|
||||
setupComponent(
|
||||
{ clearTopicMessages: mockClearTopicMessages },
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
|
||||
const clearMessagesButton = screen.getByText('Clear Messages');
|
||||
userEvent.click(clearMessagesButton);
|
||||
expect(mockClearTopicMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const clearMessagesButton = screen.getByText('Clear Messages');
|
||||
userEvent.click(clearMessagesButton);
|
||||
|
||||
expect(mockClearTopicMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('when the table partition dropdown appearance', () => {
|
||||
it('should check if the dropdown is not present when it is readOnly', () => {
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...defaultProps,
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: true,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
|
@ -155,32 +162,41 @@ describe('Overview', () => {
|
|||
});
|
||||
|
||||
it('should check if the dropdown is not present when it is internal', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
partitions: mockPartitions,
|
||||
internal: true,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
});
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: true,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
expect(screen.queryByText('Clear Messages')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should check if the dropdown is not present when cleanUpPolicy is not DELETE', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.COMPACT,
|
||||
});
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.COMPACT,
|
||||
}
|
||||
);
|
||||
expect(screen.queryByText('Clear Messages')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should check if the dropdown action to be in visible', () => {
|
||||
setupComponent({
|
||||
...defaultProps,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
});
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
...mockTopic,
|
||||
partitions: mockPartitions,
|
||||
internal: false,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
expect(screen.getByText('Clear Messages')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import React from 'react';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import { TopicConfig } from 'generated-sources';
|
||||
import React from 'react';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { getTopicConfig } from 'redux/reducers/topics/selectors';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import ConfigListItem from './ConfigListItem';
|
||||
|
||||
interface Props {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
config?: TopicConfig[];
|
||||
export interface Props {
|
||||
isFetched: boolean;
|
||||
fetchTopicConfig: (payload: {
|
||||
clusterName: ClusterName;
|
||||
|
@ -18,13 +18,11 @@ interface Props {
|
|||
}) => void;
|
||||
}
|
||||
|
||||
const Settings: React.FC<Props> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
isFetched,
|
||||
fetchTopicConfig,
|
||||
config,
|
||||
}) => {
|
||||
const Settings: React.FC<Props> = ({ isFetched, fetchTopicConfig }) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const config = useAppSelector((state) => getTopicConfig(state, topicName));
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchTopicConfig({ clusterName, topicName });
|
||||
}, [fetchTopicConfig, clusterName, topicName]);
|
||||
|
|
|
@ -1,32 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState, ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { fetchTopicConfig } from 'redux/reducers/topics/topicsSlice';
|
||||
import {
|
||||
getTopicConfig,
|
||||
getTopicConfigFetched,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
import { getTopicConfigFetched } from 'redux/reducers/topics/selectors';
|
||||
|
||||
import Settings from './Settings';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
config: getTopicConfig(state, topicName),
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isFetched: getTopicConfigFetched(state),
|
||||
});
|
||||
|
||||
|
@ -34,6 +13,4 @@ const mapDispatchToProps = {
|
|||
fetchTopicConfig,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Settings)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Settings);
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import Settings from 'components/Topics/Topic/Details/Settings/Settings';
|
||||
import Settings, {
|
||||
Props,
|
||||
} from 'components/Topics/Topic/Details/Settings/Settings';
|
||||
import { TopicConfig } from 'generated-sources';
|
||||
import { clusterTopicSettingsPath } from 'lib/paths';
|
||||
import { getTopicStateFixtures } from 'redux/reducers/topics/__test__/fixtures';
|
||||
|
||||
describe('Settings', () => {
|
||||
const mockClusterName = 'Cluster_Name';
|
||||
const mockTopicName = 'Topic_Name';
|
||||
|
||||
let expectedResult: number;
|
||||
const mockFn = jest.fn();
|
||||
const mockClusterName = 'Cluster Name';
|
||||
const mockTopicName = 'Topic Name';
|
||||
|
||||
const mockConfig: TopicConfig[] = [
|
||||
{
|
||||
name: 'first',
|
||||
|
@ -20,43 +26,50 @@ describe('Settings', () => {
|
|||
},
|
||||
];
|
||||
|
||||
it('should check it returns null if no config is passed', () => {
|
||||
render(
|
||||
<Settings
|
||||
clusterName={mockClusterName}
|
||||
topicName={mockTopicName}
|
||||
isFetched
|
||||
fetchTopicConfig={mockFn}
|
||||
/>
|
||||
const setUpComponent = (
|
||||
props: Partial<Props> = {},
|
||||
config?: TopicConfig[]
|
||||
) => {
|
||||
const topic = {
|
||||
name: mockTopicName,
|
||||
config,
|
||||
};
|
||||
const topics = getTopicStateFixtures([topic]);
|
||||
|
||||
return render(
|
||||
<WithRoute path={clusterTopicSettingsPath()}>
|
||||
<Settings isFetched fetchTopicConfig={mockFn} {...props} />
|
||||
</WithRoute>,
|
||||
{
|
||||
initialEntries: [
|
||||
clusterTopicSettingsPath(mockClusterName, mockTopicName),
|
||||
],
|
||||
preloadedState: {
|
||||
topics,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
mockFn.mockClear();
|
||||
});
|
||||
|
||||
it('should check it returns null if no config is passed', () => {
|
||||
setUpComponent();
|
||||
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Page loader when it is in fetching state and config is given', () => {
|
||||
render(
|
||||
<Settings
|
||||
clusterName={mockClusterName}
|
||||
topicName={mockTopicName}
|
||||
isFetched={false}
|
||||
fetchTopicConfig={mockFn}
|
||||
config={mockConfig}
|
||||
/>
|
||||
);
|
||||
setUpComponent({ isFetched: false }, mockConfig);
|
||||
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should check and return null if it is not fetched and config is not given', () => {
|
||||
render(
|
||||
<Settings
|
||||
clusterName={mockClusterName}
|
||||
topicName={mockTopicName}
|
||||
isFetched={false}
|
||||
fetchTopicConfig={mockFn}
|
||||
/>
|
||||
);
|
||||
setUpComponent({ isFetched: false });
|
||||
|
||||
expect(screen.queryByRole('table')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -64,15 +77,7 @@ describe('Settings', () => {
|
|||
describe('Settings Component with Data', () => {
|
||||
beforeEach(() => {
|
||||
expectedResult = mockConfig.length + 1; // include the header table row as well
|
||||
render(
|
||||
<Settings
|
||||
clusterName={mockClusterName}
|
||||
topicName={mockTopicName}
|
||||
isFetched
|
||||
fetchTopicConfig={mockFn}
|
||||
config={mockConfig}
|
||||
/>
|
||||
);
|
||||
setUpComponent({ isFetched: true }, mockConfig);
|
||||
});
|
||||
|
||||
it('should view the correct number of table row with header included elements after config fetching', () => {
|
||||
|
|
|
@ -3,36 +3,35 @@ import { screen } from '@testing-library/react';
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import Details from 'components/Topics/Topic/Details/Details';
|
||||
import { internalTopicPayload } from 'redux/reducers/topics/__test__/fixtures';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import {
|
||||
clusterTopicEditPath,
|
||||
clusterTopicPath,
|
||||
clusterTopicsPath,
|
||||
} from 'lib/paths';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
getTopicStateFixtures,
|
||||
internalTopicPayload,
|
||||
} from 'redux/reducers/topics/__test__/fixtures';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import { clusterTopicEditRelativePath, clusterTopicPath } from 'lib/paths';
|
||||
import { CleanUpPolicy, Topic } from 'generated-sources';
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
describe('Details', () => {
|
||||
const mockDelete = jest.fn();
|
||||
const mockClusterName = 'local';
|
||||
const mockClearTopicMessages = jest.fn();
|
||||
const mockInternalTopicPayload = internalTopicPayload.internal;
|
||||
const mockRecreateTopic = jest.fn();
|
||||
const defaultPathname = clusterTopicPath(
|
||||
mockClusterName,
|
||||
internalTopicPayload.name
|
||||
);
|
||||
const mockHistory = createMemoryHistory({
|
||||
initialEntries: [defaultPathname],
|
||||
});
|
||||
jest.spyOn(mockHistory, 'push');
|
||||
|
||||
const setupComponent = (
|
||||
pathname = defaultPathname,
|
||||
history = mockHistory,
|
||||
props = {}
|
||||
) =>
|
||||
const topic: Topic = {
|
||||
...internalTopicPayload,
|
||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
internal: false,
|
||||
};
|
||||
|
||||
const mockTopicsState = getTopicStateFixtures([topic]);
|
||||
|
||||
const setupComponent = (props = {}) =>
|
||||
render(
|
||||
<ClusterContext.Provider
|
||||
value={{
|
||||
|
@ -42,24 +41,33 @@ describe('Details', () => {
|
|||
isTopicDeletionAllowed: true,
|
||||
}}
|
||||
>
|
||||
<Router history={history}>
|
||||
<WithRoute path={clusterTopicPath()}>
|
||||
<Details
|
||||
clusterName={mockClusterName}
|
||||
topicName={internalTopicPayload.name}
|
||||
name={internalTopicPayload.name}
|
||||
isInternal={false}
|
||||
deleteTopic={mockDelete}
|
||||
recreateTopic={mockRecreateTopic}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
isDeleted={false}
|
||||
isDeletePolicy
|
||||
{...props}
|
||||
/>
|
||||
</Router>
|
||||
</WithRoute>
|
||||
</ClusterContext.Provider>,
|
||||
{ pathname }
|
||||
{
|
||||
initialEntries: [
|
||||
clusterTopicPath(mockClusterName, internalTopicPayload.name),
|
||||
],
|
||||
preloadedState: {
|
||||
topics: mockTopicsState,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
mockNavigate.mockClear();
|
||||
mockDelete.mockClear();
|
||||
mockClearTopicMessages.mockClear();
|
||||
mockRecreateTopic.mockClear();
|
||||
});
|
||||
|
||||
describe('when it has readonly flag', () => {
|
||||
it('does not render the Action button a Topic', () => {
|
||||
render(
|
||||
|
@ -72,15 +80,10 @@ describe('Details', () => {
|
|||
}}
|
||||
>
|
||||
<Details
|
||||
clusterName={mockClusterName}
|
||||
topicName={internalTopicPayload.name}
|
||||
name={internalTopicPayload.name}
|
||||
isInternal={mockInternalTopicPayload}
|
||||
deleteTopic={mockDelete}
|
||||
recreateTopic={mockRecreateTopic}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
isDeleted={false}
|
||||
isDeletePolicy
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
);
|
||||
|
@ -148,30 +151,23 @@ describe('Details', () => {
|
|||
const button = screen.getAllByText('Edit settings')[0];
|
||||
userEvent.click(button);
|
||||
|
||||
const redirectRoute = clusterTopicEditPath(
|
||||
mockClusterName,
|
||||
internalTopicPayload.name
|
||||
);
|
||||
|
||||
expect(mockHistory.push).toHaveBeenCalledWith(redirectRoute);
|
||||
expect(mockNavigate).toHaveBeenCalledWith(clusterTopicEditRelativePath);
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to the correct route if topic is deleted', () => {
|
||||
setupComponent(defaultPathname, mockHistory, { isDeleted: true });
|
||||
const redirectRoute = clusterTopicsPath(mockClusterName);
|
||||
setupComponent({ isDeleted: true });
|
||||
|
||||
expect(mockHistory.push).toHaveBeenCalledWith(redirectRoute);
|
||||
expect(mockNavigate).toHaveBeenCalledWith('../..');
|
||||
});
|
||||
|
||||
it('shows a confirmation popup on deleting topic messages', () => {
|
||||
setupComponent();
|
||||
const { getByText } = screen;
|
||||
const clearMessagesButton = getByText(/Clear messages/i);
|
||||
const clearMessagesButton = screen.getAllByText(/Clear messages/i)[0];
|
||||
userEvent.click(clearMessagesButton);
|
||||
|
||||
expect(
|
||||
getByText(/Are you sure want to clear topic messages?/i)
|
||||
screen.getByText(/Are you sure want to clear topic messages?/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@ import { FormError } from 'components/common/Input/Input.styled';
|
|||
import { InputLabel } from 'components/common/Input/InputLabel.styled';
|
||||
import React from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import * as S from './DangerZone.styled';
|
||||
|
||||
export interface Props {
|
||||
clusterName: string;
|
||||
topicName: string;
|
||||
defaultPartitions: number;
|
||||
defaultReplicationFactor: number;
|
||||
partitionsCountIncreased: boolean;
|
||||
|
@ -30,8 +30,6 @@ export interface Props {
|
|||
}
|
||||
|
||||
const DangerZone: React.FC<Props> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
defaultPartitions,
|
||||
defaultReplicationFactor,
|
||||
partitionsCountIncreased,
|
||||
|
@ -39,6 +37,8 @@ const DangerZone: React.FC<Props> = ({
|
|||
updateTopicPartitionsCount,
|
||||
updateTopicReplicationFactor,
|
||||
}) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const [isPartitionsConfirmationVisible, setIsPartitionsConfirmationVisible] =
|
||||
React.useState<boolean>(false);
|
||||
const [
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState, ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
updateTopicPartitionsCount,
|
||||
updateTopicReplicationFactor,
|
||||
|
@ -12,11 +11,6 @@ import {
|
|||
|
||||
import DangerZone from './DangerZone';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = {
|
||||
defaultPartitions: number;
|
||||
defaultReplicationFactor: number;
|
||||
|
@ -24,16 +18,8 @@ type OwnProps = {
|
|||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
defaultPartitions,
|
||||
defaultReplicationFactor,
|
||||
}: OwnProps & RouteComponentProps<RouteProps>
|
||||
{ defaultPartitions, defaultReplicationFactor }: OwnProps
|
||||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
defaultPartitions,
|
||||
defaultReplicationFactor,
|
||||
partitionsCountIncreased: getTopicPartitionsCountIncreased(state),
|
||||
|
@ -45,6 +31,4 @@ const mapDispatchToProps = {
|
|||
updateTopicReplicationFactor,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(DangerZone)
|
||||
);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DangerZone);
|
||||
|
|
|
@ -4,28 +4,30 @@ import DangerZone, {
|
|||
} from 'components/Topics/Topic/Edit/DangerZone/DangerZone';
|
||||
import { act, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { render, WithRoute } from 'lib/testHelpers';
|
||||
import {
|
||||
topicName,
|
||||
clusterName,
|
||||
} from 'components/Topics/Topic/Edit/__test__/fixtures';
|
||||
import { clusterTopicSendMessagePath } from 'lib/paths';
|
||||
|
||||
const defaultPartitions = 3;
|
||||
const defaultReplicationFactor = 3;
|
||||
|
||||
const renderComponent = (props?: Partial<Props>) =>
|
||||
render(
|
||||
<DangerZone
|
||||
clusterName={clusterName}
|
||||
topicName={topicName}
|
||||
defaultPartitions={defaultPartitions}
|
||||
defaultReplicationFactor={defaultReplicationFactor}
|
||||
partitionsCountIncreased={false}
|
||||
replicationFactorUpdated={false}
|
||||
updateTopicPartitionsCount={jest.fn()}
|
||||
updateTopicReplicationFactor={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
<WithRoute path={clusterTopicSendMessagePath()}>
|
||||
<DangerZone
|
||||
defaultPartitions={defaultPartitions}
|
||||
defaultReplicationFactor={defaultReplicationFactor}
|
||||
partitionsCountIncreased={false}
|
||||
replicationFactorUpdated={false}
|
||||
updateTopicPartitionsCount={jest.fn()}
|
||||
updateTopicReplicationFactor={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
</WithRoute>,
|
||||
{ initialEntries: [clusterTopicSendMessagePath(clusterName, topicName)] }
|
||||
);
|
||||
|
||||
const clickOnDialogSubmitButton = () => {
|
||||
|
@ -199,8 +201,6 @@ describe('DangerZone', () => {
|
|||
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
||||
rerender(
|
||||
<DangerZone
|
||||
clusterName={clusterName}
|
||||
topicName={topicName}
|
||||
defaultPartitions={defaultPartitions}
|
||||
defaultReplicationFactor={defaultReplicationFactor}
|
||||
partitionsCountIncreased
|
||||
|
@ -228,8 +228,6 @@ describe('DangerZone', () => {
|
|||
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
||||
rerender(
|
||||
<DangerZone
|
||||
clusterName={clusterName}
|
||||
topicName={topicName}
|
||||
defaultPartitions={defaultPartitions}
|
||||
defaultReplicationFactor={defaultReplicationFactor}
|
||||
partitionsCountIncreased={false}
|
||||
|
|
|
@ -9,20 +9,20 @@ import {
|
|||
} from 'redux/interfaces';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import TopicForm from 'components/Topics/shared/Form/TopicForm';
|
||||
import { clusterTopicPath } from 'lib/paths';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { topicFormValidationSchema } from 'lib/yupExtended';
|
||||
import { TOPIC_CUSTOM_PARAMS_PREFIX, TOPIC_CUSTOM_PARAMS } from 'lib/constants';
|
||||
import styled from 'styled-components';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import { getFullTopic } from 'redux/reducers/topics/selectors';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
|
||||
import DangerZoneContainer from './DangerZone/DangerZoneContainer';
|
||||
|
||||
export interface Props {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
topic?: TopicWithDetailedInfo;
|
||||
isFetched: boolean;
|
||||
isTopicUpdated: boolean;
|
||||
fetchTopicConfig: (payload: {
|
||||
|
@ -34,11 +34,6 @@ export interface Props {
|
|||
topicName: TopicName;
|
||||
form: TopicFormDataRaw;
|
||||
}) => void;
|
||||
updateTopicPartitionsCount: (payload: {
|
||||
clusterName: string;
|
||||
topicname: string;
|
||||
partitions: number;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const EditWrapperStyled = styled.div`
|
||||
|
@ -83,22 +78,24 @@ const topicParams = (topic: TopicWithDetailedInfo | undefined) => {
|
|||
let formInit = false;
|
||||
|
||||
const Edit: React.FC<Props> = ({
|
||||
clusterName,
|
||||
topicName,
|
||||
topic,
|
||||
isFetched,
|
||||
isTopicUpdated,
|
||||
fetchTopicConfig,
|
||||
updateTopic,
|
||||
}) => {
|
||||
const { clusterName, topicName } = useAppParams<RouteParamsClusterTopic>();
|
||||
|
||||
const topic = useAppSelector((state) => getFullTopic(state, topicName));
|
||||
|
||||
const defaultValues = React.useMemo(() => topicParams(topic), [topic]);
|
||||
|
||||
const methods = useForm<TopicFormData>({
|
||||
defaultValues,
|
||||
resolver: yupResolver(topicFormValidationSchema),
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);
|
||||
const history = useHistory();
|
||||
const navigate = useNavigate();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchTopicConfig({ clusterName, topicName });
|
||||
|
@ -106,10 +103,9 @@ const Edit: React.FC<Props> = ({
|
|||
|
||||
React.useEffect(() => {
|
||||
if (isSubmitting && isTopicUpdated) {
|
||||
const { name } = methods.getValues();
|
||||
history.push(clusterTopicPath(clusterName, name));
|
||||
navigate('../');
|
||||
}
|
||||
}, [isSubmitting, isTopicUpdated, clusterName, methods, history]);
|
||||
}, [isSubmitting, isTopicUpdated, clusterName, navigate]);
|
||||
|
||||
if (!isFetched || !topic || !topic.config) {
|
||||
return null;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState, ClusterName, TopicName } from 'redux/interfaces';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import {
|
||||
updateTopic,
|
||||
fetchTopicConfig,
|
||||
|
@ -8,29 +7,11 @@ import {
|
|||
import {
|
||||
getTopicConfigFetched,
|
||||
getTopicUpdated,
|
||||
getFullTopic,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
|
||||
import Edit from './Edit';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { topicName, clusterName },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
clusterName,
|
||||
topicName,
|
||||
topic: getFullTopic(state, topicName),
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isFetched: getTopicConfigFetched(state),
|
||||
isTopicUpdated: getTopicUpdated(state),
|
||||
});
|
||||
|
@ -40,4 +21,4 @@ const mapDispatchToProps = {
|
|||
updateTopic,
|
||||
};
|
||||
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Edit));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Edit);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue