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.
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
+ };