ydb-embedded-ui 1.8.8 → 1.10.1

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 (41) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/components/BasicNodeViewer/BasicNodeViewer.scss +43 -0
  3. package/dist/components/BasicNodeViewer/BasicNodeViewer.tsx +53 -0
  4. package/dist/components/BasicNodeViewer/index.ts +1 -0
  5. package/dist/components/EntityStatus/EntityStatus.js +15 -3
  6. package/dist/components/FullNodeViewer/FullNodeViewer.js +29 -48
  7. package/dist/components/FullNodeViewer/FullNodeViewer.scss +0 -45
  8. package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +52 -0
  9. package/dist/components/InfoViewer/index.ts +4 -0
  10. package/dist/components/InfoViewer/utils.ts +32 -0
  11. package/dist/components/ProgressViewer/ProgressViewer.js +1 -1
  12. package/dist/containers/Node/Node.scss +5 -1
  13. package/dist/containers/Node/Node.tsx +7 -1
  14. package/dist/containers/Node/NodeOverview/NodeOverview.tsx +1 -3
  15. package/dist/containers/Node/NodeStructure/NodeStructure.scss +30 -1
  16. package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +25 -0
  17. package/dist/containers/Node/NodeStructure/Pdisk.tsx +24 -2
  18. package/dist/containers/Nodes/Nodes.js +1 -0
  19. package/dist/containers/Storage/Pdisk/Pdisk.tsx +25 -33
  20. package/dist/containers/Storage/Vdisk/Vdisk.js +2 -0
  21. package/dist/containers/Tablet/Tablet.js +2 -2
  22. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +24 -14
  23. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
  24. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +24 -3
  25. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +80 -10
  26. package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +20 -16
  27. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -0
  28. package/dist/containers/Tenant/utils/schema.ts +73 -28
  29. package/dist/containers/Tenant/utils/schemaActions.ts +45 -32
  30. package/dist/services/api.js +13 -8
  31. package/dist/store/reducers/executeQuery.js +1 -1
  32. package/dist/store/reducers/executeTopQueries.js +1 -1
  33. package/dist/store/reducers/olapStats.js +5 -1
  34. package/dist/store/reducers/preview.js +1 -1
  35. package/dist/store/reducers/shardsWorkload.js +32 -4
  36. package/dist/types/api/schema.ts +43 -1
  37. package/dist/types/api/storage.ts +54 -0
  38. package/dist/utils/getNodesColumns.js +2 -0
  39. package/dist/utils/pdisk.ts +74 -0
  40. package/dist/utils/tooltip.js +27 -0
  41. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import {Dispatch} from 'react';
2
- import type {NavigationTreeNodeType} from 'ydb-ui-components';
2
+ import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';
3
3
 
4
4
  import {changeUserInput} from '../../../store/reducers/executeQuery';
5
5
  import {setShowPreview} from '../../../store/reducers/schema';
@@ -73,6 +73,8 @@ const bindActions = (
73
73
  };
74
74
  };
75
75
 
