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