Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 36 additions & 0 deletions src/components/VersionsBar/VersionsBar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@use '../../styles/mixins.scss';

.ydb-versions-bar {
&__bar {
width: 100%;
height: 10px;
}

&__titles-wrapper {
width: max-content;
}

&__title {
overflow: hidden;

text-overflow: ellipsis;

color: var(--g-color-text-primary);

@include mixins.body-1-typography();
}

&__version {
min-width: 10px;

border-radius: var(--g-border-radius-xs);
}

&__title,
&__version,
&__version-icon {
&_dimmed {
opacity: 0.5;
}
}
}
170 changes: 170 additions & 0 deletions src/components/VersionsBar/VersionsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';

import {Button, Flex, Tooltip} from '@gravity-ui/uikit';
import {debounce} from 'lodash';

import {cn} from '../../utils/cn';
import type {PreparedVersion} from '../../utils/versions/types';

import i18n from './i18n';

import './VersionsBar.scss';

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

const TRUNCATION_THRESHOLD = 4;
// One more line for Show more / Hide button
const MAX_DISPLAYED_VERSIONS = TRUNCATION_THRESHOLD - 1;

const HOVER_DELAY = 200;
const TOOLTIP_OPEN_DELAY = 200;

interface VersionsBarProps {
preparedVersions: PreparedVersion[];
}

export function VersionsBar({preparedVersions}: VersionsBarProps) {
const shouldTruncateVersions = preparedVersions.length > TRUNCATION_THRESHOLD;

const [hoveredVersion, setHoveredVersion] = React.useState<string | undefined>();
const [allVersionsDisplayed, setAllVersionsDisplayed] = React.useState<boolean>(false);

const displayedVersions = React.useMemo(() => {
const total = preparedVersions.reduce((acc, item) => acc + (item.count || 0), 0);

return preparedVersions.map((item) => {
return {
value: ((item.count || 0) / total) * 100,
color: item.color,
version: item.version,
count: item.count,
};
});
}, [preparedVersions]);

const truncatedDisplayedVersions = React.useMemo(() => {
if (allVersionsDisplayed) {
return preparedVersions;
}

return shouldTruncateVersions
? preparedVersions.slice(0, MAX_DISPLAYED_VERSIONS)
: preparedVersions;
}, [allVersionsDisplayed, preparedVersions, shouldTruncateVersions]);

const handleShowAllVersions = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
event.stopPropagation();
setAllVersionsDisplayed(true);
};
const handleHideAllVersions = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
event.stopPropagation();
setAllVersionsDisplayed(false);
};

const renderButton = () => {
if (!shouldTruncateVersions) {
return null;
}

const truncatedVersionsCount = preparedVersions.length - MAX_DISPLAYED_VERSIONS;

if (allVersionsDisplayed) {
return (
<Button view="flat-secondary" size={'s'} onClick={handleHideAllVersions}>
{i18n('action_hide', {
count: truncatedVersionsCount,
})}
</Button>
);
} else {
return (
<Button view="flat-secondary" size={'s'} onClick={handleShowAllVersions}>
{i18n('action_show_more', {
count: truncatedVersionsCount,
})}
</Button>
);
}
};

const handleMouseEnter = React.useMemo(() => {
return debounce((version: string) => {
setHoveredVersion(version);
}, HOVER_DELAY);
}, []);

const handleMouseLeave = () => {
handleMouseEnter.cancel();
setHoveredVersion(undefined);
};

const isDimmed = (version: string) => {
return hoveredVersion && hoveredVersion !== version;
};

