Skip to content

Commit e5d0823

Browse files
authored
feat: implement simple and narrow vertical progress bar (#1560)
1 parent 3287f99 commit e5d0823

File tree

18 files changed

+206
-18
lines changed

18 files changed

+206
-18
lines changed

src/components/CellWithPopover/CellWithPopover.scss

+4
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,9 @@
1616
.g-popover__handler {
1717
display: inline;
1818
}
19+
20+
&_full-width {
21+
width: 100%;
22+
}
1923
}
2024
}

src/components/CellWithPopover/CellWithPopover.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const b = cn('ydb-cell-with-popover');
99

1010
interface CellWithPopoverProps extends PopoverProps {
1111
wrapperClassName?: string;
12+
fullWidth?: boolean;
1213
}
1314

1415
const DELAY_TIMEOUT = 100;
@@ -17,14 +18,15 @@ export function CellWithPopover({
1718
children,
1819
className,
1920
wrapperClassName,
21+
fullWidth,
2022
...props
2123
}: CellWithPopoverProps) {
2224
return (
23-
<div className={b(null, wrapperClassName)}>
25+
<div className={b({fullWidth}, wrapperClassName)}>
2426
<Popover
2527
delayClosing={DELAY_TIMEOUT}
2628
delayOpening={DELAY_TIMEOUT}
27-
className={b('popover', className)}
29+
className={b('popover', {'full-width': fullWidth}, className)}
2830
{...props}
2931
>
3032
{children}

src/components/ProgressViewer/ProgressViewer.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface ProgressViewerProps {
4848
inverseColorize?: boolean;
4949
warningThreshold?: number;
5050
dangerThreshold?: number;
51+
hideCapacity?: boolean;
5152
}
5253

5354
export function ProgressViewer({
@@ -61,6 +62,7 @@ export function ProgressViewer({
6162
inverseColorize,
6263
warningThreshold = 60,
6364
dangerThreshold = 80,
65+
hideCapacity,
6466
}: ProgressViewerProps) {
6567
const theme = useTheme();
6668

@@ -94,7 +96,7 @@ export function ProgressViewer({
9496
};
9597

9698
const renderContent = () => {
97-
if (isNumeric(capacity)) {
99+
if (isNumeric(capacity) && !hideCapacity) {
98100
return `${valueText} ${divider} ${capacityText}`;
99101
}
100102

src/components/TooltipsContent/PoolTooltipContent/PoolTooltipContent.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {TPoolStats} from '../../../types/api/nodes';
22
import {InfoViewer, createInfoFormatter, formatObject} from '../../InfoViewer';
33

4-
const formatPool = createInfoFormatter<TPoolStats>({
4+
export const formatPool = createInfoFormatter<TPoolStats>({
55
values: {
66
Usage: (value) => value && `${(Number(value) * 100).toFixed(2)} %`,
77
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.ydb-nodes-columns {
2+
&__column-ram,
3+
&__column-cpu {
4+
min-width: 40px;
5+
}
6+
}

src/components/nodesColumns/columns.tsx

+126-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
import DataTable from '@gravity-ui/react-data-table';
2+
import {DefinitionList} from '@gravity-ui/uikit';
23

34
import {getLoadSeverityForNode} from '../../store/reducers/nodes/utils';
45
import type {TPoolStats} from '../../types/api/nodes';
56
import type {TTabletStateInfo} from '../../types/api/tablet';
67
import {valueIsDefined} from '../../utils';
8+
import {cn} from '../../utils/cn';
79
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
8-
import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters';
10+
import {
11+
formatStorageValues,
12+
formatStorageValuesToGb,
13+
} from '../../utils/dataFormatters/dataFormatters';
914
import {getSpaceUsageSeverity} from '../../utils/storage';
1015
import type {Column} from '../../utils/tableUtils/types';
16+
import {isNumeric} from '../../utils/utils';
1117
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
1218
import {NodeHostWrapper} from '../NodeHostWrapper/NodeHostWrapper';
1319
import type {NodeHostData} from '../NodeHostWrapper/NodeHostWrapper';
1420
import {PoolsGraph} from '../PoolsGraph/PoolsGraph';
1521
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
1622
import {TabletsStatistic} from '../TabletsStatistic';
23+
import {formatPool} from '../TooltipsContent';
1724
import {UsageLabel} from '../UsageLabel/UsageLabel';
1825

1926
import {NODES_COLUMNS_IDS, NODES_COLUMNS_TITLES} from './constants';
27+
import i18n from './i18n';
2028
import type {GetNodesColumnsParams} from './types';
2129

30+
import './NodesColumns.scss';
31+
32+
const b = cn('ydb-nodes-columns');
33+
2234
export function getNodeIdColumn<T extends {NodeId?: string | number}>(): Column<T> {
2335
return {
2436
name: NODES_COLUMNS_IDS.NodeId,
@@ -111,6 +123,57 @@ export function getMemoryColumn<
111123
resizeMinWidth: 170,
112124
};
113125
}
126+
127+
export function getRAMColumn<T extends {MemoryUsed?: string; MemoryLimit?: string}>(): Column<T> {
128+
return {
129+
name: NODES_COLUMNS_IDS.RAM,
130+
header: NODES_COLUMNS_TITLES.RAM,
131+
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
132+
defaultOrder: DataTable.DESCENDING,
133+
render: ({row}) => {
134+
const [memoryUsed, memoryLimit] =
135+
isNumeric(row.MemoryUsed) && isNumeric(row.MemoryLimit)
136+
? formatStorageValues(
137+
Number(row.MemoryUsed),
138+
Number(row.MemoryLimit),
139+
'gb',
140+
undefined,
141+
true,
142+
)
143+
: [0, 0];
144+
return (
145+
<CellWithPopover
146+
placement={['top', 'auto']}
147+
fullWidth
148+
content={
149+
<DefinitionList responsive>
150+
<DefinitionList.Item name={i18n('field_memory-used')}>
151+
{memoryUsed}
152+
</DefinitionList.Item>
153+
<DefinitionList.Item name={i18n('field_memory-limit')}>
154+
{memoryLimit}
155+
</DefinitionList.Item>
156+
</DefinitionList>
157+
}
158+
>
159+
<ProgressViewer
160+
value={row.MemoryUsed}
161+
capacity={row.MemoryLimit}
162+
formatValues={(value, total) =>
163+
formatStorageValues(value, total, 'gb', undefined, true)
164+
}
165+
className={b('column-ram')}
166+
colorizeProgress
167+
hideCapacity
168+
/>
169+
</CellWithPopover>
170+
);
171+
},
172+
align: DataTable.LEFT,
173+
width: 80,
174+
resizeMinWidth: 40,
175+
};
176+
}
114177
export function getSharedCacheUsageColumn<
115178
T extends {SharedCacheUsed?: string | number; SharedCacheLimit?: string | number},
116179
>(): Column<T> {
@@ -130,10 +193,10 @@ export function getSharedCacheUsageColumn<
130193
resizeMinWidth: 170,
131194
};
132195
}
133-
export function getCpuColumn<T extends {PoolStats?: TPoolStats[]}>(): Column<T> {
196+
export function getPoolsColumn<T extends {PoolStats?: TPoolStats[]}>(): Column<T> {
134197
return {
135-
name: NODES_COLUMNS_IDS.CPU,
136-
header: NODES_COLUMNS_TITLES.CPU,
198+
name: NODES_COLUMNS_IDS.Pools,
199+
header: NODES_COLUMNS_TITLES.Pools,
137200
sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
138201
defaultOrder: DataTable.DESCENDING,
139202
render: ({row}) =>
@@ -143,6 +206,65 @@ export function getCpuColumn<T extends {PoolStats?: TPoolStats[]}>(): Column<T>
143206
resizeMinWidth: 60,
144207
};
145208
}
209+
export function getCpuColumn<
210+
T extends {PoolStats?: TPoolStats[]; CoresUsed?: number; CoresTotal?: number},
211+
>(): Column<T> {
212+
return {
213+
name: NODES_COLUMNS_IDS.CPU,
214+
header: NODES_COLUMNS_TITLES.CPU,
215+
sortAccessor: ({PoolStats = []}) => Math.max(...PoolStats.map(({Usage}) => Number(Usage))),
216+
defaultOrder: DataTable.DESCENDING,
217+
render: ({row}) => {
218+
if (!row.PoolStats) {
219+
return EMPTY_DATA_PLACEHOLDER;
220+
}
221+
222+
let totalPoolUsage =
223+
isNumeric(row.CoresUsed) && isNumeric(row.CoresTotal)
224+
? row.CoresUsed / row.CoresTotal
225+
: undefined;
226+
227+
if (totalPoolUsage === undefined) {
228+
let totalThreadsCount = 0;
229+
totalPoolUsage = row.PoolStats.reduce((acc, pool) => {
230+
totalThreadsCount += Number(pool.Threads);
231+
return acc + Number(pool.Usage) * Number(pool.Threads);
232+
}, 0);
233+
234+
totalPoolUsage = totalPoolUsage / totalThreadsCount;
235+
}
236+
237+
return (
238+
<CellWithPopover
239+
placement={['top', 'auto']}
240+
fullWidth
241+
content={
242+
<DefinitionList responsive>
243+
{row.PoolStats.map((pool) =>
244+
isNumeric(pool.Usage) ? (
245+
<DefinitionList.Item key={pool.Name} name={pool.Name}>
246+
{formatPool('Usage', pool.Usage).value}
247+
</DefinitionList.Item>
248+
) : null,
249+
)}
250+
</DefinitionList>
251+
}
252+
>
253+
<ProgressViewer
254+
className={b('column-cpu')}
255+
value={totalPoolUsage}
256+
capacity={1}
257+
colorizeProgress
258+
percents
259+
/>
260+
</CellWithPopover>
261+
);
262+
},
263+
align: DataTable.LEFT,
264+
width: 80,
265+
resizeMinWidth: 40,
266+
};
267+
}
146268
export function getLoadAverageColumn<T extends {LoadAveragePercents?: number[]}>(): Column<T> {
147269
return {
148270
name: NODES_COLUMNS_IDS.LoadAverage,

src/components/nodesColumns/constants.ts

+10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export const NODES_COLUMNS_IDS = {
1414
Version: 'Version',
1515
Uptime: 'Uptime',
1616
Memory: 'Memory',
17+
RAM: 'RAM',
1718
CPU: 'CPU',
19+
Pools: 'Pools',
1820
LoadAverage: 'LoadAverage',
1921
Load: 'Load',
2022
DiskSpaceUsage: 'DiskSpaceUsage',
@@ -54,6 +56,12 @@ export const NODES_COLUMNS_TITLES = {
5456
get Memory() {
5557
return i18n('memory');
5658
},
59+
get RAM() {
60+
return i18n('ram');
61+
},
62+
get Pools() {
63+
return i18n('pools');
64+
},
5765
get CPU() {
5866
return i18n('cpu');
5967
},
@@ -94,6 +102,8 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
94102
Version: ['Version'],
95103
Uptime: ['Uptime'],
96104
Memory: ['Memory'],
105+
RAM: ['Memory'],
106+
Pools: ['CPU'],
97107
CPU: ['CPU'],
98108
LoadAverage: ['LoadAverage'],
99109
Load: ['LoadAverage'],

src/components/nodesColumns/i18n/en.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
"version": "Version",
88
"uptime": "Uptime",
99
"memory": "Memory",
10+
"ram": "RAM",
1011
"cpu": "CPU",
12+
"pools": "Pools",
1113
"disk-usage": "Disk usage",
1214
"tablets": "Tablets",
1315
"load-average": "Load Average",
1416
"load": "Load",
1517
"caches": "Caches",
1618
"sessions": "Sessions",
1719
"missing": "Missing",
18-
"pdisks": "PDisks"
20+
"pdisks": "PDisks",
21+
"field_memory-used": "Memory used",
22+
"field_memory-limit": "Memory limit"
1923
}

src/containers/Nodes/columns/columns.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
getMemoryColumn,
77
getNodeIdColumn,
88
getNodeNameColumn,
9+
getPoolsColumn,
10+
getRAMColumn,
911
getRackColumn,
1012
getTabletsColumn,
1113
getUptimeColumn,
@@ -26,6 +28,8 @@ export function getNodesColumns(params: GetNodesColumnsParams): Column<NodesPrep
2628
getVersionColumn<NodesPreparedEntity>(),
2729
getUptimeColumn<NodesPreparedEntity>(),
2830
getMemoryColumn<NodesPreparedEntity>(),
31+
getRAMColumn<NodesPreparedEntity>(),
32+
getPoolsColumn<NodesPreparedEntity>(),
2933
getCpuColumn<NodesPreparedEntity>(),
3034
getLoadAverageColumn<NodesPreparedEntity>(),
3135
getTabletsColumn<NodesPreparedEntity>(params),

src/containers/Nodes/columns/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const DEFAULT_NODES_COLUMNS: NodesColumnId[] = [
1010
'Version',
1111
'Uptime',
1212
'Memory',
13-
'CPU',
13+
'Pools',
1414
'LoadAverage',
1515
'Tablets',
1616
];

src/containers/Nodes/getNodes.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {prepareNodesData} from '../../store/reducers/nodes/utils';
55
import type {NodesRequestParams} from '../../types/api/nodes';
66
import {prepareSortValue} from '../../utils/filters';
77
import {
8+
NODES_SORT_VALUE_TO_FIELD,
89
getProblemParamValue,
910
getUptimeParamValue,
1011
isSortableNodesProperty,
@@ -35,7 +36,7 @@ export const getNodes: FetchData<
3536
const {path, database, searchValue, problemFilter, uptimeFilter} = filters ?? {};
3637

3738
const sort = isSortableNodesProperty(columnId)
38-
? prepareSortValue(columnId, sortOrder)
39+
? prepareSortValue(NODES_SORT_VALUE_TO_FIELD[columnId], sortOrder)
3940
: undefined;
4041

4142
const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);

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

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
getMissingDisksColumn,
1010
getNodeIdColumn,
1111
getNodeNameColumn,
12+
getPoolsColumn,
13+
getRAMColumn,
1214
getRackColumn,
1315
getUptimeColumn,
1416
getVersionColumn,
@@ -72,6 +74,8 @@ export const getStorageNodesColumns = ({
7274
getRackColumn<PreparedStorageNode>(),
7375
getVersionColumn<PreparedStorageNode>(),
7476
getMemoryColumn<PreparedStorageNode>(),
77+
getRAMColumn<PreparedStorageNode>(),
78+
getPoolsColumn<PreparedStorageNode>(),
7579
getCpuColumn<PreparedStorageNode>(),
7680
getDiskSpaceUsageColumn<PreparedStorageNode>(),
7781
getUptimeColumn<PreparedStorageNode>(),

src/containers/Storage/StorageNodes/columns/constants.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const DEFAULT_STORAGE_NODES_COLUMNS: NodesColumnId[] = [
1313
'Host',
1414
'DC',
1515
'Rack',
16-
'CPU',
16+
'Pools',
1717
'Uptime',
1818
'PDisks',
1919
];

src/containers/Storage/StorageNodes/getNodes.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import type {
77
import {prepareStorageNodesResponse} from '../../../store/reducers/storage/utils';
88
import type {NodesRequestParams} from '../../../types/api/nodes';
99
import {prepareSortValue} from '../../../utils/filters';
10-
import {getUptimeParamValue, isSortableNodesProperty} from '../../../utils/nodes';
10+
import {
11+
NODES_SORT_VALUE_TO_FIELD,
12+
getUptimeParamValue,
13+
isSortableNodesProperty,
14+
} from '../../../utils/nodes';
1115
import {getRequiredDataFields} from '../../../utils/tableUtils/getRequiredDataFields';
1216

1317
export const getStorageNodes: FetchData<
@@ -37,7 +41,7 @@ export const getStorageNodes: FetchData<
3741
const {sortOrder, columnId} = sortParams ?? {};
3842

3943
const sort = isSortableNodesProperty(columnId)
40-
? prepareSortValue(columnId, sortOrder)
44+
? prepareSortValue(NODES_SORT_VALUE_TO_FIELD[columnId], sortOrder)
4145
: undefined;
4246

4347
const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);

0 commit comments

Comments
 (0)