Skip to content

Commit d22b37b

Browse files
committed
fix: paginatedTable - calculate visible range on resize
1 parent 4759d5f commit d22b37b

File tree

3 files changed

+238
-35
lines changed

3 files changed

+238
-35
lines changed

src/components/PaginatedTable/useScrollBasedChunks.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'react';
22

3-
import {throttle} from 'lodash';
4-
5-
import {calculateElementOffsetTop} from './utils';
3+
import {calculateElementOffsetTop, rafThrottle} from './utils';
64

75
interface UseScrollBasedChunksProps {
86
parentRef: React.RefObject<HTMLElement>;
@@ -14,7 +12,6 @@ interface UseScrollBasedChunksProps {
1412
}
1513

1614
const DEFAULT_OVERSCAN_COUNT = 1;
17-
const THROTTLE_DELAY = 100;
1815

1916
export const useScrollBasedChunks = ({
2017
parentRef,
@@ -54,38 +51,45 @@ export const useScrollBasedChunks = ({
5451
return {start, end};
5552
}, [parentRef, tableRef, rowHeight, chunkSize, overscanCount, chunksCount]);
5653

57-
React.useEffect(() => {
54+
const updateVisibleChunks = React.useCallback(() => {
5855
const newRange = calculateVisibleRange();
59-
6056
if (newRange) {
6157
setStartChunk(newRange.start);
6258
setEndChunk(newRange.end);
6359
}
64-
}, [chunksCount, calculateVisibleRange]);
60+
}, [calculateVisibleRange]);
61+
62+
React.useEffect(() => {
63+
updateVisibleChunks();
64+
}, [chunksCount, updateVisibleChunks]);
6565

6666
const handleScroll = React.useCallback(() => {
67-
const newRange = calculateVisibleRange();
68-
if (newRange) {
69-
setStartChunk(newRange.start);
70-
setEndChunk(newRange.end);
71-
}
72-
}, [calculateVisibleRange]);
67+
updateVisibleChunks();
68+
}, [updateVisibleChunks]);
69+
70+
React.useEffect(() => {
71+
const throttledHandleZoom = rafThrottle(() => {
72+
updateVisibleChunks();
73+
});
74+
75+
window.addEventListener('resize', throttledHandleZoom);
76+
77+
return () => {
78+
window.removeEventListener('resize', throttledHandleZoom);
79+
};
80+
}, [updateVisibleChunks]);
7381

7482
React.useEffect(() => {
7583
const container = parentRef?.current;
7684
if (!container) {
7785
return undefined;
7886
}
7987

80-
const throttledHandleScroll = throttle(handleScroll, THROTTLE_DELAY, {
81-
leading: true,
82-
trailing: true,
83-
});
88+
const throttledHandleScroll = rafThrottle(handleScroll);
8489

8590
container.addEventListener('scroll', throttledHandleScroll);
8691
return () => {
8792
container.removeEventListener('scroll', throttledHandleScroll);
88-
throttledHandleScroll.cancel();
8993
};
9094
}, [handleScroll, parentRef]);
9195

src/containers/Storage/StorageNodes/getNodes.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {prepareSortValue} from '../../../utils/filters';
1313
import {getUptimeParamValue} from '../../../utils/nodes';
1414
import {getRequiredDataFields} from '../../../utils/tableUtils/getRequiredDataFields';
1515

16+
import {generateNodes} from './nodes';
17+
1618
export const getStorageNodes: FetchData<
1719
PreparedStorageNode,
1820
PreparedStorageNodeFilters,
@@ -43,23 +45,31 @@ export const getStorageNodes: FetchData<
4345
const sort = sortField ? prepareSortValue(sortField, sortOrder) : undefined;
4446

4547
const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);
46-
47-
const response = await window.api.viewer.getNodes({
48-
type,
49-
storage,
50-
limit,
51-
offset,
52-
sort,
53-
filter: searchValue,
54-
uptime: getUptimeParamValue(nodesUptimeFilter),
55-
with: visibleEntities,
56-
database,
57-
node_id: nodeId,
58-
group_id: groupId,
59-
filter_group: filterGroup,
60-
filter_group_by: filterGroupBy,
61-
fieldsRequired: dataFieldsRequired,
62-
});
48+
let response;
49+
const urlParams = new URLSearchParams(window.location.search);
50+
if (urlParams.get('mocks')) {
51+
// Get mock configuration from URL parameters or use defaults
52+
const pdisks = parseInt(urlParams.get('pdisks') || '10', 10);
53+
const vdisksPerPDisk = parseInt(urlParams.get('vdisksPerPDisk') || '2', 10);
54+
response = generateNodes(5, {maxVdisksPerPDisk: vdisksPerPDisk, maxPdisks: pdisks});
55+
} else {
56+
response = await window.api.viewer.getNodes({
57+
type,
58+
storage,
59+
limit,
60+
offset,
61+
sort,
62+
filter: searchValue,
63+
uptime: getUptimeParamValue(nodesUptimeFilter),
64+
with: visibleEntities,
65+
database,
66+
node_id: nodeId,
67+
group_id: groupId,
68+
filter_group: filterGroup,
69+
filter_group_by: filterGroupBy,
70+
fieldsRequired: dataFieldsRequired,
71+
});
72+
}
6373
const preparedResponse = prepareStorageNodesResponse(response);
6474
return {
6575
data: preparedResponse.nodes || [],
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import {EFlag} from '../../../types/api/enums';
2+
import type {
3+
TEndpoint,
4+
TNodeInfo,
5+
TNodesInfo,
6+
TPoolStats,
7+
TSystemStateInfo,
8+
} from '../../../types/api/nodes';
9+
import {TPDiskState} from '../../../types/api/pdisk';
10+
import {EVDiskState} from '../../../types/api/vdisk';
11+
12+
// Different disk sizes to simulate variety (in bytes)
13+
const DISK_SIZES = [
14+
'68719476736', // 64 GB
15+
'137438953472', // 128 GB
16+
'274877906944', // 256 GB
17+
'549755813888', // 512 GB
18+
'1099511627776', // 1 TB
19+
];
20+
21+
const getRandomDiskSize = () => DISK_SIZES[Math.floor(Math.random() * DISK_SIZES.length)];
22+
23+
const generatePoolStats = (count = 5): TPoolStats[] => {
24+
const poolNames = ['System', 'User', 'Batch', 'IO', 'IC'] as const;
25+
return poolNames.slice(0, count).map((Name) => ({
26+
Name,
27+
Usage: Math.random() * 0.02,
28+
Threads: Math.floor(Math.random() * 3) + 1,
29+
}));
30+
};
31+
32+
const generateEndpoints = (): TEndpoint[] => [
33+
{Name: 'ic', Address: ':19001'},
34+
{Name: 'http-mon', Address: ':8765'},
35+
{Name: 'grpcs', Address: ':2135'},
36+
{Name: 'grpc', Address: ':2136'},
37+
];
38+
39+
const generateSystemState = (nodeId: number): TSystemStateInfo => ({
40+
StartTime: '1734358137851',
41+
ChangeTime: '1734358421375',
42+
LoadAverage: [3.381347656, 2.489257813, 1.279296875],
43+
NumberOfCpus: 8,
44+
SystemState: EFlag.Green,
45+
NodeId: nodeId,
46+
Host: `localhost-${nodeId}`,
47+
Version: 'main.95ce0df',
48+
PoolStats: generatePoolStats(),
49+
Endpoints: generateEndpoints(),
50+
Roles: ['Bootstrapper', 'StateStorage', 'StateStorageBoard', 'SchemeBoard', 'Storage'],
51+
MemoryLimit: '2147483648',
52+
MaxDiskUsage: 0.002349853516,
53+
Location: {
54+
DataCenter: '1',
55+
Rack: '1',
56+
Unit: '1',
57+
},
58+
TotalSessions: 0,
59+
CoresUsed: 0.07583969556,
60+
CoresTotal: 8,
61+
});
62+
63+
const generatePDisk = (nodeId: number, pdiskId: number, totalSize = '68719476736') => ({
64+
PDiskId: pdiskId,
65+
ChangeTime: '1734358142074',
66+
Path: `/ydb_data/pdisk${pdiskId}l3ki78no.data`,
67+
Guid: pdiskId.toString(),
68+
Category: '0',
69+
TotalSize: totalSize,
70+
AvailableSize: (Number(totalSize) * 0.9).toString(), // 90% available by default
71+
State: TPDiskState.Normal,
72+
NodeId: nodeId,
73+
Device: EFlag.Green,
74+
Realtime: EFlag.Green,
75+
SerialNumber: '',
76+
SystemSize: '213909504',
77+
LogUsedSize: '35651584',
78+
LogTotalSize: '68486692864',
79+
EnforcedDynamicSlotSize: '22817013760',
80+
});
81+
82+
const generateVDisk = (nodeId: number, vdiskId: number, pdiskId: number) => ({
83+
VDiskId: {
84+
GroupID: vdiskId,
85+
GroupGeneration: 1,
86+
Ring: 0,
87+
Domain: 0,
88+
VDisk: 0,
89+
},
90+
ChangeTime: '1734358420919',
91+
PDiskId: pdiskId,
92+
VDiskSlotId: vdiskId,
93+
Guid: '1',
94+
Kind: '0',
95+
NodeId: nodeId,
96+
VDiskState: EVDiskState.OK,
97+
DiskSpace: EFlag.Green,
98+
SatisfactionRank: {
99+
FreshRank: {
100+
Flag: EFlag.Green,
101+
},
102+
LevelRank: {
103+
Flag: EFlag.Green,
104+
},
105+
},
106+
Replicated: true,
107+
ReplicationProgress: 1,
108+
ReplicationSecondsRemaining: 0,
109+
AllocatedSize: '0',
110+
AvailableSize: '22817013760',
111+
HasUnreadableBlobs: false,
112+
IncarnationGuid: '11528832187803248876',
113+
InstanceGuid: '14836434871903384493',
114+
FrontQueues: EFlag.Green,
115+
StoragePoolName: 'static',
116+
ReadThroughput: '0',
117+
WriteThroughput: '420',
118+
});
119+
120+
interface NodeGeneratorOptions {
121+
maxVdisksPerPDisk?: number;
122+
maxPdisks?: number;
123+
}
124+
125+
const DEFAULT_OPTIONS: NodeGeneratorOptions = {
126+
maxVdisksPerPDisk: 3,
127+
maxPdisks: 4,
128+
};
129+
130+
const generateNode = (nodeId: number, options: NodeGeneratorOptions = {}): TNodeInfo => {
131+
const maxPdisks = options.maxPdisks ?? DEFAULT_OPTIONS.maxPdisks!;
132+
const maxVdisksPerPDisk = options.maxVdisksPerPDisk ?? DEFAULT_OPTIONS.maxVdisksPerPDisk!;
133+
134+
// Generate a random number of pdisks up to maxPdisks
135+
const pdisksCount = Math.floor(Math.random() * maxPdisks) + 1;
136+
137+
// For each pdisk, generate a random number of vdisks up to maxVdisksPerPDisk
138+
const pdiskVdisksCounts = Array.from({length: pdisksCount}, () =>
139+
Math.floor(Math.random() * maxVdisksPerPDisk),
140+
);
141+
const totalVdisks = pdiskVdisksCounts.reduce((sum: number, count: number) => sum + count, 0);
142+
143+
return {
144+
NodeId: nodeId,
145+
UptimeSeconds: 284,
146+
CpuUsage: 0.00947996,
147+
DiskSpaceUsage: 0.234985,
148+
SystemState: generateSystemState(nodeId),
149+
PDisks: Array.from({length: pdisksCount}, (_, i) =>
150+
generatePDisk(nodeId, i + 1, getRandomDiskSize()),
151+
),
152+
VDisks: Array.from({length: totalVdisks}, (_, i) => {
153+
// Find which pdisk this vdisk belongs to based on the distribution
154+
let pdiskIndex = 0;
155+
let vdiskCount = pdiskVdisksCounts[0];
156+
while (i >= vdiskCount && pdiskIndex < pdisksCount - 1) {
157+
pdiskIndex++;
158+
vdiskCount += pdiskVdisksCounts[pdiskIndex];
159+
}
160+
return generateVDisk(nodeId, i, pdiskIndex + 1);
161+
}),
162+
};
163+
};
164+
165+
export const generateNodes = (count = 1, options?: NodeGeneratorOptions): TNodesInfo => {
166+
const nodes = Array.from({length: count}, (_, i) => generateNode(i + 1, options));
167+
168+
// Calculate MaximumSlotsPerDisk as the maximum number of vdisks on any pdisk
169+
let maxSlotsPerDisk = 0;
170+
nodes.forEach((node) => {
171+
if (node.VDisks) {
172+
const pdiskVdiskCounts = new Map<number, number>();
173+
node.VDisks.forEach((vdisk) => {
174+
if (typeof vdisk.PDiskId === 'number') {
175+
const count = (pdiskVdiskCounts.get(vdisk.PDiskId) || 0) + 1;
176+
pdiskVdiskCounts.set(vdisk.PDiskId, count);
177+
maxSlotsPerDisk = Math.max(maxSlotsPerDisk, count);
178+
}
179+
});
180+
}
181+
});
182+
183+
return {
184+
TotalNodes: count.toString(),
185+
FoundNodes: count.toString(),
186+
Nodes: nodes,
187+
MaximumSlotsPerDisk: maxSlotsPerDisk.toString(),
188+
};
189+
};

0 commit comments

Comments
 (0)