Skip to content

Commit db11791

Browse files
authored
feat: set pdisks column width (#1793)
1 parent 1095d96 commit db11791

File tree

9 files changed

+222
-48
lines changed

9 files changed

+222
-48
lines changed

src/containers/Storage/PDisk/PDisk.scss

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
.pdisk-storage {
2-
--pdisk-vdisk-width: 3px;
3-
--pdisk-gap-width: 2px;
4-
52
position: relative;
63

74
display: flex;
85
flex-direction: column;
96
justify-content: flex-end;
107

11-
width: calc(
12-
var(--pdisk-max-slots, 1) * var(--pdisk-vdisk-width) + (var(--pdisk-max-slots, 1) - 1) *
13-
var(--pdisk-gap-width)
14-
);
15-
min-width: 120px;
8+
width: var(--pdisk-width);
9+
min-width: var(--pdisk-min-width);
1610

1711
&__content {
1812
position: relative;

src/containers/Storage/PDisk/PDisk.tsx

+1-15
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import './PDisk.scss';
1616

1717
const b = cn('pdisk-storage');
1818

19-
const PDISK_MAX_SLOTS_CSS_VAR = '--pdisk-max-slots';
20-
2119
interface PDiskProps {
2220
data?: PreparedPDisk;
2321
vDisks?: PreparedVDisk[];
@@ -27,7 +25,6 @@ interface PDiskProps {
2725
className?: string;
2826
progressBarClassName?: string;
2927
viewContext?: StorageViewContext;
30-
maximumSlotsPerDisk?: string;
3128
}
3229

3330
export const PDisk = ({
@@ -39,7 +36,6 @@ export const PDisk = ({
3936
className,
4037
progressBarClassName,
4138
viewContext,
42-
maximumSlotsPerDisk,
4339
}: PDiskProps) => {
4440
const {NodeId, PDiskId} = data;
4541
const pDiskIdsDefined = valueIsDefined(NodeId) && valueIsDefined(PDiskId);
@@ -77,17 +73,7 @@ export const PDisk = ({
7773
}
7874

7975
return (
80-
<div
81-
className={b(null, className)}
82-
ref={anchorRef}
83-
style={
84-
maximumSlotsPerDisk
85-
? ({
86-
[PDISK_MAX_SLOTS_CSS_VAR]: maximumSlotsPerDisk,
87-
} as React.CSSProperties)
88-
: undefined
89-
}
90-
>
76+
<div className={b(null, className)} ref={anchorRef}>
9177
{renderVDisks()}
9278
<HoverPopup
9379
showPopup={showPopup}

src/containers/Storage/StorageNodes/columns/StorageNodesColumns.scss

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
@import '../../../../styles/mixins.scss';
2+
13
.ydb-storage-nodes-columns {
24
&__pdisks-column {
35
overflow: visible; // to enable stacked disks overflow the row
46
}
57

68
&__pdisks-wrapper {
79
display: flex;
8-
gap: 10px;
10+
gap: var(--pdisk-margin);
911

10-
width: 100%;
12+
width: var(--pdisks-container-width);
1113
height: 40px;
14+
15+
@include calculate-storage-nodes-pdisk-variables();
1216
}
1317

1418
&__pdisks-item {

src/containers/Storage/StorageNodes/columns/columns.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,30 @@ import './StorageNodesColumns.scss';
3030

3131
const b = cn('ydb-storage-nodes-columns');
3232

33+
const MAX_SLOTS_CSS_VAR = '--maximum-slots';
34+
const MAX_DISKS_CSS_VAR = '--maximum-disks';
35+
3336
const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNodesColumn => {
3437
return {
3538
name: NODES_COLUMNS_IDS.PDisks,
3639
header: NODES_COLUMNS_TITLES.PDisks,
3740
className: b('pdisks-column'),
3841
render: ({row}) => {
42+
const pDiskStyles = {
43+
[MAX_SLOTS_CSS_VAR]: row.MaximumSlotsPerDisk,
44+
[MAX_DISKS_CSS_VAR]: row.MaximumDisksPerNode,
45+
} as React.CSSProperties;
46+
3947
return (
40-
<div className={b('pdisks-wrapper')}>
48+
<div className={b('pdisks-wrapper')} style={pDiskStyles}>
4149
{row.PDisks?.map((pDisk) => {
4250
const vDisks = row.VDisks?.filter(
4351
(vdisk) => vdisk.PDiskId === pDisk.PDiskId,
4452
);
4553

4654
return (
4755
<div className={b('pdisks-item')} key={pDisk.PDiskId}>
48-
<PDisk
49-
data={pDisk}
50-
vDisks={vDisks}
51-
viewContext={viewContext}
52-
maximumSlotsPerDisk={row.MaximumSlotsPerDisk}
53-
/>
56+
<PDisk data={pDisk} vDisks={vDisks} viewContext={viewContext} />
5457
</div>
5558
);
5659
})}
@@ -59,7 +62,6 @@ const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNo
5962
},
6063
align: DataTable.CENTER,
6164
sortable: false,
62-
width: 900,
6365
resizeable: false,
6466
};
6567
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import type {TNodeInfo} from '../../../../types/api/nodes';
2+
import {TPDiskState} from '../../../../types/api/pdisk';
3+
import {calculateMaximumDisksPerNode} from '../utils';
4+
5+
describe('calculateMaximumDisksPerNode', () => {
6+
it('should return providedMaximumDisksPerNode when it is provided', () => {
7+
const nodes: TNodeInfo[] = [];
8+
const providedMaximumDisksPerNode = '5';
9+
10+
expect(calculateMaximumDisksPerNode(nodes, providedMaximumDisksPerNode)).toBe('5');
11+
});
12+
13+
it('should return "1" for empty nodes array', () => {
14+
const nodes: TNodeInfo[] = [];
15+
16+
expect(calculateMaximumDisksPerNode(nodes)).toBe('1');
17+
});
18+
19+
it('should return "1" for undefined nodes', () => {
20+
expect(calculateMaximumDisksPerNode(undefined)).toBe('1');
21+
});
22+
23+
it('should return "1" for nodes without PDisks', () => {
24+
const nodes: TNodeInfo[] = [
25+
{
26+
NodeId: 1,
27+
SystemState: {},
28+
},
29+
];
30+
31+
expect(calculateMaximumDisksPerNode(nodes)).toBe('1');
32+
});
33+
34+
it('should calculate maximum disks correctly for single node with multiple PDisks', () => {
35+
const nodes: TNodeInfo[] = [
36+
{
37+
NodeId: 1,
38+
SystemState: {},
39+
PDisks: [
40+
{
41+
PDiskId: 1,
42+
State: TPDiskState.Normal,
43+
},
44+
{
45+
PDiskId: 2,
46+
State: TPDiskState.Normal,
47+
},
48+
{
49+
PDiskId: 3,
50+
State: TPDiskState.Normal,
51+
},
52+
],
53+
},
54+
];
55+
56+
expect(calculateMaximumDisksPerNode(nodes)).toBe('3');
57+
});
58+
59+
it('should calculate maximum disks across multiple nodes', () => {
60+
const nodes: TNodeInfo[] = [
61+
{
62+
NodeId: 1,
63+
SystemState: {},
64+
PDisks: [
65+
{
66+
PDiskId: 1,
67+
State: TPDiskState.Normal,
68+
},
69+
],
70+
},
71+
{
72+
NodeId: 2,
73+
SystemState: {},
74+
PDisks: [
75+
{
76+
PDiskId: 2,
77+
State: TPDiskState.Normal,
78+
},
79+
{
80+
PDiskId: 3,
81+
State: TPDiskState.Normal,
82+
},
83+
],
84+
},
85+
{
86+
NodeId: 3,
87+
SystemState: {},
88+
PDisks: [
89+
{
90+
PDiskId: 4,
91+
State: TPDiskState.Normal,
92+
},
93+
{
94+
PDiskId: 5,
95+
State: TPDiskState.Normal,
96+
},
97+
{
98+
PDiskId: 6,
99+
State: TPDiskState.Normal,
100+
},
101+
{
102+
PDiskId: 7,
103+
State: TPDiskState.Normal,
104+
},
105+
],
106+
},
107+
];
108+
109+
expect(calculateMaximumDisksPerNode(nodes)).toBe('4');
110+
});
111+
112+
it('should handle nodes with empty PDisks array', () => {
113+
const nodes: TNodeInfo[] = [
114+
{
115+
NodeId: 1,
116+
SystemState: {},
117+
PDisks: [],
118+
},
119+
{
120+
NodeId: 2,
121+
SystemState: {},
122+
PDisks: [
123+
{
124+
PDiskId: 1,
125+
State: TPDiskState.Normal,
126+
},
127+
{
128+
PDiskId: 2,
129+
State: TPDiskState.Normal,
130+
},
131+
],
132+
},
133+
];
134+
135+
expect(calculateMaximumDisksPerNode(nodes)).toBe('2');
136+
});
137+
});

src/store/reducers/storage/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface PreparedStorageNode extends PreparedNodeSystemState {
3636

3737
Missing: number;
3838
MaximumSlotsPerDisk: string;
39+
MaximumDisksPerNode: string;
3940
}
4041

4142
export interface PreparedStorageGroupFilters {

src/store/reducers/storage/utils.ts

+43-15
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ const prepareStorageGroups = (
191191
const prepareStorageNodeData = (
192192
node: TNodeInfo,
193193
maximumSlotsPerDisk: string,
194+
maximumDisksPerNode: string,
194195
): PreparedStorageNode => {
195196
const missing =
196197
node.PDisks?.filter((pDisk) => {
@@ -218,35 +219,59 @@ const prepareStorageNodeData = (
218219
VDisks: vDisks,
219220
Missing: missing,
220221
MaximumSlotsPerDisk: maximumSlotsPerDisk,
222+
MaximumDisksPerNode: maximumDisksPerNode,
221223
};
222224
};
223225

226+
/**
227+
* Calculates the maximum number of VDisk slots per PDisk across all nodes
228+
* A slot represents a VDisk that can be allocated to a PDisk
229+
*/
224230
export const calculateMaximumSlotsPerDisk = (
225231
nodes: TNodeInfo[] | undefined,
226232
providedMaximumSlotsPerDisk?: string,
227-
) => {
233+
): string => {
228234
if (providedMaximumSlotsPerDisk) {
229235
return providedMaximumSlotsPerDisk;
230236
}
231237

232-
return String(
233-
Math.max(
234-
1,
235-
...(nodes || []).flatMap((node) =>
236-
(node.PDisks || []).map(
237-
(pDisk) =>
238-
(node.VDisks || []).filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId)
239-
.length || 0,
240-
),
241-
),
242-
),
243-
);
238+
const safeNodes = nodes || [];
239+
const slotsPerDiskCounts = safeNodes.flatMap((node) => {
240+
const safePDisks = node.PDisks || [];
241+
const safeVDisks = node.VDisks || [];
242+
243+
return safePDisks.map((pDisk) => {
244+
const vDisksOnPDisk = safeVDisks.filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId);
245+
return vDisksOnPDisk.length || 0;
246+
});
247+
});
248+
249+
const maxSlots = Math.max(1, ...slotsPerDiskCounts);
250+
return String(maxSlots);
251+
};
252+
253+
/**
254+
* Calculates the maximum number of PDisks per node across all nodes
255+
*/
256+
export const calculateMaximumDisksPerNode = (
257+
nodes: TNodeInfo[] | undefined,
258+
providedMaximumDisksPerNode?: string,
259+
): string => {
260+
if (providedMaximumDisksPerNode) {
261+
return providedMaximumDisksPerNode;
262+
}
263+
264+
const safeNodes = nodes || [];
265+
const disksPerNodeCounts = safeNodes.map((node) => node.PDisks?.length || 0);
266+
const maxDisks = Math.max(1, ...disksPerNodeCounts);
267+
return String(maxDisks);
244268
};
245269

246270
// ==== Prepare responses ====
247271

248272
export const prepareStorageNodesResponse = (data: TNodesInfo): PreparedStorageResponse => {
249-
const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk} = data;
273+
const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk, MaximumDisksPerNode} =
274+
data;
250275

251276
const tableGroups = NodeGroups?.map(({GroupName, NodeCount}) => {
252277
if (GroupName && NodeCount) {
@@ -259,7 +284,10 @@ export const prepareStorageNodesResponse = (data: TNodesInfo): PreparedStorageRe
259284
}).filter((group): group is TableGroup => Boolean(group));
260285

261286
const maximumSlots = calculateMaximumSlotsPerDisk(Nodes, MaximumSlotsPerDisk);
262-
const preparedNodes = Nodes?.map((node) => prepareStorageNodeData(node, maximumSlots));
287+
const maximumDisks = calculateMaximumDisksPerNode(Nodes, MaximumDisksPerNode);
288+
const preparedNodes = Nodes?.map((node) =>
289+
prepareStorageNodeData(node, maximumSlots, maximumDisks),
290+
);
263291

264292
return {
265293
nodes: preparedNodes,

src/styles/mixins.scss

+20
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,23 @@
395395
background-color: unset;
396396
}
397397
}
398+
399+
@mixin calculate-storage-nodes-pdisk-variables() {
400+
--pdisk-vdisk-width: 3px;
401+
--pdisk-gap-width: 2px;
402+
--pdisk-min-width: 120px;
403+
--pdisk-margin: 10px;
404+
405+
--pdisk-width: max(
406+
calc(
407+
var(--maximum-slots, 1) * var(--pdisk-vdisk-width) + (var(--maximum-slots, 1) - 1) *
408+
var(--pdisk-gap-width)
409+
),
410+
var(--pdisk-min-width)
411+
);
412+
413+
--pdisks-container-width: calc(
414+
var(--maximum-disks, 1) * var(--pdisk-width) + (var(--maximum-disks, 1) - 1) *
415+
var(--pdisk-margin)
416+
);
417+
}

0 commit comments

Comments
 (0)