ydb-embedded-ui 3.4.5 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/InfoViewer/formatters/table.ts +6 -0
  3. package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
  4. package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
  5. package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
  6. package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
  7. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
  8. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
  9. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
  10. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
  11. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
  12. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
  13. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -3
  14. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.scss +8 -0
  15. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.tsx +21 -0
  16. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +58 -82
  17. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -33
  18. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +83 -0
  19. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.scss +57 -0
  20. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +84 -0
  21. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +23 -0
  22. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +12 -23
  23. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +4 -6
  24. package/dist/containers/Tenant/QueryEditor/i18n/en.json +3 -0
  25. package/dist/containers/Tenant/QueryEditor/i18n/index.ts +11 -0
  26. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +3 -0
  27. package/dist/containers/UserSettings/UserSettings.tsx +30 -1
  28. package/dist/routes.ts +1 -1
  29. package/dist/services/api.d.ts +4 -3
  30. package/dist/services/api.js +5 -5
  31. package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +48 -43
  32. package/dist/store/reducers/executeTopQueries.ts +5 -1
  33. package/dist/store/reducers/{explainQuery.js → explainQuery.ts} +44 -59
  34. package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
  35. package/dist/store/reducers/settings.js +19 -4
  36. package/dist/store/reducers/storage.js +5 -7
  37. package/dist/types/api/error.ts +14 -0
  38. package/dist/types/api/query.ts +227 -115
  39. package/dist/types/api/schema.ts +523 -3
  40. package/dist/types/common.ts +1 -0
  41. package/dist/types/store/executeQuery.ts +38 -0
  42. package/dist/types/store/explainQuery.ts +38 -0
  43. package/dist/types/store/olapStats.ts +14 -0
  44. package/dist/types/store/query.ts +23 -3
  45. package/dist/utils/constants.ts +2 -1
  46. package/dist/utils/error.ts +25 -0
  47. package/dist/utils/index.js +0 -49
  48. package/dist/utils/prepareQueryExplain.ts +7 -24
  49. package/dist/utils/query.test.ts +148 -213
  50. package/dist/utils/query.ts +68 -90
  51. package/dist/utils/timeParsers/formatDuration.ts +30 -12
  52. package/dist/utils/timeParsers/i18n/en.json +9 -5
  53. package/dist/utils/timeParsers/i18n/ru.json +9 -5
  54. package/dist/utils/timeParsers/parsers.ts +9 -0
  55. package/dist/utils/utils.js +1 -2
  56. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ import type {AxiosError} from 'axios';
2
+
3
+ import type {IResponseError, NetworkError} from '../types/api/error';
4
+ import type {ErrorResponse as QueryErrorResponse} from '../types/api/query';
5
+ import type {QueryError} from '../types/store/query';
6
+
7
+ type RequestError = NetworkError | IResponseError | AxiosError | QueryErrorResponse | unknown;
8
+
9
+ const isNetworkError = (error: RequestError): error is NetworkError => {
10
+ return Boolean(
11
+ error &&
12
+ typeof error === 'object' &&
13
+ 'message' in error &&
14
+ (error as {message: unknown}).message === 'Network Error',
15
+ );
16
+ };
17
+
18
+ export const parseQueryError = (error: QueryError): QueryErrorResponse | string | undefined => {
19
+ if (isNetworkError(error)) {
20
+ return error.message;
21
+ }
22
+
23
+ // 401 Unauthorized error is handled by GenericAPI
24
+ return error ?? 'Unauthorized';
25
+ };
@@ -113,52 +113,3 @@ export const renderExplainNode = (node) => {
113
113
  const parts = node.name.split('|');
114
114
  return parts.length > 1 ? parts[1] : node.name;
115
115
  };
116
-
117
- export const getExplainNodeId = (...values) => {
118
- return values.join('|');
119
- };
120
-
121
- const getStringFromProps = (props) => {
122
- return props
123
- .map(([name, value]) => {
124
- return value && `${name}: ${Array.isArray(value) ? value.join(', ') : value}`;
125
- })
126
- .filter(Boolean)
127
- .join('\n');
128
- };
129
-
130
- export const getMetaForExplainNode = (node) => {
131
- switch (node.type) {
132
- case 'MultiLookup':
133
- case 'Lookup': {
134
- return getStringFromProps([
135
- ['lookup by', node.lookup_by],
136
- ['columns', node.columns],
137
- ]);
138
- }
139
- case 'FullScan':
140
- case 'Scan': {
141
- return getStringFromProps([
142
- ['scan by', node.scan_by],
143
- ['limit', node.limit],
144
- ['columns', node.columns],
145
- ]);
146
- }
147
- case 'Upsert':
148
- case 'MultiUpsert': {
149
- return getStringFromProps([
150
- ['key', node.key],
151
- ['columns', node.columns],
152
- ]);
153
- }
154
- case 'Erase':
155
- case 'MultiErase': {
156
- return getStringFromProps([
157
- ['key', node.key],
158
- ['columns', node.columns],
159
- ]);
160
- }
161
- default:
162
- return '';
163
- }
164
- };
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  Link,
3
3
  GraphNode,
