From 2f37304d5811231034bc744dd315ec79f1ebe692 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Wed, 22 Oct 2025 13:04:00 +0300 Subject: [PATCH 1/2] fix: use SlotSize as VDisk size limit if AvailableSize is 0 --- .../DiskStateProgressBar.tsx | 6 +- src/components/VDiskInfo/VDiskInfo.tsx | 7 +- .../preparePDiskDataResponse.test.ts | 34 ++++++++ src/store/reducers/pdisk/utils.ts | 13 +++- .../__tests__/prepareGroupsDisks.test.ts | 6 +- .../reducers/storage/prepareGroupsDisks.ts | 3 +- src/store/reducers/vdisk/utils.ts | 16 ++-- src/utils/disks/__test__/prepareDisks.test.ts | 77 +++++++++++++++++-- src/utils/disks/prepareDisks.ts | 21 +++-- src/utils/disks/types.ts | 2 +- 10 files changed, 152 insertions(+), 33 deletions(-) diff --git a/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx index c4cffc191c..a5e806de34 100644 --- a/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx +++ b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx @@ -44,7 +44,11 @@ export function DiskStateProgressBar({ return
; } - const fillWidth = inverted ? 100 - diskAllocatedPercent : diskAllocatedPercent; + // diskAllocatedPercent could be more than 100 + let fillWidth = Math.min(diskAllocatedPercent, 100); + if (inverted) { + fillWidth = Math.max(100 - diskAllocatedPercent, 0); + } if (diskAllocatedPercent >= 0) { return
; diff --git a/src/components/VDiskInfo/VDiskInfo.tsx b/src/components/VDiskInfo/VDiskInfo.tsx index 27cccc90aa..d2017ecebe 100644 --- a/src/components/VDiskInfo/VDiskInfo.tsx +++ b/src/components/VDiskInfo/VDiskInfo.tsx @@ -49,6 +49,7 @@ export function VDiskInfo({ const { AllocatedSize, + SizeLimit, DiskSpace, FrontQueues, Guid, @@ -60,7 +61,6 @@ export function VDiskInfo({ VDiskSlotId, Kind, SatisfactionRank, - AvailableSize, HasUnreadableBlobs, IncarnationGuid, InstanceGuid, @@ -83,13 +83,14 @@ export function VDiskInfo({ value: VDiskState, }); } - if (Number(AllocatedSize) >= 0 && Number(AvailableSize) >= 0) { + + if (Number(AllocatedSize) >= 0 && Number(SizeLimit) >= 0) { leftColumn.push({ label: vDiskInfoKeyset('size'), value: ( diff --git a/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts b/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts index beb1b6f9d7..2ca4839a62 100644 --- a/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts +++ b/src/store/reducers/pdisk/__tests__/preparePDiskDataResponse.test.ts @@ -255,4 +255,38 @@ describe('preparePDiskDataResponse', () => { 1, ); }); + + test('Should use used size as total for VDisk slot when used size exceeds size limit', () => { + const dataWithExceededVDiskUsage: TPDiskInfoResponse = { + ...rawData, + Whiteboard: { + ...rawData.Whiteboard, + PDisk: { + ...rawData.Whiteboard?.PDisk, + EnforcedDynamicSlotSize: '15000000000', // 15GB slot size + }, + VDisks: [ + { + ...rawData.Whiteboard?.VDisks?.[0], + AllocatedSize: '20000000000', // 20GB used (exceeds 15GB slot size) + AvailableSize: '0', // 0 available, so slot size should be used as limit + }, + ], + }, + BSC: { + ...rawData.BSC, + PDisk: { + ...rawData.BSC?.PDisk, + EnforcedDynamicSlotSize: '15000000000', // 15GB slot size + }, + }, + }; + const preparedData = preparePDiskDataResponse([dataWithExceededVDiskUsage, {}]); + + const vDiskSlot = preparedData.SlotItems?.find((slot) => slot.SlotType === 'vDisk'); + + expect(vDiskSlot?.Used).toEqual(20_000_000_000); // 20GB used + // Since used (20GB) > sizeLimit (15GB), total should be set to used size + expect(vDiskSlot?.Total).toEqual(20_000_000_000); // Total equals used when used exceeds limit + }); }); diff --git a/src/store/reducers/pdisk/utils.ts b/src/store/reducers/pdisk/utils.ts index e0daa5d7c8..fc65a55a2c 100644 --- a/src/store/reducers/pdisk/utils.ts +++ b/src/store/reducers/pdisk/utils.ts @@ -70,13 +70,22 @@ export function preparePDiskDataResponse([pdiskResponse = {}, nodeResponse]: [ // VDisks with their full statuses can be seen in popup on hover, in Storage table and on vdisks pages const slotSeverity = getSpaceSeverity(preparedVDisk.AllocatedPercent); + const used = Number(preparedVDisk.AllocatedSize); + let total = Number(preparedVDisk.SizeLimit); + + // In case used size is more than limit + // use used size as total to show correct slot relative size + if (used > total) { + total = used; + } + return { SlotType: 'vDisk', Id: preparedVDisk.VDiskId?.GroupID, Title: preparedVDisk.StoragePoolName, Severity: slotSeverity, - Used: Number(preparedVDisk.AllocatedSize), - Total: Number(preparedVDisk.TotalSize), + Used: used, + Total: total, UsagePercent: preparedVDisk.AllocatedPercent, SlotData: preparedVDisk, diff --git a/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts b/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts index a5bd6e75a5..1d79ffac43 100644 --- a/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts +++ b/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts @@ -91,7 +91,7 @@ describe('prepareGroupsVDisk', () => { AllocatedSize: 30943477760, AvailableSize: 234461593600, - TotalSize: 265405071360, + SizeLimit: 265405071360, AllocatedPercent: 11, Donors: undefined, @@ -134,7 +134,7 @@ describe('prepareGroupsVDisk', () => { AllocatedSize: 30943477760, AvailableSize: 234461593600, - TotalSize: 265405071360, + SizeLimit: 265405071360, AllocatedPercent: 11, PDisk: { @@ -236,7 +236,7 @@ describe('prepareGroupsVDisk', () => { AllocatedSize: 30943477760, AvailableSize: 234461593600, - TotalSize: 265405071360, + SizeLimit: 265405071360, AllocatedPercent: 11, Donors: undefined, diff --git a/src/store/reducers/storage/prepareGroupsDisks.ts b/src/store/reducers/storage/prepareGroupsDisks.ts index d49875a3e8..6ff584fdaf 100644 --- a/src/store/reducers/storage/prepareGroupsDisks.ts +++ b/src/store/reducers/storage/prepareGroupsDisks.ts @@ -25,8 +25,9 @@ export function prepareGroupsVDisk(data: TStorageVDisk = {}): PreparedVDisk { const Severity = calculateVDiskSeverity(mergedVDiskData); const vDiskSizeFields = prepareVDiskSizeFields({ - AvailableSize: mergedVDiskData.AvailableSize ?? PDisk?.AvailableSize, + AvailableSize: mergedVDiskData.AvailableSize, AllocatedSize: mergedVDiskData.AllocatedSize, + SlotSize: PDisk?.SlotSize, }); const preparedDonors = bscVDisk.Donors?.map((donor) => { diff --git a/src/store/reducers/vdisk/utils.ts b/src/store/reducers/vdisk/utils.ts index 09a8ef696a..50067aa17a 100644 --- a/src/store/reducers/vdisk/utils.ts +++ b/src/store/reducers/vdisk/utils.ts @@ -1,10 +1,7 @@ import type {StorageGroupsResponse} from '../../../types/api/storage'; import type {TEvSystemStateResponse} from '../../../types/api/systemState'; -import { - prepareWhiteboardPDiskData, - prepareWhiteboardVDiskData, -} from '../../../utils/disks/prepareDisks'; import {prepareNodeSystemState} from '../../../utils/nodes'; +import {prepareGroupsVDisk} from '../storage/prepareGroupsDisks'; import type {VDiskData} from './types'; @@ -18,21 +15,20 @@ export function prepareVDiskDataResponse( const rawVDisk = storageGroupResponse?.StorageGroups?.[0].VDisks?.find( ({VDiskId}) => VDiskId === vDiskId, ); - const preparedVDisk = prepareWhiteboardVDiskData(rawVDisk?.Whiteboard); - const rawPDisk = rawVDisk?.PDisk?.Whiteboard; - const preparedPDisk = prepareWhiteboardPDiskData(rawPDisk); + const preparedVDisk = prepareGroupsVDisk(rawVDisk); + const preparedPDisk = preparedVDisk.PDisk; const rawNode = nodeResponse?.SystemStateInfo?.[0]; const preparedNode = prepareNodeSystemState(rawNode); - const NodeId = preparedVDisk.NodeId ?? preparedPDisk.NodeId ?? preparedNode.NodeId; + const NodeId = preparedVDisk.NodeId ?? preparedPDisk?.NodeId ?? preparedNode.NodeId; const NodeHost = preparedNode.Host; const NodeType = preparedNode.Roles?.[0]; const NodeDC = preparedNode.DC; - const PDiskId = preparedVDisk.PDiskId ?? preparedPDisk.PDiskId; - const PDiskType = preparedPDisk.Type; + const PDiskId = preparedVDisk.PDiskId ?? preparedPDisk?.PDiskId; + const PDiskType = preparedPDisk?.Type; return { ...preparedVDisk, diff --git a/src/utils/disks/__test__/prepareDisks.test.ts b/src/utils/disks/__test__/prepareDisks.test.ts index 85a9df4266..22dd375f0f 100644 --- a/src/utils/disks/__test__/prepareDisks.test.ts +++ b/src/utils/disks/__test__/prepareDisks.test.ts @@ -90,7 +90,7 @@ describe('prepareWhiteboardVDiskData', () => { AvailableSize: 188523479040, AllocatedSize: 8996782080, - TotalSize: 197520261120, + SizeLimit: 197520261120, AllocatedPercent: 4, }; @@ -180,29 +180,92 @@ describe('prepareWhiteboardPDiskData', () => { }); describe('prepareVDiskSizeFields', () => { - test('Should prepare VDisk size fields', () => { + test('Should prepare VDisk size fields with allocated + available as size limit', () => { expect( prepareVDiskSizeFields({ AvailableSize: '400', AllocatedSize: '100', + SlotSize: '500', }), ).toEqual({ AvailableSize: 400, AllocatedSize: 100, - TotalSize: 500, - AllocatedPercent: 20, + SizeLimit: 500, // allocated (100) + available (400) = 500 + AllocatedPercent: 20, // 100 / 500 * 100 = 20% }); }); - test('Returns NaN if on undefined data', () => { + + test('Should use SlotSize as size limit when AvailableSize is 0', () => { + expect( + prepareVDiskSizeFields({ + AvailableSize: '0', + AllocatedSize: '500', + SlotSize: '500', + }), + ).toEqual({ + AvailableSize: 0, + AllocatedSize: 500, + SizeLimit: 500, // SlotSize is used when available is 0 + AllocatedPercent: 100, // 500 / 500 * 100 = 100% + }); + }); + + test('Should use SlotSize as size limit when AvailableSize is undefined', () => { + expect( + prepareVDiskSizeFields({ + AvailableSize: undefined, + AllocatedSize: '300', + SlotSize: '500', + }), + ).toEqual({ + AvailableSize: 0, + AllocatedSize: 300, + SizeLimit: 500, // SlotSize is used when available is undefined + AllocatedPercent: 60, // 300 / 500 * 100 = 60% + }); + }); + + test('Should use allocated when SlotSize is undefined and available is 0', () => { + expect( + prepareVDiskSizeFields({ + AvailableSize: '0', + AllocatedSize: '500', + SlotSize: undefined, + }), + ).toEqual({ + AvailableSize: 0, + AllocatedSize: 500, + SizeLimit: 500, // allocated (500) + AllocatedPercent: 100, // 500 / 500 * 100 = 100% + }); + }); + + test('Should handle case when used size exceeds slot size', () => { + expect( + prepareVDiskSizeFields({ + AvailableSize: '0', + AllocatedSize: '800', + SlotSize: '500', + }), + ).toEqual({ + AvailableSize: 0, + AllocatedSize: 800, + SizeLimit: 500, // SlotSize is used as limit + AllocatedPercent: 160, // 800 / 500 * 100 = 160% + }); + }); + + test('Should return NaN for undefined data', () => { expect( prepareVDiskSizeFields({ AvailableSize: undefined, AllocatedSize: undefined, + SlotSize: undefined, }), ).toEqual({ - AvailableSize: NaN, + AvailableSize: 0, AllocatedSize: NaN, - TotalSize: NaN, + SizeLimit: NaN, AllocatedPercent: NaN, }); }); diff --git a/src/utils/disks/prepareDisks.ts b/src/utils/disks/prepareDisks.ts index efe4d24ccc..e40c64e602 100644 --- a/src/utils/disks/prepareDisks.ts +++ b/src/utils/disks/prepareDisks.ts @@ -56,8 +56,9 @@ export function prepareWhiteboardVDiskData( const actualPDiskId = PDiskId ?? preparedPDisk?.PDiskId; const vDiskSizeFields = prepareVDiskSizeFields({ - AvailableSize: AvailableSize ?? PDisk?.AvailableSize, + AvailableSize: AvailableSize, AllocatedSize: AllocatedSize, + SlotSize: PDisk?.EnforcedDynamicSlotSize, }); const Severity = calculateVDiskSeverity(vDiskState); @@ -145,19 +146,29 @@ export function prepareWhiteboardPDiskData(pdiskState: TPDiskStateInfo = {}): Pr export function prepareVDiskSizeFields({ AvailableSize, AllocatedSize, + SlotSize, }: { AvailableSize: string | number | undefined; AllocatedSize: string | number | undefined; + SlotSize: string | number | undefined; }) { - const available = Number(AvailableSize); + const available = Number(AvailableSize ?? 0); const allocated = Number(AllocatedSize); - const total = allocated + available; - const allocatedPercent = Math.floor((allocated * 100) / total); + const slotSize = Number(SlotSize); + + let sizeLimit = allocated + available; + + // If no available size or available size is 0, slot size should be used as limit + if (!available && slotSize) { + sizeLimit = slotSize; + } + + const allocatedPercent = Math.floor((allocated * 100) / sizeLimit); return { AvailableSize: available, AllocatedSize: allocated, - TotalSize: total, + SizeLimit: sizeLimit, AllocatedPercent: allocatedPercent, }; } diff --git a/src/utils/disks/types.ts b/src/utils/disks/types.ts index 45371819fd..28b4a8fbe3 100644 --- a/src/utils/disks/types.ts +++ b/src/utils/disks/types.ts @@ -28,9 +28,9 @@ export interface PreparedVDisk StringifiedId?: string; AvailableSize?: number; - TotalSize?: number; AllocatedSize?: number; AllocatedPercent?: number; + SizeLimit?: number; Donors?: PreparedVDisk[]; } From 5dd8183b9edee92a76e457a65ee0d6bdaf6a303f Mon Sep 17 00:00:00 2001 From: mufazalov Date: Wed, 22 Oct 2025 15:31:33 +0300 Subject: [PATCH 2/2] copilot fix --- src/utils/disks/prepareDisks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/disks/prepareDisks.ts b/src/utils/disks/prepareDisks.ts index e40c64e602..6770b0d80c 100644 --- a/src/utils/disks/prepareDisks.ts +++ b/src/utils/disks/prepareDisks.ts @@ -153,6 +153,7 @@ export function prepareVDiskSizeFields({ SlotSize: string | number | undefined; }) { const available = Number(AvailableSize ?? 0); + // Unlike available, allocated is displayed in UI, it is incorrect to fallback it to 0 const allocated = Number(AllocatedSize); const slotSize = Number(SlotSize); @@ -163,7 +164,7 @@ export function prepareVDiskSizeFields({ sizeLimit = slotSize; } - const allocatedPercent = Math.floor((allocated * 100) / sizeLimit); + const allocatedPercent = sizeLimit > 0 ? Math.floor((allocated * 100) / sizeLimit) : NaN; return { AvailableSize: available,