Skip to content

Commit 6cc07d5

Browse files
authored
feat(Versions): show overall version info in Versions tab (#1442)
1 parent 9daa5a3 commit 6cc07d5

File tree

11 files changed

+150
-19
lines changed

11 files changed

+150
-19
lines changed

src/containers/Cluster/Cluster.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export function Cluster({
200200
getLocationObjectFromHref(getClusterPath(clusterTabsIds.versions)).pathname
201201
}
202202
>
203-
<Versions versionToColor={versionToColor} />
203+
<Versions versionToColor={versionToColor} cluster={cluster} />
204204
</Route>
205205
<Route
206206
render={() => (

src/containers/Cluster/VersionsBar/VersionsBar.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
display: flex;
33
flex-direction: column;
44

5-
width: 600px;
5+
min-width: 600px;
66

77
& .g-progress {
88
width: 100%;

src/containers/Cluster/VersionsBar/VersionsBar.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {ProgressProps} from '@gravity-ui/uikit';
12
import {Progress} from '@gravity-ui/uikit';
23

34
import type {VersionValue} from '../../../types/versions';
@@ -9,12 +10,18 @@ const b = cn('ydb-cluster-versions-bar');
910

1011
interface VersionsBarProps {
1112
versionsValues?: VersionValue[];
13+
size?: ProgressProps['size'];
14+
progressClassName?: string;
1215
}
1316

14-
export const VersionsBar = ({versionsValues = []}: VersionsBarProps) => {
17+
export const VersionsBar = ({
18+
versionsValues = [],
19+
size = 's',
20+
progressClassName: className,
21+
}: VersionsBarProps) => {
1522
return (
1623
<div className={b()}>
17-
<Progress value={100} stack={versionsValues} size="s" />
24+
<Progress value={100} stack={versionsValues} size={size} className={className} />
1825
<div className={b('versions')}>
1926
{versionsValues.map((item, index) => (
2027
<div

src/containers/Versions/Versions.scss

+26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
.ydb-versions {
44
$_: &;
55

6+
--ydb-info-viewer-font-size: var(--g-text-body-2-font-size);
7+
--ydb-info-viewer-line-height: var(--g-text-body-2-line-height);
8+
9+
font-size: var(--ydb-info-viewer-font-size);
10+
line-height: var(--ydb-info-viewer-line-height);
11+
612
&__controls {
713
display: flex;
814
align-items: center;
@@ -25,4 +31,24 @@
2531
margin-right: 25px;
2632
}
2733
}
34+
&__overall-wrapper {
35+
margin-top: 10px;
36+
margin-bottom: 10px;
37+
padding: 20px;
38+
39+
border: 1px solid var(--g-color-line-generic);
40+
border-radius: 10px;
41+
}
42+
&__overall-progress {
43+
height: 20px;
44+
45+
line-height: 20px;
46+
47+
border-radius: 5px;
48+
.g-progress__stack {
49+
height: 20px;
50+
51+
line-height: 20px;
52+
}
53+
}
2854
}

src/containers/Versions/Versions.tsx

+25-5
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,30 @@ import {Checkbox, RadioButton} from '@gravity-ui/uikit';
44

55
import {Loader} from '../../components/Loader';
66
import {nodesApi} from '../../store/reducers/nodes/nodes';
7+
import type {TClusterInfo} from '../../types/api/cluster';
78
import type {VersionToColorMap} from '../../types/versions';
89
import {cn} from '../../utils/cn';
910
import {useAutoRefreshInterval} from '../../utils/hooks';
11+
import {VersionsBar} from '../Cluster/VersionsBar/VersionsBar';
1012

1113
import {GroupedNodesTree} from './GroupedNodesTree/GroupedNodesTree';
1214
import {getGroupedStorageNodes, getGroupedTenantNodes, getOtherNodes} from './groupNodes';
15+
import i18n from './i18n';
1316
import {GroupByValue} from './types';
17+
import {useGetVersionValues} from './utils';
1418

1519
import './Versions.scss';
1620

1721
const b = cn('ydb-versions');
1822

1923
interface VersionsProps {
2024
versionToColor?: VersionToColorMap;
25+
cluster?: TClusterInfo;
2126
}
2227

23-
export const Versions = ({versionToColor}: VersionsProps) => {
28+
export const Versions = ({versionToColor, cluster}: VersionsProps) => {
2429
const [autoRefreshInterval] = useAutoRefreshInterval();
30+
const versionsValues = useGetVersionValues(cluster, versionToColor);
2531
const {currentData, isLoading: isNodesLoading} = nodesApi.useGetNodesQuery(
2632
{tablets: false},
2733
{pollingInterval: autoRefreshInterval},
@@ -74,7 +80,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
7480
const otherNodes = getOtherNodes(nodes, versionToColor);
7581
const storageNodesContent = storageNodes?.length ? (
7682
<React.Fragment>
77-
<h3>Storage nodes</h3>
83+
<h4>{i18n('title_storage')}</h4>
7884
{storageNodes.map(({title, nodes: itemNodes, items, versionColor}) => (
7985
<GroupedNodesTree
8086
key={`storage-nodes-${title}`}
@@ -88,7 +94,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
8894
) : null;
8995
const tenantNodesContent = tenantNodes?.length ? (
9096
<React.Fragment>
91-
<h3>Database nodes</h3>
97+
<h4>{i18n('title_database')}</h4>
9298
{renderControls()}
9399
{tenantNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => (
94100
<GroupedNodesTree
@@ -105,7 +111,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
105111
) : null;
106112
const otherNodesContent = otherNodes?.length ? (
107113
<React.Fragment>
108-
<h3>Other nodes</h3>
114+
<h4>{i18n('title_other')}</h4>
109115
{otherNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => (
110116
<GroupedNodesTree
111117
key={`other-nodes-${title}`}
@@ -119,8 +125,22 @@ export const Versions = ({versionToColor}: VersionsProps) => {
119125
</React.Fragment>
120126
) : null;
121127

128+
const overallContent = (
129+
<React.Fragment>
130+
<h4>{i18n('title_overall')}</h4>
131+
<div className={b('overall-wrapper')}>
132+
<VersionsBar
133+
progressClassName={b('overall-progress')}
134+
versionsValues={versionsValues.filter((el) => el.title !== 'unknown')}
135+
size="m"
136+
/>
137+
</div>
138+
</React.Fragment>
139+
);
140+
122141
return (
123-
<div className={b('versions')}>
142+
<div className={b()}>
143+
{overallContent}
124144
{storageNodesContent}
125145
{tenantNodesContent}
126146
{otherNodesContent}

src/containers/Versions/i18n/en.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"title_overall": "Overall",
3+
"title_storage": "Storage nodes",
4+
"title_database": "Database nodes",
5+
"title_other": "Other nodes"
6+
}

src/containers/Versions/i18n/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-versions';
6+
7+
export default registerKeysets(COMPONENT, {en});

src/containers/Versions/utils.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
3+
import {skipToken} from '@reduxjs/toolkit/query';
4+
5+
import {nodesApi} from '../../store/reducers/nodes/nodes';
6+
import {isClusterInfoV2} from '../../types/api/cluster';
7+
import type {TClusterInfo} from '../../types/api/cluster';
8+
import type {VersionToColorMap} from '../../types/versions';
9+
import {parseNodeGroupsToVersionsValues, parseNodesToVersionsValues} from '../../utils/versions';
10+
11+
export const useGetVersionValues = (cluster?: TClusterInfo, versionToColor?: VersionToColorMap) => {
12+
const {currentData} = nodesApi.useGetNodesQuery(
13+
isClusterInfoV2(cluster)
14+
? skipToken
15+
: {
16+
tablets: false,
17+
group: 'Version',
18+
},
19+
);
20+
21+
const versionsValues = React.useMemo(() => {
22+
if (isClusterInfoV2(cluster) && cluster.MapVersions) {
23+
const groups = Object.entries(cluster.MapVersions).map(([version, count]) => ({
24+
name: version,
25+
count,
26+
}));
27+
return parseNodeGroupsToVersionsValues(groups, versionToColor, cluster.NodesTotal);
28+
}
29+
if (!currentData) {
30+
return [];
31+
}
32+
if (Array.isArray(currentData.NodeGroups)) {
33+
return parseNodeGroupsToVersionsValues(
34+
currentData.NodeGroups,
35+
versionToColor,
36+
cluster?.NodesTotal,
37+
);
38+
}
39+
return parseNodesToVersionsValues(currentData.Nodes, versionToColor);
40+
}, [currentData, versionToColor, cluster]);
41+
42+
return versionsValues;
43+
};

src/utils/clusterVersionColors.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import uniqBy from 'lodash/uniqBy';
33
import type {MetaClusterVersion} from '../types/api/meta';
44
import type {VersionToColorMap} from '../types/versions';
55

6-
import {COLORS, GREY_COLOR, getMinorVersion, hashCode} from './versions';
6+
import {COLORS, DEFAULT_COLOR, getMinorVersion, hashCode} from './versions';
77

88
const UNDEFINED_COLOR_INDEX = '__no_color__';
99

@@ -35,7 +35,7 @@ export const getVersionColors = (versionMap: VersionsMap) => {
3535
.sort((a, b) => hashCode(b) - hashCode(a))
3636
.forEach((minor, minorIndex) => {
3737
if (baseColorIndex === UNDEFINED_COLOR_INDEX) {
38-
versionToColor.set(minor, GREY_COLOR);
38+
versionToColor.set(minor, DEFAULT_COLOR);
3939
} else {
4040
// baseColorIndex is numeric as we check if it is UNDEFINED_COLOR_INDEX before
4141
const currentColorIndex = Number(baseColorIndex) % COLORS.length;

src/utils/versions/getVersionsColors.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export const hashCode = (s: string) => {
1212
// TODO: colors used in charts as well, need to move to constants
1313
// 11 distinct colors from https://mokole.com/palette.html
1414
export const COLORS = [
15-
'#008000', // green
1615
'#4169e1', // royalblue
1716
'#ffd700', // gold
1817
'#ff8c00', // darkorange
@@ -25,7 +24,7 @@ export const COLORS = [
2524
'#b22222', // firebrick
2625
];
2726

28-
export const GREY_COLOR = '#bfbfbf';
27+
export const DEFAULT_COLOR = '#008000'; // green
2928

3029
export const getVersionsMap = (versions: string[], initialMap: VersionsMap = new Map()) => {
3130
versions.forEach((version) => {
@@ -88,7 +87,7 @@ export const getVersionToColorMap = (versionsMap: VersionsMap) => {
8887
versionToColor.set(minor.version, versionColor);
8988
});
9089
} else {
91-
versionToColor.set(item.version, GREY_COLOR);
90+
versionToColor.set(item.version, DEFAULT_COLOR);
9291
}
9392
});
9493
return versionToColor;

src/utils/versions/parseNodesToVersionsValues.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type {VersionToColorMap, VersionValue} from '../../types/versions';
44

55
import {getMinorVersion} from './parseVersion';
66

7+
const MIN_VALUE = 0.5;
8+
79
export const parseNodesToVersionsValues = (
810
nodes: TSystemStateInfo[] = [],
911
versionsToColor?: VersionToColorMap,
@@ -18,15 +20,16 @@ export const parseNodesToVersionsValues = (
1820
}
1921
return acc;
2022
}, {});
21-
22-
return Object.keys(versionsCount).map((version) => {
23+
const result = Object.keys(versionsCount).map((version) => {
24+
const value = (versionsCount[version] / nodes.length) * 100;
2325
return {
2426
title: version,
2527
version: version,
2628
color: versionsToColor?.get(getMinorVersion(version)),
27-
value: (versionsCount[version] / nodes.length) * 100,
29+
value: value < MIN_VALUE ? MIN_VALUE : value,
2830
};
2931
});
32+
return normalizeResult(result);
3033
};
3134

3235
export function parseNodeGroupsToVersionsValues(
@@ -35,12 +38,32 @@ export function parseNodeGroupsToVersionsValues(
3538
total?: number,
3639
) {
3740
const normalizedTotal = total ?? groups.reduce((acc, group) => acc + group.count, 0);
38-
return groups.map((group) => {
41+
const result = groups.map((group) => {
42+
const value = (group.count / normalizedTotal) * 100;
3943
return {
4044
title: group.name,
4145
version: group.name,
4246
color: versionsToColor?.get(group.name),
43-
value: (group.count / normalizedTotal) * 100,
47+
value: value < MIN_VALUE ? MIN_VALUE : value,
4448
};
4549
});
50+
const normalized = normalizeResult(result);
51+
return normalized;
52+
}
53+
54+
function normalizeResult(data: VersionValue[]) {
55+
let maximum = data[0].value;
56+
let maximumIndex = 0;
57+
let total = 0;
58+
data.forEach((item, index) => {
59+
total += item.value;
60+
if (item.value > maximum) {
61+
maximum = item.value;
62+
maximumIndex = index;
63+
}
64+
});
65+
const result = [...data];
66+
//Progress breakes if sum of values more than 100, so we need to subtrackt difference appeared because of MIN_VALUE from the biggest value in set
67+
result[maximumIndex] = {...data[maximumIndex], value: maximum + 100 - total};
68+
return result;
4669
}

0 commit comments

Comments
 (0)