diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index d9f559c4cc..f6c48d67db 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -180,7 +180,7 @@ export function Cluster({ getLocationObjectFromHref(getClusterPath(clusterTabsIds.versions)).pathname } > - + ( diff --git a/src/containers/Cluster/VersionsBar/VersionsBar.scss b/src/containers/Cluster/VersionsBar/VersionsBar.scss index df9a9a0c08..7631afd47f 100644 --- a/src/containers/Cluster/VersionsBar/VersionsBar.scss +++ b/src/containers/Cluster/VersionsBar/VersionsBar.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: column; - width: 600px; + min-width: 600px; & .g-progress { width: 100%; diff --git a/src/containers/Cluster/VersionsBar/VersionsBar.tsx b/src/containers/Cluster/VersionsBar/VersionsBar.tsx index 1458a3d689..e04c649705 100644 --- a/src/containers/Cluster/VersionsBar/VersionsBar.tsx +++ b/src/containers/Cluster/VersionsBar/VersionsBar.tsx @@ -1,3 +1,4 @@ +import type {ProgressProps} from '@gravity-ui/uikit'; import {Progress} from '@gravity-ui/uikit'; import type {VersionValue} from '../../../types/versions'; @@ -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 (
- +
{versionsValues.map((item, index) => (
{ +export const Versions = ({versionToColor, cluster}: VersionsProps) => { const [autoRefreshInterval] = useAutoRefreshInterval(); + const versionsValues = useGetVersionValues(cluster, versionToColor); const {currentData, isLoading: isNodesLoading} = nodesApi.useGetNodesQuery( {tablets: false}, {pollingInterval: autoRefreshInterval}, @@ -74,7 +80,7 @@ export const Versions = ({versionToColor}: VersionsProps) => { const otherNodes = getOtherNodes(nodes, versionToColor); const storageNodesContent = storageNodes?.length ? ( -

Storage nodes

+

{i18n('title_storage')}

{storageNodes.map(({title, nodes: itemNodes, items, versionColor}) => ( { ) : null; const tenantNodesContent = tenantNodes?.length ? ( -

Database nodes

+

{i18n('title_database')}

{renderControls()} {tenantNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => ( { ) : null; const otherNodesContent = otherNodes?.length ? ( -

Other nodes

+

{i18n('title_other')}

{otherNodes.map(({title, nodes: itemNodes, items, versionColor, versionsValues}) => ( {
) : null; + const overallContent = ( + +

{i18n('title_overall')}

+
+ el.title !== 'unknown')} + size="m" + /> +
+
+ ); + return ( -
+
+ {overallContent} {storageNodesContent} {tenantNodesContent} {otherNodesContent} diff --git a/src/containers/Versions/i18n/en.json b/src/containers/Versions/i18n/en.json new file mode 100644 index 0000000000..9d0c096a85 --- /dev/null +++ b/src/containers/Versions/i18n/en.json @@ -0,0 +1,6 @@ +{ + "title_overall": "Overall", + "title_storage": "Storage nodes", + "title_database": "Database nodes", + "title_other": "Other nodes" +} diff --git a/src/containers/Versions/i18n/index.ts b/src/containers/Versions/i18n/index.ts new file mode 100644 index 0000000000..493da67aca --- /dev/null +++ b/src/containers/Versions/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-versions'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Versions/utils.ts b/src/containers/Versions/utils.ts new file mode 100644 index 0000000000..cac2feab99 --- /dev/null +++ b/src/containers/Versions/utils.ts @@ -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; +}; diff --git a/src/utils/clusterVersionColors.ts b/src/utils/clusterVersionColors.ts index c45a1e246d..a97c173798 100644 --- a/src/utils/clusterVersionColors.ts +++ b/src/utils/clusterVersionColors.ts @@ -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__'; @@ -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; diff --git a/src/utils/versions/getVersionsColors.ts b/src/utils/versions/getVersionsColors.ts index ff19d1d652..0835d1aa1d 100644 --- a/src/utils/versions/getVersionsColors.ts +++ b/src/utils/versions/getVersionsColors.ts @@ -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 @@ -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) => { @@ -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; diff --git a/src/utils/versions/parseNodesToVersionsValues.ts b/src/utils/versions/parseNodesToVersionsValues.ts index e33eb208d7..37f43f8b71 100644 --- a/src/utils/versions/parseNodesToVersionsValues.ts +++ b/src/utils/versions/parseNodesToVersionsValues.ts @@ -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, @@ -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( @@ -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; }