Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/containers/Cluster/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you reuse the same hook from /containers/Cluster/ClusterInfo/utils.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cause very soon it will be removed from /containers/Cluster/ClusterInfo/utils.ts. Currently in #1473 .

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed with @adameat and decided to set green color as defaut.


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
32 changes: 27 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why MIN_VALUE is 0.5 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was kinda a shot in the dark. It looks nice - not wide, but sufficient to hove a mouse.

};
});
return normalizeResult(result);
};

export function parseNodeGroupsToVersionsValues(
Expand All @@ -35,12 +38,31 @@ 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];
result[maximumIndex] = {...data[maximumIndex], value: maximum + 100 - total};
Copy link
Member

@artemmufazalov artemmufazalov Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need such normalization? If my most common version is 24-3 and I have 1000 nodes out of 1300 with such version, its value will be -200 (converted to 0.5)? So most common version will have least space on the bar?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in pm, I've added a comment, for what this normalization is needed.

return result;
}
Loading