Skip to content

Commit dec5b95

Browse files
feat(Storage): group disks by DC (#1823)
1 parent e3c2243 commit dec5b95

File tree

17 files changed

+157
-75
lines changed

17 files changed

+157
-75
lines changed

src/components/PDiskPopup/PDiskPopup.tsx

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
4-
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
4+
import {selectNodesMap} from '../../store/reducers/nodesList';
55
import {EFlag} from '../../types/api/enums';
66
import {valueIsDefined} from '../../utils';
77
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
@@ -17,7 +17,7 @@ const errorColors = [EFlag.Orange, EFlag.Red, EFlag.Yellow];
1717

1818
export const preparePDiskData = (
1919
data: PreparedPDisk,
20-
nodeHost?: string,
20+
nodeData?: {Host?: string; DC?: string},
2121
withDeveloperUILink?: boolean,
2222
) => {
2323
const {
@@ -46,8 +46,11 @@ export const preparePDiskData = (
4646
pdiskData.push({label: 'Node Id', value: NodeId});
4747
}
4848

49-
if (nodeHost) {
50-
pdiskData.push({label: 'Host', value: nodeHost});
49+
if (nodeData?.Host) {
50+
pdiskData.push({label: 'Host', value: nodeData.Host});
51+
}
52+
if (nodeData?.DC) {
53+
pdiskData.push({label: 'DC', value: nodeData.DC});
5154
}
5255

5356
if (Path) {
@@ -90,11 +93,11 @@ interface PDiskPopupProps {
9093

9194
export const PDiskPopup = ({data}: PDiskPopupProps) => {
9295
const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges);
93-
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
94-
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
96+
const nodesMap = useTypedSelector(selectNodesMap);
97+
const nodeData = valueIsDefined(data.NodeId) ? nodesMap?.get(data.NodeId) : undefined;
9598
const info = React.useMemo(
96-
() => preparePDiskData(data, nodeHost, isUserAllowedToMakeChanges),
97-
[data, nodeHost, isUserAllowedToMakeChanges],
99+
() => preparePDiskData(data, nodeData, isUserAllowedToMakeChanges),
100+
[data, nodeData, isUserAllowedToMakeChanges],
98101
);
99102

100103
return <InfoViewer title="PDisk" info={info} size="s" />;

src/components/VDiskPopup/VDiskPopup.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import {Label} from '@gravity-ui/uikit';
44

55
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
6-
import {selectNodeHostsMap} from '../../store/reducers/nodesList';
6+
import {selectNodesMap} from '../../store/reducers/nodesList';
77
import {EFlag} from '../../types/api/enums';
88
import {valueIsDefined} from '../../utils';
99
import {cn} from '../../utils/cn';
@@ -188,14 +188,14 @@ export const VDiskPopup = ({data}: VDiskPopupProps) => {
188188
[data, isFullData, isUserAllowedToMakeChanges],
189189
);
190190

191-
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
192-
const nodeHost = valueIsDefined(data.NodeId) ? nodeHostsMap?.get(data.NodeId) : undefined;
191+
const nodesMap = useTypedSelector(selectNodesMap);
192+
const nodeData = valueIsDefined(data.NodeId) ? nodesMap?.get(data.NodeId) : undefined;
193193
const pdiskInfo = React.useMemo(
194194
() =>
195195
isFullData &&
196196
data.PDisk &&
197-
preparePDiskData(data.PDisk, nodeHost, isUserAllowedToMakeChanges),
198-
[data, nodeHost, isFullData, isUserAllowedToMakeChanges],
197+
preparePDiskData(data.PDisk, nodeData, isUserAllowedToMakeChanges),
198+
[data, nodeData, isFullData, isUserAllowedToMakeChanges],
199199
);
200200

201201
const donorsInfo: InfoViewerItem[] = [];

src/containers/Storage/Disks/Disks.scss

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
display: flex;
1111
flex-direction: row;
1212
justify-content: left;
13-
gap: 6px;
1413

1514
width: max-content;
1615
}
@@ -27,6 +26,15 @@
2726

2827
&__pdisk-item {
2928
min-width: 80px;
29+
margin-right: 4px;
30+
31+
&_with-dc-margin {
32+
margin-right: 12px;
33+
}
34+
35+
&:last-child {
36+
margin-right: 0;
37+
}
3038
}
3139
&__pdisk-progress-bar {
3240
--progress-bar-full-height: 20px;

src/containers/Storage/Disks/Disks.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {cn} from '../../../utils/cn';
88
import type {PreparedVDisk} from '../../../utils/disks/types';
99
import {PDisk} from '../PDisk';
1010
import type {StorageViewContext} from '../types';
11-
import {isVdiskActive} from '../utils';
11+
import {isVdiskActive, useVDisksWithDCMargins} from '../utils';
1212

1313
import './Disks.scss';
1414

@@ -24,6 +24,8 @@ interface DisksProps {
2424
export function Disks({vDisks = [], viewContext}: DisksProps) {
2525
const [highlightedVDisk, setHighlightedVDisk] = React.useState<string | undefined>();
2626

27+
const vDisksWithDCMargins = useVDisksWithDCMargins(vDisks);
28+
2729
const {
2830
theme: {spaceBaseSize},
2931
} = useLayoutContext();
@@ -51,12 +53,13 @@ export function Disks({vDisks = [], viewContext}: DisksProps) {
5153
</Flex>
5254

5355
<div className={b('pdisks-wrapper')}>
54-
{vDisks?.map((vDisk) => (
56+
{vDisks?.map((vDisk, index) => (
5557
<PDiskItem
5658
key={vDisk?.PDisk?.StringifiedId}
5759
vDisk={vDisk}
5860
highlightedVDisk={highlightedVDisk}
5961
setHighlightedVDisk={setHighlightedVDisk}
62+
withDCMargin={vDisksWithDCMargins.includes(index)}
6063
/>
6164
))}
6265
</div>
@@ -70,6 +73,7 @@ interface DisksItemProps {
7073
highlightedVDisk: string | undefined;
7174
setHighlightedVDisk: (id: string | undefined) => void;
7275
unavailableVDiskWidth?: number;
76+
withDCMargin?: boolean;
7377
}
7478

7579
function VDiskItem({
@@ -103,7 +107,7 @@ function VDiskItem({
103107
);
104108
}
105109

106-
function PDiskItem({vDisk, highlightedVDisk, setHighlightedVDisk}: DisksItemProps) {
110+
function PDiskItem({vDisk, highlightedVDisk, setHighlightedVDisk, withDCMargin}: DisksItemProps) {
107111
const vDiskId = vDisk.StringifiedId;
108112

109113
if (!vDisk.PDisk) {
@@ -112,7 +116,7 @@ function PDiskItem({vDisk, highlightedVDisk, setHighlightedVDisk}: DisksItemProp
112116

113117
return (
114118
<PDisk
115-
className={b('pdisk-item')}
119+
className={b('pdisk-item', {['with-dc-margin']: withDCMargin})}
116120
progressBarClassName={b('pdisk-progress-bar')}
117121
data={vDisk.PDisk}
118122
showPopup={highlightedVDisk === vDiskId}

src/containers/Storage/StorageGroups/columns/StorageGroupsColumns.scss

-19
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,6 @@
44
overflow: visible; // to enable stacked disks overflow the row
55
}
66

7-
&__vdisks-wrapper {
8-
display: flex;
9-
justify-content: center;
10-
gap: 10px;
11-
12-
min-width: 500px;
13-
}
14-
&__vdisks-item {
15-
flex-grow: 1;
16-
17-
max-width: 200px;
18-
19-
.stack__layer {
20-
.data-table__row:hover & {
21-
background: var(--ydb-data-table-color-hover);
22-
}
23-
}
24-
}
25-
267
&__pool-name-wrapper {
278
overflow: hidden;
289

src/containers/Storage/StorageGroups/columns/columns.tsx

+3-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {CellWithPopover} from '../../../../components/CellWithPopover/CellWithPo
88
import {InternalLink} from '../../../../components/InternalLink';
99
import {StatusIcon} from '../../../../components/StatusIcon/StatusIcon';
1010
import {UsageLabel} from '../../../../components/UsageLabel/UsageLabel';
11-
import {VDiskWithDonorsStack} from '../../../../components/VDisk/VDiskWithDonorsStack';
1211
import {getStorageGroupPath} from '../../../../routes';
1312
import {valueIsDefined} from '../../../../utils';
1413
import {cn} from '../../../../utils/cn';
@@ -18,7 +17,8 @@ import {getUsageSeverity} from '../../../../utils/generateEvaluator';
1817
import {formatToMs} from '../../../../utils/timeParsers';
1918
import {bytesToGB, bytesToSpeed} from '../../../../utils/utils';
2019
import {Disks} from '../../Disks/Disks';
21-
import {getDegradedSeverity, isVdiskActive} from '../../utils';
20+
import {VDisks} from '../../VDisks/VDisks';
21+
import {getDegradedSeverity} from '../../utils';
2222
import i18n from '../i18n';
2323

2424
import {
@@ -230,18 +230,7 @@ const getVDisksColumn = (data?: GetStorageColumnsData): StorageGroupsColumn => (
230230
name: STORAGE_GROUPS_COLUMNS_IDS.VDisks,
231231
header: STORAGE_GROUPS_COLUMNS_TITLES.VDisks,
232232
className: b('vdisks-column'),
233-
render: ({row}) => (
234-
<div className={b('vdisks-wrapper')}>
235-
{row.VDisks?.map((vDisk) => (
236-
<VDiskWithDonorsStack
237-
key={vDisk.StringifiedId}
238-
data={vDisk}
239-
inactive={!isVdiskActive(vDisk, data?.viewContext)}
240-
className={b('vdisks-item')}
241-
/>
242-
))}
243-
</div>
244-
),
233+
render: ({row}) => <VDisks vDisks={row.VDisks} viewContext={data?.viewContext} />,
245234
align: DataTable.CENTER,
246235
width: 900,
247236
resizeable: false,
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.ydb-storage-vdisks {
2+
&__wrapper {
3+
display: flex;
4+
justify-content: center;
5+
6+
min-width: 500px;
7+
}
8+
9+
&__item {
10+
flex-grow: 1;
11+
12+
max-width: 200px;
13+
margin-right: 6px;
14+
15+
&_with-dc-margin {
16+
margin-right: 12px;
17+
}
18+
19+
&:last-child {
20+
margin-right: 0px;
21+
}
22+
23+
.stack__layer {
24+
.data-table__row:hover & {
25+
background: var(--ydb-data-table-color-hover);
26+
}
27+
}
28+
}
29+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {VDiskWithDonorsStack} from '../../../components/VDisk/VDiskWithDonorsStack';
2+
import {cn} from '../../../utils/cn';
3+
import type {PreparedVDisk} from '../../../utils/disks/types';
4+
import type {StorageViewContext} from '../types';
5+
import {isVdiskActive, useVDisksWithDCMargins} from '../utils';
6+
7+
import './VDisks.scss';
8+
9+
const b = cn('ydb-storage-vdisks');
10+
11+
interface VDisksProps {
12+
vDisks?: PreparedVDisk[];
13+
viewContext?: StorageViewContext;
14+
}
15+
16+
export function VDisks({vDisks, viewContext}: VDisksProps) {
17+
const vDisksWithDCMargins = useVDisksWithDCMargins(vDisks);
18+
19+
return (
20+
<div className={b('wrapper')}>
21+
{vDisks?.map((vDisk, index) => (
22+
<VDiskWithDonorsStack
23+
key={vDisk.StringifiedId}
24+
data={vDisk}
25+
inactive={!isVdiskActive(vDisk, viewContext)}
26+
className={b('item', {
27+
'with-dc-margin': vDisksWithDCMargins.includes(index),
28+
})}
29+
/>
30+
))}
31+
</div>
32+
);
33+
}

src/containers/Storage/utils/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import React from 'react';
2+
3+
import {selectNodesMap} from '../../../store/reducers/nodesList';
14
import type {PreparedStorageGroup} from '../../../store/reducers/storage/types';
25
import {valueIsDefined} from '../../../utils';
36
import type {PreparedVDisk} from '../../../utils/disks/types';
47
import {generateEvaluator} from '../../../utils/generateEvaluator';
8+
import {useTypedSelector} from '../../../utils/hooks';
59
import type {StorageViewContext} from '../types';
610

711
const defaultDegradationEvaluator = generateEvaluator(['success', 'warning', 'danger'], 1, 2);
@@ -79,3 +83,23 @@ export function getStorageGroupsInitialEntitiesCount(
7983

8084
return DEFAULT_ENTITIES_COUNT;
8185
}
86+
87+
export function useVDisksWithDCMargins(vDisks: PreparedVDisk[] = []) {
88+
const nodesMap = useTypedSelector(selectNodesMap);
89+
90+
return React.useMemo(() => {
91+
const disksWithMargins: number[] = [];
92+
93+
// Backend returns disks sorted by DC, so we don't need to apply any additional sorting
94+
vDisks.forEach((disk, index) => {
95+
const dc1 = nodesMap?.get(Number(disk?.NodeId))?.DC;
96+
const dc2 = nodesMap?.get(Number(vDisks[index + 1]?.NodeId))?.DC;
97+
98+
if (dc1 !== dc2) {
99+
disksWithMargins.push(index);
100+
}
101+
});
102+
103+
return disksWithMargins;
104+
}, [vDisks, nodesMap]);
105+
}

src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {skipToken} from '@reduxjs/toolkit/query';
55
import {ResponseError} from '../../../../components/Errors/ResponseError';
66
import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable';
77
import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton';
8-
import {nodesListApi, selectNodeHostsMap} from '../../../../store/reducers/nodesList';
8+
import {nodesListApi, selectNodesMap} from '../../../../store/reducers/nodesList';
99
import {partitionsApi, setSelectedConsumer} from '../../../../store/reducers/partitions/partitions';
1010
import {selectConsumersNames, topicApi} from '../../../../store/reducers/topic';
1111
import {cn} from '../../../../utils/cn';
@@ -55,7 +55,7 @@ export const Partitions = ({path, database}: PartitionsProps) => {
5555
error: nodesError,
5656
} = nodesListApi.useGetNodesListQuery(undefined);
5757
const nodesLoading = nodesIsFetching && nodesData === undefined;
58-
const nodeHostsMap = useTypedSelector(selectNodeHostsMap);
58+
const nodeHostsMap = useTypedSelector(selectNodesMap);
5959

6060
const [hiddenColumns, setHiddenColumns] = useSetting<string[]>(PARTITIONS_HIDDEN_COLUMNS_KEY);
6161

src/containers/Tenant/Diagnostics/Partitions/utils/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import type {PreparedPartitionData} from '../../../../../store/reducers/partitions/types';
2-
import type {NodeHostsMap} from '../../../../../types/store/nodesList';
2+
import type {NodesMap} from '../../../../../types/store/nodesList';
33

44
import type {PreparedPartitionDataWithHosts} from './types';
55

66
export const addHostToPartitions = (
77
partitions: PreparedPartitionData[] = [],
8-
nodeHosts?: NodeHostsMap,
8+
nodeHosts?: NodesMap,
99
): PreparedPartitionDataWithHosts[] => {
1010
return partitions?.map((partition) => {
1111
const partitionHost =
1212
partition.partitionNodeId && nodeHosts
13-
? nodeHosts.get(partition.partitionNodeId)
13+
? nodeHosts.get(partition.partitionNodeId)?.Host
1414
: undefined;
1515

1616
const connectionHost =
1717
partition.connectionNodeId && nodeHosts
18-
? nodeHosts.get(partition.connectionNodeId)
18+
? nodeHosts.get(partition.connectionNodeId)?.Host
1919
: undefined;
2020

2121
return {

src/store/reducers/cluster/cluster.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {CLUSTER_DEFAULT_TITLE, DEFAULT_CLUSTER_TAB_KEY} from '../../../utils/con
1313
import {isQueryErrorResponse} from '../../../utils/query';
1414
import type {RootState} from '../../defaultStore';
1515
import {api} from '../api';
16-
import {selectNodeHostsMap} from '../nodesList';
16+
import {selectNodesMap} from '../nodesList';
1717

1818
import type {ClusterGroupsStats, ClusterState} from './types';
1919
import {
@@ -181,7 +181,7 @@ export const selectClusterTitle = createSelector(
181181

182182
export const selectClusterTabletsWithFqdn = createSelector(
183183
(state: RootState, clusterName?: string) => selectClusterInfo(state, clusterName),
184-
(state: RootState) => selectNodeHostsMap(state),
184+
(state: RootState) => selectNodesMap(state),
185185
(data, nodeHostsMap): (TTabletStateInfo & {fqdn?: string})[] => {
186186
const tablets = data?.clusterData?.SystemTablets;
187187
if (!tablets) {
@@ -191,7 +191,8 @@ export const selectClusterTabletsWithFqdn = createSelector(
191191
return tablets;
192192
}
193193
return tablets.map((tablet) => {
194-
const fqdn = tablet.NodeId === undefined ? undefined : nodeHostsMap.get(tablet.NodeId);
194+
const fqdn =
195+
tablet.NodeId === undefined ? undefined : nodeHostsMap.get(tablet.NodeId)?.Host;
195196
return {...tablet, fqdn};
196197
});
197198
},

0 commit comments

Comments
 (0)