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.
Files changed (90) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/components/Errors/ResponseError/ResponseError.tsx +2 -2
  3. package/dist/components/InfoViewer/formatters/topicStats.tsx +8 -29
  4. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +20 -0
  5. package/dist/components/LabelWithPopover/index.ts +1 -0
  6. package/dist/components/LagImages/LagImages.tsx +205 -0
  7. package/dist/components/LagImages/index.ts +1 -0
  8. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.scss +92 -0
  9. package/dist/components/SpeedMultiMeter/SpeedMultiMeter.tsx +120 -0
  10. package/dist/components/SpeedMultiMeter/i18n/en.json +6 -0
  11. package/dist/components/SpeedMultiMeter/i18n/index.ts +13 -0
  12. package/dist/components/SpeedMultiMeter/i18n/ru.json +6 -0
  13. package/dist/components/SpeedMultiMeter/index.ts +1 -0
  14. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +26 -14
  15. package/dist/containers/Storage/VDisk/VDisk.tsx +20 -5
  16. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +34 -5
  17. package/dist/containers/Storage/utils/types.ts +5 -0
  18. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +32 -3
  19. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +62 -69
  20. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +13 -0
  21. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +27 -0
  22. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/index.ts +1 -0
  23. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.scss +32 -0
  24. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/ConsumersTopicStats.tsx +43 -0
  25. package/dist/containers/Tenant/Diagnostics/Consumers/TopicStats/index.ts +1 -0
  26. package/dist/containers/Tenant/Diagnostics/Consumers/columns/Columns.scss +5 -0
  27. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +66 -0
  28. package/dist/containers/Tenant/Diagnostics/Consumers/columns/index.ts +1 -0
  29. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +4 -3
  30. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +4 -3
  31. package/dist/containers/Tenant/Diagnostics/Consumers/utils/constants.ts +23 -0
  32. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +7 -3
  33. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -9
  34. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +9 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +6 -8
  36. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +33 -0
  37. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +76 -0
  38. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/index.ts +1 -0
  39. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +45 -0
  40. package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +254 -0
  41. package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsWrapper.tsx +79 -0
  42. package/dist/containers/Tenant/Diagnostics/Partitions/columns/Columns.scss +13 -0
  43. package/dist/containers/Tenant/Diagnostics/Partitions/columns/columns.tsx +246 -0
  44. package/dist/containers/Tenant/Diagnostics/Partitions/columns/index.ts +1 -0
  45. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +13 -0
  46. package/dist/containers/Tenant/Diagnostics/{OverloadedShards → Partitions}/i18n/index.ts +1 -1
  47. package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +13 -0
  48. package/dist/containers/Tenant/Diagnostics/Partitions/index.ts +1 -0
  49. package/dist/containers/Tenant/Diagnostics/Partitions/utils/constants.ts +74 -0
  50. package/dist/containers/Tenant/Diagnostics/Partitions/utils/types.ts +6 -0
  51. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.scss +8 -0
  52. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/Filters.tsx +56 -0
  53. package/dist/containers/Tenant/Diagnostics/TopShards/Filters/index.ts +1 -0
  54. package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.scss → TopShards/TopShards.scss} +2 -10
  55. package/dist/containers/Tenant/Diagnostics/{OverloadedShards/OverloadedShards.tsx → TopShards/TopShards.tsx} +61 -31
  56. package/dist/containers/Tenant/Diagnostics/TopShards/i18n/en.json +6 -0
  57. package/dist/containers/Tenant/Diagnostics/TopShards/i18n/index.ts +11 -0
  58. package/dist/containers/Tenant/Diagnostics/TopShards/i18n/ru.json +6 -0
  59. package/dist/containers/Tenant/Diagnostics/TopShards/index.ts +1 -0
  60. package/dist/containers/Tenant/utils/schema.ts +1 -16
  61. package/dist/services/api.d.ts +4 -0
  62. package/dist/services/api.js +22 -6
  63. package/dist/store/reducers/authentication.js +0 -15
  64. package/dist/store/reducers/consumer.ts +160 -0
  65. package/dist/store/reducers/index.ts +2 -0
  66. package/dist/store/reducers/settings.js +2 -0
  67. package/dist/store/reducers/shardsWorkload.ts +28 -2
  68. package/dist/store/reducers/topic.ts +82 -2
  69. package/dist/store/state-url-mapping.js +3 -0
  70. package/dist/types/store/consumer.ts +55 -0
  71. package/dist/types/store/shardsWorkload.ts +6 -0
  72. package/dist/types/store/topic.ts +23 -6
  73. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +24 -0
  74. package/dist/utils/bytesParsers/formatBytesCustom.ts +57 -0
  75. package/dist/utils/bytesParsers/i18n/en.json +7 -0
  76. package/dist/utils/bytesParsers/i18n/index.ts +11 -0
  77. package/dist/utils/bytesParsers/i18n/ru.json +7 -0
  78. package/dist/utils/bytesParsers/index.ts +2 -0
  79. package/dist/utils/constants.ts +3 -0
  80. package/dist/utils/index.js +6 -0
  81. package/dist/utils/storage.ts +2 -2
  82. package/dist/utils/timeParsers/index.ts +2 -1
  83. package/dist/utils/timeParsers/parsers.ts +18 -0
  84. package/dist/utils/timeParsers/{protobuf.ts → protobufParsers.ts} +0 -0
  85. package/dist/utils/typecheckers.ts +5 -0
  86. package/dist/utils/utils.js +3 -3
  87. package/package.json +2 -2
  88. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/en.json +0 -4
  89. package/dist/containers/Tenant/Diagnostics/OverloadedShards/i18n/ru.json +0 -4
  90. 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 pathTypeToIsCdcStream: Record<EPathType, boolean> = {
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
 
@@ -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
  }
@@ -141,12 +141,28 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
141
141
  path,
142
142
  });
143
143
  }
144
- getTopic({path}) {
145
- return this.get(this.getPath('/viewer/json/describe_topic'), {
146
- enums: true,
147
- include_stats: true,
148
- path,
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 createShardQuery(
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: createShardQuery(path, filters, sortOrder, database),
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 {ITopicAction, ITopicState} from '../../types/store/topic';
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;
@@ -48,6 +48,9 @@ const paramSetup = {
48
48
  generalTab: {
49
49
  stateKey: 'tenant.diagnosticsTab',
50
50
  },
51
+ shardsMode: {
52
+ stateKey: 'shardsWorkload.filters.mode',
53
+ },
51
54
  shardsDateFrom: {
52
55
  stateKey: 'shardsWorkload.filters.from',
53
56
  type: 'number',
@@ -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 = ApiRequestAction<
14
- typeof FETCH_TOPIC,
15
- DescribeTopicResult,
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
+ };