Skip to content

Commit 39acbf1

Browse files
fix: add SpeedMultiMeter component
1 parent 2f18c22 commit 39acbf1

File tree

8 files changed

+263
-0
lines changed

8 files changed

+263
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
.speed-multimeter {
2+
display: flex;
3+
4+
width: 100%;
5+
6+
&__content {
7+
display: flex;
8+
flex-grow: 1;
9+
flex-direction: row;
10+
justify-content: flex-end;
11+
12+
line-height: 22px;
13+
}
14+
15+
&__displayed-value {
16+
display: flex;
17+
flex-direction: row;
18+
justify-content: flex-end;
19+
20+
margin-right: 10px;
21+
}
22+
23+
&__bars {
24+
display: flex;
25+
overflow: hidden;
26+
flex-direction: column;
27+
align-items: flex-start;
28+
29+
width: 32px;
30+
margin-right: 5px;
31+
}
32+
33+
&__bar-container {
34+
width: 100%;
35+
height: 6px;
36+
37+
&_highlighted {
38+
background: var(--yc-color-line-generic);
39+
}
40+
}
41+
42+
&__bar {
43+
min-width: 2px;
44+
height: 100%;
45+
46+
&_color_light {
47+
background: var(--yc-color-infographics-info-medium);
48+
}
49+
50+
&_color_dark {
51+
background: var(--yc-color-infographics-info-heavy);
52+
}
53+
}
54+
55+
&__bar-container + &__bar-container {
56+
margin-top: 2px;
57+
}
58+
59+
&__popover-container {
60+
display: flex;
61+
justify-content: center;
62+
align-items: center;
63+
}
64+
65+
&__popover-content {
66+
padding: 10px;
67+
}
68+
69+
&__popover-header {
70+
display: block;
71+
72+
margin-bottom: 7px;
73+
74+
font-size: 18px;
75+
line-height: 24px;
76+
}
77+
78+
&__popover-row {
79+
display: block;
80+
81+
font-size: 13px;
82+
line-height: 18px;
83+
84+
&_color_primary {
85+
color: var(--yc-color-text-primary);
86+
}
87+
88+
&_color_secondary {
89+
color: var(--yc-color-text-secondary);
90+
}
91+
}
92+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {useState} from 'react';
2+
import cn from 'bem-cn-lite';
3+
import {Popover} from '@gravity-ui/uikit';
4+
5+
import {formatBytesCustom, IBytesSizes, IProcessSpeedStats} from '../../utils/bytesParsers';
6+
7+
import './SpeedMultiMeter.scss';
8+
9+
import i18n from './i18n';
10+
11+
const b = cn('speed-multimeter');
12+
13+
interface SpeedMultiMeterProps {
14+
data?: IProcessSpeedStats;
15+
speedSize?: IBytesSizes;
16+
withValue?: boolean;
17+
withPopover?: boolean;
18+
}
19+
20+
export const SpeedMultiMeter = ({
21+
data,
22+
speedSize = 'kb',
23+
withValue = true,
24+
withPopover = true,
25+
}: SpeedMultiMeterProps) => {
26+
const {perMinute = 0, perHour = 0, perDay = 0} = data || {};
27+
const rawValues = [perMinute, perHour, perDay];
28+
29+
const formatValue = (value: number) =>
30+
formatBytesCustom({value: value, size: speedSize, isSpeed: true});
31+
32+
const formattedValues = [
33+
{value: formatValue(perMinute), label: i18n('perMinute')},
34+
{value: formatValue(perHour), label: i18n('perHour')},
35+
{value: formatValue(perDay), label: i18n('perDay')},
36+
];
37+
38+
const [valueToDisplay, setValueToDisplay] = useState(perMinute);
39+
const [highlightedValueIndex, setHighlightedValueIndex] = useState(withValue ? 0 : undefined);
40+
const [highlightedContainerIndex, setHighlightedContainerIndex] = useState<
41+
number | undefined
42+
>();
43+
44+
const onEnterDiagram = (values: number[], index: number) => {
45+
setValueToDisplay(values[index]);
46+
setHighlightedValueIndex(index);
47+
setHighlightedContainerIndex(index);
48+
};
49+
50+
const onLeaveDiagram = () => {
51+
setValueToDisplay(perMinute);
52+
setHighlightedValueIndex(withValue ? 0 : undefined);
53+
setHighlightedContainerIndex(undefined);
54+
};
55+
56+
const isValueHighlighted = (index: number) => highlightedValueIndex === index;
57+
const isContainerHighlighted = (index: number) => highlightedContainerIndex === index;
58+
59+
const getModifier = (flag: boolean) => (flag ? {color: 'primary'} : {color: 'secondary'});
60+
61+
const renderValues = () => {
62+
const max = Math.max(...rawValues, 0) || 1;
63+
64+
return rawValues.map((value, index) => (
65+
<div
66+
key={index}
67+
className={b('bar-container', {
68+
highlighted: isContainerHighlighted(index),
69+
})}
70+
onMouseEnter={onEnterDiagram.bind(null, rawValues, index)}
71+
>
72+
<div
73+
className={b('bar', {
74+
color: isValueHighlighted(index) ? 'dark' : 'light',
75+
})}
76+
style={{width: `${(100 * value) / max}%`}}
77+
/>
78+
</div>
79+
));
80+
};
81+
82+
const renderPopoverContent = () => {
83+
return (
84+
<div className={b('popover-content')}>
85+
<span className={b('popover-header')}>{i18n('averageSpeed')}</span>
86+
{formattedValues.map((formattedValue, index) => (
87+
<span
88+
key={index}
89+
className={b('popover-row', getModifier(isValueHighlighted(index)))}
90+
>
91+
{`${formattedValue.label}: ${formattedValue.value}`}
92+
</span>
93+
))}
94+
</div>
95+
);
96+
};
97+
98+
return (
99+
<div className={b()}>
100+
<div className={b('content')}>
101+
{withValue && (
102+
<div className={b('displayed-value')}>{formatValue(valueToDisplay)}</div>
103+
)}
104+
<div className={b('popover-container')}>
105+
<Popover
106+
content={renderPopoverContent()}
107+
placement={'bottom'}
108+
disabled={!withPopover}
109+
hasArrow={true}
110+
size="s"
111+
>
112+
<div className={b('bars')} onMouseLeave={onLeaveDiagram}>
113+
{renderValues()}
114+
</div>
115+
</Popover>
116+
</div>
117+
</div>
118+
</div>
119+
);
120+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"averageSpeed": "Average speed",
3+
"perMinute": "per minute",
4+
"perHour": "per hour",
5+
"perDay": "per day"
6+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {i18n, Lang} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
import ru from './ru.json';
5+
6+
const COMPONENT = 'ydb-components-speed-multimeter';
7+
8+
i18n.registerKeyset(Lang.En, COMPONENT, en);
9+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10+
11+
export default i18n.keyset(COMPONENT);
12+
13+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"averageSpeed": "Средняя скорость",
3+
"perMinute": "за минуту",
4+
"perHour": "за час",
5+
"perDay": "за день"
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './SpeedMultiMeter';
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type {MultipleWindowsStat} from '../../types/api/consumer';
2+
3+
import {DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS} from '../constants';
4+
5+
export interface IProcessSpeedStats {
6+
perMinute: number;
7+
perHour: number;
8+
perDay: number;
9+
}
10+
11+
/**
12+
* Convert data of type MultipleWindowsStat.
13+
* This data format is specific for describe_topic and describe_consumer endpoints
14+
*/
15+
export const convertBytesObjectToSpeed = (
16+
data: MultipleWindowsStat | undefined,
17+
): IProcessSpeedStats => {
18+
return {
19+
perMinute:
20+
data && data.per_minute ? Math.round(Number(data.per_minute) / MINUTE_IN_SECONDS) : 0,
21+
perHour: data && data.per_hour ? Math.round(Number(data.per_hour) / HOUR_IN_SECONDS) : 0,
22+
perDay: data && data.per_day ? Math.round(Number(data.per_day) / DAY_IN_SECONDS) : 0,
23+
};
24+
};

src/utils/bytesParsers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './formatBytesCustom';
2+
export * from './convertBytesObjectToSpeed';

0 commit comments

Comments
 (0)