Skip to content

Commit

Permalink
feat: set pdisks column width (#1793)
Browse files Browse the repository at this point in the history
  • Loading branch information
astandrik authored Dec 27, 2024
1 parent 1095d96 commit db11791
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 48 deletions.
10 changes: 2 additions & 8 deletions src/containers/Storage/PDisk/PDisk.scss
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
.pdisk-storage {
--pdisk-vdisk-width: 3px;
--pdisk-gap-width: 2px;

position: relative;

display: flex;
flex-direction: column;
justify-content: flex-end;

width: calc(
var(--pdisk-max-slots, 1) * var(--pdisk-vdisk-width) + (var(--pdisk-max-slots, 1) - 1) *
var(--pdisk-gap-width)
);
min-width: 120px;
width: var(--pdisk-width);
min-width: var(--pdisk-min-width);

&__content {
position: relative;
Expand Down
16 changes: 1 addition & 15 deletions src/containers/Storage/PDisk/PDisk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import './PDisk.scss';

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

const PDISK_MAX_SLOTS_CSS_VAR = '--pdisk-max-slots';

interface PDiskProps {
data?: PreparedPDisk;
vDisks?: PreparedVDisk[];
Expand All @@ -27,7 +25,6 @@ interface PDiskProps {
className?: string;
progressBarClassName?: string;
viewContext?: StorageViewContext;
maximumSlotsPerDisk?: string;
}

export const PDisk = ({
Expand All @@ -39,7 +36,6 @@ export const PDisk = ({
className,
progressBarClassName,
viewContext,
maximumSlotsPerDisk,
}: PDiskProps) => {
const {NodeId, PDiskId} = data;
const pDiskIdsDefined = valueIsDefined(NodeId) && valueIsDefined(PDiskId);
Expand Down Expand Up @@ -77,17 +73,7 @@ export const PDisk = ({
}

return (
<div
className={b(null, className)}
ref={anchorRef}
style={
maximumSlotsPerDisk
? ({
[PDISK_MAX_SLOTS_CSS_VAR]: maximumSlotsPerDisk,
} as React.CSSProperties)
: undefined
}
>
<div className={b(null, className)} ref={anchorRef}>
{renderVDisks()}
<HoverPopup
showPopup={showPopup}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
@import '../../../../styles/mixins.scss';

.ydb-storage-nodes-columns {
&__pdisks-column {
overflow: visible; // to enable stacked disks overflow the row
}

&__pdisks-wrapper {
display: flex;
gap: 10px;
gap: var(--pdisk-margin);

width: 100%;
width: var(--pdisks-container-width);
height: 40px;

@include calculate-storage-nodes-pdisk-variables();
}

&__pdisks-item {
Expand Down
18 changes: 10 additions & 8 deletions src/containers/Storage/StorageNodes/columns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,30 @@ import './StorageNodesColumns.scss';

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

const MAX_SLOTS_CSS_VAR = '--maximum-slots';
const MAX_DISKS_CSS_VAR = '--maximum-disks';

const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNodesColumn => {
return {
name: NODES_COLUMNS_IDS.PDisks,
header: NODES_COLUMNS_TITLES.PDisks,
className: b('pdisks-column'),
render: ({row}) => {
const pDiskStyles = {
[MAX_SLOTS_CSS_VAR]: row.MaximumSlotsPerDisk,
[MAX_DISKS_CSS_VAR]: row.MaximumDisksPerNode,
} as React.CSSProperties;

return (
<div className={b('pdisks-wrapper')}>
<div className={b('pdisks-wrapper')} style={pDiskStyles}>
{row.PDisks?.map((pDisk) => {
const vDisks = row.VDisks?.filter(
(vdisk) => vdisk.PDiskId === pDisk.PDiskId,
);

return (
<div className={b('pdisks-item')} key={pDisk.PDiskId}>
<PDisk
data={pDisk}
vDisks={vDisks}
viewContext={viewContext}
maximumSlotsPerDisk={row.MaximumSlotsPerDisk}
/>
<PDisk data={pDisk} vDisks={vDisks} viewContext={viewContext} />
</div>
);
})}
Expand All @@ -59,7 +62,6 @@ const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNo
},
align: DataTable.CENTER,
sortable: false,
width: 900,
resizeable: false,
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type {TNodeInfo} from '../../../../types/api/nodes';
import {TPDiskState} from '../../../../types/api/pdisk';
import {calculateMaximumDisksPerNode} from '../utils';

describe('calculateMaximumDisksPerNode', () => {
it('should return providedMaximumDisksPerNode when it is provided', () => {
const nodes: TNodeInfo[] = [];
const providedMaximumDisksPerNode = '5';

expect(calculateMaximumDisksPerNode(nodes, providedMaximumDisksPerNode)).toBe('5');
});

it('should return "1" for empty nodes array', () => {
const nodes: TNodeInfo[] = [];

expect(calculateMaximumDisksPerNode(nodes)).toBe('1');
});

it('should return "1" for undefined nodes', () => {
expect(calculateMaximumDisksPerNode(undefined)).toBe('1');
});

it('should return "1" for nodes without PDisks', () => {
const nodes: TNodeInfo[] = [
{
NodeId: 1,
SystemState: {},
},
];

expect(calculateMaximumDisksPerNode(nodes)).toBe('1');
});

it('should calculate maximum disks correctly for single node with multiple PDisks', () => {
const nodes: TNodeInfo[] = [
{
NodeId: 1,
SystemState: {},
PDisks: [
{
PDiskId: 1,
State: TPDiskState.Normal,
},
{
PDiskId: 2,
State: TPDiskState.Normal,
},
{
PDiskId: 3,
State: TPDiskState.Normal,
},
],
},
];

expect(calculateMaximumDisksPerNode(nodes)).toBe('3');
});

it('should calculate maximum disks across multiple nodes', () => {
const nodes: TNodeInfo[] = [
{
NodeId: 1,
SystemState: {},
PDisks: [
{
PDiskId: 1,
State: TPDiskState.Normal,
},
],
},
{
NodeId: 2,
SystemState: {},
PDisks: [
{
PDiskId: 2,
State: TPDiskState.Normal,
},
{
PDiskId: 3,
State: TPDiskState.Normal,
},
],
},
{
NodeId: 3,
SystemState: {},
PDisks: [
{
PDiskId: 4,
State: TPDiskState.Normal,
},
{
PDiskId: 5,
State: TPDiskState.Normal,
},
{
PDiskId: 6,
State: TPDiskState.Normal,
},
{
PDiskId: 7,
State: TPDiskState.Normal,
},
],
},
];

expect(calculateMaximumDisksPerNode(nodes)).toBe('4');
});

it('should handle nodes with empty PDisks array', () => {
const nodes: TNodeInfo[] = [
{
NodeId: 1,
SystemState: {},
PDisks: [],
},
{
NodeId: 2,
SystemState: {},
PDisks: [
{
PDiskId: 1,
State: TPDiskState.Normal,
},
{
PDiskId: 2,
State: TPDiskState.Normal,
},
],
},
];

expect(calculateMaximumDisksPerNode(nodes)).toBe('2');
});
});
1 change: 1 addition & 0 deletions src/store/reducers/storage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface PreparedStorageNode extends PreparedNodeSystemState {

Missing: number;
MaximumSlotsPerDisk: string;
MaximumDisksPerNode: string;
}

export interface PreparedStorageGroupFilters {
Expand Down
58 changes: 43 additions & 15 deletions src/store/reducers/storage/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const prepareStorageGroups = (
const prepareStorageNodeData = (
node: TNodeInfo,
maximumSlotsPerDisk: string,
maximumDisksPerNode: string,
): PreparedStorageNode => {
const missing =
node.PDisks?.filter((pDisk) => {
Expand Down Expand Up @@ -218,35 +219,59 @@ const prepareStorageNodeData = (
VDisks: vDisks,
Missing: missing,
MaximumSlotsPerDisk: maximumSlotsPerDisk,
MaximumDisksPerNode: maximumDisksPerNode,
};
};

/**
* Calculates the maximum number of VDisk slots per PDisk across all nodes
* A slot represents a VDisk that can be allocated to a PDisk
*/
export const calculateMaximumSlotsPerDisk = (
nodes: TNodeInfo[] | undefined,
providedMaximumSlotsPerDisk?: string,
) => {
): string => {
if (providedMaximumSlotsPerDisk) {
return providedMaximumSlotsPerDisk;
}

return String(
Math.max(
1,
...(nodes || []).flatMap((node) =>
(node.PDisks || []).map(
(pDisk) =>
(node.VDisks || []).filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId)
.length || 0,
),
),
),
);
const safeNodes = nodes || [];
const slotsPerDiskCounts = safeNodes.flatMap((node) => {
const safePDisks = node.PDisks || [];
const safeVDisks = node.VDisks || [];

return safePDisks.map((pDisk) => {
const vDisksOnPDisk = safeVDisks.filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId);
return vDisksOnPDisk.length || 0;
});
});

const maxSlots = Math.max(1, ...slotsPerDiskCounts);
return String(maxSlots);
};

/**
* Calculates the maximum number of PDisks per node across all nodes
*/
export const calculateMaximumDisksPerNode = (
nodes: TNodeInfo[] | undefined,
providedMaximumDisksPerNode?: string,
): string => {
if (providedMaximumDisksPerNode) {
return providedMaximumDisksPerNode;
}

const safeNodes = nodes || [];
const disksPerNodeCounts = safeNodes.map((node) => node.PDisks?.length || 0);
const maxDisks = Math.max(1, ...disksPerNodeCounts);
return String(maxDisks);
};

// ==== Prepare responses ====

export const prepareStorageNodesResponse = (data: TNodesInfo): PreparedStorageResponse => {
const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk} = data;
const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk, MaximumDisksPerNode} =
data;

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

const maximumSlots = calculateMaximumSlotsPerDisk(Nodes, MaximumSlotsPerDisk);
const preparedNodes = Nodes?.map((node) => prepareStorageNodeData(node, maximumSlots));
const maximumDisks = calculateMaximumDisksPerNode(Nodes, MaximumDisksPerNode);
const preparedNodes = Nodes?.map((node) =>
prepareStorageNodeData(node, maximumSlots, maximumDisks),
);

return {
nodes: preparedNodes,
Expand Down
20 changes: 20 additions & 0 deletions src/styles/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,23 @@
background-color: unset;
}
}

@mixin calculate-storage-nodes-pdisk-variables() {
--pdisk-vdisk-width: 3px;
--pdisk-gap-width: 2px;
--pdisk-min-width: 120px;
--pdisk-margin: 10px;

--pdisk-width: max(
calc(
var(--maximum-slots, 1) * var(--pdisk-vdisk-width) + (var(--maximum-slots, 1) - 1) *
var(--pdisk-gap-width)
),
var(--pdisk-min-width)
);

--pdisks-container-width: calc(
var(--maximum-disks, 1) * var(--pdisk-width) + (var(--maximum-disks, 1) - 1) *
var(--pdisk-margin)
);
}
Loading

0 comments on commit db11791

Please sign in to comment.