Skip to content

Commit b4a243f

Browse files
authored
[Issue 191] Fix Cluster Widgets on dashboard (#201)
* [ISSUE-191] Update Bulma to 0.9.2 * [ISSUE-191] Add links to Cluster Widget * [ISSUE-191] Cluster Widget improvements
1 parent 78a9711 commit b4a243f

File tree

10 files changed

+297
-55
lines changed

10 files changed

+297
-55
lines changed

kafka-ui-react-app/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

kafka-ui-react-app/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"bulma": "^0.8.2",
6+
"bulma": "^0.9.2",
77
"bulma-switch": "^2.0.0",
88
"classnames": "^2.2.6",
99
"date-fns": "^2.16.1",
@@ -112,6 +112,8 @@
112112
},
113113
"proxy": "http://localhost:8080",
114114
"jest": {
115-
"snapshotSerializers": ["enzyme-to-json/serializer"]
115+
"snapshotSerializers": [
116+
"enzyme-to-json/serializer"
117+
]
116118
}
117119
}

kafka-ui-react-app/src/components/Dashboard/ClustersWidget/ClusterWidget.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
2-
import formatBytes from 'lib/utils/formatBytes';
32
import { NavLink } from 'react-router-dom';
4-
import { clusterBrokersPath } from 'lib/paths';
3+
import { clusterBrokersPath, clusterTopicsPath } from 'lib/paths';
54
import { Cluster, ServerStatus } from 'generated-sources';
5+
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
66

