ydb-embedded-ui 3.3.4 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. package/CHANGELOG.md +19 -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 +18 -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 +4 -0
  33. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +8 -2
  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/Partitions/i18n/index.ts +11 -0
  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/utils/schema.ts +1 -16
  52. package/dist/services/api.d.ts +4 -0
  53. package/dist/services/api.js +22 -6
  54. package/dist/store/reducers/consumer.ts +160 -0
  55. package/dist/store/reducers/index.ts +2 -0
  56. package/dist/store/reducers/settings.js +2 -0
  57. package/dist/store/reducers/topic.ts +82 -2
  58. package/dist/types/store/consumer.ts +55 -0
  59. package/dist/types/store/topic.ts +23 -6
  60. package/dist/utils/bytesParsers/convertBytesObjectToSpeed.ts +24 -0
  61. package/dist/utils/bytesParsers/formatBytesCustom.ts +57 -0
  62. package/dist/utils/bytesParsers/i18n/en.json +7 -0
  63. package/dist/utils/bytesParsers/i18n/index.ts +11 -0
  64. package/dist/utils/bytesParsers/i18n/ru.json +7 -0
  65. package/dist/utils/bytesParsers/index.ts +2 -0
  66. package/dist/utils/constants.ts +3 -0
  67. package/dist/utils/index.js +6 -0
  68. package/dist/utils/storage.ts +2 -2
  69. package/dist/utils/timeParsers/index.ts +2 -1
  70. package/dist/utils/timeParsers/parsers.ts +18 -0
  71. package/dist/utils/timeParsers/{protobuf.ts → protobufParsers.ts} +0 -0
  72. package/dist/utils/utils.js +3 -3
  73. package/package.json +2 -2
@@ -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;
@@ -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
+ }
@@ -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
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "b": "B",
3
+ "kb": "KB",
4
+ "mb": "MB",
5
+ "gb": "GB",
6
+ "perSecond": "/s"
7
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-bytes-parsers';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,7 @@
1
+ {
2
+ "b": "Б",
3
+ "kb": "КБ",
4
+ "mb": "МБ",
5
+ "gb": "ГБ",
6
+ "perSecond": "/с"
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './formatBytesCustom';
2
+ export * from './convertBytesObjectToSpeed';
@@ -9,6 +9,7 @@ export const AUTO_RELOAD_INTERVAL = 10 * SECOND;
9
9
  // by agreement, display all byte values in decimal scale
10
10
  // values in data are always in bytes, never in higher units,
11
11
  // therefore there is no issue arbitrary converting them in UI
12
+ export const KILOBYTE = 1_000;
12
13
  export const MEGABYTE = 1_000_000;
13
14
  export const GIGABYTE = 1_000_000_000;
14
15
  export const TERABYTE = 1_000_000_000_000;
@@ -118,3 +119,5 @@ export const DEFAULT_TABLE_SETTINGS = {
118
119
 
119
120
  export const TENANT_INITIAL_TAB_KEY = 'saved_tenant_initial_tab';
120
121
  export const QUERY_INITIAL_RUN_ACTION_KEY = 'query_initial_run_action';
122
+
123
+ export const PARTITIONS_SELECTED_COLUMNS_KEY = 'partitionsSelectedColumns';
@@ -9,6 +9,8 @@ import {isNumeric} from './utils';
9
9
 
10
10
  numeral.locale(i18n.lang);
11
11
 
12
+ // Here you can't control displayed size and precision
13
+ // If you need more custom format, use formatBytesCustom instead
12
14
  export const formatBytes = (bytes) => {
13
15
  if (!isNumeric(bytes)) {
14
16
  return '';
@@ -42,6 +44,10 @@ export const formatUptime = (seconds) => {
42
44
  return uptime;
43
45
  };
44
46
 
47
+ export const formatMsToUptime = (ms) => {
48
+ return formatUptime(ms / 1000);
49
+ };
50
+
45
51
  export const formatIOPS = (value, capacity) => {
46
52
  return [Math.floor(value), Math.floor(capacity) + ' IOPS'];
47
53
  };
@@ -1,8 +1,8 @@
1
1
  import type {TVSlotId, TVDiskStateInfo} from '../types/api/vdisk';
2
2
  import type {IStoragePoolGroup} from '../types/store/storage';
3
3
 
4
- export const isFullDonorData = (donor: TVDiskStateInfo | TVSlotId): donor is TVDiskStateInfo =>
5
- 'VDiskId' in donor;
4
+ export const isFullVDiksData = (disk: TVDiskStateInfo | TVSlotId): disk is TVDiskStateInfo =>
5
+ 'VDiskId' in disk;
6
6
 
7
7
  export const getUsage = (data: IStoragePoolGroup, step = 1) => {
8
8
  // if limit is 0, display 0
@@ -1,2 +1,3 @@
1
1
  export * from './formatDuration';
2
- export * from './protobuf';
2
+ export * from './protobufParsers';
3
+ export * from './parsers';
@@ -0,0 +1,18 @@
1
+ import type {IProtobufTimeObject} from '../../types/api/common';
2
+
3
+ import {parseProtobufDurationToMs, parseProtobufTimestampToMs} from '.';
4
+
5
+ export const parseLag = (value: string | IProtobufTimeObject | undefined) =>
6
+ value ? parseProtobufDurationToMs(value) : 0;
7
+
8
+ export const parseTimestampToIdleTime = (value: string | IProtobufTimeObject | undefined) => {
9
+ if (!value) {
10
+ return 0;
11
+ }
12
+
13
+ const duration = Date.now() - parseProtobufTimestampToMs(value);
14
+
15
+ // Duration could be negative because of the difference between server and local time
16
+ // Usually it below 100ms, so it could be omitted
17
+ return duration < 0 ? 0 : duration;
18
+ };
@@ -76,9 +76,9 @@ export function bytesToGB(bytes, shouldRound) {
76
76
 
77
77
  export function pad9(val) {
78
78
  const len = String(val).length;
79
- let result = val
79
+ let result = val;
80
80
  for (let i = len; i < 9; i++) {
81
- result = "0" + result;
81
+ result = '0' + result;
82
82
  }
83
83
  return result;
84
84
  }
@@ -88,4 +88,4 @@ export function isNumeric(value) {
88
88
  // - isNaN treats true/false/''/etc. as numbers, parseFloat fixes this
89
89
  // - parseFloat treats '123qwe' as number, isNaN fixes this
90
90
  return !isNaN(value) && !isNaN(parseFloat(value));
91
- };
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "3.3.4",
3
+ "version": "3.4.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -39,7 +39,7 @@
39
39
  "reselect": "4.1.6",
40
40
  "sass": "1.32.8",
41
41
  "web-vitals": "1.1.2",
42
- "ydb-ui-components": "^3.0.2"
42
+ "ydb-ui-components": "^3.0.3"
43
43
  },
44
44
  "scripts": {
45
45
  "start": "react-app-rewired start",