ydb-embedded-ui 3.3.3 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +28 -0
- package/dist/components/Errors/ResponseError/ResponseError.tsx +2 -2
- package/dist/components/InfoViewer/formatters/topicStats.tsx +8 -29
- package/dist/components/LabelWithPopover/LabelWithPopover.tsx +20 -0
- package/dist/components/LabelWithPopover/index.ts +1 -0
- package/dist/components/LagImages/LagImages.tsx +205 -0
- package/dist/components/LagImages/index.ts +1 -0
- package/dist/components/SpeedMultiMeter/SpeedMultiMeter.scss +92 -0
- package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +120 -0
- package/dist/components/SpeedMultiMeter/i18n/en.json +6 -0
- package/dist/components/SpeedMultiMeter/i18n/index.ts +13 -0
- package/dist/components/SpeedMultiMeter/i18n/ru.json +6 -0
- package/dist/components/SpeedMultiMeter/index.ts +1 -0
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +26 -14
- package/dist/containers/Storage/VDisk/VDisk.tsx +20 -5
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +34 -5
- package/dist/containers/Storage/utils/types.ts +5 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +32 -3
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +62 -69
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +13 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +27 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.scss +32 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.tsx +43 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/columns/Columns.scss +5 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +66 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/columns/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +4 -3
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +4 -3
- package/dist/containers/Tenant/Diagnostics/Consumers/utils/constants.ts +23 -0
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +7 -3
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -9
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +9 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +6 -8
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +33 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +76 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +45 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +254 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +79 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/columns/Columns.scss +13 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx +246 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/columns/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +13 -0
- package/dist/containers/Tenant/Diagnostics/{OverloadedShards → Partitions}/i18n/index.ts +1 -1
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +13 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/utils/constants.ts +74 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/utils/types.ts +6 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.scss +8 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +56 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/Filters/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.scss → TopShards/TopShards.scss} +2 -10
- package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.tsx → TopShards/TopShards.tsx} +61 -31
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/en.json +6 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/i18n/ru.json +6 -0
- package/dist/containers/Tenant/Diagnostics/TopShards/index.ts +1 -0
- package/dist/containers/Tenant/utils/schema.ts +1 -16
- package/dist/services/api.d.ts +4 -0
- package/dist/services/api.js +22 -6
- package/dist/store/reducers/authentication.js +0 -15
- package/dist/store/reducers/consumer.ts +160 -0
- package/dist/store/reducers/index.ts +2 -0
- package/dist/store/reducers/settings.js +2 -0
- package/dist/store/reducers/shardsWorkload.ts +28 -2
- package/dist/store/reducers/topic.ts +82 -2
- package/dist/store/state-url-mapping.js +3 -0
- package/dist/types/store/consumer.ts +55 -0
- package/dist/types/store/shardsWorkload.ts +6 -0
- package/dist/types/store/topic.ts +23 -6
- package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +24 -0
- package/dist/utils/bytesParsers/formatBytesCustom.ts +57 -0
- package/dist/utils/bytesParsers/i18n/en.json +7 -0
- package/dist/utils/bytesParsers/i18n/index.ts +11 -0
- package/dist/utils/bytesParsers/i18n/ru.json +7 -0
- package/dist/utils/bytesParsers/index.ts +2 -0
- package/dist/utils/constants.ts +3 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/storage.ts +2 -2
- package/dist/utils/timeParsers/index.ts +2 -1
- package/dist/utils/timeParsers/parsers.ts +18 -0
- package/dist/utils/timeParsers/{protobuf.ts → protobufParsers.ts} +0 -0
- package/dist/utils/typecheckers.ts +5 -0
- package/dist/utils/utils.js +3 -3
- package/package.json +2 -2
- package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/en.json +0 -4
- package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/ru.json +0 -4
- package/dist/containers/Tenant/Diagnostics/OverloadedShards/index.ts +0 -1
@@ -0,0 +1 @@
|
|
1
|
+
export * from './TopShards';
|
@@ -155,22 +155,7 @@ export const isDatabaseEntityType = (type?: EPathType) =>
|
|
155
155
|
|
156
156
|
// ====================
|
157
157
|
|
158
|
-
const
|
159
|
-
[EPathType.EPathTypeCdcStream]: true,
|
160
|
-
|
161
|
-
[EPathType.EPathTypeInvalid]: false,
|
162
|
-
[EPathType.EPathTypeColumnStore]: false,
|
163
|
-
[EPathType.EPathTypeColumnTable]: false,
|
164
|
-
[EPathType.EPathTypeDir]: false,
|
165
|
-
[EPathType.EPathTypeTable]: false,
|
166
|
-
[EPathType.EPathTypeSubDomain]: false,
|
167
|
-
[EPathType.EPathTypeTableIndex]: false,
|
168
|
-
[EPathType.EPathTypeExtSubDomain]: false,
|
169
|
-
[EPathType.EPathTypePersQueueGroup]: false,
|
170
|
-
};
|
171
|
-
|
172
|
-
export const isCdcStreamEntityType = (type?: EPathType) =>
|
173
|
-
(type && pathTypeToIsCdcStream[type]) ?? false;
|
158
|
+
export const isCdcStreamEntityType = (type?: EPathType) => type === EPathType.EPathTypeCdcStream;
|
174
159
|
|
175
160
|
// ====================
|
176
161
|
|
package/dist/services/api.d.ts
CHANGED
@@ -62,6 +62,10 @@ interface Window {
|
|
62
62
|
getTopic: (params: {
|
63
63
|
path?: string;
|
64
64
|
}) => Promise<import('../types/api/topic').DescribeTopicResult>;
|
65
|
+
getConsumer: (params: {
|
66
|
+
path?: string;
|
67
|
+
consumer?: string;
|
68
|
+
}) => Promise<import('../types/api/consumer').DescribeConsumerResult>;
|
65
69
|
[method: string]: Function;
|
66
70
|
};
|
67
71
|
}
|
package/dist/services/api.js
CHANGED
@@ -141,12 +141,28 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
141
141
|
path,
|
142
142
|
});
|
143
143
|
}
|
144
|
-
getTopic({path}) {
|
145
|
-
return this.get(
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
144
|
+
getTopic({path}, {concurrentId} = {}) {
|
145
|
+
return this.get(
|
146
|
+
this.getPath('/viewer/json/describe_topic'),
|
147
|
+
{
|
148
|
+
enums: true,
|
149
|
+
include_stats: true,
|
150
|
+
path,
|
151
|
+
},
|
152
|
+
{concurrentId: concurrentId || 'getTopic'},
|
153
|
+
);
|
154
|
+
}
|
155
|
+
getConsumer({path, consumer}, {concurrentId} = {}) {
|
156
|
+
return this.get(
|
157
|
+
this.getPath('/viewer/json/describe_consumer'),
|
158
|
+
{
|
159
|
+
enums: true,
|
160
|
+
include_stats: true,
|
161
|
+
path,
|
162
|
+
consumer,
|
163
|
+
},
|
164
|
+
{concurrentId: concurrentId || 'getConsumer'},
|
165
|
+
);
|
150
166
|
}
|
151
167
|
getPoolInfo(poolName) {
|
152
168
|
return this.get(this.getPath('/viewer/json/storage'), {
|
@@ -34,21 +34,6 @@ const authentication = function (state = initialState, action) {
|
|
34
34
|
}
|
35
35
|
};
|
36
36
|
|
37
|
-
export const setIsNotAuthenticated = () => {
|
38
|
-
return (dispatch) => {
|
39
|
-
dispatch({
|
40
|
-
type: SET_UNAUTHENTICATED.SUCCESS,
|
41
|
-
});
|
42
|
-
};
|
43
|
-
};
|
44
|
-
export const setIsAuthenticated = () => {
|
45
|
-
return (dispatch) => {
|
46
|
-
dispatch({
|
47
|
-
type: SET_AUTHENTICATED.SUCCESS,
|
48
|
-
});
|
49
|
-
};
|
50
|
-
};
|
51
|
-
|
52
37
|
export const authenticate = (user, password) => {
|
53
38
|
return createApiRequest({
|
54
39
|
request: window.api.authenticate(user, password),
|
@@ -0,0 +1,160 @@
|
|
1
|
+
/* eslint-disable camelcase */
|
2
|
+
import type {Reducer} from 'redux';
|
3
|
+
import {createSelector, Selector} from 'reselect';
|
4
|
+
|
5
|
+
import type {
|
6
|
+
IConsumerAction,
|
7
|
+
IConsumerRootStateSlice,
|
8
|
+
IConsumerState,
|
9
|
+
IPreparedPartitionData,
|
10
|
+
} from '../../types/store/consumer';
|
11
|
+
|
12
|
+
import '../../services/api';
|
13
|
+
|
14
|
+
import {convertBytesObjectToSpeed} from '../../utils/bytesParsers';
|
15
|
+
import {parseLag, parseTimestampToIdleTime} from '../../utils/timeParsers';
|
16
|
+
import {isNumeric} from '../../utils/utils';
|
17
|
+
|
18
|
+
import {createRequestActionTypes, createApiRequest} from '../utils';
|
19
|
+
|
20
|
+
export const FETCH_CONSUMER = createRequestActionTypes('consumer', 'FETCH_CONSUMER');
|
21
|
+
|
22
|
+
const SET_DATA_WAS_NOT_LOADED = 'consumer/SET_DATA_WAS_NOT_LOADED';
|
23
|
+
|
24
|
+
const initialState = {
|
25
|
+
loading: false,
|
26
|
+
wasLoaded: false,
|
27
|
+
data: {},
|
28
|
+
};
|
29
|
+
|
30
|
+
const consumer: Reducer<IConsumerState, IConsumerAction> = (state = initialState, action) => {
|
31
|
+
switch (action.type) {
|
32
|
+
case FETCH_CONSUMER.REQUEST: {
|
33
|
+
return {
|
34
|
+
...state,
|
35
|
+
loading: true,
|
36
|
+
};
|
37
|
+
}
|
38
|
+
case FETCH_CONSUMER.SUCCESS: {
|
39
|
+
// On older version it can return HTML page of Internal Viewer with an error
|
40
|
+
if (typeof action.data !== 'object') {
|
41
|
+
return {...state, loading: false, error: {}};
|
42
|
+
}
|
43
|
+
|
44
|
+
return {
|
45
|
+
...state,
|
46
|
+
data: action.data,
|
47
|
+
loading: false,
|
48
|
+
wasLoaded: true,
|
49
|
+
error: undefined,
|
50
|
+
};
|
51
|
+
}
|
52
|
+
case FETCH_CONSUMER.FAILURE: {
|
53
|
+
if (action.error?.isCancelled) {
|
54
|
+
return state;
|
55
|
+
}
|
56
|
+
|
57
|
+
return {
|
58
|
+
...state,
|
59
|
+
error: action.error,
|
60
|
+
loading: false,
|
61
|
+
};
|
62
|
+
}
|
63
|
+
case SET_DATA_WAS_NOT_LOADED: {
|
64
|
+
return {
|
65
|
+
...state,
|
66
|
+
wasLoaded: false,
|
67
|
+
};
|
68
|
+
}
|
69
|
+
default:
|
70
|
+
return state;
|
71
|
+
}
|
72
|
+
};
|
73
|
+
|
74
|
+
export const setDataWasNotLoaded = () => {
|
75
|
+
return {
|
76
|
+
type: SET_DATA_WAS_NOT_LOADED,
|
77
|
+
} as const;
|
78
|
+
};
|
79
|
+
|
80
|
+
export function getConsumer(path?: string, consumerName?: string) {
|
81
|
+
return createApiRequest({
|
82
|
+
request: window.api.getConsumer({path, consumer: consumerName}),
|
83
|
+
actions: FETCH_CONSUMER,
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
87
|
+
export const selectPartitions = (state: IConsumerRootStateSlice) => state.consumer.data?.partitions;
|
88
|
+
|
89
|
+
export const selectPreparedPartitionsData: Selector<
|
90
|
+
IConsumerRootStateSlice,
|
91
|
+
IPreparedPartitionData[] | undefined
|
92
|
+
> = createSelector([selectPartitions], (partitions) => {
|
93
|
+
return partitions?.map((partition) => {
|
94
|
+
// describe_consumer endpoint doesn't return zero values, so some values will be initialized with 0
|
95
|
+
const {partition_id = '0', partition_stats, partition_consumer_stats} = partition;
|
96
|
+
|
97
|
+
const {
|
98
|
+
partition_offsets,
|
99
|
+
store_size_bytes = '0',
|
100
|
+
last_write_time: partition_last_write_time,
|
101
|
+
max_write_time_lag: partition_write_lag,
|
102
|
+
bytes_written,
|
103
|
+
partition_node_id = 0,
|
104
|
+
} = partition_stats || {};
|
105
|
+
|
106
|
+
const {start: start_offset = '0', end: end_offset = '0'} = partition_offsets || {};
|
107
|
+
|
108
|
+
const {
|
109
|
+
last_read_offset = '0',
|
110
|
+
committed_offset = '0',
|
111
|
+
read_session_id,
|
112
|
+
last_read_time: consumer_last_read_time,
|
113
|
+
max_read_time_lag: consumer_read_lag,
|
114
|
+
max_write_time_lag: consumer_write_lag,
|
115
|
+
bytes_read,
|
116
|
+
reader_name,
|
117
|
+
connection_node_id = 0,
|
118
|
+
} = partition_consumer_stats || {};
|
119
|
+
|
120
|
+
const uncommitedMessages =
|
121
|
+
isNumeric(end_offset) && isNumeric(committed_offset)
|
122
|
+
? Number(end_offset) - Number(committed_offset)
|
123
|
+
: 0;
|
124
|
+
|
125
|
+
const unreadMessages =
|
126
|
+
isNumeric(end_offset) && isNumeric(last_read_offset)
|
127
|
+
? Number(end_offset) - Number(last_read_offset)
|
128
|
+
: 0;
|
129
|
+
|
130
|
+
return {
|
131
|
+
partitionId: partition_id,
|
132
|
+
storeSize: store_size_bytes,
|
133
|
+
|
134
|
+
writeSpeed: convertBytesObjectToSpeed(bytes_written),
|
135
|
+
readSpeed: convertBytesObjectToSpeed(bytes_read),
|
136
|
+
|
137
|
+
partitionWriteLag: parseLag(partition_write_lag),
|
138
|
+
partitionWriteIdleTime: parseTimestampToIdleTime(partition_last_write_time),
|
139
|
+
|
140
|
+
consumerWriteLag: parseLag(consumer_write_lag),
|
141
|
+
consumerReadLag: parseLag(consumer_read_lag),
|
142
|
+
consumerReadIdleTime: parseTimestampToIdleTime(consumer_last_read_time),
|
143
|
+
|
144
|
+
uncommitedMessages,
|
145
|
+
unreadMessages,
|
146
|
+
|
147
|
+
startOffset: start_offset,
|
148
|
+
endOffset: end_offset,
|
149
|
+
commitedOffset: committed_offset,
|
150
|
+
|
151
|
+
readSessionId: read_session_id,
|
152
|
+
readerName: reader_name,
|
153
|
+
|
154
|
+
partitionNodeId: partition_node_id,
|
155
|
+
connectionNodeId: connection_node_id,
|
156
|
+
};
|
157
|
+
});
|
158
|
+
});
|
159
|
+
|
160
|
+
export default consumer;
|
@@ -18,6 +18,7 @@ import pool from './pool';
|
|
18
18
|
import tenants from './tenants';
|
19
19
|
import tablet from './tablet';
|
20
20
|
import topic from './topic';
|
21
|
+
import consumer from './consumer';
|
21
22
|
import executeQuery from './executeQuery';
|
22
23
|
import explainQuery from './explainQuery';
|
23
24
|
import tabletsFilters from './tabletsFilters';
|
@@ -57,6 +58,7 @@ export const rootReducer = {
|
|
57
58
|
tenants,
|
58
59
|
tablet,
|
59
60
|
topic,
|
61
|
+
consumer,
|
60
62
|
executeQuery,
|
61
63
|
explainQuery,
|
62
64
|
tabletsFilters,
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
INVERTED_DISKS_KEY,
|
8
8
|
ASIDE_HEADER_COMPACT_KEY,
|
9
9
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
10
|
+
PARTITIONS_SELECTED_COLUMNS_KEY,
|
10
11
|
} from '../../utils/constants';
|
11
12
|
import '../../services/api';
|
12
13
|
import {getValueFromLS} from '../../utils/utils';
|
@@ -50,6 +51,7 @@ export const initialState = {
|
|
50
51
|
ASIDE_HEADER_COMPACT_KEY,
|
51
52
|
legacyAsideNavCompactState || 'true',
|
52
53
|
),
|
54
|
+
[PARTITIONS_SELECTED_COLUMNS_KEY]: readSavedSettingsValue(PARTITIONS_SELECTED_COLUMNS_KEY),
|
53
55
|
},
|
54
56
|
systemSettings,
|
55
57
|
};
|
@@ -6,6 +6,7 @@ import type {
|
|
6
6
|
IShardsWorkloadFilters,
|
7
7
|
IShardsWorkloadState,
|
8
8
|
} from '../../types/store/shardsWorkload';
|
9
|
+
import {EShardsWorkloadMode} from '../../types/store/shardsWorkload';
|
9
10
|
|
10
11
|
import {parseQueryAPIExecuteResponse} from '../../utils/query';
|
11
12
|
|
@@ -51,7 +52,7 @@ function getFiltersConditions(filters?: IShardsWorkloadFilters) {
|
|
51
52
|
return conditions.join(' AND ');
|
52
53
|
}
|
53
54
|
|
54
|
-
function
|
55
|
+
function createShardQueryHistorical(
|
55
56
|
path: string,
|
56
57
|
filters?: IShardsWorkloadFilters,
|
57
58
|
sortOrder?: SortOrder[],
|
@@ -85,6 +86,28 @@ ${orderBy}
|
|
85
86
|
LIMIT 20`;
|
86
87
|
}
|
87
88
|
|
89
|
+
function createShardQueryImmediate(path: string, sortOrder?: SortOrder[], tenantName?: string) {
|
90
|
+
const pathSelect = tenantName
|
91
|
+
? `CAST(SUBSTRING(CAST(Path AS String), ${tenantName.length}) AS Utf8) AS Path`
|
92
|
+
: 'Path';
|
93
|
+
|
94
|
+
const orderBy = sortOrder ? `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` : '';
|
95
|
+
|
96
|
+
return `SELECT
|
97
|
+
${pathSelect},
|
98
|
+
TabletId,
|
99
|
+
CPUCores,
|
100
|
+
DataSize,
|
101
|
+
NodeId,
|
102
|
+
InFlightTxCount
|
103
|
+
FROM \`.sys/partition_stats\`
|
104
|
+
WHERE
|
105
|
+
Path='${path}'
|
106
|
+
OR Path LIKE '${path}/%'
|
107
|
+
${orderBy}
|
108
|
+
LIMIT 20`;
|
109
|
+
}
|
110
|
+
|
88
111
|
const queryAction = 'execute-scan';
|
89
112
|
|
90
113
|
const shardsWorkload: Reducer<IShardsWorkloadState, IShardsWorkloadAction> = (
|
@@ -147,7 +170,10 @@ export const sendShardQuery = ({database, path = '', sortOrder, filters}: SendSh
|
|
147
170
|
request: window.api.sendQuery(
|
148
171
|
{
|
149
172
|
schema: 'modern',
|
150
|
-
query:
|
173
|
+
query:
|
174
|
+
filters?.mode === EShardsWorkloadMode.Immediate
|
175
|
+
? createShardQueryImmediate(path, sortOrder, database)
|
176
|
+
: createShardQueryHistorical(path, filters, sortOrder, database),
|
151
177
|
database,
|
152
178
|
action: queryAction,
|
153
179
|
},
|
@@ -1,16 +1,28 @@
|
|
1
|
+
/* eslint-disable camelcase */
|
1
2
|
import type {Reducer} from 'redux';
|
3
|
+
import {createSelector, Selector} from 'reselect';
|
2
4
|
|
3
|
-
import type {
|
5
|
+
import type {
|
6
|
+
IPreparedConsumerData,
|
7
|
+
IPreparedTopicStats,
|
8
|
+
ITopicAction,
|
9
|
+
ITopicRootStateSlice,
|
10
|
+
ITopicState,
|
11
|
+
} from '../../types/store/topic';
|
4
12
|
import '../../services/api';
|
5
13
|
|
6
14
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
15
|
+
import {parseLag, parseTimestampToIdleTime} from '../../utils/timeParsers';
|
16
|
+
import {convertBytesObjectToSpeed} from '../../utils/bytesParsers';
|
7
17
|
|
8
18
|
export const FETCH_TOPIC = createRequestActionTypes('topic', 'FETCH_TOPIC');
|
9
19
|
|
20
|
+
const SET_DATA_WAS_NOT_LOADED = 'topic/SET_DATA_WAS_NOT_LOADED';
|
21
|
+
|
10
22
|
const initialState = {
|
11
23
|
loading: true,
|
12
24
|
wasLoaded: false,
|
13
|
-
data:
|
25
|
+
data: undefined,
|
14
26
|
};
|
15
27
|
|
16
28
|
const topic: Reducer<ITopicState, ITopicAction> = (state = initialState, action) => {
|
@@ -22,6 +34,11 @@ const topic: Reducer<ITopicState, ITopicAction> = (state = initialState, action)
|
|
22
34
|
};
|
23
35
|
}
|
24
36
|
case FETCH_TOPIC.SUCCESS: {
|
37
|
+
// On older version it can return HTML page of Internal Viewer with an error
|
38
|
+
if (typeof action.data !== 'object') {
|
39
|
+
return {...state, loading: false, error: {}};
|
40
|
+
}
|
41
|
+
|
25
42
|
return {
|
26
43
|
...state,
|
27
44
|
data: action.data,
|
@@ -31,17 +48,33 @@ const topic: Reducer<ITopicState, ITopicAction> = (state = initialState, action)
|
|
31
48
|
};
|
32
49
|
}
|
33
50
|
case FETCH_TOPIC.FAILURE: {
|
51
|
+
if (action.error?.isCancelled) {
|
52
|
+
return state;
|
53
|
+
}
|
54
|
+
|
34
55
|
return {
|
35
56
|
...state,
|
36
57
|
error: action.error,
|
37
58
|
loading: false,
|
38
59
|
};
|
39
60
|
}
|
61
|
+
case SET_DATA_WAS_NOT_LOADED: {
|
62
|
+
return {
|
63
|
+
...state,
|
64
|
+
wasLoaded: false,
|
65
|
+
};
|
66
|
+
}
|
40
67
|
default:
|
41
68
|
return state;
|
42
69
|
}
|
43
70
|
};
|
44
71
|
|
72
|
+
export const setDataWasNotLoaded = () => {
|
73
|
+
return {
|
74
|
+
type: SET_DATA_WAS_NOT_LOADED,
|
75
|
+
} as const;
|
76
|
+
};
|
77
|
+
|
45
78
|
export function getTopic(path?: string) {
|
46
79
|
return createApiRequest({
|
47
80
|
request: window.api.getTopic({path}),
|
@@ -49,4 +82,51 @@ export function getTopic(path?: string) {
|
|
49
82
|
});
|
50
83
|
}
|
51
84
|
|
85
|
+
const selectTopicStats = (state: ITopicRootStateSlice) => state.topic.data?.topic_stats;
|
86
|
+
const selectConsumers = (state: ITopicRootStateSlice) => state.topic.data?.consumers;
|
87
|
+
|
88
|
+
export const selectPreparedTopicStats: Selector<
|
89
|
+
ITopicRootStateSlice,
|
90
|
+
IPreparedTopicStats | undefined
|
91
|
+
> = createSelector([selectTopicStats], (rawTopicStats) => {
|
92
|
+
if (!rawTopicStats) {
|
93
|
+
return undefined;
|
94
|
+
}
|
95
|
+
|
96
|
+
const {
|
97
|
+
store_size_bytes = '0',
|
98
|
+
min_last_write_time,
|
99
|
+
max_write_time_lag,
|
100
|
+
bytes_written,
|
101
|
+
} = rawTopicStats || {};
|
102
|
+
|
103
|
+
return {
|
104
|
+
storeSize: store_size_bytes,
|
105
|
+
partitionsIdleTime: parseTimestampToIdleTime(min_last_write_time),
|
106
|
+
partitionsWriteLag: parseLag(max_write_time_lag),
|
107
|
+
writeSpeed: convertBytesObjectToSpeed(bytes_written),
|
108
|
+
};
|
109
|
+
});
|
110
|
+
|
111
|
+
export const selectPreparedConsumersData: Selector<
|
112
|
+
ITopicRootStateSlice,
|
113
|
+
IPreparedConsumerData[] | undefined
|
114
|
+
> = createSelector([selectConsumers], (consumers) => {
|
115
|
+
return consumers?.map((consumer) => {
|
116
|
+
const {name, consumer_stats} = consumer || {};
|
117
|
+
|
118
|
+
const {min_partitions_last_read_time, max_read_time_lag, max_write_time_lag, bytes_read} =
|
119
|
+
consumer_stats || {};
|
120
|
+
|
121
|
+
return {
|
122
|
+
name,
|
123
|
+
readSpeed: convertBytesObjectToSpeed(bytes_read),
|
124
|
+
|
125
|
+
writeLag: parseLag(max_write_time_lag),
|
126
|
+
readLag: parseLag(max_read_time_lag),
|
127
|
+
readIdleTime: parseTimestampToIdleTime(min_partitions_last_read_time),
|
128
|
+
};
|
129
|
+
});
|
130
|
+
});
|
131
|
+
|
52
132
|
export default topic;
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import type {IProcessSpeedStats} from '../../utils/bytesParsers';
|
2
|
+
import type {ApiRequestAction} from '../../store/utils';
|
3
|
+
|
4
|
+
import {FETCH_CONSUMER, setDataWasNotLoaded} from '../../store/reducers/consumer';
|
5
|
+
|
6
|
+
import type {DescribeConsumerResult} from '../api/consumer';
|
7
|
+
import type {IResponseError} from '../api/error';
|
8
|
+
|
9
|
+
// All fields should be present though they could be undefined
|
10
|
+
export interface IPreparedPartitionData {
|
11
|
+
partitionId: string;
|
12
|
+
storeSize: string;
|
13
|
+
|
14
|
+
writeSpeed: IProcessSpeedStats;
|
15
|
+
readSpeed: IProcessSpeedStats;
|
16
|
+
|
17
|
+
partitionWriteLag: number;
|
18
|
+
partitionWriteIdleTime: number;
|
19
|
+
|
20
|
+
consumerWriteLag: number;
|
21
|
+
consumerReadLag: number;
|
22
|
+
consumerReadIdleTime: number;
|
23
|
+
|
24
|
+
uncommitedMessages: number;
|
25
|
+
unreadMessages: number;
|
26
|
+
|
27
|
+
startOffset: string;
|
28
|
+
endOffset: string;
|
29
|
+
commitedOffset: string;
|
30
|
+
|
31
|
+
readSessionId: string | undefined;
|
32
|
+
readerName: string | undefined;
|
33
|
+
|
34
|
+
partitionNodeId: number;
|
35
|
+
connectionNodeId: number;
|
36
|
+
}
|
37
|
+
|
38
|
+
export interface IConsumerState {
|
39
|
+
loading: boolean;
|
40
|
+
wasLoaded: boolean;
|
41
|
+
data?: DescribeConsumerResult;
|
42
|
+
error?: IResponseError;
|
43
|
+
}
|
44
|
+
|
45
|
+
type IConsumerApiRequestAction = ApiRequestAction<
|
46
|
+
typeof FETCH_CONSUMER,
|
47
|
+
DescribeConsumerResult,
|
48
|
+
IResponseError
|
49
|
+
>;
|
50
|
+
|
51
|
+
export type IConsumerAction = IConsumerApiRequestAction | ReturnType<typeof setDataWasNotLoaded>;
|
52
|
+
|
53
|
+
export interface IConsumerRootStateSlice {
|
54
|
+
consumer: IConsumerState;
|
55
|
+
}
|
@@ -3,11 +3,17 @@ import type {ApiRequestAction} from '../../store/utils';
|
|
3
3
|
import type {IResponseError} from '../api/error';
|
4
4
|
import type {IQueryResult} from './query';
|
5
5
|
|
6
|
+
export enum EShardsWorkloadMode {
|
7
|
+
Immediate = 'immediate',
|
8
|
+
History = 'history',
|
9
|
+
}
|
10
|
+
|
6
11
|
export interface IShardsWorkloadFilters {
|
7
12
|
/** ms from epoch */
|
8
13
|
from?: number;
|
9
14
|
/** ms from epoch */
|
10
15
|
to?: number;
|
16
|
+
mode?: EShardsWorkloadMode;
|
11
17
|
}
|
12
18
|
|
13
19
|
export interface IShardsWorkloadState {
|
@@ -1,8 +1,27 @@
|
|
1
|
-
import {FETCH_TOPIC} from '../../store/reducers/topic';
|
1
|
+
import {FETCH_TOPIC, setDataWasNotLoaded} from '../../store/reducers/topic';
|
2
2
|
import type {ApiRequestAction} from '../../store/utils';
|
3
|
+
import type {IProcessSpeedStats} from '../../utils/bytesParsers';
|
3
4
|
import type {IResponseError} from '../api/error';
|
4
5
|
import type {DescribeTopicResult} from '../api/topic';
|
5
6
|
|
7
|
+
export interface IPreparedConsumerData {
|
8
|
+
name: string | undefined;
|
9
|
+
readSpeed: IProcessSpeedStats;
|
10
|
+
|
11
|
+
writeLag: number;
|
12
|
+
readLag: number;
|
13
|
+
readIdleTime: number;
|
14
|
+
}
|
15
|
+
|
16
|
+
export interface IPreparedTopicStats {
|
17
|
+
storeSize: string;
|
18
|
+
|
19
|
+
partitionsWriteLag: number;
|
20
|
+
partitionsIdleTime: number;
|
21
|
+
|
22
|
+
writeSpeed: IProcessSpeedStats;
|
23
|
+
}
|
24
|
+
|
6
25
|
export interface ITopicState {
|
7
26
|
loading: boolean;
|
8
27
|
wasLoaded: boolean;
|
@@ -10,11 +29,9 @@ export interface ITopicState {
|
|
10
29
|
error?: IResponseError;
|
11
30
|
}
|
12
31
|
|
13
|
-
export type ITopicAction =
|
14
|
-
typeof FETCH_TOPIC,
|
15
|
-
|
16
|
-
IResponseError
|
17
|
-
>;
|
32
|
+
export type ITopicAction =
|
33
|
+
| ApiRequestAction<typeof FETCH_TOPIC, DescribeTopicResult, IResponseError>
|
34
|
+
| ReturnType<typeof setDataWasNotLoaded>;
|
18
35
|
|
19
36
|
export interface ITopicRootStateSlice {
|
20
37
|
topic: ITopicState;
|
@@ -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
|
+
};
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import {GIGABYTE, KILOBYTE, MEGABYTE} from '../constants';
|
2
|
+
import {isNumeric} from '../utils';
|
3
|
+
|
4
|
+
import i18n from './i18n';
|
5
|
+
|
6
|
+
const sizes = {
|
7
|
+
b: {
|
8
|
+
value: 1,
|
9
|
+
label: i18n('b'),
|
10
|
+
},
|
11
|
+
kb: {
|
12
|
+
value: KILOBYTE,
|
13
|
+
label: i18n('kb'),
|
14
|
+
},
|
15
|
+
mb: {
|
16
|
+
value: MEGABYTE,
|
17
|
+
label: i18n('mb'),
|
18
|
+
},
|
19
|
+
gb: {
|
20
|
+
value: GIGABYTE,
|
21
|
+
label: i18n('gb'),
|
22
|
+
},
|
23
|
+
};
|
24
|
+
|
25
|
+
export type IBytesSizes = keyof typeof sizes;
|
26
|
+
|
27
|
+
interface FormatBytesArgs {
|
28
|
+
value: number | string | undefined;
|
29
|
+
size?: IBytesSizes;
|
30
|
+
precision?: number;
|
31
|
+
withLabel?: boolean;
|
32
|
+
isSpeed?: boolean;
|
33
|
+
}
|
34
|
+
|
35
|
+
export const formatBytesCustom = ({
|
36
|
+
value,
|
37
|
+
size = 'mb',
|
38
|
+
precision = 0,
|
39
|
+
withLabel = true,
|
40
|
+
isSpeed = false,
|
41
|
+
}: FormatBytesArgs) => {
|
42
|
+
if (!isNumeric(value)) {
|
43
|
+
return '';
|
44
|
+
}
|
45
|
+
|
46
|
+
let result = (Number(value) / sizes[size].value).toFixed(precision);
|
47
|
+
|
48
|
+
if (withLabel) {
|
49
|
+
result += ` ${sizes[size].label}`;
|
50
|
+
|
51
|
+
if (isSpeed) {
|
52
|
+
result += i18n('perSecond');
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
return result;
|
57
|
+
};
|