ydb-embedded-ui 3.3.3 → 3.4.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 +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
|
+
};
|