77
interface ClusterWidgetProps {
88
cluster: Cluster;
@@ -19,9 +19,9 @@ const ClusterWidget: React.FC<ClusterWidgetProps> = ({
1919
onlinePartitionCount,
2020
},
2121
}) => (
22-
<NavLink to={clusterBrokersPath(name)} className="column is-full-modile is-6">
23-
<div className="box is-hoverable">
24-
<div className="title is-6 has-text-overflow-ellipsis" title={name}>
22+
<div className="column is-full-modile is-6">
23+
<div className="box">
24+
<div className="title is-6 has-text-overflow-ellipsis">
2525
<div
2626
className={`tag has-margin-right ${
2727
status === ServerStatus.Online ? 'is-primary' : 'is-danger'
@@ -36,28 +36,36 @@ const ClusterWidget: React.FC<ClusterWidgetProps> = ({
3636
<tbody>
3737
<tr>
3838
<th>Brokers</th>
39-
<td>{brokerCount}</td>
39+
<td>
40+
<NavLink to={clusterBrokersPath(name)}>{brokerCount}</NavLink>
41+
</td>
4042
</tr>
4143
<tr>
4244
<th>Partitions</th>
4345
<td>{onlinePartitionCount}</td>
4446
</tr>
4547
<tr>
4648
<th>Topics</th>
47-
<td>{topicCount}</td>
49+
<td>
50+
<NavLink to={clusterTopicsPath(name)}>{topicCount}</NavLink>
51+
</td>
4852
</tr>
4953
<tr>
5054
<th>Production</th>
51-
<td>{formatBytes(bytesInPerSec || 0)}</td>
55+
<td>
56+
<BytesFormatted value={bytesInPerSec} />
57+
</td>
5258
</tr>
5359
<tr>
5460
<th>Consumption</th>
55-
<td>{formatBytes(bytesOutPerSec || 0)}</td>
61+
<td>
62+
<BytesFormatted value={bytesOutPerSec} />
63+
</td>
5664
</tr>
5765
</tbody>
5866
</table>
5967
</div>
60-
</NavLink>
68+
</div>
6169
);
6270

6371
export default ClusterWidget;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import { ServerStatus } from 'generated-sources';
4+
import { clusterBrokersPath, clusterTopicsPath } from 'lib/paths';
5+
import ClusterWidget from '../ClusterWidget';
6+
import { offlineCluster, onlineCluster } from './fixtures';
7+
8+
describe('ClusterWidget', () => {
9+
describe('when cluster is online', () => {
10+
it('renders with correct tag', () => {
11+
const tag = shallow(<ClusterWidget cluster={onlineCluster} />).find(
12+
'.tag'
13+
);
14+
expect(tag.hasClass('is-primary')).toBeTruthy();
15+
expect(tag.text()).toEqual(ServerStatus.Online);
16+
});
17+
18+
it('renders table', () => {
19+
const table = shallow(<ClusterWidget cluster={onlineCluster} />).find(
20+
'table'
21+
);
22+
expect(table.hasClass('is-fullwidth')).toBeTruthy();
23+
24+
expect(
25+
table.find(`NavLink[to="${clusterBrokersPath(onlineCluster.name)}"]`)
26+
.exists
27+
).toBeTruthy();
28+
expect(
29+
table.find(`NavLink[to="${clusterTopicsPath(onlineCluster.name)}"]`)
30+
.exists
31+
).toBeTruthy();
32+
});
33+
34+
it('matches snapshot', () => {
35+
expect(
36+
shallow(<ClusterWidget cluster={onlineCluster} />)
37+
).toMatchSnapshot();
38+
});
39+
});
40+
41+
describe('when cluster is offline', () => {
42+
it('renders with correct tag', () => {
43+
const tag = shallow(<ClusterWidget cluster={offlineCluster} />).find(
44+
'.tag'
45+
);
46+
47+
expect(tag.hasClass('is-danger')).toBeTruthy();
48+
expect(tag.text()).toEqual(ServerStatus.Offline);
49+
});
50+
51+
it('renders table', () => {
52+
const table = shallow(<ClusterWidget cluster={offlineCluster} />).find(
53+
'table'
54+
);
55+
expect(table.hasClass('is-fullwidth')).toBeTruthy();
56+
57+
expect(
58+
table.find(`NavLink[to="${clusterBrokersPath(onlineCluster.name)}"]`)
59+
.exists
60+
).toBeTruthy();
61+
expect(
62+
table.find(`NavLink[to="${clusterTopicsPath(onlineCluster.name)}"]`)
63+
.exists
64+
).toBeTruthy();
65+
});
66+
67+
it('matches snapshot', () => {
68+
expect(
69+
shallow(<ClusterWidget cluster={offlineCluster} />)
70+
).toMatchSnapshot();
71+
});
72+
});
73+
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ClusterWidget when cluster is offline matches snapshot 1`] = `
4+
<div
5+
className="column is-full-modile is-6"
6+
>
7+
<div
8+
className="box"
9+
>
10+
<div
11+
className="title is-6 has-text-overflow-ellipsis"
12+
>
13+
<div
14+
className="tag has-margin-right is-danger"
15+
>
16+
offline
17+
</div>
18+
local
19+
</div>
20+
<table
21+
className="table is-fullwidth"
22+
>
23+
<tbody>
24+
<tr>
25+
<th>
26+
Brokers
27+
</th>
28+
<td>
29+
<NavLink
30+
to="/ui/clusters/local/brokers"
31+
>
32+
1
33+
</NavLink>
34+
</td>
35+
</tr>
36+
<tr>
37+
<th>
38+
Partitions
39+
</th>
40+
<td>
41+
2
42+
</td>
43+
</tr>
44+
<tr>
45+
<th>
46+
Topics
47+
</th>
48+
<td>
49+
<NavLink
50+
to="/ui/clusters/local/topics"
51+
>
52+
2
53+
</NavLink>
54+
</td>
55+
</tr>
56+
<tr>
57+
<th>
58+
Production
59+
</th>
60+
<td>
61+
<BytesFormatted
62+
value={8000.00000673768}
63+
/>
64+
</td>
65+
</tr>
66+
<tr>
67+
<th>
68+
Consumption
69+
</th>
70+
<td>
71+
<BytesFormatted
72+
value={0.815306356729712}
73+
/>
74+
</td>
75+
</tr>
76+
</tbody>
77+
</table>
78+
</div>
79+
</div>
80+
`;
81+
82+
exports[`ClusterWidget when cluster is online matches snapshot 1`] = `
83+
<div
84+
className="column is-full-modile is-6"
85+
>
86+
<div
87+
className="box"
88+
>
89+
<div
90+
className="title is-6 has-text-overflow-ellipsis"
91+
>
92+
<div
93+
className="tag has-margin-right is-primary"
94+
>
95+
online
96+
</div>
97+
secondLocal
98+
</div>
99+
<table
100+
className="table is-fullwidth"
101+
>
102+
<tbody>
103+
<tr>
104+
<th>
105+
Brokers
106+
</th>
107+
<td>
108+
<NavLink
109+
to="/ui/clusters/secondLocal/brokers"
110+
>
111+
1
112+
</NavLink>
113+
</td>
114+
</tr>
115+
<tr>
116+
<th>
117+
Partitions
118+
</th>
119+
<td>
120+
6
121+
</td>
122+
</tr>
123+
<tr>
124+
<th>
125+
Topics
126+
</th>
127+
<td>
128+
<NavLink
129+
to="/ui/clusters/secondLocal/topics"
130+
>
131+
3
132+
</NavLink>
133+
</td>
134+
</tr>
135+
<tr>
136+
<th>
137+
Production
138+
</th>
139+
<td>
140+
<BytesFormatted
141+
value={0.00003061819685376472}
142+
/>
143+
</td>
144+
</tr>
145+
<tr>
146+
<th>
147+
Consumption
148+
</th>
149+
<td>
150+
<BytesFormatted
151+
value={5.737800890036267}
152+
/>
153+
</td>
154+
</tr>
155+
</tbody>
156+
</table>
157+
</div>
158+
</div>
159+
`;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Cluster, ServerStatus } from 'generated-sources';
2+
3+
export const onlineCluster: Cluster = {
4+
name: 'secondLocal',
5+
defaultCluster: false,
6+
status: ServerStatus.Online,
7+
brokerCount: 1,
8+
onlinePartitionCount: 6,
9+
topicCount: 3,
10+
bytesInPerSec: 0.000030618196853764715,
11+
bytesOutPerSec: 5.737800890036267075817,
12+
};
13+
14+
export const offlineCluster: Cluster = {
15+
name: 'local',
16+
defaultCluster: true,
17+
status: ServerStatus.Offline,
18+
brokerCount: 1,
19+
onlinePartitionCount: 2,
20+
topicCount: 2,
21+
bytesInPerSec: 8000.0000067376808542600021,
22+
bytesOutPerSec: 0.8153063567297119490871,
23+
};
24+
25+
export const clusters: Cluster[] = [onlineCluster, offlineCluster];

kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,23 @@ interface Props {
55
precision?: number;
66
}
77

8-
const BytesFormatted: React.FC<Props> = ({ value, precision }) => {
9-
const formatBytes = React.useCallback(() => {
10-
const numVal = typeof value === 'string' ? parseInt(value, 10) : value;
11-
if (!numVal) return 0;
12-
const pow = Math.floor(Math.log2(numVal) / 10);
8+
const BytesFormatted: React.FC<Props> = ({ value, precision = 0 }) => {
9+
const formatedValue = React.useMemo(() => {
10+
const bytes = typeof value === 'string' ? parseInt(value, 10) : value;
11+
12+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
13+
if (!bytes || bytes === 0) return [0, sizes[0]];
14+
15+
if (bytes < 1024) return [Math.ceil(bytes), sizes[0]];
16+
17+
const pow = Math.floor(Math.log2(bytes) / 10);
1318
const multiplier = 10 ** (precision || 2);
1419
return (
15-
Math.round((numVal * multiplier) / 1024 ** pow) / multiplier +
16-
['Bytes', 'KB', 'MB', 'GB', 'TB'][pow]
20+
Math.round((bytes * multiplier) / 1024 ** pow) / multiplier + sizes[pow]
1721
);
1822
}, [value]);
19-
return <span>{formatBytes()}</span>;
23+
24+
return <span>{formatedValue}</span>;
2025
};
2126

2227
export default BytesFormatted;

0 commit comments

Comments
 (0)