Skip to content

Commit

Permalink
feat(Versions): show overall version info in Versions tab (#1442)
Browse files Browse the repository at this point in the history
  • Loading branch information
Raubzeug authored Oct 17, 2024
1 parent 9daa5a3 commit 6cc07d5
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/containers/Cluster/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export function Cluster({
getLocationObjectFromHref(getClusterPath(clusterTabsIds.versions)).pathname
}
>
<Versions versionToColor={versionToColor} />
<Versions versionToColor={versionToColor} cluster={cluster} />
</Route>
<Route
render={() => (
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Cluster/VersionsBar/VersionsBar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
display: flex;
flex-direction: column;

width: 600px;
min-width: 600px;

& .g-progress {
width: 100%;
Expand Down
11 changes: 9 additions & 2 deletions src/containers/Cluster/VersionsBar/VersionsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {ProgressProps} from '@gravity-ui/uikit';
import {Progress} from '@gravity-ui/uikit';

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

interface VersionsBarProps {
versionsValues?: VersionValue[];
size?: ProgressProps['size'];
progressClassName?: string;
}

export const VersionsBar = ({versionsValues = []}: VersionsBarProps) => {
export const VersionsBar = ({
versionsValues = [],
size = 's',
progressClassName: className,
}: VersionsBarProps) => {
return (
<div className={b()}>
<Progress value={100} stack={versionsValues} size="s" />
<Progress value={100} stack={versionsValues} size={size} className={className} />
<div className={b('versions')}>
{versionsValues.map((item, index) => (
<div
Expand Down
26 changes: 26 additions & 0 deletions src/containers/Versions/Versions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
.ydb-versions {
$_: &;

--ydb-info-viewer-font-size: var(--g-text-body-2-font-size);
--ydb-info-viewer-line-height: var(--g-text-body-2-line-height);

font-size: var(--ydb-info-viewer-font-size);
line-height: var(--ydb-info-viewer-line-height);

&__controls {
display: flex;
align-items: center;
Expand All @@ -25,4 +31,24 @@
margin-right: 25px;
}
}
&__overall-wrapper {
margin-top: 10px;
margin-bottom: 10px;
padding: 20px;

border: 1px solid var(--g-color-line-generic);
border-radius: 10px;
}
&__overall-progress {
height: 20px;

line-height: 20px;

border-radius: 5px;
.g-progress__stack {
height: 20px;

line-height: 20px;
}
}
}
30 changes: 25 additions & 5 deletions src/containers/Versions/Versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,30 @@ import {Checkbox, RadioButton} from '@gravity-ui/uikit';

import {Loader} from '../../components/Loader';
import {nodesApi} from '../../store/reducers/nodes/nodes';
import type {TClusterInfo} from '../../types/api/cluster';
import type {VersionToColorMap} from '../../types/versions';
import {cn} from '../../utils/cn';
import {useAutoRefreshInterval} from '../../utils/hooks';
import {VersionsBar} from '../Cluster/VersionsBar/VersionsBar';

import {GroupedNodesTree} from './GroupedNodesTree/GroupedNodesTree';
import {getGroupedStorageNodes, getGroupedTenantNodes, getOtherNodes} from './groupNodes';
import i18n from './i18n';
import {GroupByValue} from './types';
import {useGetVersionValues} from './utils';

import './Versions.scss';

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

interface VersionsProps {
versionToColor?: VersionToColorMap;
cluster?: TClusterInfo;
}

export const Versions = ({versionToColor}: VersionsProps) => {
export const Versions = ({versionToColor, cluster}: VersionsProps) => {
const [autoRefreshInterval] = useAutoRefreshInterval();
const versionsValues = useGetVersionValues(cluster, versionToColor);
const {currentData, isLoading: isNodesLoading} = nodesApi.useGetNodesQuery(
{tablets: false},
{pollingInterval: autoRefreshInterval},
Expand Down Expand Up @@ -74,7 +80,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
const otherNodes = getOtherNodes(nodes, versionToColor);
const storageNodesContent = storageNodes?.length ? (
<React.Fragment>
<h3>Storage nodes</h3>
<h4>{i18n('title_storage')}</h4>
{storageNodes.map(({title, nodes: itemNodes, items, versionColor}) => (
<GroupedNodesTree
key={`storage-nodes-${title}`}
Expand All @@ -88,7 +94,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
) : null;
const tenantNodesContent = tenantNodes?.length ? (
<React.Fragment>
<h3>Database nodes</h3>
<h4>{i18n('title_database')}</h4>
{renderControls()}
{tenantNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => (
<GroupedNodesTree
Expand All @@ -105,7 +111,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
) : null;
const otherNodesContent = otherNodes?.length ? (
<React.Fragment>
<h3>Other nodes</h3>
<h4>{i18n('title_other')}</h4>
{otherNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => (
<GroupedNodesTree
key={`other-nodes-${title}`}
Expand All @@ -119,8 +125,22 @@ export const Versions = ({versionToColor}: VersionsProps) => {
</React.Fragment>
) : null;

const overallContent = (
<React.Fragment>
<h4>{i18n('title_overall')}</h4>
<div className={b('overall-wrapper')}>
<VersionsBar
progressClassName={b('overall-progress')}
versionsValues={versionsValues.filter((el) => el.title !== 'unknown')}
size="m"
/>
</div>
</React.Fragment>
);

return (
<div className={b('versions')}>
<div className={b()}>
{overallContent}
{storageNodesContent}
{tenantNodesContent}
{otherNodesContent}
Expand Down
6 changes: 6 additions & 0 deletions src/containers/Versions/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title_overall": "Overall",
"title_storage": "Storage nodes",
"title_database": "Database nodes",
"title_other": "Other nodes"
}
7 changes: 7 additions & 0 deletions src/containers/Versions/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeysets} from '../../../utils/i18n';

import en from './en.json';

const COMPONENT = 'ydb-versions';

export default registerKeysets(COMPONENT, {en});
43 changes: 43 additions & 0 deletions src/containers/Versions/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';

import {skipToken} from '@reduxjs/toolkit/query';

import {nodesApi} from '../../store/reducers/nodes/nodes';
import {isClusterInfoV2} from '../../types/api/cluster';
import type {TClusterInfo} from '../../types/api/cluster';
import type {VersionToColorMap} from '../../types/versions';
import {parseNodeGroupsToVersionsValues, parseNodesToVersionsValues} from '../../utils/versions';

export const useGetVersionValues = (cluster?: TClusterInfo, versionToColor?: VersionToColorMap) => {
const {currentData} = nodesApi.useGetNodesQuery(
isClusterInfoV2(cluster)
? skipToken
: {
tablets: false,
group: 'Version',
},
);

const versionsValues = React.useMemo(() => {
if (isClusterInfoV2(cluster) && cluster.MapVersions) {
const groups = Object.entries(cluster.MapVersions).map(([version, count]) => ({
name: version,
count,
}));
return parseNodeGroupsToVersionsValues(groups, versionToColor, cluster.NodesTotal);
}
if (!currentData) {
return [];
}
if (Array.isArray(currentData.NodeGroups)) {
return parseNodeGroupsToVersionsValues(
currentData.NodeGroups,
versionToColor,
cluster?.NodesTotal,
);
}
return parseNodesToVersionsValues(currentData.Nodes, versionToColor);
}, [currentData, versionToColor, cluster]);

return versionsValues;
};
4 changes: 2 additions & 2 deletions src/utils/clusterVersionColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import uniqBy from 'lodash/uniqBy';
import type {MetaClusterVersion} from '../types/api/meta';
import type {VersionToColorMap} from '../types/versions';

import {COLORS, GREY_COLOR, getMinorVersion, hashCode} from './versions';
import {COLORS, DEFAULT_COLOR, getMinorVersion, hashCode} from './versions';

const UNDEFINED_COLOR_INDEX = '__no_color__';

Expand Down Expand Up @@ -35,7 +35,7 @@ export const getVersionColors = (versionMap: VersionsMap) => {
.sort((a, b) => hashCode(b) - hashCode(a))
.forEach((minor, minorIndex) => {
if (baseColorIndex === UNDEFINED_COLOR_INDEX) {
versionToColor.set(minor, GREY_COLOR);
versionToColor.set(minor, DEFAULT_COLOR);
} else {
// baseColorIndex is numeric as we check if it is UNDEFINED_COLOR_INDEX before
const currentColorIndex = Number(baseColorIndex) % COLORS.length;
Expand Down
5 changes: 2 additions & 3 deletions src/utils/versions/getVersionsColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const hashCode = (s: string) => {
// TODO: colors used in charts as well, need to move to constants
// 11 distinct colors from https://mokole.com/palette.html
export const COLORS = [
'#008000', // green
'#4169e1', // royalblue
'#ffd700', // gold
'#ff8c00', // darkorange
Expand All @@ -25,7 +24,7 @@ export const COLORS = [
'#b22222', // firebrick
];

export const GREY_COLOR = '#bfbfbf';
export const DEFAULT_COLOR = '#008000'; // green

export const getVersionsMap = (versions: string[], initialMap: VersionsMap = new Map()) => {
versions.forEach((version) => {
Expand Down Expand Up @@ -88,7 +87,7 @@ export const getVersionToColorMap = (versionsMap: VersionsMap) => {
versionToColor.set(minor.version, versionColor);
});
} else {
versionToColor.set(item.version, GREY_COLOR);
versionToColor.set(item.version, DEFAULT_COLOR);
}
});
return versionToColor;
Expand Down
33 changes: 28 additions & 5 deletions src/utils/versions/parseNodesToVersionsValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {VersionToColorMap, VersionValue} from '../../types/versions';

import {getMinorVersion} from './parseVersion';

const MIN_VALUE = 0.5;

export const parseNodesToVersionsValues = (
nodes: TSystemStateInfo[] = [],
versionsToColor?: VersionToColorMap,
Expand All @@ -18,15 +20,16 @@ export const parseNodesToVersionsValues = (
}
return acc;
}, {});

return Object.keys(versionsCount).map((version) => {
const result = Object.keys(versionsCount).map((version) => {
const value = (versionsCount[version] / nodes.length) * 100;
return {
title: version,
version: version,
color: versionsToColor?.get(getMinorVersion(version)),
value: (versionsCount[version] / nodes.length) * 100,
value: value < MIN_VALUE ? MIN_VALUE : value,
};
});
return normalizeResult(result);
};

export function parseNodeGroupsToVersionsValues(
Expand All @@ -35,12 +38,32 @@ export function parseNodeGroupsToVersionsValues(
total?: number,
) {
const normalizedTotal = total ?? groups.reduce((acc, group) => acc + group.count, 0);
return groups.map((group) => {
const result = groups.map((group) => {
const value = (group.count / normalizedTotal) * 100;
return {
title: group.name,
version: group.name,
color: versionsToColor?.get(group.name),
value: (group.count / normalizedTotal) * 100,
value: value < MIN_VALUE ? MIN_VALUE : value,
};
});
const normalized = normalizeResult(result);
return normalized;
}

function normalizeResult(data: VersionValue[]) {
let maximum = data[0].value;
let maximumIndex = 0;
let total = 0;
data.forEach((item, index) => {
total += item.value;
if (item.value > maximum) {
maximum = item.value;
maximumIndex = index;
}
});
const result = [...data];
//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
result[maximumIndex] = {...data[maximumIndex], value: maximum + 100 - total};
return result;
}

0 comments on commit 6cc07d5

Please sign in to comment.