76
+ type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;
77
+
76
78
  export const getActions = (
77
79
  dispatch: Dispatch<any>,
78
80
  setActivePath: (path: string) => void,
@@ -81,35 +83,46 @@ export const getActions = (
81
83
  const actions = bindActions(path, dispatch, setActivePath);
82
84
  const copyItem = {text: 'Copy path', action: actions.copyPath};
83
85
 
84
- switch (type) {
85
- case 'database':
86
- case 'directory':
87
- return [
88
- [
89
- copyItem,
90
- ],
91
- [
92
- {text: 'Create table...', action: actions.createTable},
93
- ],
94
- ];
95
- case 'table':
96
- return [
97
- [
98
- {text: 'Open preview', action: actions.openPreview},
99
- copyItem,
100
- ],
101
- [
102
- {text: 'Alter table...', action: actions.alterTable},
103
- {text: 'Select query...', action: actions.selectQuery},
104
- {text: 'Upsert query...', action: actions.upsertQuery},
105
- ],
106
- ];
107
- case 'index_table':
108
- return [
109
- copyItem,
110
- ];
111
- case 'index':
112
- default:
113
- return [];
114
- }
86
+ const DIR_SET: ActionsSet = [
87
+ [
88
+ copyItem,
89
+ ],
90
+ [
91
+ {text: 'Create table...', action: actions.createTable},
92
+ ],
93
+ ];
94
+ const TABLE_SET: ActionsSet = [
95
+ [
96
+ {text: 'Open preview', action: actions.openPreview},
97
+ copyItem,
98
+ ],
99
+ [
100
+ {text: 'Alter table...', action: actions.alterTable},
101
+ {text: 'Select query...', action: actions.selectQuery},
102
+ {text: 'Upsert query...', action: actions.upsertQuery},
103
+ ],
104
+ ];
105
+
106
+ const JUST_COPY: ActionsSet = [
107
+ copyItem,
108
+ ];
109
+
110
+ const EMPTY_SET: ActionsSet = [];
111
+
112
+ // verbose mapping to guarantee a correct actions set for new node types
113
+ // TS will error when a new type is added in the lib but is not mapped here
114
+ const nodeTypeToActions: Record<NavigationTreeNodeType, ActionsSet> = {
115
+ database: DIR_SET,
116
+ directory: DIR_SET,
117
+
118
+ table: TABLE_SET,
119
+ column_table: TABLE_SET,
120
+
121
+ index_table: JUST_COPY,
122
+ topic: JUST_COPY,
123
+
124
+ index: EMPTY_SET,
125
+ };
126
+
127
+ return nodeTypeToActions[type];
115
128
  };
@@ -147,14 +147,19 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
147
147
  state: 0,
148
148
  });
149
149
  }
