Skip to content

Commit 0dae9d8

Browse files
feat(Diagnostics): rework Consumers tab
1 parent 914702b commit 0dae9d8

File tree

16 files changed

+391
-79
lines changed

16 files changed

+391
-79
lines changed
Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
1-
.ydb-consumers {
1+
@import '../../../../styles/mixins.scss';
2+
3+
.ydb-diagnostics-consumers {
4+
overflow: auto;
5+
flex-grow: 1;
6+
7+
height: 100%;
8+
9+
@include flex-container();
10+
11+
&__controls {
12+
@include controls();
13+
}
14+
215
&__search {
3-
width: 200px;
4-
margin-bottom: 20px;
16+
@include search();
17+
}
18+
19+
&__table-settings .yc-icon {
20+
width: 20px;
21+
}
22+
23+
&__table-wrapper {
24+
overflow: auto;
25+
@include flex-container();
26+
}
27+
28+
&__table-content {
29+
overflow: auto;
30+
31+
height: 100%;
32+
33+
@include freeze-nth-column(1);
534
}
635
}
Lines changed: 62 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,118 @@
1-
import {useCallback, useEffect, useState} from 'react';
1+
import {useCallback, useMemo, useState} from 'react';
22
import {useDispatch} from 'react-redux';
33
import block from 'bem-cn-lite';
44
import {escapeRegExp} from 'lodash/fp';
55

6-
import DataTable, {Column} from '@gravity-ui/react-data-table';
6+
import DataTable from '@gravity-ui/react-data-table';
77

88
import type {EPathType} from '../../../../types/api/schema';
9+
910
import {Loader} from '../../../../components/Loader';
10-
import {prepareQueryError} from '../../../../utils/query';
11-
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
12-
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
1311
import {Search} from '../../../../components/Search';
12+
import {ResponseError} from '../../../../components/Errors/ResponseError';
13+
14+
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
15+
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
16+
1417
import {
15-
getDescribe,
16-
selectConsumers,
17-
setCurrentDescribePath,
18+
selectPreparedConsumersData,
19+
selectPreparedTopicStats,
20+
getTopic,
1821
setDataWasNotLoaded,
19-
} from '../../../../store/reducers/describe';
20-
import {selectSchemaMergedChildrenPaths} from '../../../../store/reducers/schema';
22+
} from '../../../../store/reducers/topic';
2123

2224
import {isCdcStreamEntityType} from '../../utils/schema';
2325

26+
import {ConsumersTopicStats} from './TopicStats';
27+
import {columns} from './columns';
28+
2429
import i18n from './i18n';
2530

2631
import './Consumers.scss';
2732

28-
const b = block('ydb-consumers');
33+
const b = block('ydb-diagnostics-consumers');
2934

3035
interface ConsumersProps {
3136
path: string;
3237
type?: EPathType;
3338
}
3439