return (
<Flex gap={2} direction={'column'} className={b(null)} wrap>
<Flex className={b('bar')} grow={1} gap={0.5}>
{displayedVersions.map((item) => (
<Tooltip
key={item.version}
content={
<React.Fragment>
{i18n('tooltip_nodes', {count: item.count})}
<br />
{item.version}
</React.Fragment>
}
placement={'top-start'}
openDelay={TOOLTIP_OPEN_DELAY}
>
<span
onMouseEnter={() => {
handleMouseEnter(item.version);
}}
onMouseLeave={handleMouseLeave}
className={b('version', {dimmed: isDimmed(item.version)})}
style={{backgroundColor: item.color, width: `${item.value}%`}}
/>
</Tooltip>
))}
</Flex>

<Flex gap={0.5} direction={'column'}>
{truncatedDisplayedVersions.map((item) => (
<Tooltip
key={item.version}
content={i18n('tooltip_nodes', {count: item.count})}
placement={'bottom-end'}
openDelay={TOOLTIP_OPEN_DELAY}
>
<Flex gap={1} alignItems={'center'} className={b('titles-wrapper')}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="6"
height="6"
viewBox="0 0 6 6"
fill="none"
className={b('version-icon', {dimmed: isDimmed(item.version)})}
>
<circle cx="3" cy="3" r="3" fill={item.color} />
</svg>
<div
className={b('title', {dimmed: isDimmed(item.version)})}
onMouseEnter={() => {
handleMouseEnter(item.version);
}}
onMouseLeave={handleMouseLeave}
>
{item.version}
</div>
</Flex>
</Tooltip>
))}
<Flex>{renderButton()}</Flex>
</Flex>
</Flex>
);
}
8 changes: 8 additions & 0 deletions src/components/VersionsBar/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"action_show_more": "Show {{count}} more",
"action_hide": "Hide {{count}}",
"tooltip_nodes": {
"one": "{{count}} Node",
"other": "{{count}} Nodes"
}
}
7 changes: 7 additions & 0 deletions src/components/VersionsBar/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-bar';

export default registerKeysets(COMPONENT, {en});
2 changes: 1 addition & 1 deletion src/containers/Cluster/VersionsBar/VersionsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {ProgressProps} from '@gravity-ui/uikit';
import {Progress} from '@gravity-ui/uikit';

import type {VersionValue} from '../../../types/versions';
import {cn} from '../../../utils/cn';
import type {VersionValue} from '../../../utils/versions/types';

import './VersionsBar.scss';

Expand Down
4 changes: 0 additions & 4 deletions src/containers/Clusters/Clusters.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@
&__cluster-versions {
text-decoration: none;
}
&__cluster-version {
overflow: hidden;

text-overflow: ellipsis;
}
&__cluster-dc {
white-space: normal;
}
Expand Down
29 changes: 3 additions & 26 deletions src/containers/Clusters/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

import {Pencil, TrashBin} from '@gravity-ui/icons';
import DataTable from '@gravity-ui/react-data-table';
import type {Column} from '@gravity-ui/react-data-table';
Expand All @@ -15,6 +13,7 @@ import {
} from '@gravity-ui/uikit';