150
- sendQuery(query, database, action, stats) {
151
- return this.post(this.getPath('/viewer/json/query'), {
152
- query,
153
- database,
154
- action,
155
- stats,
156
- timeout: 600000,
157
- });
150
+ sendQuery({query, database, action, stats}, {concurrentId} = {}) {
151
+ return this.post(
152
+ this.getPath('/viewer/json/query'),
153
+ {
154
+ query,
155
+ database,
156
+ action,
157
+ stats,
158
+ timeout: 600000,
159
+ },
160
+ null,
161
+ {concurrentId},
162
+ );
158
163
  }
159
164
  getExplainQuery(query, database) {
160
165
  return this.post(this.getPath('/viewer/json/query'), {
@@ -141,7 +141,7 @@ const executeQuery = (state = initialState, action) => {
141
141
 
142
142
  export const sendQuery = ({query, database, action}) => {
143
143
  return createApiRequest({
144
- request: window.api.sendQuery(query, database, action, 'profile'),
144
+ request: window.api.sendQuery({query, database, action, stats: 'profile'}),
145
145
  actions: SEND_QUERY,
146
146
  dataHandler: (result) => {
147
147
  const resultData = result.result ?? result;
@@ -47,7 +47,7 @@ const executeTopQueries = (state = initialState, action) => {
47
47
 
48
48
  export const sendQuery = ({query, database, action}) => {
49
49
  return createApiRequest({
50
- request: window.api.sendQuery(query, database, action),
50
+ request: window.api.sendQuery({query, database, action}),
51
51
  actions: SEND_QUERY,
52
52
  dataHandler: (result) => {
53
53
  if (result && typeof result === 'string') {
@@ -52,7 +52,11 @@ const olapStats = (state = initialState, action) => {
52
52
 
53
53
  export const getOlapStats = ({path = ''}) => {
54
54
  return createApiRequest({
55
- request: window.api.sendQuery(createOlatStatsQuery(path), path, queryAction),
55
+ request: window.api.sendQuery({
56
+ query: createOlatStatsQuery(path),
57
+ database: path,
58
+ action: queryAction,
59
+ }),
56
60
  actions: FETCH_OLAP_STATS,
57
61
  dataHandler: (result) => {
58
62
  if (result && typeof result === 'string') {
@@ -47,7 +47,7 @@ const preview = (state = initialState, action) => {
47
47
 
48
48
  export const sendQuery = ({query, database, action}) => {
49
49
  return createApiRequest({
50
- request: window.api.sendQuery(query, database, action),
50
+ request: window.api.sendQuery({query, database, action}),
51
51
  actions: SEND_QUERY,
52
52
  dataHandler: (data) => {
53
53
  if (!Array.isArray(data)) {
@@ -9,8 +9,30 @@ const initialState = {
9
9
  wasLoaded: false,
10
10
  };
11
11
 
12
- function createShardQuery(path) {
13
- return `SELECT Path, TabletId, CPUCores FROM \`.sys/partition_stats\` WHERE Path='${path}' OR Path LIKE '${path}/%' ORDER BY CPUCores DESC LIMIT 20`;
12
+ function formatSortOrder({columnId, order}) {
13
+ return `${columnId} ${order}`;
14
+ }
15
+
16
+ function createShardQuery(path, sortOrder, tenantName) {
17
+ const orderBy = Array.isArray(sortOrder) ?
18
+ `ORDER BY ${sortOrder.map(formatSortOrder).join(', ')}` :
19
+ '';
20
+
21
+ const pathSelect = tenantName ?
22
+ `CAST(SUBSTRING(CAST(Path AS String), ${tenantName.length}) AS Utf8) AS Path` :
23
+ 'Path';
24
+
25
+ return `SELECT
26
+ ${pathSelect},
27
+ TabletId,
28
+ CPUCores,
29
+ DataSize
30
+ FROM \`.sys/partition_stats\`
31
+ WHERE
32
+ Path='${path}'
33
+ OR Path LIKE '${path}/%'
34
+ ${orderBy}
35
+ LIMIT 20`;
14
36
  }
15
37
 
16
38
  const queryAction = 'execute-scan';
@@ -51,9 +73,15 @@ const shardsWorkload = (state = initialState, action) => {
51
73
  }
52
74
  };
53
75
 
54
- export const sendShardQuery = ({database, path = ''}) => {
76
+ export const sendShardQuery = ({database, path = '', sortOrder}) => {
55
77
  return createApiRequest({
56
- request: window.api.sendQuery(createShardQuery(path), database, queryAction),
78
+ request: window.api.sendQuery({
79
+ query: createShardQuery(path, sortOrder, database),
80
+ database,
81
+ action: queryAction,
82
+ }, {
83
+ concurrentId: 'topShards',
84
+ }),
57
85
  actions: SEND_SHARD_QUERY,
58
86
  dataHandler: (result) => {
59
87
  if (result && typeof result === 'string') {
@@ -54,6 +54,8 @@ interface TPathDescription {
54
54
 
55
55
  ColumnStoreDescription?: unknown;
56
56
  ColumnTableDescription?: unknown;
57
+
58
+ TableIndex?: TIndexDescription;
57
59
  }
58
60
 
59
61
  interface TDirEntry {
@@ -80,15 +82,42 @@ interface TDirEntry {
80
82
  Version?: TPathVersion;
81
83
  }
82
84
 
85
+ export interface TIndexDescription {
86
+ Name?: string;
87
+ /** uint64 */
88
+ LocalPathId?: string;
89
+
90
+ Type?: EIndexType;
91
+ State?: EIndexState;
92
+
93
+ KeyColumnNames?: string[];
94
+
95
+ /** uint64 */
96
+ SchemaVersion?: string;
97
+
98
+ /** uint64 */
99
+ PathOwnerId?: string;
100
+
101
+ DataColumnNames?: string[];
102
+ /** uint64 */
103
+ DataSize?: string;
104
+ }
105
+
83
106
  // incomplete
84
107
  export enum EPathType {
85
108
  EPathTypeInvalid = 'EPathTypeInvalid',
86
109
  EPathTypeDir = 'EPathTypeDir',
87
110
  EPathTypeTable = 'EPathTypeTable',
111
+
88
112
  EPathTypeSubDomain = 'EPathTypeSubDomain',
113
+
114
+ EPathTypeTableIndex = 'EPathTypeTableIndex',
115
+ EPathTypeExtSubDomain = 'EPathTypeExtSubDomain',
116
+
89
117
  EPathTypeColumnStore = 'EPathTypeColumnStore',
90
118
  EPathTypeColumnTable = 'EPathTypeColumnTable',
91
- EPathTypeTableIndex = 'EPathTypeTableIndex',
119
+ EPathTypeCdcStream = 'EPathTypeCdcStream',
120
+
92
121
  }
93
122
 
94
123
  export enum EPathSubType {
@@ -112,6 +141,19 @@ enum EPathState {
112
141
  EPathStateMoving = 'EPathStateMoving',
113
142
  }
114
143
 
144
+ enum EIndexType {
145
+ EIndexTypeInvalid = 'EIndexTypeInvalid',
146
+ EIndexTypeGlobal = 'EIndexTypeGlobal',
147
+ EIndexTypeGlobalAsync = 'EIndexTypeGlobalAsync',
148
+ }
149
+
150
+ enum EIndexState {
151
+ EIndexStateInvalid = 'EIndexStateInvalid',
152
+ EIndexStateReady = 'EIndexStateReady',
153
+ EIndexStateNotReady = 'EIndexStateNotReady',
154
+ EIndexStateWriteOnly = 'EIndexStateWriteOnly',
155
+ }
156
+
115
157
  // incomplete
116
158
  interface TPathVersion {
117
159
  /** uint64 */
@@ -0,0 +1,54 @@
1
+ enum EFlag {
2
+ Grey = 'Grey',
3
+ Green = 'Green',
4
+ Yellow = 'Yellow',
5
+ Orange = 'Orange',
6
+ Red = 'Red',
7
+ }
8
+
9
+ export enum TPDiskState {
10
+ Initial = 'Initial',
11
+ InitialFormatRead = 'InitialFormatRead',
12
+ InitialFormatReadError = 'InitialFormatReadError',
13
+ InitialSysLogRead = 'InitialSysLogRead',
14
+ InitialSysLogReadError = 'InitialSysLogReadError',
15
+ InitialSysLogParseError = 'InitialSysLogParseError',
16
+ InitialCommonLogRead = 'InitialCommonLogRead',
17
+ InitialCommonLogReadError = 'InitialCommonLogReadError',
18
+ InitialCommonLogParseError = 'InitialCommonLogParseError',
19
+ CommonLoggerInitError = 'CommonLoggerInitError',
20
+ Normal = 'Normal',
21
+ OpenFileError = 'OpenFileError',
22
+ ChunkQuotaError = 'ChunkQuotaError',
23
+ DeviceIoError = 'DeviceIoError',
24
+
25
+ Missing = 'Missing',
26
+ Timeout = 'Timeout',
27
+ NodeDisconnected = 'NodeDisconnected',
28
+ Unknown = 'Unknown',
29
+ }
30
+
31
+ export interface TPDiskStateInfo {
32
+ PDiskId?: number;
33
+ /** uint64 */
34
+ CreateTime?: string;
35
+ /** uint64 */
36
+ ChangeTime?: string;
37
+ Path?: string;
38
+ /** uint64 */
39
+ Guid?: string;
40
+ /** uint64 */
41
+ Category?: string;
42
+ /** uint64 */
43
+ AvailableSize?: string;
44
+ /** uint64 */
45
+ TotalSize?: string;
46
+ State?: TPDiskState;
47
+ NodeId?: number;
48
+ Count?: number;
49
+ Device?: EFlag;
50
+ Realtime?: EFlag;
51
+ StateFlag?: EFlag;
52
+ Overall?: EFlag;
53
+ SerialNumber?: string;
54
+ }
@@ -33,6 +33,8 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
33
33
  <div className={b('host-name-wrapper')}>
34
34
  <EntityStatus
35
35
  name={row.Host}
36
+ onNameMouseEnter={(e) => showTooltip(e.target, row, 'nodeEndpoints')}
37
+ onNameMouseLeave={hideTooltip}
36
38
  status={row.Overall}
37
39
  path={getDefaultNodePath(row.NodeId)}
38
40
  hasClipboardButton
@@ -0,0 +1,74 @@
1
+ import type {TPDiskStateInfo} from "../types/api/storage";
2
+
3
+ // TODO: move to utils or index after converting them to TS
4
+ /**
5
+ * Parses a binary string containing a bit field into an object with binary values.
6
+ * This is an implementation based on string manipulation, since JS can only perform
7
+ * bitwise operations with 32-bits integers, and YDB sends uint64.
8
+ * @see https://en.cppreference.com/w/c/language/bit_field
9
+ * @param binaryString - binary string representing a bit field
10
+ * @param bitFieldStruct - bit field description, <field => size in bits>, in order starting from the rightmost bit
11
+ * @returns object with binary values
12
+ */
13
+ export const parseBitField = <T extends Record<string, number>>(
14
+ binaryString: string,
15
+ bitFieldStruct: T,
16
+ ): Record<keyof T, string> => {
17
+ const fields: Partial<Record<keyof T, string>> = {};
18
+
19
+ Object.entries(bitFieldStruct).reduce((prefixSize, [field, size]: [keyof T, number]) => {
20
+ const end = binaryString.length - prefixSize;
21
+ const start = end - size;
22
+ fields[field] = binaryString.substring(start, end) || '0';
23
+
24
+ return prefixSize + size;
25
+ }, 0);
26
+
27
+ return fields as Record<keyof T, string>;
28
+ };
29
+
30
+ export enum IPDiskType {
31
+ ROT = 'ROT',
32
+ SSD = 'SSD',
33
+ MVME = 'NVME',
34
+ }
35
+
36
+ // Bear with me.
37
+ // Disk type is determined by the field Category.
38
+ // Category is a bit field defined as follows:
39
+ // struct {
40
+ // ui64 IsSolidState : 1;
41
+ // ui64 Kind : 55;
42
+ // ui64 TypeExt : 8;
43
+ // }
44
+ // For compatibility TypeExt is not used for old types (ROT, SSD), so the following scheme is used:
45
+ // ROT -> IsSolidState# 0, TypeExt# 0
46
+ // SSD -> IsSolidState# 1, TypeExt# 0
47
+ // NVME -> IsSolidState# 1, TypeExt# 2
48
+ // Reference on bit fields: https://en.cppreference.com/w/c/language/bit_field
49
+ export const getPDiskType = (data: TPDiskStateInfo): IPDiskType | undefined => {
50
+ if (!data.Category) {
51
+ return undefined;
52
+ }
53
+
54
+ // Category is uint64, too big for Number or bitwise operators, thus BigInt and a custom parser
55
+ const categotyBin = BigInt(data.Category).toString(2);
56
+ const categoryBitField = parseBitField(categotyBin, {
57
+ isSolidState: 1,
58
+ kind: 55,
59
+ typeExt: 8,
60
+ });
61
+
62
+ if (categoryBitField.isSolidState === '1') {
63
+ switch (parseInt(categoryBitField.typeExt, 2)) {
64
+ case 0:
65
+ return IPDiskType.SSD;
66
+ case 2:
67
+ return IPDiskType.MVME;
68
+ }
69
+ } else if (categoryBitField.typeExt === '0') {
70
+ return IPDiskType.ROT;
71
+ }
72
+
73
+ return undefined;
74
+ };
@@ -109,6 +109,32 @@ const NodeTooltip = (props) => {
109
109
  );
110
110
  };
111
111
 
112
+ const NodeEndpointsTooltip = (props) => {
113
+ const {data} = props;
114
+ return (
115
+ data && (
116
+ <div className={nodeB()}>
117
+ <table>
118
+ <tbody>
119
+ {data.Rack && (
120
+ <tr>
121
+ <td className={nodeB('label')}>Rack</td>
122
+ <td className={nodeB('value')}>{data.Rack}</td>
123
+ </tr>
124
+ )}
125
+ {data.Endpoints && data.Endpoints.length && data.Endpoints.map(({Name, Address}) => (
126
+ <tr key={Name}>
127
+ <td className={nodeB('label')}>{Name}</td>
128
+ <td className={nodeB('value')}>{Address}</td>
129
+ </tr>
130
+ ))}
131
+ </tbody>
132
+ </table>
133
+ </div>
134
+ )
135
+ );
136
+ };
137
+
112
138
  const tabletsOverallB = cn('tabletsOverall-tooltip');
113
139
 
114
140
  const TabletsOverallTooltip = (props) => {
@@ -175,6 +201,7 @@ export const tooltipTemplates = {
175
201
  tablet: (data, additionalData) => <TabletTooltip data={data} additionalData={additionalData} />,
176
202
  // eslint-disable-next-line react/display-name
177
203
  node: (data) => <NodeTooltip data={data} />,
204
+ nodeEndpoints: (data) => <NodeEndpointsTooltip data={data} />,
178
205
  // eslint-disable-next-line react/display-name
179
206
  tabletsOverall: (data) => <TabletsOverallTooltip data={data} />,
180
207
  // eslint-disable-next-line react/display-name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.8.8",
3
+ "version": "1.10.1",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -40,7 +40,7 @@
40
40
  "reselect": "4.0.0",
41
41
  "sass": "1.32.8",
42
42
  "web-vitals": "1.1.2",
43
- "ydb-ui-components": "2.3.0"
43
+ "ydb-ui-components": "2.4.1"
44
44
  },
45
45
  "scripts": {
46
46
  "start": "react-app-rewired start",