ydb-embedded-ui 3.3.4 → 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 (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",