import {EntityStatus} from '../../components/EntityStatusNew/EntityStatus';
import {VersionsBar} from '../../components/VersionsBar/VersionsBar';
import type {PreparedCluster} from '../../store/reducers/clusters/types';
import {EFlag} from '../../types/api/enums';
import {uiFactory} from '../../uiFactory/uiFactory';
Expand Down Expand Up @@ -158,7 +157,7 @@ const CLUSTERS_COLUMNS: Column<PreparedCluster>[] = [
{
name: COLUMNS_NAMES.VERSIONS,
header: COLUMNS_TITLES[COLUMNS_NAMES.VERSIONS],
width: 300,
width: 400,
defaultOrder: DataTable.DESCENDING,
sortAccessor: ({preparedVersions}) => {
const versions = preparedVersions
Expand All @@ -181,16 +180,6 @@ const CLUSTERS_COLUMNS: Column<PreparedCluster>[] = [
return EMPTY_CELL;
}

const total = versions.reduce((acc, item) => acc + item.count, 0);
const versionsValues = versions.map((item) => {
return {
value: (item.count / total) * 100,
color: preparedVersions.find(
(versionItem) => versionItem.version === item.version,
)?.color,
};
});

return (
preparedVersions.length > 0 && (
<ExternalLink
Expand All @@ -201,19 +190,7 @@ const CLUSTERS_COLUMNS: Column<PreparedCluster>[] = [
{withBasename: true},
)}
>
<React.Fragment>
{preparedVersions.map((item, index) => (
<div
className={b('cluster-version')}
style={{color: item.color}}
key={index}
title={item.version}
>
{item.version}
</div>
))}
{<Progress size="s" value={100} stack={versionsValues} />}
</React.Fragment>
<VersionsBar preparedVersions={preparedVersions} />
</ExternalLink>
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import React from 'react';
import {TreeView} from 'ydb-ui-components';

import type {NodesPreparedEntity} from '../../../store/reducers/nodes/types';
import type {VersionValue} from '../../../types/versions';
import {cn} from '../../../utils/cn';
import type {VersionValue} from '../../../utils/versions/types';
import {NodesTable} from '../NodesTable/NodesTable';
import {NodesTreeTitle} from '../NodesTreeTitle/NodesTreeTitle';
import type {GroupedNodesItem} from '../types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {ClipboardButton, Progress} from '@gravity-ui/uikit';

import type {VersionValue} from '../../../types/versions';
import {cn} from '../../../utils/cn';
import type {PreparedNodeSystemState} from '../../../utils/nodes';
import type {VersionValue} from '../../../utils/versions/types';
import type {GroupedNodesItem} from '../types';

import './NodesTreeTitle.scss';
Expand Down
20 changes: 10 additions & 10 deletions src/containers/Versions/Versions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import {LoaderWrapper} from '../../components/LoaderWrapper/LoaderWrapper';
import {nodesApi} from '../../store/reducers/nodes/nodes';
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
import type {TClusterInfo} from '../../types/api/cluster';
import type {VersionToColorMap, VersionValue} from '../../types/versions';
import {cn} from '../../utils/cn';
import {useAutoRefreshInterval} from '../../utils/hooks';
import type {VersionValue, VersionsDataMap} from '../../utils/versions/types';
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, useVersionToColorMap} from './utils';
import {useGetVersionValues, useVersionsDataMap} from './utils';

import './Versions.scss';

Expand All @@ -32,16 +32,16 @@ export function VersionsContainer({cluster, loading}: VersionsContainerProps) {
{tablets: false, fieldsRequired: ['SystemState', 'SubDomainKey']},
{pollingInterval: autoRefreshInterval},
);
const versionToColor = useVersionToColorMap(cluster);
const versionsDataMap = useVersionsDataMap(cluster);

const versionsValues = useGetVersionValues({cluster, versionToColor, clusterLoading: loading});
const versionsValues = useGetVersionValues({cluster, versionsDataMap, clusterLoading: loading});

return (
<LoaderWrapper loading={loading || isNodesLoading}>
<Versions
versionsValues={versionsValues}
nodes={currentData?.Nodes}
versionToColor={versionToColor}
versionsDataMap={versionsDataMap}
/>
</LoaderWrapper>
);
Expand All @@ -50,10 +50,10 @@ export function VersionsContainer({cluster, loading}: VersionsContainerProps) {
interface VersionsProps {
nodes?: NodesPreparedEntity[];
versionsValues: VersionValue[];
versionToColor?: VersionToColorMap;
versionsDataMap?: VersionsDataMap;
}

function Versions({versionsValues, nodes, versionToColor}: VersionsProps) {
function Versions({versionsValues, nodes, versionsDataMap}: VersionsProps) {
const [groupByValue, setGroupByValue] = React.useState<GroupByValue>(GroupByValue.VERSION);
const [expanded, setExpanded] = React.useState(false);

Expand Down Expand Up @@ -91,9 +91,9 @@ function Versions({versionsValues, nodes, versionToColor}: VersionsProps) {
);
};

const tenantNodes = getGroupedTenantNodes(nodes, versionToColor, groupByValue);
const storageNodes = getGroupedStorageNodes(nodes, versionToColor);
const otherNodes = getOtherNodes(nodes, versionToColor);
const tenantNodes = getGroupedTenantNodes(nodes, versionsDataMap, groupByValue);
const storageNodes = getGroupedStorageNodes(nodes, versionsDataMap);
const otherNodes = getOtherNodes(nodes, versionsDataMap);
const storageNodesContent = storageNodes?.length ? (
<React.Fragment>
<h4>{i18n('title_storage')}</h4>
Expand Down
Loading
Loading