4
4
  TopologyNodeDataStats,
@@ -7,28 +7,11 @@ import {
7
7
  TopologyNodeDataStatsItem,
8
8
  } from '@gravity-ui/paranoid';
9
9
 
10
- interface PlanOperator {
11
- Name: string;
12
- [key: string]: any;
13
- }
14
-
15
- export interface Plan {
16
- PlanNodeId: number;
17
- 'Node Type': string;
18
- Plans?: Plan[];
19
- Operators?: PlanOperator[];
20
- Tables?: string[];
21
- PlanNodeType?: string;
22
- [key: string]: any;
23
- }
24
-
25
- export interface RootPlan {
26
- Plan: Plan;
27
- }
10
+ import type {PlanNode} from '../types/api/query';
28
11
 
29
12
  const CONNECTION_NODE_META_FIELDS = new Set(['PlanNodeId', 'PlanNodeType', 'Node Type', 'Plans']);
30
13
 
31
- function prepareStats(plan: Plan) {
14
+ function prepareStats(plan: PlanNode) {
32
15
  const stats: TopologyNodeDataStats[] = [];
33
16
 
34
17
  if (plan.Operators) {
@@ -77,7 +60,7 @@ function prepareStats(plan: Plan) {
77
60
  return stats;
78
61
  }
79
62
 
80
- function getNodeType(plan: Plan) {
63
+ function getNodeType(plan: PlanNode) {
81
64
  switch (plan.PlanNodeType) {
82
65
  case 'Connection':
83
66
  return 'connection';
@@ -90,11 +73,11 @@ function getNodeType(plan: Plan) {
90
73
  }
91
74
  }
92
75
 
93
- export function preparePlan(plan: Plan) {
94
- const nodes: GraphNode[] = [];
76
+ export function preparePlan(plan: PlanNode) {
77
+ const nodes: GraphNode<ExplainPlanNodeData>[] = [];
95
78
  const links: Link[] = [];
96
79
 
97
- function parsePlans(plans: Plan[] = [], from: string) {
80
+ function parsePlans(plans: PlanNode[] = [], from: string) {
98
81
  plans.forEach((p) => {
99
82
  const node: GraphNode<ExplainPlanNodeData> = {
100
83
  name: String(p.PlanNodeId),
@@ -1,247 +1,182 @@
1
+ import type {PlanMeta, PlanNode, PlanTable, TKqpStatsQuery} from '../types/api/query';
1
2
  import {
2
3
  parseQueryAPIExecuteResponse,
3
4
  parseQueryAPIExplainResponse,
5
+ parseQueryExplainPlan,
4
6
  } from './query';
5
7
 
6
8
  describe('API utils', () => {
7
9
  describe('json/viewer/query', () => {
8
10
  describe('parseQueryAPIExecuteResponse', () => {
9
- describe('old format', () => {
10
- describe('plain response', () => {
11
- it('should handle empty response', () => {
12
- expect(parseQueryAPIExecuteResponse(null).result).toBeUndefined();
13
- });
14
-
15
- it('should parse json string', () => {
16
- const json = {foo: 'bar'};
17
- const response = JSON.stringify(json);
18
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(json);
19
- });
20
-
21
- // it should not be in the response, but is there because of a bug
22
- it('should ignore request plan as the response', () => {
23
- const response = {queries: 'some queries'};
24
- expect(parseQueryAPIExecuteResponse(response).result).toBeUndefined();
25
- });
26
-
27
- it('should accept key-value rows', () => {
28
- const response = [{foo: 'bar'}];
29
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(response);
30
- });
11
+ describe('should handle responses with incorrect format', () => {
12
+ it('should handle null response', () => {
13
+ expect(parseQueryAPIExecuteResponse(null)).toEqual({});
31
14
  });
32
-
33
- describe('deep response without stats', () => {
34
- it('should parse json string in the result field', () => {
35
- const json = {foo: 'bar'};
36
- const response = {result: JSON.stringify(json)};
37
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(json);
38
- });
39
-
40
- // it should not be in the response, but is there because of a bug
41
- it('should ignore request plan in the result field', () => {
42
- const response = {result: {queries: 'some queries'}};
43
- expect(parseQueryAPIExecuteResponse(response).result).toBeUndefined();
44
- });
45
-
46
- it('should accept key-value rows in the result field', () => {
47
- const response = {result: [{foo: 'bar'}]};
48
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(response.result);
49
- });
15
+ it('should handle undefined response', () => {
16
+ expect(parseQueryAPIExecuteResponse(undefined)).toEqual({});
50
17
  });
51
-
52
- describe('deep response with stats', () => {
53
- it('should parse json string in the result field', () => {
54
- const json = {foo: 'bar'};
55
- const response = {
56
- result: JSON.stringify(json),
57
- stats: {metric: 'good'},
58
- };
59
- const actual = parseQueryAPIExecuteResponse(response);
60
- expect(actual.result).toEqual(json);
61
- expect(actual.stats).toEqual(response.stats);
62
- });
63
-
64
- // it should not be in the response, but is there because of a bug
65
- it('should ignore request plan in the result field', () => {
66
- const response = {
67
- result: {queries: 'some queries'},
68
- stats: {metric: 'good'},
69
- };
70
- const actual = parseQueryAPIExecuteResponse(response);
71
- expect(actual.result).toBeUndefined();
72
- expect(actual.stats).toEqual(response.stats);
73
- });
74
-
75
- it('should accept key-value rows in the result field', () => {
76
- const response = {
77
- result: [{foo: 'bar'}],
78
- stats: {metric: 'good'},
79
- };
80
- const actual = parseQueryAPIExecuteResponse(response);
81
- expect(actual.result).toEqual(response.result);
82
- expect(actual.stats).toEqual(response.stats);
83
- });
84
-
85
- it('should accept stats without a result', () => {
86
- const response = {
87
- stats: {metric: 'good'},
88
- };
89
- const actual = parseQueryAPIExecuteResponse(response);
90
- expect(actual.result).toBeUndefined();
91
- expect(actual.stats).toEqual(response.stats);
92
- });
18
+ it('should handle string response', () => {
19
+ expect(parseQueryAPIExecuteResponse('foo')).toEqual({});
20
+ });
21
+ it('should handle array response', () => {
22
+ expect(parseQueryAPIExecuteResponse([{foo: 'bar'}])).toEqual({});
23
+ });
24
+ it('should handle json string in the result field', () => {
25
+ const json = {foo: 'bar'};
26
+ const response = {result: JSON.stringify(json)};
27
+ expect(parseQueryAPIExecuteResponse(response)).toEqual({});
28
+ });
29
+ it('should handle object with request plan in the result field', () => {
30
+ const response = {result: {queries: 'some queries'}};
31
+ expect(parseQueryAPIExecuteResponse(response)).toEqual({});
93
32
  });
94
33
  });
95
-
96
- describe('new format', () => {
97
- describe('response without stats', () => {
98
- it('should parse modern schema', () => {
99
- const response = {
100
- result: [['42', 'hello world']],
101
- columns: [{
34
+ describe('should correctly parse data', () => {
35
+ it('should parse modern schema result to KeyValueRow', () => {
36
+ const response = {
37
+ result: [['42', 'hello world']],
38
+ columns: [
39
+ {
102
40
  name: 'id',
103
- type: 'Uint64?'
104
- }, {
41
+ type: 'Uint64?',
42
+ },
43
+ {
105
44
  name: 'value',
106
- type: 'Utf8?'
107
- }],
108
- };
109
- const actual = parseQueryAPIExecuteResponse(response);
110
- expect(actual.result).toEqual([{
45
+ type: 'Utf8?',
46
+ },
47
+ ],
48
+ };
49
+ const parsedResponse = parseQueryAPIExecuteResponse(response);
50
+
51
+ expect(parsedResponse.result).toEqual([
52
+ {
111
53
  id: '42',
112
- value: 'hello world'
113
- }]);
114
- expect(actual.columns).toEqual(response.columns);
115
- });
116
-
117
- it('should handle empty response for classic schema', () => {
118
- expect(parseQueryAPIExecuteResponse(null).result).toBeUndefined();
119
- });
120
-
121
- it('should parse plain classic schema', () => {
122
- const response = [{foo: 'bar'}];
123
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(response);
124
- });
125
-
126
- it('should parse deep classic schema', () => {
127
- const response = {result: [{foo: 'bar'}]};
128
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(response.result);
129
- });
130
-
131
- it('should parse ydb schema', () => {
132
- const response = {result: [{foo: 'bar'}]};
133
- expect(parseQueryAPIExecuteResponse(response).result).toEqual(response.result);
134
- });
54
+ value: 'hello world',
55
+ },
56
+ ]);
57
+ expect(parsedResponse.columns).toEqual(response.columns);
135
58
  });
136
-
137
- describe('response with stats', () => {
138
- it('should parse modern schema', () => {
139
- const response = {
140
- result: [['42', 'hello world']],
141
- columns: [{
142
- name: 'id',
143
- type: 'Uint64?'
144
- }, {
145
- name: 'value',
146
- type: 'Utf8?'
147
- }],
148
- stats: {metric: 'good'},
149
- };
150
- const actual = parseQueryAPIExecuteResponse(response);
151
- expect(actual.result).toEqual([{
59
+ it('should return KeyValueRow result for ydb and classic schemas unchanged', () => {
60
+ const response = {result: [{foo: 'bar'}]};
61
+ expect(parseQueryAPIExecuteResponse(response).result).toEqual(response.result);
62
+ });
63
+ it('shoudl return stats for modern schema', () => {
64
+ const result = [['42', 'hello world']];
65
+ const columns = [
66
+ {
67
+ name: 'id',
68
+ type: 'Uint64?',
69
+ },
70
+ {
71
+ name: 'value',
72
+ type: 'Utf8?',
73
+ },
74
+ ];
75
+
76
+ const stats = {metric: 'good'} as TKqpStatsQuery;
77
+
78
+ const response = {result, columns, stats};
79
+ const parsedResponse = parseQueryAPIExecuteResponse(response);
80
+
81
+ expect(parsedResponse.result).toEqual([
82
+ {
152
83
  id: '42',
153
- value: 'hello world'
154
- }]);
155
- expect(actual.columns).toEqual(response.columns);
156
- expect(actual.stats).toEqual(response.stats);
157
- });
158
-
159
- it('should parse classic schema', () => {
160
- const response = {
161
- result: [{foo: 'bar'}],
162
- stats: {metric: 'good'},
163
- };
164
- const actual = parseQueryAPIExecuteResponse(response);
165
- expect(actual.result).toEqual(response.result);
166
- expect(actual.stats).toEqual(response.stats);
167
- });
84
+ value: 'hello world',
85
+ },
86
+ ]);
87
+ expect(parsedResponse.columns).toEqual(response.columns);
88
+ expect(parsedResponse.stats).toEqual(response.stats);
89
+ });
90
+ it('shoudl return stats for ydb and classic schemas', () => {
91
+ const result = [{foo: 'bar'}];
92
+ const stats = {metric: 'good'} as TKqpStatsQuery;
168
93
 
169
- it('should parse ydb schema', () => {
170
- const response = {
171
- result: [{foo: 'bar'}],
172
- stats: {metric: 'good'},
173
- };
174
- const actual = parseQueryAPIExecuteResponse(response);
175
- expect(actual.result).toEqual(response.result);
176
- expect(actual.stats).toEqual(response.stats);
177
- });
94
+ const response = {result, stats};
95
+ const parsedResponse = parseQueryAPIExecuteResponse(response);
178
96
 
179
- it('should accept stats without a result', () => {
180
- const response = {
181
- stats: {metric: 'good'},
182
- };
183
- const actual = parseQueryAPIExecuteResponse(response);
184
- expect(actual.result).toBeUndefined();
185
- expect(actual.columns).toBeUndefined();
186
- expect(actual.stats).toEqual(response.stats);
187
- });
97
+ expect(parsedResponse.result).toEqual(response.result);
98
+ expect(parsedResponse.stats).toEqual(response.stats);
99
+ });
100
+ it('should accept stats without a result', () => {
101
+ const stats = {metric: 'good'} as TKqpStatsQuery;
102
+ const response = {stats};
103
+ const actual = parseQueryAPIExecuteResponse(response);
104
+ expect(actual.result).toBeUndefined();
105
+ expect(actual.columns).toBeUndefined();
106
+ expect(actual.stats).toEqual(response.stats);
188
107
  });
189
108
  });
190
109
  });
191
110
 
192
111
  describe('parseQueryAPIExplainResponse', () => {
193
- it('should handle empty response', () => {
194
- expect(parseQueryAPIExplainResponse(null)).toEqual({});
195
- });
196
-
197
- it('should accept stats without a plan', () => {
198
- const stats = {metric: 'good'};
199
- expect(parseQueryAPIExplainResponse({stats}).stats).toEqual(stats);
200
- });
201
-
202
- describe('old format', () => {
203
- describe('explain', () => {
204
- it('should parse plan data in the root', () => {
205
- const plan = {foo: 'bar'};
206
- expect(parseQueryAPIExplainResponse(plan).plan).toEqual(plan);
207
- });
208
-
209
- it('should parse plan in the result field with stats', () => {
210
- const plan = {foo: 'bar'};
211
- const stats = {metric: 'good'};
212
- const actual = parseQueryAPIExplainResponse({result: plan, stats});
213
- expect(actual.plan).toEqual(plan);
214
- expect(actual.stats).toEqual(stats);
215
- });
112
+ describe('should handle responses with incorrect format', () => {
113
+ it('should handle null response', () => {
114
+ expect(parseQueryAPIExecuteResponse(null)).toEqual({});
216
115
  });
217
-
218
- describe('explain-ast', () => {
219
- it('should parse ast field in the root', () => {
220
- const ast = 'ast';
221
- expect(parseQueryAPIExplainResponse({ast}).ast).toBe(ast);
222
- });
223
-
224
- it('should parse ast in the result field with stats', () => {
225
- const ast = 'ast';
226
- const stats = {metric: 'good'};
227
- const actual = parseQueryAPIExplainResponse({result: {ast}, stats});
228
- expect(actual.ast).toBe(ast);
229
- expect(actual.stats).toEqual(stats);
230
- });
116
+ it('should handle undefined response', () => {
117
+ expect(parseQueryAPIExecuteResponse(undefined)).toEqual({});
118
+ });
119
+ it('should handle object with plan in the result field', () => {
120
+ const response = {result: {foo: 'bar'}};
121
+ expect(parseQueryAPIExecuteResponse(response)).toEqual({});
231
122
  });
232
123
  });
233
124
 
234
- describe('new format', () => {
235
- it('should parse explain response with stats', () => {
236
- const plan = {foo: 'bar'};
125
+ describe('should correctly parse data', () => {
126
+ it('should parse explain-scan', () => {
127
+ const plan: PlanNode = {};
128
+ const tables: PlanTable[] = [];
129
+ const meta: PlanMeta = {version: '0.2', type: 'script'};
237
130
  const ast = 'ast';
238
- const stats = {metric: 'good'};
239
- const actual = parseQueryAPIExplainResponse({plan, ast, stats});
240
- expect(actual.plan).toEqual(plan);
241
- expect(actual.ast).toBe(ast);
242
- expect(actual.stats).toEqual(stats);
131
+ const response = {plan: {Plan: plan, tables, meta}, ast};
132
+ expect(parseQueryAPIExplainResponse(response)).toBe(response);
133
+ });
134
+ it('should parse explain-script', () => {
135
+ const plan: PlanNode = {};
136
+ const tables: PlanTable[] = [];
137
+ const meta: PlanMeta = {version: '0.2', type: 'script'};
138
+
139
+ const response = {
140
+ plan: {queries: [{Plan: plan, tables}], meta},
141
+ };
142
+ expect(parseQueryAPIExplainResponse(response)).toBe(response);
243
143
  });
244
144
  });
245
145
  });
146
+
147
+ describe('parseQueryExplainPlan', () => {
148
+ it('should parse explain script plan to explain scan', () => {
149
+ const plan: PlanNode = {};
150
+ const tables: PlanTable[] = [];
151
+ const meta: PlanMeta = {version: '0.2', type: 'script'};
152
+
153
+ const rawPlan = {
154
+ queries: [
155
+ {
156
+ Plan: plan,
157
+ tables,
158
+ },
159
+ ],
160
+ meta,
161
+ };
162
+ const parsedPlan = parseQueryExplainPlan(rawPlan);
163
+ expect(parsedPlan.Plan).toEqual(plan);
164
+ expect(parsedPlan.tables).toBe(tables);
165
+ expect(parsedPlan.meta).toEqual(meta);
166
+ });
167
+ it('should left scan plan unchanged', () => {
168
+ const plan: PlanNode = {};
169
+ const tables: PlanTable[] = [];
170
+ const meta: PlanMeta = {version: '0.2', type: 'script'};
171
+
172
+ const rawPlan = {
173
+ Plan: plan,
174
+ tables: tables,
175
+ meta: meta,
176
+ };
177
+ const parsedPlan = parseQueryExplainPlan(rawPlan);
178
+ expect(parsedPlan).toEqual(rawPlan);
179
+ });
180
+ });
246
181
  });
247
182
  });