ydb-embedded-ui 3.4.4 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/components/Icon/Icon.tsx +6 -0
- package/dist/components/InfoViewer/InfoViewer.tsx +2 -2
- package/dist/components/InfoViewer/formatters/index.ts +0 -1
- package/dist/components/InfoViewer/formatters/table.ts +6 -0
- package/dist/components/LabelWithPopover/LabelWithPopover.tsx +3 -7
- package/dist/components/LagPopoverContent/LagPopoverContent.scss +13 -0
- package/dist/components/LagPopoverContent/LagPopoverContent.tsx +19 -0
- package/dist/components/LagPopoverContent/index.ts +1 -0
- package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.scss +5 -0
- package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.tsx +31 -0
- package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
- package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
- package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
- package/dist/containers/Nodes/Nodes.tsx +2 -6
- package/dist/containers/Nodes/NodesTable.scss +11 -10
- package/dist/containers/Nodes/getNodesColumns.tsx +29 -24
- package/dist/containers/Storage/PDisk/PDisk.scss +2 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +6 -9
- package/dist/containers/Storage/Storage.js +12 -5
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -1
- package/dist/containers/Storage/StorageNodes/StorageNodes.scss +20 -7
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +43 -7
- package/dist/containers/Storage/VDisk/VDisk.tsx +3 -2
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +4 -2
- package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +0 -8
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +3 -10
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
- package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
- package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +51 -32
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +2 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +2 -1
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +0 -8
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +7 -21
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -0
- package/dist/routes.ts +1 -1
- package/dist/services/api.d.ts +4 -0
- package/dist/services/api.js +3 -3
- package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +51 -21
- package/dist/store/reducers/executeTopQueries.ts +5 -1
- package/dist/store/reducers/{nodesList.js → nodesList.ts} +19 -7
- package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
- package/dist/store/reducers/settings.js +1 -1
- package/dist/store/reducers/storage.js +8 -18
- package/dist/types/api/nodesList.ts +25 -0
- package/dist/types/api/query.ts +4 -1
- package/dist/types/api/schema.ts +523 -3
- package/dist/types/common.ts +1 -0
- package/dist/types/store/executeQuery.ts +42 -0
- package/dist/types/store/nodesList.ts +24 -0
- package/dist/types/store/olapStats.ts +14 -0
- package/dist/utils/index.js +9 -1
- package/dist/utils/nodes.ts +4 -0
- package/dist/utils/query.test.ts +42 -29
- package/dist/utils/query.ts +34 -22
- package/dist/utils/timeParsers/formatDuration.ts +30 -12
- package/dist/utils/timeParsers/i18n/en.json +4 -0
- package/dist/utils/timeParsers/i18n/ru.json +4 -0
- package/dist/utils/tooltip.js +2 -28
- package/package.json +1 -1
- package/dist/components/InfoViewer/formatters/topicStats.tsx +0 -29
@@ -3,6 +3,8 @@ import cn from 'bem-cn-lite';
|
|
3
3
|
|
4
4
|
import {Label, Popup, PopupProps} from '@gravity-ui/uikit';
|
5
5
|
|
6
|
+
import type {NodesMap} from '../../../types/store/nodesList';
|
7
|
+
|
6
8
|
import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
|
7
9
|
|
8
10
|
import {EFlag} from '../../../types/api/enums';
|
@@ -13,7 +15,7 @@ import {isFullVDiskData} from '../../../utils/storage';
|
|
13
15
|
|
14
16
|
import type {IUnavailableDonor} from '../utils/types';
|
15
17
|
|
16
|
-
import {
|
18
|
+
import {preparePDiskData} from '../PDiskPopup';
|
17
19
|
|
18
20
|
import './VDiskPopup.scss';
|
19
21
|
|
@@ -128,7 +130,7 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => {
|
|
128
130
|
interface VDiskPopupProps extends PopupProps {
|
129
131
|
data: TVDiskStateInfo | IUnavailableDonor;
|
130
132
|
poolName?: string;
|
131
|
-
nodes?:
|
133
|
+
nodes?: NodesMap;
|
132
134
|
}
|
133
135
|
|
134
136
|
export const VDiskPopup = ({data, poolName, nodes, ...props}: VDiskPopupProps) => {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import block from 'bem-cn-lite';
|
2
2
|
|
3
|
-
import {ReadLagImage} from '../../../../../components/LagImages';
|
4
3
|
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
|
4
|
+
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
|
5
5
|
|
6
6
|
import {CONSUMERS_COLUMNS_IDS, CONSUMERS_COLUMNS_TITILES} from '../utils/constants';
|
7
7
|
|
@@ -14,14 +14,7 @@ const b = block('ydb-diagnostics-consumers-columns-header');
|
|
14
14
|
export const ReadLagsHeader = () => (
|
15
15
|
<LabelWithPopover
|
16
16
|
className={b('lags')}
|
17
|
-
|
18
|
-
popoverContent={
|
19
|
-
<div className={b('lags-popover-content')}>
|
20
|
-
<div>{i18n('lagsPopover.readLags')}</div>
|
21
|
-
<div>
|
22
|
-
<ReadLagImage />
|
23
|
-
</div>
|
24
|
-
</div>
|
25
|
-
}
|
17
|
+
text={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
|
18
|
+
popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
|
26
19
|
/>
|
27
20
|
);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"noConsumersMessage.topic": "У этого топика нет читателей",
|
3
3
|
"noConsumersMessage.stream": "У этого стрима нет читателей",
|
4
|
-
"lagsPopover.readLags": "Статистика лагов чтения,
|
4
|
+
"lagsPopover.readLags": "Статистика лагов чтения, максимальное значение среди всех партиций читателя (формат времени дд чч:мм:сс)",
|
5
5
|
"table.emptyDataMessage": "По заданному поиску нет читателей",
|
6
6
|
"controls.search": "Consumer"
|
7
7
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {ReactNode, useCallback
|
1
|
+
import {ReactNode, useCallback} from 'react';
|
2
2
|
import {shallowEqual, useDispatch, useSelector} from 'react-redux';
|
3
3
|
|
4
4
|
import {Loader} from '../../../../components/Loader';
|
@@ -11,7 +11,7 @@ import {TopicInfo} from './TopicInfo';
|
|
11
11
|
import {ChangefeedInfo} from './ChangefeedInfo';
|
12
12
|
import {TableInfo} from './TableInfo';
|
13
13
|
|
14
|
-
import {EPathType
|
14
|
+
import {EPathType} from '../../../../types/api/schema';
|
15
15
|
import {
|
16
16
|
isEntityWithMergedImplementation,
|
17
17
|
isColumnEntityType,
|
@@ -33,35 +33,6 @@ import {
|
|
33
33
|
} from '../../../../store/reducers/olapStats';
|
34
34
|
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
|
35
35
|
|
36
|
-
function prepareOlapTableGeneral(item?: TEvDescribeSchemeResult, olapStats?: any[]) {
|
37
|
-
const tableData = item?.PathDescription?.ColumnTableDescription;
|
38
|
-
|
39
|
-
const Bytes = olapStats?.reduce((acc, el) => {
|
40
|
-
acc += parseInt(el.Bytes) || 0;
|
41
|
-
return acc;
|
42
|
-
}, 0);
|
43
|
-
const Rows = olapStats?.reduce((acc, el) => {
|
44
|
-
acc += parseInt(el.Rows) || 0;
|
45
|
-
return acc;
|
46
|
-
}, 0);
|
47
|
-
const tabletIds = olapStats?.reduce((acc, el) => {
|
48
|
-
acc.add(el.TabletId);
|
49
|
-
return acc;
|
50
|
-
}, new Set());
|
51
|
-
|
52
|
-
return {
|
53
|
-
PathDescription: {
|
54
|
-
Self: item?.PathDescription?.Self,
|
55
|
-
TableStats: {
|
56
|
-
ColumnShardCount: tableData?.ColumnShardCount,
|
57
|
-
Bytes: Bytes?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
|
58
|
-
Rows: Rows?.toLocaleString('ru-RU', {useGrouping: true}) ?? 0,
|
59
|
-
Parts: tabletIds?.size ?? 0,
|
60
|
-
},
|
61
|
-
},
|
62
|
-
};
|
63
|
-
}
|
64
|
-
|
65
36
|
interface OverviewProps {
|
66
37
|
type?: EPathType;
|
67
38
|
className?: string;
|
@@ -81,7 +52,7 @@ function Overview({type, tenantName, className}: OverviewProps) {
|
|
81
52
|
} = useSelector((state: any) => state.schema);
|
82
53
|
|
83
54
|
const {data: {result: olapStats} = {result: undefined}, loading: olapStatsLoading} =
|
84
|
-
|
55
|
+
useTypedSelector((state) => state.olapStats);
|
85
56
|
|
86
57
|
const loading = schemaLoading || olapStatsLoading;
|
87
58
|
|
@@ -134,13 +105,6 @@ function Overview({type, tenantName, className}: OverviewProps) {
|
|
134
105
|
|
135
106
|
useAutofetcher(fetchData, [fetchData], autorefresh);
|
136
107
|
|
137
|
-
const schemaData = useMemo(() => {
|
138
|
-
return isTableType(type) && isColumnEntityType(type)
|
139
|
-
? // process data for ColumnTable
|
140
|
-
prepareOlapTableGeneral(currentItem, olapStats)
|
141
|
-
: currentItem;
|
142
|
-
}, [type, olapStats, currentItem]);
|
143
|
-
|
144
108
|
const renderContent = () => {
|
145
109
|
// verbose mapping to guarantee a correct render for new path types
|
146
110
|
// TS will error when a new type is added but not mapped here
|
@@ -149,17 +113,21 @@ function Overview({type, tenantName, className}: OverviewProps) {
|
|
149
113
|
[EPathType.EPathTypeDir]: undefined,
|
150
114
|
[EPathType.EPathTypeTable]: undefined,
|
151
115
|
[EPathType.EPathTypeSubDomain]: undefined,
|
152
|
-
[EPathType.EPathTypeTableIndex]: () => <TableIndexInfo data={
|
116
|
+
[EPathType.EPathTypeTableIndex]: () => <TableIndexInfo data={currentItem} />,
|
153
117
|
[EPathType.EPathTypeExtSubDomain]: undefined,
|
154
118
|
[EPathType.EPathTypeColumnStore]: undefined,
|
155
119
|
[EPathType.EPathTypeColumnTable]: undefined,
|
156
120
|
[EPathType.EPathTypeCdcStream]: () => (
|
157
|
-
<ChangefeedInfo data={
|
121
|
+
<ChangefeedInfo data={currentItem} childrenPaths={mergedChildrenPaths} />
|
158
122
|
),
|
159
|
-
[EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={
|
123
|
+
[EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={currentItem} />,
|
160
124
|
};
|
161
125
|
|
162
|
-
return (
|
126
|
+
return (
|
127
|
+
(type && pathTypeToComponent[type]?.()) || (
|
128
|
+
<TableInfo data={currentItem} type={type} olapStats={olapStats} />
|
129
|
+
)
|
130
|
+
);
|
163
131
|
};
|
164
132
|
|
165
133
|
if ((loading && !wasLoaded) || (isEntityWithMergedImpl && !mergedChildrenPaths)) {
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import {useMemo} from 'react';
|
2
2
|
import cn from 'bem-cn-lite';
|
3
3
|
|
4
|
-
import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema';
|
5
|
-
|
4
|
+
import type {EPathType, TEvDescribeSchemeResult} from '../../../../../types/api/schema';
|
5
|
+
import type {KeyValueRow} from '../../../../../types/api/query';
|
6
6
|
import {InfoViewer} from '../../../../../components/InfoViewer';
|
7
7
|
|
8
8
|
import {getEntityName} from '../../../utils';
|
@@ -17,27 +17,40 @@ const b = cn('ydb-diagnostics-table-info');
|
|
17
17
|
|
18
18
|
interface TableInfoProps {
|
19
19
|
data?: TEvDescribeSchemeResult;
|
20
|
+
type?: EPathType;
|
21
|
+
olapStats?: KeyValueRow[];
|
20
22
|
}
|
21
23
|
|
22
|
-
export const TableInfo = ({data}: TableInfoProps) => {
|
24
|
+
export const TableInfo = ({data, type, olapStats}: TableInfoProps) => {
|
23
25
|
const entityName = getEntityName(data?.PathDescription);
|
24
26
|
|
25
27
|
const {
|
26
|
-
|
28
|
+
generalInfo = [],
|
27
29
|
tableStatsInfo = [],
|
28
30
|
tabletMetricsInfo = [],
|
29
31
|
partitionConfigInfo = [],
|
30
|
-
} = useMemo(() => prepareTableInfo(data), [data]);
|
32
|
+
} = useMemo(() => prepareTableInfo(data, type, olapStats), [data, type, olapStats]);
|
31
33
|
|
32
34
|
return (
|
33
35
|
<div className={b()}>
|
34
36
|
<InfoViewer
|
35
|
-
info={
|
37
|
+
info={generalInfo}
|
36
38
|
title={entityName}
|
37
39
|
className={b('info-block')}
|
38
40
|
renderEmptyState={() => <div className={b('title')}>{entityName}</div>}
|
39
41
|
/>
|
40
42
|
<div className={b('row')}>
|
43
|
+
<div className={b('col')}>
|
44
|
+
{tableStatsInfo.map((info, index) => (
|
45
|
+
<InfoViewer
|
46
|
+
key={index}
|
47
|
+
info={info}
|
48
|
+
title={index === 0 ? i18n('tableStats') : undefined}
|
49
|
+
className={b('info-block')}
|
50
|
+
renderEmptyState={() => null}
|
51
|
+
/>
|
52
|
+
))}
|
53
|
+
</div>
|
41
54
|
{tabletMetricsInfo.length > 0 || partitionConfigInfo.length > 0 ? (
|
42
55
|
<div className={b('col')}>
|
43
56
|
<InfoViewer
|
@@ -54,17 +67,6 @@ export const TableInfo = ({data}: TableInfoProps) => {
|
|
54
67
|
/>
|
55
68
|
</div>
|
56
69
|
) : null}
|
57
|
-
<div className={b('col')}>
|
58
|
-
{tableStatsInfo.map((info, index) => (
|
59
|
-
<InfoViewer
|
60
|
-
key={index}
|
61
|
-
info={info}
|
62
|
-
title={index === 0 ? i18n('tableStats') : undefined}
|
63
|
-
className={b('info-block')}
|
64
|
-
renderEmptyState={() => null}
|
65
|
-
/>
|
66
|
-
))}
|
67
|
-
</div>
|
68
70
|
</div>
|
69
71
|
</div>
|
70
72
|
);
|
@@ -1,6 +1,16 @@
|
|
1
|
-
import type {
|
2
|
-
|
3
|
-
|
1
|
+
import type {
|
2
|
+
TColumnDataLifeCycle,
|
3
|
+
TColumnTableDescription,
|
4
|
+
TEvDescribeSchemeResult,
|
5
|
+
TPartitionConfig,
|
6
|
+
TTTLSettings,
|
7
|
+
} from '../../../../../types/api/schema';
|
8
|
+
import type {KeyValueRow} from '../../../../../types/api/query';
|
9
|
+
import {EPathType} from '../../../../../types/api/schema';
|
10
|
+
import {isNumeric} from '../../../../../utils/utils';
|
11
|
+
import {formatBytes, formatNumber} from '../../../../../utils';
|
12
|
+
import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers';
|
13
|
+
import {formatObject, InfoViewerItem} from '../../../../../components/InfoViewer';
|
4
14
|
import {
|
5
15
|
formatFollowerGroupItem,
|
6
16
|
formatPartitionConfigItem,
|
@@ -8,7 +18,134 @@ import {
|
|
8
18
|
formatTabletMetricsItem,
|
9
19
|
} from '../../../../../components/InfoViewer/formatters';
|
10
20
|
|
11
|
-
|
21
|
+
const isInStoreColumnTable = (table: TColumnTableDescription) => {
|
22
|
+
// SchemaPresetId could be 0
|
23
|
+
return table.SchemaPresetName && table.SchemaPresetId !== undefined;
|
24
|
+
};
|
25
|
+
|
26
|
+
const prepareOlapStats = (olapStats?: KeyValueRow[]) => {
|
27
|
+
const Bytes = olapStats?.reduce((acc, el) => {
|
28
|
+
const value = isNumeric(el.Bytes) ? Number(el.Bytes) : 0;
|
29
|
+
return acc + value;
|
30
|
+
}, 0);
|
31
|
+
const Rows = olapStats?.reduce((acc, el) => {
|
32
|
+
const value = isNumeric(el.Rows) ? Number(el.Rows) : 0;
|
33
|
+
return acc + value;
|
34
|
+
}, 0);
|
35
|
+
const tabletIds = olapStats?.reduce((acc, el) => {
|
36
|
+
acc.add(el.TabletId);
|
37
|
+
return acc;
|
38
|
+
}, new Set());
|
39
|
+
|
40
|
+
return [
|
41
|
+
{label: 'PartCount', value: tabletIds?.size ?? 0},
|
42
|
+
{label: 'RowCount', value: formatNumber(Rows) ?? 0},
|
43
|
+
{label: 'DataSize', value: formatBytes(Bytes) ?? 0},
|
44
|
+
];
|
45
|
+
};
|
46
|
+
|
47
|
+
const prepareTTL = (ttl: TTTLSettings | TColumnDataLifeCycle) => {
|
48
|
+
// ExpireAfterSeconds could be 0
|
49
|
+
if (ttl.Enabled && ttl.Enabled.ColumnName && ttl.Enabled.ExpireAfterSeconds !== undefined) {
|
50
|
+
const value = `column: '${
|
51
|
+
ttl.Enabled.ColumnName
|
52
|
+
}', expire after: ${formatDurationToShortTimeFormat(
|
53
|
+
ttl.Enabled.ExpireAfterSeconds * 1000,
|
54
|
+
1,
|
55
|
+
)}`;
|
56
|
+
|
57
|
+
return {label: 'TTL for rows', value};
|
58
|
+
}
|
59
|
+
return undefined;
|
60
|
+
};
|
61
|
+
|
62
|
+
function prepareColumnTableGeneralInfo(columnTable: TColumnTableDescription) {
|
63
|
+
const columnTableGeneralInfo: InfoViewerItem[] = [];
|
64
|
+
|
65
|
+
columnTableGeneralInfo.push({
|
66
|
+
label: 'Standalone',
|
67
|
+
value: String(!isInStoreColumnTable(columnTable)),
|
68
|
+
});
|
69
|
+
|
70
|
+
if (columnTable.Sharding && columnTable.Sharding.HashSharding) {
|
71
|
+
columnTableGeneralInfo.push({label: 'Sharding', value: 'hash'});
|
72
|
+
}
|
73
|
+
|
74
|
+
if (columnTable.TtlSettings) {
|
75
|
+
const ttlInfo = prepareTTL(columnTable?.TtlSettings);
|
76
|
+
if (ttlInfo) {
|
77
|
+
columnTableGeneralInfo.push(ttlInfo);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
return columnTableGeneralInfo;
|
82
|
+
}
|
83
|
+
|
84
|
+
const prepareTableGeneralInfo = (PartitionConfig: TPartitionConfig, TTLSettings?: TTTLSettings) => {
|
85
|
+
const {PartitioningPolicy = {}, FollowerGroups, EnableFilterByKey} = PartitionConfig;
|
86
|
+
|
87
|
+
const generalTableInfo: InfoViewerItem[] = [];
|
88
|
+
|
89
|
+
const partitioningBySize = PartitioningPolicy.SizeToSplit
|
90
|
+
? `Enabled, split size: ${formatBytes(PartitioningPolicy.SizeToSplit)}`
|
91
|
+
: 'Disabled';
|
92
|
+
|
93
|
+
const partitioningByLoad = PartitioningPolicy.SplitByLoadSettings?.Enabled
|
94
|
+
? 'Enabled'
|
95
|
+
: 'Disabled';
|
96
|
+
|
97
|
+
generalTableInfo.push(
|
98
|
+
{label: 'Partitioning by size', value: partitioningBySize},
|
99
|
+
{label: 'Partitioning by load', value: partitioningByLoad},
|
100
|
+
{
|
101
|
+
label: 'Min number of partitions',
|
102
|
+
value: PartitioningPolicy.MinPartitionsCount || 0,
|
103
|
+
},
|
104
|
+
);
|
105
|
+
|
106
|
+
if (PartitioningPolicy.MaxPartitionsCount) {
|
107
|
+
generalTableInfo.push({
|
108
|
+
label: 'Max number of partitions',
|
109
|
+
value: PartitioningPolicy.MaxPartitionsCount || 0,
|
110
|
+
});
|
111
|
+
}
|
112
|
+
|
113
|
+
if (FollowerGroups && FollowerGroups.length) {
|
114
|
+
const {RequireAllDataCenters, FollowerCountPerDataCenter, FollowerCount} =
|
115
|
+
FollowerGroups[0];
|
116
|
+
|
117
|
+
let readReplicasConfig: string;
|
118
|
+
|
119
|
+
if (RequireAllDataCenters && FollowerCountPerDataCenter) {
|
120
|
+
readReplicasConfig = `PER_AZ: ${FollowerCount}`;
|
121
|
+
} else {
|
122
|
+
readReplicasConfig = `ANY_AZ: ${FollowerCount}`;
|
123
|
+
}
|
124
|
+
|
125
|
+
generalTableInfo.push({label: 'Read replicas (followers)', value: readReplicasConfig});
|
126
|
+
}
|
127
|
+
|
128
|
+
if (TTLSettings) {
|
129
|
+
const ttlInfo = prepareTTL(TTLSettings);
|
130
|
+
if (ttlInfo) {
|
131
|
+
generalTableInfo.push(ttlInfo);
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
generalTableInfo.push({
|
136
|
+
label: 'Bloom filter',
|
137
|
+
value: EnableFilterByKey ? 'Enabled' : 'Disabled',
|
138
|
+
});
|
139
|
+
|
140
|
+
return generalTableInfo;
|
141
|
+
};
|
142
|
+
|
143
|
+
/** Prepares data for Table, ColumnTable and ColumnStore */
|
144
|
+
export const prepareTableInfo = (
|
145
|
+
data?: TEvDescribeSchemeResult,
|
146
|
+
type?: EPathType,
|
147
|
+
olapStats?: KeyValueRow[],
|
148
|
+
) => {
|
12
149
|
if (!data) {
|
13
150
|
return {};
|
14
151
|
}
|
@@ -18,7 +155,8 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
|
|
18
155
|
const {
|
19
156
|
TableStats = {},
|
20
157
|
TabletMetrics = {},
|
21
|
-
Table: {PartitionConfig = {}} = {},
|
158
|
+
Table: {PartitionConfig = {}, TTLSettings} = {},
|
159
|
+
ColumnTableDescription = {},
|
22
160
|
} = PathDescription;
|
23
161
|
|
24
162
|
const {
|
@@ -42,45 +180,62 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
|
|
42
180
|
RowReads,
|
43
181
|
RangeReads,
|
44
182
|
RangeReadRows,
|
45
|
-
|
46
|
-
...restTableStats
|
47
183
|
} = TableStats;
|
48
184
|
|
49
185
|
const {FollowerGroups, FollowerCount, CrossDataCenterFollowerCount} = PartitionConfig;
|
50
186
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
187
|
+
let generalInfo: InfoViewerItem[] = [];
|
188
|
+
|
189
|
+
switch (type) {
|
190
|
+
case EPathType.EPathTypeTable: {
|
191
|
+
generalInfo = prepareTableGeneralInfo(PartitionConfig, TTLSettings);
|
192
|
+
break;
|
193
|
+
}
|
194
|
+
case EPathType.EPathTypeColumnTable: {
|
195
|
+
generalInfo = prepareColumnTableGeneralInfo(ColumnTableDescription);
|
196
|
+
break;
|
197
|
+
}
|
198
|
+
}
|
58
199
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
200
|
+
let tableStatsInfo: InfoViewerItem[][];
|
201
|
+
|
202
|
+
// There is no TableStats and TabletMetrics for ColumnTables inside ColumnStore
|
203
|
+
// Therefore we parse olapStats
|
204
|
+
if (type === EPathType.EPathTypeColumnTable && isInStoreColumnTable(ColumnTableDescription)) {
|
205
|
+
tableStatsInfo = [prepareOlapStats(olapStats)];
|
206
|
+
} else {
|
207
|
+
tableStatsInfo = [
|
208
|
+
formatObject(formatTableStatsItem, {
|
209
|
+
PartCount,
|
210
|
+
RowCount,
|
211
|
+
DataSize,
|
212
|
+
IndexSize,
|
213
|
+
}),
|
214
|
+
formatObject(formatTableStatsItem, {
|
215
|
+
LastAccessTime,
|
216
|
+
LastUpdateTime,
|
217
|
+
}),
|
218
|
+
formatObject(formatTableStatsItem, {
|
219
|
+
ImmediateTxCompleted,
|
220
|
+
PlannedTxCompleted,
|
221
|
+
TxRejectedByOverload,
|
222
|
+
TxRejectedBySpace,
|
223
|
+
TxCompleteLagMsec,
|
224
|
+
InFlightTxCount,
|
225
|
+
}),
|
226
|
+
formatObject(formatTableStatsItem, {
|
227
|
+
RowUpdates,
|
228
|
+
RowDeletes,
|
229
|
+
RowReads,
|
230
|
+
RangeReads,
|
231
|
+
RangeReadRows,
|
232
|
+
}),
|
233
|
+
];
|
234
|
+
}
|
80
235
|
|
81
236
|
const tabletMetricsInfo = formatObject(formatTabletMetricsItem, TabletMetrics);
|
82
237
|
|
83
|
-
let partitionConfigInfo = [];
|
238
|
+
let partitionConfigInfo: InfoViewerItem[] = [];
|
84
239
|
|
85
240
|
if (Array.isArray(FollowerGroups) && FollowerGroups.length > 0) {
|
86
241
|
partitionConfigInfo = formatObject(formatFollowerGroupItem, FollowerGroups[0]);
|
@@ -92,5 +247,5 @@ export const prepareTableInfo = (data?: TEvDescribeSchemeResult) => {
|
|
92
247
|
);
|
93
248
|
}
|
94
249
|
|
95
|
-
return {
|
250
|
+
return {generalInfo, tableStatsInfo, tabletMetricsInfo, partitionConfigInfo};
|
96
251
|
};
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import cn from 'bem-cn-lite';
|
2
|
-
import {isEmpty} from 'lodash/fp';
|
3
2
|
|
4
|
-
import type {
|
3
|
+
import type {IPreparedTopicStats} from '../../../../../types/store/topic';
|
5
4
|
|
6
5
|
import {Loader} from '../../../../../components/Loader';
|
7
|
-
import {InfoViewerItem,
|
8
|
-
|
9
|
-
import {
|
6
|
+
import {InfoViewerItem, InfoViewer} from '../../../../../components/InfoViewer';
|
7
|
+
import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
|
8
|
+
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
|
9
|
+
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
|
10
|
+
import {ResponseError} from '../../../../../components/Errors/ResponseError';
|
10
11
|
|
11
12
|
import {useTypedSelector} from '../../../../../utils/hooks';
|
12
|
-
import {
|
13
|
-
import {formatBps} from '../../../../../utils';
|
13
|
+
import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers';
|
14
|
+
import {formatBps, formatBytes} from '../../../../../utils';
|
15
|
+
|
16
|
+
import {selectPreparedTopicStats} from '../../../../../store/reducers/topic';
|
14
17
|
|
15
18
|
import i18n from './i18n';
|
16
19
|
|
@@ -18,35 +21,61 @@ import './TopicStats.scss';
|
|
18
21
|
|
19
22
|
const b = cn('ydb-overview-topic-stats');
|
20
23
|
|
21
|
-
const prepareTopicInfo = (data:
|
24
|
+
const prepareTopicInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
|
22
25
|
return [
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
{label: 'Store size', value: formatBytes(data.storeSize)},
|
27
|
+
{
|
28
|
+
label: (
|
29
|
+
<LabelWithPopover
|
30
|
+
text={'Write idle time'}
|
31
|
+
popoverContent={
|
32
|
+
<LagPopoverContent text={i18n('writeIdleTimePopover')} type="write" />
|
33
|
+
}
|
34
|
+
/>
|
35
|
+
),
|
36
|
+
value: formatDurationToShortTimeFormat(data.partitionsIdleTime),
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: (
|
40
|
+
<LabelWithPopover
|
41
|
+
text={'Write lag'}
|
42
|
+
popoverContent={
|
43
|
+
<LagPopoverContent text={i18n('writeLagPopover')} type="write" />
|
44
|
+
}
|
45
|
+
/>
|
46
|
+
),
|
47
|
+
value: formatDurationToShortTimeFormat(data.partitionsWriteLag),
|
48
|
+
},
|
49
|
+
{
|
50
|
+
label: 'Average write speed',
|
51
|
+
value: <SpeedMultiMeter data={data.writeSpeed} withValue={false} />,
|
52
|
+
},
|
26
53
|
];
|
27
54
|
};
|
28
55
|
|
29
|
-
const prepareBytesWrittenInfo = (data:
|
30
|
-
const
|
56
|
+
const prepareBytesWrittenInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
|
57
|
+
const writeSpeed = data.writeSpeed;
|
31
58
|
|
32
59
|
return [
|
33
60
|
{
|
34
61
|
label: 'per minute',
|
35
|
-
value: formatBps(
|
62
|
+
value: formatBps(writeSpeed.perMinute),
|
36
63
|
},
|
37
64
|
{
|
38
65
|
label: 'per hour',
|
39
|
-
value: formatBps(
|
66
|
+
value: formatBps(writeSpeed.perHour),
|
40
67
|
},
|
41
68
|
{
|
42
69
|
label: 'per day',
|
43
|
-
value: formatBps(
|
70
|
+
value: formatBps(writeSpeed.perDay),
|
44
71
|
},
|
45
72
|
];
|
46
73
|
};
|
47
74
|
|
48
75
|
export const TopicStats = () => {
|
49
|
-
const {
|
76
|
+
const {error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
|
77
|
+
|
78
|
+
const data = useTypedSelector(selectPreparedTopicStats);
|
50
79
|
|
51
80
|
if (loading && !wasLoaded) {
|
52
81
|
return (
|
@@ -56,24 +85,14 @@ export const TopicStats = () => {
|
|
56
85
|
);
|
57
86
|
}
|
58
87
|
|
59
|
-
//
|
60
|
-
//
|
61
|
-
//
|
62
|
-
|
63
|
-
// 3. HTML page of Internal Viewer with an error
|
64
|
-
// 4. Data with no topic stats
|
65
|
-
// 5. Topic Stats as an empty object
|
66
|
-
if (
|
67
|
-
error ||
|
68
|
-
!data ||
|
69
|
-
typeof data !== 'object' ||
|
70
|
-
!data.topic_stats ||
|
71
|
-
isEmpty(data.topic_stats)
|
72
|
-
) {
|
88
|
+
// If there is at least some empty data object
|
89
|
+
// we initialize its fields with zero values
|
90
|
+
// so no data at all is considered to be error as well
|
91
|
+
if (error || !data) {
|
73
92
|
return (
|
74
93
|
<div className={b()}>
|
75
94
|
<div className={b('title')}>Stats</div>
|
76
|
-
<
|
95
|
+
<ResponseError error={error} />
|
77
96
|
</div>
|
78
97
|
);
|
79
98
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
{
|
2
|
-
"
|
2
|
+
"writeLagPopover": "Write lag, maximum among all topic partitions",
|
3
|
+
"writeIdleTimePopover": "Write idle time, maximum among all topic partitions"
|
3
4
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
{
|
2
|
-
"
|
2
|
+
"writeLagPopover": "Лаг записи, максимальное значение среди всех партиций топика",
|
3
|
+
"writeIdleTimePopover": "Время без записи, максимальное значение среди всех партиций топика"
|
3
4
|
}
|