3540
export const Consumers = ({path, type}: ConsumersProps) => {
41+
const isCdcStream = isCdcStreamEntityType(type);
42+
3643
const dispatch = useDispatch();
3744

38-
const isCdcStream = isCdcStreamEntityType(type);
45+
const [searchValue, setSearchValue] = useState('');
3946

40-
const mergedChildrenPaths = useTypedSelector((state) =>
41-
selectSchemaMergedChildrenPaths(state, path, type),
42-
);
47+
const {autorefresh} = useTypedSelector((state) => state.schema);
48+
const {loading, wasLoaded, error} = useTypedSelector((state) => state.topic);
4349

44-
const dataPath = isCdcStream ? mergedChildrenPaths?.[0] : path;
50+
const consumers = useTypedSelector((state) => selectPreparedConsumersData(state));
51+
const topic = useTypedSelector((state) => selectPreparedTopicStats(state));
4552

4653
const fetchData = useCallback(
47-
(isBackground: boolean) => {
54+
(isBackground) => {
4855
if (!isBackground) {
49-
dispatch(setDataWasNotLoaded());
56+
dispatch(setDataWasNotLoaded);
5057
}
5158

52-
if (dataPath) {
53-
dispatch(setCurrentDescribePath(dataPath));
54-
dispatch(getDescribe({path: dataPath}));
55-
}
59+
dispatch(getTopic(path));
5660
},
57-
58-
[dispatch, dataPath],
61+
[dispatch, path],
5962
);
6063

61-
const {autorefresh} = useTypedSelector((state) => state.schema);
62-
6364
useAutofetcher(fetchData, [fetchData], autorefresh);
6465

65-
const {loading, wasLoaded, error} = useTypedSelector((state) => state.describe);
66-
const consumers = useTypedSelector((state) => selectConsumers(state, dataPath));
66+
const dataToRender = useMemo(() => {
67+
if (!consumers) {
68+
return [];
69+
}
6770

68-
const [consumersToRender, setConsumersToRender] = useState(consumers);
71+
const searchRe = new RegExp(escapeRegExp(searchValue), 'i');
6972

70-
useEffect(() => {
71-
setConsumersToRender(consumers);
72-
}, [consumers]);
73+
return consumers.filter((consumer) => {
74+
return searchRe.test(String(consumer.name));
75+
});
76+
}, [consumers, searchValue]);
7377

74-
const filterConsumersByName = (search: string) => {
75-
const filteredConsumers = search
76-
? consumers.filter((consumer) => {
77-
const re = new RegExp(escapeRegExp(search), 'i');
78-
return re.test(consumer.name);
79-
})
80-
: consumers;
81-
82-
setConsumersToRender(filteredConsumers);
83-
};
84-
85-
const handleSearch = (value: string) => {
86-
filterConsumersByName(value);
78+
const handleSearchChange = (value: string) => {
79+
setSearchValue(value);
8780
};
8881

89-
const columns: Column<any>[] = [
90-
{
91-
name: 'name',
92-
header: i18n('table.columns.consumerName'),
93-
width: 200,
94-
},
95-
];
96-
9782
if (loading && !wasLoaded) {
9883
return <Loader size="m" />;
9984
}
10085

101-
if (!loading && error) {
102-
return <div className={b('message', 'error')}>{prepareQueryError(error)}</div>;
86+
if (error) {
87+
return <ResponseError error={error} />;
10388
}
10489

105-
if (consumers.length === 0) {
106-
return <div className={b('message')}>{i18n('noConsumersMessage')}</div>;
90+
if (!consumers || !consumers.length) {
91+
return <div>{i18n(`noConsumersMessage.${isCdcStream ? 'stream' : 'topic'}`)}</div>;
10792
}
10893

10994
return (
11095
<div className={b()}>
111-
<Search
112-
onChange={handleSearch}
113-
placeholder={i18n('search.placeholder')}
114-
className={b('search')}
115-
/>
116-
<DataTable
117-
theme="yandex-cloud"
118-
settings={DEFAULT_TABLE_SETTINGS}
119-
columns={columns}
120-
data={consumersToRender}
121-
emptyDataMessage={i18n('table.emptyDataMessage')}
122-
/>
96+
<div className={b('controls')}>
97+
<Search
98+
onChange={handleSearchChange}
99+
placeholder={i18n('controls.search')}
100+
className={b('search')}
101+
value={searchValue}
102+
/>
103+
{topic && <ConsumersTopicStats data={topic} />}
104+
</div>
105+
<div className={b('table-wrapper')}>
106+
<div className={b('table-content')}>
107+
<DataTable
108+
theme="yandex-cloud"
109+
data={dataToRender}
110+
columns={columns}
111+
settings={DEFAULT_TABLE_SETTINGS}
112+
emptyDataMessage={i18n('table.emptyDataMessage')}
113+
/>
114+
</div>
115+
</div>
123116
</div>
124117
);
125118
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.ydb-diagnostics-consumers-columns-header {
2+
&__lags {
3+
white-space: nowrap;
4+
}
5+
6+
&__lags-popover-content {
7+
max-width: 300px;
8+
9+
div:nth-child(1) {
10+
margin-bottom: 10px;
11+
}
12+
}
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import block from 'bem-cn-lite';
2+
3+
import {ReadLagImage} from '../../../../../components/LagImages';
4+
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
5+
6+
import {CONSUMERS_COLUMNS_IDS, CONSUMERS_COLUMNS_TITILES} from '../utils/constants';
7+
8+
import i18n from '../i18n';
9+
10+
import './Headers.scss';
11+
12+
const b = block('ydb-diagnostics-consumers-columns-header');
13+
14+
export const ReadLagsHeader = () => (
15+
<LabelWithPopover
16+
className={b('lags')}
17+
headerText={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
18+
popoverContent={
19+
<div className={b('lags-popover-content')}>
20+
<div>{i18n('lagsPopover.readLags')}</div>
21+
<div>
22+
<ReadLagImage />
23+
</div>
24+
</div>
25+
}
26+
/>
27+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Headers';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.ydb-diagnostics-consumers-topic-stats {
2+
font-size: var(--yc-text-body-2-font-size);
3+
line-height: var(--yc-text-body-2-line-height);
4+
5+
&__wrapper {
6+
display: flex;
7+
flex-direction: row;
8+
9+
padding-left: 16px;
10+
11+
border-left: 1px solid var(--yc-color-line-generic);
12+
}
13+
14+
&__item {
15+
display: flex;
16+
flex-direction: column;
17+
18+
margin-right: 20px;
19+
}
20+
&__label {
21+
margin-bottom: 4px;
22+
23+
color: var(--yc-color-text-secondary);
24+
}
25+
&__value {
26+
display: flex;
27+
justify-content: flex-end;
28+
align-items: center;
29+
30+
height: 30px;
31+
}
32+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import block from 'bem-cn-lite';
2+
3+
import type {IPreparedTopicStats} from '../../../../../types/store/topic';
4+
import {formatMsToUptime} from '../../../../../utils';
5+
import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
6+
7+
import './ConsumersTopicStats.scss';
8+
9+
const b = block('ydb-diagnostics-consumers-topic-stats');
10+
11+
interface ConsumersTopicStatsProps {
12+
data?: IPreparedTopicStats;
13+
}
14+
15+
export const ConsumersTopicStats = ({data}: ConsumersTopicStatsProps) => {
16+
const {writeSpeed, partitionsWriteLag, partitionsIdleTime} = data || {};
17+
18+
const values = [
19+
{
20+
label: 'Write speed',
21+
value: <SpeedMultiMeter data={writeSpeed} />,
22+
},
23+
{
24+
label: 'Write lag',
25+
value: formatMsToUptime(partitionsWriteLag || 0),
26+
},
27+
{
28+
label: 'Write idle time',
29+
value: formatMsToUptime(partitionsIdleTime || 0),
30+
},
31+
];
32+
33+
return (
34+
<div className={b('wrapper')}>
35+
{values.map((value, index) => (
36+
<div key={index} className={b('item')}>
37+
<div className={b('label')}>{value.label}</div>
38+
<div className={b('value')}>{value.value}</div>
39+
</div>
40+
))}
41+
</div>
42+
);
43+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ConsumersTopicStats';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.ydb-diagnostics-consumers-columns {
2+
&__lags-header {
3+
text-align: center;
4+
}
5+
}

0 commit comments

Comments
 (0)