Cleanup styling (#365)

This commit is contained in:
Oleg Shur 2021-04-09 14:29:39 +03:00 committed by GitHub
parent 6e8226298f
commit d471759b79
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1033 additions and 504 deletions

View file

@ -17,7 +17,7 @@ $navbar-width: 250px;
z-index: 20; z-index: 20;
} }
&__navbar { &__sidebar{
width: $navbar-width; width: $navbar-width;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -28,6 +28,19 @@ $navbar-width: 250px;
bottom: 0; bottom: 0;
padding: 20px 20px; padding: 20px 20px;
overflow-y: scroll; overflow-y: scroll;
transition: width .25s,opacity .25s,transform .25s,-webkit-transform .25s;
&Overlay {
position: fixed;
top: 0;
height: 120vh;
z-index: 99;
display: block;
visibility: hidden;
opacity: 0;
-webkit-transition: all .5s ease;
transition: all .5s ease;
}
} }
&__alerts { &__alerts {
@ -47,3 +60,35 @@ $navbar-width: 250px;
.react-datepicker-popper { .react-datepicker-popper {
z-index: 30 !important; z-index: 30 !important;
} }
@media screen and (max-width: 1023px) {
.Layout {
&__container {
margin-left: initial;
margin-top: 1.5rem;
}
&__sidebar {
left: -$navbar-width;
z-index: 100;
}
&__alerts {
max-width: initial;
}
&--sidebarVisible {
.Layout__sidebar {
transform: translate3d($navbar-width,0,0);
&Overlay {
background-color: rgba(34,41,47,.5);
left: 0;
right: 0;
opacity: 1;
visibility: visible;
}
}
}
}
}

View file

@ -1,51 +1,97 @@
import './App.scss'; import './App.scss';
import React from 'react'; import React from 'react';
import { Switch, Route } from 'react-router-dom'; import cx from 'classnames';
import { Cluster } from 'generated-sources';
import { Switch, Route, useLocation } from 'react-router-dom';
import { GIT_TAG, GIT_COMMIT } from 'lib/constants'; import { GIT_TAG, GIT_COMMIT } from 'lib/constants';
import { Alerts } from 'redux/interfaces'; import { Alerts } from 'redux/interfaces';
import NavContainer from './Nav/NavContainer'; import Nav from './Nav/Nav';
import PageLoader from './common/PageLoader/PageLoader'; import PageLoader from './common/PageLoader/PageLoader';
import Dashboard from './Dashboard/Dashboard'; import Dashboard from './Dashboard/Dashboard';
import Cluster from './Cluster/Cluster'; import ClusterPage from './Cluster/Cluster';
import Version from './Version/Version'; import Version from './Version/Version';
import Alert from './Alert/Alert'; import Alert from './Alert/Alert';
export interface AppProps { export interface AppProps {
isClusterListFetched?: boolean; isClusterListFetched?: boolean;
alerts: Alerts; alerts: Alerts;
clusters: Cluster[];
fetchClustersList: () => void; fetchClustersList: () => void;
} }
const App: React.FC<AppProps> = ({ const App: React.FC<AppProps> = ({
isClusterListFetched, isClusterListFetched,
alerts, alerts,
clusters,
fetchClustersList, fetchClustersList,
}) => { }) => {
const [isSidebarVisible, setIsSidebarVisible] = React.useState(false);
const onBurgerClick = React.useCallback(
() => setIsSidebarVisible(!isSidebarVisible),
[isSidebarVisible]
);
const closeSidebar = React.useCallback(() => setIsSidebarVisible(false), []);
const location = useLocation();
React.useEffect(() => {
closeSidebar();
}, [location]);
React.useEffect(() => { React.useEffect(() => {
fetchClustersList(); fetchClustersList();
}, [fetchClustersList]); }, [fetchClustersList]);
return ( return (
<div className="Layout"> <div
className={cx('Layout', { 'Layout--sidebarVisible': isSidebarVisible })}
>
<nav <nav
className="navbar is-fixed-top is-white Layout__header" className="navbar is-fixed-top is-white Layout__header"
role="navigation" role="navigation"
aria-label="main navigation" aria-label="main navigation"
> >
<div className="navbar-brand"> <div className="navbar-brand">
<div
className={cx('navbar-burger', 'ml-0', {
'is-active': isSidebarVisible,
})}
onClick={onBurgerClick}
onKeyDown={onBurgerClick}
role="button"
tabIndex={0}
>
<span />
<span />
<span />
</div>
<a className="navbar-item title is-5 is-marginless" href="/ui"> <a className="navbar-item title is-5 is-marginless" href="/ui">
Kafka UI Kafka UI
</a> </a>
</div>
<div className="navbar-end"> <div className="navbar-item">
<div className="navbar-item mr-2">
<Version tag={GIT_TAG} commit={GIT_COMMIT} /> <Version tag={GIT_TAG} commit={GIT_COMMIT} />
</div> </div>
</div> </div>
</nav> </nav>
<main className="Layout__container"> <main className="Layout__container">
<NavContainer className="Layout__navbar" /> <div className="Layout__sidebar has-shadow has-background-white">
<Nav
clusters={clusters}
isClusterListFetched={isClusterListFetched}
/>
</div>
<div
className="Layout__sidebarOverlay is-overlay"
onClick={closeSidebar}
onKeyDown={closeSidebar}
tabIndex={-1}
aria-hidden="true"
/>
{isClusterListFetched ? ( {isClusterListFetched ? (
<Switch> <Switch>
<Route <Route
@ -53,7 +99,7 @@ const App: React.FC<AppProps> = ({
path={['/', '/ui', '/ui/clusters']} path={['/', '/ui', '/ui/clusters']}
component={Dashboard} component={Dashboard}
/> />
<Route path="/ui/clusters/:clusterName" component={Cluster} /> <Route path="/ui/clusters/:clusterName" component={ClusterPage} />
</Switch> </Switch>
) : ( ) : (
<PageLoader fullHeight /> <PageLoader fullHeight />

View file

@ -1,6 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchClustersList } from 'redux/actions'; import { fetchClustersList } from 'redux/actions';
import { getIsClusterListFetched } from 'redux/reducers/clusters/selectors'; import {
getClusterList,
getIsClusterListFetched,
} from 'redux/reducers/clusters/selectors';
import { getAlerts } from 'redux/reducers/alerts/selectors'; import { getAlerts } from 'redux/reducers/alerts/selectors';
import { RootState } from 'redux/interfaces'; import { RootState } from 'redux/interfaces';
import App from './App'; import App from './App';
@ -8,6 +11,7 @@ import App from './App';
const mapStateToProps = (state: RootState) => ({ const mapStateToProps = (state: RootState) => ({
isClusterListFetched: getIsClusterListFetched(state), isClusterListFetched: getIsClusterListFetched(state),
alerts: getAlerts(state), alerts: getAlerts(state),
clusters: getClusterList(state),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {

View file

@ -7,16 +7,15 @@ import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
import Indicator from 'components/common/Dashboard/Indicator'; import Indicator from 'components/common/Dashboard/Indicator';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb'; import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted'; import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
import { useParams } from 'react-router';
interface Props extends ClusterStats { interface Props extends ClusterStats {
clusterName: ClusterName;
isFetched: boolean; isFetched: boolean;
fetchClusterStats: (clusterName: ClusterName) => void; fetchClusterStats: (clusterName: ClusterName) => void;
fetchBrokers: (clusterName: ClusterName) => void; fetchBrokers: (clusterName: ClusterName) => void;
} }
const Brokers: React.FC<Props> = ({ const Brokers: React.FC<Props> = ({
clusterName,
brokerCount, brokerCount,
activeControllers, activeControllers,
zooKeeperStatus, zooKeeperStatus,
@ -29,6 +28,8 @@ const Brokers: React.FC<Props> = ({
fetchClusterStats, fetchClusterStats,
fetchBrokers, fetchBrokers,
}) => { }) => {
const { clusterName } = useParams<{ clusterName: ClusterName }>();
React.useEffect(() => { React.useEffect(() => {
fetchClusterStats(clusterName); fetchClusterStats(clusterName);
fetchBrokers(clusterName); fetchBrokers(clusterName);
@ -44,9 +45,13 @@ const Brokers: React.FC<Props> = ({
<div className="section"> <div className="section">
<Breadcrumb>Brokers overview</Breadcrumb> <Breadcrumb>Brokers overview</Breadcrumb>
<MetricsWrapper title="Uptime"> <MetricsWrapper title="Uptime">
<Indicator label="Total Brokers">{brokerCount}</Indicator> <Indicator className="is-one-third" label="Total Brokers">
<Indicator label="Active Controllers">{activeControllers}</Indicator> {brokerCount}
<Indicator label="Zookeeper Status"> </Indicator>
<Indicator className="is-one-third" label="Active Controllers">
{activeControllers}
</Indicator>
<Indicator className="is-one-third" label="Zookeeper Status">
<span className={cx('tag', zkOnline ? 'is-primary' : 'is-danger')}> <span className={cx('tag', zkOnline ? 'is-primary' : 'is-danger')}>
{zkOnline ? 'Online' : 'Offline'} {zkOnline ? 'Online' : 'Offline'}
</span> </span>

View file

@ -1,43 +1,36 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchClusterStats, fetchBrokers } from 'redux/actions'; import { fetchClusterStats, fetchBrokers } from 'redux/actions';
import * as brokerSelectors from 'redux/reducers/brokers/selectors'; import { RootState } from 'redux/interfaces';
import { RootState, ClusterName } from 'redux/interfaces'; import {
import { RouteComponentProps } from 'react-router-dom'; getIsBrokerListFetched,
getBrokerCount,
getZooKeeperStatus,
getActiveControllers,
getOnlinePartitionCount,
getOfflinePartitionCount,
getInSyncReplicasCount,
getOutOfSyncReplicasCount,
getUnderReplicatedPartitionCount,
getDiskUsage,
} from 'redux/reducers/brokers/selectors';
import Brokers from './Brokers'; import Brokers from './Brokers';
interface RouteProps { const mapStateToProps = (state: RootState) => ({
clusterName: ClusterName; isFetched: getIsBrokerListFetched(state),
} brokerCount: getBrokerCount(state),
zooKeeperStatus: getZooKeeperStatus(state),
type OwnProps = RouteComponentProps<RouteProps>; activeControllers: getActiveControllers(state),
onlinePartitionCount: getOnlinePartitionCount(state),
const mapStateToProps = ( offlinePartitionCount: getOfflinePartitionCount(state),
state: RootState, inSyncReplicasCount: getInSyncReplicasCount(state),
{ outOfSyncReplicasCount: getOutOfSyncReplicasCount(state),
match: { underReplicatedPartitionCount: getUnderReplicatedPartitionCount(state),
params: { clusterName }, diskUsage: getDiskUsage(state),
},
}: OwnProps
) => ({
isFetched: brokerSelectors.getIsBrokerListFetched(state),
clusterName,
brokerCount: brokerSelectors.getBrokerCount(state),
zooKeeperStatus: brokerSelectors.getZooKeeperStatus(state),
activeControllers: brokerSelectors.getActiveControllers(state),
onlinePartitionCount: brokerSelectors.getOnlinePartitionCount(state),
offlinePartitionCount: brokerSelectors.getOfflinePartitionCount(state),
inSyncReplicasCount: brokerSelectors.getInSyncReplicasCount(state),
outOfSyncReplicasCount: brokerSelectors.getOutOfSyncReplicasCount(state),
underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(
state
),
diskUsage: brokerSelectors.getDiskUsage(state),
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
fetchClusterStats: (clusterName: ClusterName) => fetchClusterStats,
fetchClusterStats(clusterName), fetchBrokers,
fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
}; };
export default connect(mapStateToProps, mapDispatchToProps)(Brokers); export default connect(mapStateToProps, mapDispatchToProps)(Brokers);

View file

@ -57,6 +57,7 @@ const List: React.FC<ListProps> = ({
<PageLoader /> <PageLoader />
) : ( ) : (
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-fullwidth"> <table className="table is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -79,6 +80,7 @@ const List: React.FC<ListProps> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
)} )}
</div> </div>
); );

View file

@ -54,6 +54,7 @@ const Details: React.FC<Props> = ({
{isFetched ? ( {isFetched ? (
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-striped is-fullwidth"> <table className="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -77,6 +78,7 @@ const Details: React.FC<Props> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
) : ( ) : (
<PageLoader /> <PageLoader />
)} )}

View file

@ -36,6 +36,7 @@ const List: React.FC<Props> = ({ consumerGroups }) => {
/> />
</div> </div>
</div> </div>
<div className="table-container">
<table className="table is-striped is-fullwidth is-hoverable"> <table className="table is-striped is-fullwidth is-hoverable">
<thead> <thead>
<tr> <tr>
@ -60,6 +61,7 @@ const List: React.FC<Props> = ({ consumerGroups }) => {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
) : ( ) : (
'No active consumer groups' 'No active consumer groups'
)} )}

View file

@ -5,7 +5,7 @@ import { Cluster } from 'generated-sources';
import ClusterMenu from './ClusterMenu'; import ClusterMenu from './ClusterMenu';
interface Props { interface Props {
isClusterListFetched: boolean; isClusterListFetched?: boolean;
clusters: Cluster[]; clusters: Cluster[];
className?: string; className?: string;
} }

View file

@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import {
getIsClusterListFetched,
getClusterList,
} from 'redux/reducers/clusters/selectors';
import { RootState } from 'redux/interfaces';
import Nav from './Nav';
const mapStateToProps = (state: RootState) => ({
isClusterListFetched: getIsClusterListFetched(state),
clusters: getClusterList(state),
});
export default connect(mapStateToProps)(Nav);

View file

@ -0,0 +1,21 @@
import React from 'react';
import { shallow } from 'enzyme';
import { onlineClusterPayload } from 'redux/reducers/clusters/__test__/fixtures';
import Nav from '../Nav';
describe('Nav', () => {
it('renders loader', () => {
const wrapper = shallow(<Nav clusters={[]} />);
expect(wrapper.find('.loader')).toBeTruthy();
expect(wrapper.exists('ClusterMenu')).toBeFalsy();
});
it('renders ClusterMenu', () => {
const wrapper = shallow(
<Nav clusters={[onlineClusterPayload]} isClusterListFetched />
);
expect(wrapper.exists('.loader')).toBeFalsy();
expect(wrapper.exists('ClusterMenu')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
});

View file

@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Nav renders ClusterMenu 1`] = `
<aside
className="menu has-shadow has-background-white"
>
<p
className="menu-label"
>
General
</p>
<ul
className="menu-list"
>
<li>
<NavLink
activeClassName="is-active"
exact={true}
title="Dashboard"
to="/ui"
>
Dashboard
</NavLink>
</li>
</ul>
<p
className="menu-label"
>
Clusters
</p>
<ClusterMenu
cluster={
Object {
"brokerCount": 1,
"bytesInPerSec": 1.55,
"bytesOutPerSec": 9.314,
"defaultCluster": true,
"features": Array [],
"name": "secondLocal",
"onlinePartitionCount": 6,
"status": "online",
"topicCount": 3,
}
}
key="secondLocal"
/>
</aside>
`;

View file

@ -85,6 +85,7 @@ const Details: React.FC<DetailsProps> = ({
<LatestVersionItem schema={schema} /> <LatestVersionItem schema={schema} />
</div> </div>
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-striped is-fullwidth"> <table className="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -100,6 +101,7 @@ const Details: React.FC<DetailsProps> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</> </>
) : ( ) : (
<PageLoader /> <PageLoader />

View file

@ -12,6 +12,7 @@ const LatestVersionItem: React.FC<LatestVersionProps> = ({
<div className="tile is-ancestor mt-1"> <div className="tile is-ancestor mt-1">
<div className="tile is-4 is-parent"> <div className="tile is-4 is-parent">
<div className="tile is-child"> <div className="tile is-child">
<div className="table-container">
<table className="table is-fullwidth"> <table className="table is-fullwidth">
<tbody> <tbody>
<tr> <tr>
@ -30,6 +31,7 @@ const LatestVersionItem: React.FC<LatestVersionProps> = ({
</table> </table>
</div> </div>
</div> </div>
</div>
<div className="tile is-parent"> <div className="tile is-parent">
<div className="tile is-child box py-1"> <div className="tile is-child box py-1">
<JSONViewer data={JSON.parse(schema)} /> <JSONViewer data={JSON.parse(schema)} />

View file

@ -84,6 +84,9 @@ exports[`Details View Initial state matches snapshot 1`] = `
</div> </div>
<div <div
className="box" className="box"
>
<div
className="table-container"
> >
<table <table
className="table is-striped is-fullwidth" className="table is-striped is-fullwidth"
@ -105,6 +108,7 @@ exports[`Details View Initial state matches snapshot 1`] = `
</table> </table>
</div> </div>
</div> </div>
</div>
`; `;
exports[`Details View when page with schema versions is loading matches snapshot 1`] = ` exports[`Details View when page with schema versions is loading matches snapshot 1`] = `
@ -215,6 +219,9 @@ exports[`Details View when page with schema versions loaded when schema has vers
</div> </div>
<div <div
className="box" className="box"
>
<div
className="table-container"
> >
<table <table
className="table is-striped is-fullwidth" className="table is-striped is-fullwidth"
@ -263,6 +270,7 @@ exports[`Details View when page with schema versions loaded when schema has vers
</table> </table>
</div> </div>
</div> </div>
</div>
`; `;
exports[`Details View when page with schema versions loaded when versions are empty matches snapshot 1`] = ` exports[`Details View when page with schema versions loaded when versions are empty matches snapshot 1`] = `
@ -349,6 +357,9 @@ exports[`Details View when page with schema versions loaded when versions are em
</div> </div>
<div <div
className="box" className="box"
>
<div
className="table-container"
> >
<table <table
className="table is-striped is-fullwidth" className="table is-striped is-fullwidth"
@ -370,4 +381,5 @@ exports[`Details View when page with schema versions loaded when versions are em
</table> </table>
</div> </div>
</div> </div>
</div>
`; `;

View file

@ -9,6 +9,9 @@ exports[`LatestVersionItem matches snapshot 1`] = `
> >
<div <div
className="tile is-child" className="tile is-child"
>
<div
className="table-container"
> >
<table <table
className="table is-fullwidth" className="table is-fullwidth"
@ -42,6 +45,7 @@ exports[`LatestVersionItem matches snapshot 1`] = `
</table> </table>
</div> </div>
</div> </div>
</div>
<div <div
className="tile is-parent" className="tile is-parent"
> >

View file

@ -48,6 +48,7 @@ const List: React.FC<ListProps> = ({
<PageLoader /> <PageLoader />
) : ( ) : (
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-striped is-fullwidth"> <table className="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -68,6 +69,7 @@ const List: React.FC<ListProps> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
)} )}
</div> </div>
); );

View file

@ -81,6 +81,7 @@ const List: React.FC<Props> = ({
<PageLoader /> <PageLoader />
) : ( ) : (
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-fullwidth"> <table className="table is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -109,6 +110,7 @@ const List: React.FC<Props> = ({
</table> </table>
<Pagination totalPages={totalPages} /> <Pagination totalPages={totalPages} />
</div> </div>
</div>
)} )}
</div> </div>
); );

View file

@ -37,7 +37,7 @@ const ListItem: React.FC<ListItemProps> = ({
return ( return (
<tr> <tr>
<td> <td className="has-text-overflow-ellipsis">
<NavLink <NavLink
exact exact
to={`topics/${name}`} to={`topics/${name}`}

View file

@ -15,6 +15,7 @@ const MessagesTable: React.FC<MessagesTableProp> = ({ messages, onNext }) => {
return ( return (
<> <>
<div className="table-container">
<table className="table is-fullwidth"> <table className="table is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -39,6 +40,7 @@ const MessagesTable: React.FC<MessagesTableProp> = ({ messages, onNext }) => {
)} )}
</tbody> </tbody>
</table> </table>
</div>
<div className="columns"> <div className="columns">
<div className="column is-full"> <div className="column is-full">
<CustomParamButton <CustomParamButton

View file

@ -2,6 +2,9 @@
exports[`MessagesTable when topic contains messages matches snapshot 1`] = ` exports[`MessagesTable when topic contains messages matches snapshot 1`] = `
<Fragment> <Fragment>
<div
className="table-container"
>
<table <table
className="table is-fullwidth" className="table is-fullwidth"
> >
@ -45,6 +48,7 @@ exports[`MessagesTable when topic contains messages matches snapshot 1`] = `
/> />
</tbody> </tbody>
</table> </table>
</div>
<div <div
className="columns" className="columns"
> >

View file

@ -43,6 +43,7 @@ const Overview: React.FC<Props> = ({
<Indicator label="Segment count">{segmentCount}</Indicator> <Indicator label="Segment count">{segmentCount}</Indicator>
</MetricsWrapper> </MetricsWrapper>
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-striped is-fullwidth"> <table className="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -64,6 +65,7 @@ const Overview: React.FC<Props> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</> </>
); );

View file

@ -47,6 +47,7 @@ const Settings: React.FC<Props> = ({
return ( return (
<div className="box"> <div className="box">
<div className="table-container">
<table className="table is-striped is-fullwidth"> <table className="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr>
@ -62,6 +63,7 @@ const Settings: React.FC<Props> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
); );
}; };

View file

@ -1,61 +1,79 @@
import React from 'react'; import React from 'react';
import { mount, shallow } from 'enzyme'; import { mount } from 'enzyme';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom'; import { StaticRouter } from 'react-router-dom';
import { Alert } from 'redux/interfaces';
import configureStore from 'redux/store/configureStore'; import configureStore from 'redux/store/configureStore';
import App, { AppProps } from '../App'; import App, { AppProps } from 'components/App';
import AppContainer from 'components/AppContainer';
const fetchClustersList = jest.fn(); const fetchClustersList = jest.fn();
const store = configureStore(); const store = configureStore();
describe('App', () => { describe('App', () => {
describe('container', () => {
it('renders view', () => {
const wrapper = mount(
<Provider store={store}>
<StaticRouter>
<AppContainer />
</StaticRouter>
</Provider>
);
expect(wrapper.exists('App')).toBeTruthy();
});
});
describe('view', () => {
const setupComponent = (props: Partial<AppProps> = {}) => ( const setupComponent = (props: Partial<AppProps> = {}) => (
<Provider store={store}>
<StaticRouter>
<App <App
isClusterListFetched isClusterListFetched
alerts={[]} alerts={[]}
clusters={[]}
fetchClustersList={fetchClustersList} fetchClustersList={fetchClustersList}
{...props} {...props}
/> />
</StaticRouter>
</Provider>
); );
it('handles fetchClustersList', () => { it('handles fetchClustersList', () => {
const wrapper = mount( const wrapper = mount(setupComponent());
<Provider store={store}>
<StaticRouter>{setupComponent()}</StaticRouter>
</Provider>
);
expect(wrapper.exists()).toBeTruthy(); expect(wrapper.exists()).toBeTruthy();
expect(fetchClustersList).toHaveBeenCalledTimes(1); expect(fetchClustersList).toHaveBeenCalledTimes(1);
}); });
it('shows PageLoader until cluster list is fetched', () => { it('shows PageLoader until cluster list is fetched', () => {
const component = shallow(setupComponent({ isClusterListFetched: false })); let component = mount(setupComponent({ isClusterListFetched: false }));
expect(component.exists('.Layout__container PageLoader')).toBeTruthy(); expect(component.exists('.Layout__container PageLoader')).toBeTruthy();
expect(component.exists('.Layout__container Switch')).toBeFalsy(); expect(component.exists('.Layout__container Switch')).toBeFalsy();
component.setProps({ isClusterListFetched: true });
component = mount(setupComponent({ isClusterListFetched: true }));
expect(component.exists('.Layout__container PageLoader')).toBeFalsy(); expect(component.exists('.Layout__container PageLoader')).toBeFalsy();
expect(component.exists('.Layout__container Switch')).toBeTruthy(); expect(component.exists('.Layout__container Switch')).toBeTruthy();
}); });
it('correctly renders alerts', () => { it('correctly renders alerts', () => {
const alert = { const alert: Alert = {
id: 'alert-id', id: 'alert-id',
type: 'success', type: 'success',
title: 'My Custom Title', title: 'My Custom Title',
message: 'My Custom Message', message: 'My Custom Message',
createdAt: 1234567890, createdAt: 1234567890,
}; };
const wrapper = shallow(setupComponent()); let wrapper = mount(setupComponent());
expect(wrapper.exists('.Layout__alerts')).toBeTruthy(); expect(wrapper.exists('.Layout__alerts')).toBeTruthy();
expect(wrapper.exists('Alert')).toBeFalsy(); expect(wrapper.exists('Alert')).toBeFalsy();
wrapper.setProps({ alerts: [alert] }); wrapper = mount(setupComponent({ alerts: [alert] }));
expect(wrapper.exists('Alert')).toBeTruthy(); expect(wrapper.exists('Alert')).toBeTruthy();
expect(wrapper.find('Alert').length).toEqual(1); expect(wrapper.find('Alert').length).toEqual(1);
}); });
it('matches snapshot', () => { it('matches snapshot', () => {
const component = shallow(setupComponent()); const wrapper = mount(setupComponent());
expect(component).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
});
}); });
}); });

View file

@ -1,6 +1,58 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App matches snapshot 1`] = ` exports[`App view matches snapshot 1`] = `
<Provider
store={
Object {
"dispatch": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
Symbol(observable): [Function],
}
}
>
<StaticRouter>
<Router
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
staticContext={Object {}}
>
<App
alerts={Array []}
clusters={Array []}
fetchClustersList={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
isClusterListFetched={true}
>
<div <div
className="Layout" className="Layout"
> >
@ -12,18 +64,25 @@ exports[`App matches snapshot 1`] = `
<div <div
className="navbar-brand" className="navbar-brand"
> >
<div
className="navbar-burger ml-0"
onClick={[Function]}
onKeyDown={[Function]}
role="button"
tabIndex={0}
>
<span />
<span />
<span />
</div>
<a <a
className="navbar-item title is-5 is-marginless" className="navbar-item title is-5 is-marginless"
href="/ui" href="/ui"
> >
Kafka UI Kafka UI
</a> </a>
</div>
<div <div
className="navbar-end" className="navbar-item"
>
<div
className="navbar-item mr-2"
> >
<Version /> <Version />
</div> </div>
@ -32,13 +91,97 @@ exports[`App matches snapshot 1`] = `
<main <main
className="Layout__container" className="Layout__container"
> >
<Connect(Nav) <div
className="Layout__navbar" className="Layout__sidebar has-shadow has-background-white"
>
<Nav
clusters={Array []}
isClusterListFetched={true}
>
<aside
className="menu has-shadow has-background-white"
>
<p
className="menu-label"
>
General
</p>
<ul
className="menu-list"
>
<li>
<NavLink
activeClassName="is-active"
exact={true}
title="Dashboard"
to="/ui"
>
<Link
aria-current={null}
title="Dashboard"
to={
Object {
"hash": "",
"pathname": "/ui",
"search": "",
"state": null,
}
}
>
<LinkAnchor
aria-current={null}
href="/ui"
navigate={[Function]}
title="Dashboard"
>
<a
aria-current={null}
href="/ui"
onClick={[Function]}
title="Dashboard"
>
Dashboard
</a>
</LinkAnchor>
</Link>
</NavLink>
</li>
</ul>
<p
className="menu-label"
>
Clusters
</p>
</aside>
</Nav>
</div>
<div
aria-hidden="true"
className="Layout__sidebarOverlay is-overlay"
onClick={[Function]}
onKeyDown={[Function]}
tabIndex={-1}
/> />
<Switch> <Switch>
<Route <Route
component={[Function]} component={[Function]}
computedMatch={
Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
}
}
exact={true} exact={true}
location={
Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
}
}
path={ path={
Array [ Array [
"/", "/",
@ -46,15 +189,195 @@ exports[`App matches snapshot 1`] = `
"/ui/clusters", "/ui/clusters",
] ]
} }
>
<Dashboard
history={
Object {
"action": "POP",
"block": [Function],
"createHref": [Function],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"listen": [Function],
"location": Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
}
}
location={
Object {
"hash": "",
"pathname": "/",
"search": "",
"state": undefined,
}
}
match={
Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
}
}
staticContext={Object {}}
>
<div
className="section"
>
<div
className="level"
>
<div
className="level-item level-left"
>
<Breadcrumb>
<nav
aria-label="breadcrumbs"
className="breadcrumb"
>
<ul>
<li
className="is-active"
>
<span
className=""
>
Dashboard
</span>
</li>
</ul>
</nav>
</Breadcrumb>
</div>
</div>
<Connect(ClustersWidget)>
<ClustersWidget
clusters={Array []}
dispatch={[Function]}
offlineClusters={Array []}
onlineClusters={Array []}
>
<div>
<h5
className="title is-5"
>
Clusters
</h5>
<MetricsWrapper>
<div
className="box"
>
<div
className="level"
>
<Indicator
label="Online Clusters"
>
<div
className="level-item"
>
<div
title="Online Clusters"
>
<p
className="heading"
>
Online Clusters
</p>
<p
className="title has-text-centered"
>
<span
className="tag is-primary"
>
0
</span>
</p>
</div>
</div>
</Indicator>
<Indicator
label="Offline Clusters"
>
<div
className="level-item"
>
<div
title="Offline Clusters"
>
<p
className="heading"
>
Offline Clusters
</p>
<p
className="title has-text-centered"
>
<span
className="tag is-danger"
>
0
</span>
</p>
</div>
</div>
</Indicator>
<Indicator
label="Hide online clusters"
>
<div
className="level-item"
>
<div
title="Hide online clusters"
>
<p
className="heading"
>
Hide online clusters
</p>
<p
className="title has-text-centered"
>
<input
checked={false}
className="switch is-rounded"
id="switchRoundedDefault"
name="switchRoundedDefault"
onChange={[Function]}
type="checkbox"
/> />
<Route <label
component={[Function]} htmlFor="switchRoundedDefault"
path="/ui/clusters/:clusterName"
/> />
</p>
</div>
</div>
</Indicator>
</div>
</div>
</MetricsWrapper>
</div>
</ClustersWidget>
</Connect(ClustersWidget)>
</div>
</Dashboard>
</Route>
</Switch> </Switch>
</main> </main>
<div <div
className="Layout__alerts" className="Layout__alerts"
/> />
</div> </div>
</App>
</Router>
</StaticRouter>
</Provider>
`; `;

View file

@ -16,7 +16,7 @@ const Indicator: React.FC<Props> = ({
children, children,
}) => { }) => {
return ( return (
<div className={cx('level-item', 'level-left', className)}> <div className={cx('level-item', className)}>
<div title={title || label}> <div title={title || label}>
<p className="heading">{label}</p> <p className="heading">{label}</p>
<p className="title has-text-centered"> <p className="title has-text-centered">

View file

@ -6,7 +6,7 @@ exports[`Indicator matches the snapshot 1`] = `
title="title" title="title"
> >
<div <div
className="level-item level-left" className="level-item"
> >
<div <div
title="title" title="title"