ydb-embedded-ui 4.10.1 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/components/QueryResultTable/Cell/Cell.tsx +8 -8
  3. package/dist/components/QueryResultTable/i18n/en.json +1 -1
  4. package/dist/components/QueryResultTable/i18n/ru.json +1 -1
  5. package/dist/components/ShortyString/ShortyString.tsx +3 -6
  6. package/dist/components/ShortyString/i18n/en.json +8 -8
  7. package/dist/components/ShortyString/i18n/ru.json +8 -8
  8. package/dist/components/SpeedMultiMeter/i18n/index.ts +0 -2
  9. package/dist/components/Stack/Stack.tsx +16 -16
  10. package/dist/components/TableSkeleton/TableSkeleton.tsx +3 -3
  11. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -3
  12. package/dist/containers/Header/Header.tsx +2 -2
  13. package/dist/containers/Header/breadcrumbs.ts +2 -1
  14. package/dist/containers/Heatmap/Heatmap.tsx +4 -3
  15. package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +2 -8
  16. package/dist/containers/Nodes/Nodes.tsx +1 -1
  17. package/dist/containers/Storage/EmptyFilter/i18n/en.json +2 -2
  18. package/dist/containers/Storage/EmptyFilter/i18n/ru.json +2 -2
  19. package/dist/containers/Storage/StorageGroups/i18n/en.json +5 -5
  20. package/dist/containers/Storage/StorageGroups/i18n/ru.json +5 -5
  21. package/dist/containers/Storage/UsageFilter/i18n/en.json +3 -8
  22. package/dist/containers/Storage/UsageFilter/i18n/ru.json +3 -8
  23. package/dist/containers/Tablet/Tablet.tsx +2 -2
  24. package/dist/containers/Tenant/Acl/Acl.scss +1 -9
  25. package/dist/containers/Tenant/Acl/Acl.tsx +137 -0
  26. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -2
  27. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -0
  28. package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
  29. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +2 -0
  30. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +2 -3
  31. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +0 -6
  32. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +95 -83
  33. package/dist/containers/Tenant/Query/Issues/Issues.tsx +27 -23
  34. package/dist/containers/Tenant/Query/Issues/models.ts +0 -11
  35. package/dist/containers/Tenant/Query/Preview/Preview.tsx +3 -3
  36. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +2 -2
  37. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +99 -0
  38. package/dist/containers/Tenant/Tenant.tsx +1 -5
  39. package/dist/containers/Tenant/TenantPages.tsx +9 -14
  40. package/dist/containers/Tenant/i18n/en.json +11 -0
  41. package/dist/containers/Tenant/i18n/index.ts +11 -0
  42. package/dist/containers/Tenant/i18n/ru.json +11 -0
  43. package/dist/containers/Tenant/utils/schema.ts +24 -0
  44. package/dist/containers/Tenant/utils/schemaActions.ts +28 -24
  45. package/dist/containers/Tenants/Tenants.tsx +1 -4
  46. package/dist/services/api.ts +6 -7
  47. package/dist/store/index.js +1 -1
  48. package/dist/store/reducers/nodes/nodes.ts +14 -5
  49. package/dist/store/reducers/nodes/types.ts +22 -3
  50. package/dist/store/reducers/nodes/utils.ts +23 -10
  51. package/dist/store/reducers/preview.ts +6 -4
  52. package/dist/store/reducers/schemaAcl/schemaAcl.ts +17 -0
  53. package/dist/store/reducers/schemaAcl/types.ts +9 -7
  54. package/dist/store/reducers/tenant/constants.ts +6 -0
  55. package/dist/store/reducers/tenant/tenant.ts +15 -0
  56. package/dist/store/reducers/tenant/types.ts +18 -3
  57. package/dist/store/state-url-mapping.js +3 -0
  58. package/dist/types/api/cluster.ts +1 -1
  59. package/dist/types/api/compute.ts +11 -11
  60. package/dist/types/api/error.ts +2 -2
  61. package/dist/types/api/netInfo.ts +3 -3
  62. package/dist/types/api/nodes.ts +9 -8
  63. package/dist/types/api/query.ts +1 -1
  64. package/dist/types/api/schema/schema.ts +3 -0
  65. package/dist/types/api/schema/shared.ts +3 -3
  66. package/dist/types/api/schema/table.ts +22 -22
  67. package/dist/types/api/storage.ts +1 -1
  68. package/dist/types/assets.d.ts +1 -2
  69. package/dist/types/store/executeQuery.ts +2 -3
  70. package/dist/types/store/executeTopQueries.ts +8 -5
  71. package/dist/types/store/explainQuery.ts +4 -4
  72. package/dist/types/store/query.ts +4 -3
  73. package/dist/types/store/shardsWorkload.ts +8 -5
  74. package/dist/utils/constants.ts +4 -1
  75. package/dist/utils/error.ts +2 -3
  76. package/dist/utils/query.ts +3 -9
  77. package/dist/utils/tests/providers.tsx +6 -9
  78. package/package.json +6 -2
  79. package/dist/containers/Tenant/Acl/Acl.js +0 -153
  80. package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.js +0 -94
@@ -21,9 +21,8 @@ export const prepareTopicSchemaInfo = (data?: TEvDescribeSchemeResult): Array<In
21
21
 
22
22
  const {Partitions = [], PQTabletConfig = {PartitionConfig: {LifetimeSeconds: 0}}} = pqGroupData;
23
23
 
24
- const {Codecs, MeteringMode} = pqGroupData?.PQTabletConfig;
25
- const {WriteSpeedInBytesPerSecond, StorageLimitBytes} =
26
- pqGroupData?.PQTabletConfig?.PartitionConfig;
24
+ const {Codecs, MeteringMode} = PQTabletConfig;
25
+ const {WriteSpeedInBytesPerSecond, StorageLimitBytes} = PQTabletConfig.PartitionConfig;
27
26
 
28
27
  const pqGeneralInfo = formatObject<TPersQueueGroupDescription>(formatPQGroupItem, {
29
28
  Partitions,
@@ -35,12 +35,6 @@
35
35
  }
36
36
  }
37
37
 
38
- &__loader {
39
- display: flex;
40
- justify-content: center;
41
- align-items: center;
42
- }
43
-
44
38
  &__tree-wrapper {
45
39
  display: flex;
46
40
  flex-direction: column;
@@ -1,17 +1,13 @@
1
1
  import React, {ReactNode, useEffect, useReducer} from 'react';
2
- import {useDispatch, useSelector} from 'react-redux';
2
+ import {useDispatch} from 'react-redux';
3
+ import {useLocation} from 'react-router';
3
4
  import {Link} from 'react-router-dom';
4
- import cn from 'bem-cn-lite';
5
- import {useHistory, useLocation} from 'react-router';
6
5
  import qs from 'qs';
7
- import _ from 'lodash';
6
+ import cn from 'bem-cn-lite';
8
7
 
9
- import {Button, HelpPopover, Loader, Tabs} from '@gravity-ui/uikit';
8
+ import {Button, HelpPopover, Tabs} from '@gravity-ui/uikit';
10
9
 
11
10
  import SplitPane from '../../../components/SplitPane';
12
- import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
13
- import Acl from '../Acl/Acl';
14
- import SchemaViewer from '../Schema/SchemaViewer/SchemaViewer';
15
11
  import CopyToClipboard from '../../../components/CopyToClipboard/CopyToClipboard';
16
12
  import InfoViewer from '../../../components/InfoViewer/InfoViewer';
17
13
  import {
@@ -19,38 +15,43 @@ import {
19
15
  PersQueueGroupOverview,
20
16
  } from '../../../components/InfoViewer/schemaOverview';
21
17
  import {Icon} from '../../../components/Icon';
18
+ import {Loader} from '../../../components/Loader';
22
19
 
23
20
  import {
24
21
  EPathSubType,
25
22
  EPathType,
23
+ TColumnDescription,
26
24
  TColumnTableDescription,
27
- TDirEntry,
28
25
  } from '../../../types/api/schema';
29
-
26
+ import routes, {createHref} from '../../../routes';
30
27
  import {formatDateTime} from '../../../utils';
31
- import {isColumnEntityType, isIndexTable, isTableType} from '../utils/schema';
32
-
28
+ import {useTypedSelector} from '../../../utils/hooks';
33
29
  import {
34
30
  DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED,
35
31
  DEFAULT_SIZE_TENANT_SUMMARY_KEY,
36
32
  } from '../../../utils/constants';
33
+ import {setShowPreview} from '../../../store/reducers/schema/schema';
34
+ import {setQueryTab, setSummaryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
37
35
  import {
38
- TenantInfoTabsIds,
39
- TenantTabsGroups,
40
- TENANT_INFO_TABS,
41
- TENANT_SCHEMA_TAB,
42
- } from '../TenantPages';
43
- import routes, {createHref} from '../../../routes';
36
+ TENANT_PAGES_IDS,
37
+ TENANT_QUERY_TABS_ID,
38
+ TENANT_SUMMARY_TABS_IDS,
39
+ } from '../../../store/reducers/tenant/constants';
40
+
41
+ import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
42
+ import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
43
+ import {Acl} from '../Acl/Acl';
44
+
45
+ import {TenantTabsGroups, TENANT_INFO_TABS, TENANT_SCHEMA_TAB} from '../TenantPages';
44
46
  import {
45
47
  PaneVisibilityActionTypes,
46
48
  paneVisibilityToggleReducerCreator,
47
49
  PaneVisibilityToggleButtons,
48
50
  } from '../utils/paneVisibilityToggleHelpers';
49
- import {setShowPreview} from '../../../store/reducers/schema/schema';
50
- import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
51
- import {TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants';
51
+ import {isColumnEntityType, isExternalTable, isIndexTable, isTableType} from '../utils/schema';
52
52
 
53
53
  import './ObjectSummary.scss';
54
+ import i18n from '../i18n';
54
55
 
55
56
  const b = cn('object-summary');
56
57
 
@@ -72,7 +73,7 @@ function prepareOlapTableSchema(tableSchema: TColumnTableDescription = {}) {
72
73
  const KeyColumnIds = KeyColumnNames?.map((name: string) => {
73
74
  const column = Columns?.find((el) => el.Name === name);
74
75
  return column?.Id;
75
- });
76
+ }).filter((id): id is number => id !== undefined);
76
77
 
77
78
  return {
78
79
  Columns,
@@ -93,10 +94,15 @@ interface ObjectSummaryProps {
93
94
  onCollapseSummary: VoidFunction;
94
95
  onExpandSummary: VoidFunction;
95
96
  isCollapsed: boolean;
96
- additionalTenantInfo?: any;
97
97
  }
98
98
 
99
- function ObjectSummary(props: ObjectSummaryProps) {
99
+ export function ObjectSummary({
100
+ type,
101
+ subType,
102
+ onCollapseSummary,
103
+ onExpandSummary,
104
+ isCollapsed,
105
+ }: ObjectSummaryProps) {
100
106
  const dispatch = useDispatch();
101
107
  const [commonInfoVisibilityState, dispatchCommonInfoVisibilityState] = useReducer(
102
108
  paneVisibilityToggleReducerCreator(DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED),
@@ -107,49 +113,48 @@ function ObjectSummary(props: ObjectSummaryProps) {
107
113
  currentSchemaPath,
108
114
  currentSchema: currentItem = {},
109
115
  loading: loadingSchema,
110
- } = useSelector((state: any) => state.schema);
116
+ } = useTypedSelector((state) => state.schema);
117
+ const {summaryTab = TENANT_SUMMARY_TABS_IDS.overview} = useTypedSelector(
118
+ (state) => state.tenant,
119
+ );
111
120
 
112
121
  const location = useLocation();
113
122
 
114
- const history = useHistory();
115
-
116
123
  const queryParams = qs.parse(location.search, {
117
124
  ignoreQueryPrefix: true,
118
125
  });
119
126
 
120
- const {name: tenantName, info: infoTab} = queryParams;
121
- const pathData: TDirEntry | undefined = _.get(
122
- data[tenantName as string],
123
- 'PathDescription.Self',
124
- );
125
- const currentSchemaData: TDirEntry | undefined = _.get(
126
- data[currentSchemaPath],
127
- 'PathDescription.Self',
128
- );
129
-
130
- const tableSchema =
131
- currentItem?.PathDescription?.Table || currentItem?.PathDescription?.ColumnTableDescription;
132
-
133
- const schema =
134
- isTableType(props.type) && isColumnEntityType(props.type)
135
- ? // process data for ColumnTable
136
- prepareOlapTableSchema(tableSchema)
137
- : tableSchema;
127
+ const {name: tenantName} = queryParams;
128
+
129
+ const pathData = tenantName ? data[tenantName.toString()]?.PathDescription?.Self : undefined;
130
+ const currentObjectData = currentSchemaPath ? data[currentSchemaPath] : undefined;
131
+ const currentSchemaData = currentObjectData?.PathDescription?.Self;
132
+
133
+ let keyColumnIds: number[] | undefined;
134
+ let columns: TColumnDescription[] | undefined;
135
+
136
+ if (isTableType(type) && isColumnEntityType(type)) {
137
+ const description = currentObjectData?.PathDescription?.ColumnTableDescription;
138
+ const columnTableSchema = prepareOlapTableSchema(description);
139
+ keyColumnIds = columnTableSchema.KeyColumnIds;
140
+ columns = columnTableSchema.Columns;
141
+ } else if (isExternalTable(type)) {
142
+ columns = currentObjectData?.PathDescription?.ExternalTableDescription?.Columns;
143
+ } else {
144
+ keyColumnIds = currentObjectData?.PathDescription?.Table?.KeyColumnIds;
145
+ columns = currentObjectData?.PathDescription?.Table?.Columns;
146
+ }
138
147
 
139
148
  useEffect(() => {
140
- const {type} = props;
141
149
  const isTable = isTableType(type);
142
150
 
143
- if (type && !isTable && !TENANT_INFO_TABS.find((el) => el.id === infoTab)) {
144
- history.push({
145
- pathname: location.pathname,
146
- search: qs.stringify({...queryParams, info: TenantInfoTabsIds.overview}),
147
- });
151
+ if (type && !isTable && !TENANT_INFO_TABS.find((el) => el.id === summaryTab)) {
152
+ dispatch(setSummaryTab(TENANT_SUMMARY_TABS_IDS.overview));
148
153
  }
149
- }, [props.type]);
154
+ }, [dispatch, type, summaryTab]);
150
155
 
151
156
  const renderTabs = () => {
152
- const isTable = isTableType(props.type);
157
+ const isTable = isTableType(type);
153
158
  const tabsItems = isTable ? [...TENANT_INFO_TABS, ...TENANT_SCHEMA_TAB] : TENANT_INFO_TABS;
154
159
 
155
160
  return (
@@ -157,12 +162,12 @@ function ObjectSummary(props: ObjectSummaryProps) {
157
162
  <Tabs
158
163
  size="l"
159
164
  items={tabsItems}
160
- activeTab={infoTab as string}
165
+ activeTab={summaryTab}
161
166
  wrapTo={({id}, node) => {
162
167
  const path = createHref(routes.tenant, undefined, {
163
168
  ...queryParams,
164
169
  name: tenantName as string,
165
- [TenantTabsGroups.info]: id,
170
+ [TenantTabsGroups.summaryTab]: id,
166
171
  });
167
172
  return (
168
173
  <Link to={path} key={id} className={b('tab')}>
@@ -188,12 +193,12 @@ function ObjectSummary(props: ObjectSummaryProps) {
188
193
  [EPathType.EPathTypeExtSubDomain]: undefined,
189
194
  [EPathType.EPathTypeColumnStore]: undefined,
190
195
  [EPathType.EPathTypeColumnTable]: undefined,
191
- [EPathType.EPathTypeCdcStream]: () => (
192
- <CDCStreamOverview data={data[currentSchemaPath]} />
193
- ),
196
+ [EPathType.EPathTypeCdcStream]: () => <CDCStreamOverview data={currentObjectData} />,
194
197
  [EPathType.EPathTypePersQueueGroup]: () => (
195
- <PersQueueGroupOverview data={data[currentSchemaPath]} />
198
+ <PersQueueGroupOverview data={currentObjectData} />
196
199
  ),
200
+ [EPathType.EPathTypeExternalTable]: undefined,
201
+ [EPathType.EPathTypeExternalDataSource]: undefined,
197
202
  };
198
203
 
199
204
  let component =
@@ -212,17 +217,26 @@ function ObjectSummary(props: ObjectSummaryProps) {
212
217
  return <div className={b('overview-wrapper')}>{component}</div>;
213
218
  };
214
219
 
220
+ const renderLoader = () => {
221
+ // If Loader isn't wrapped with div, SplitPane doesn't calculate panes height correctly
222
+ return (
223
+ <div>
224
+ <Loader />
225
+ </div>
226
+ );
227
+ };
228
+
215
229
  const renderTabContent = () => {
216
- switch (infoTab) {
217
- case TenantInfoTabsIds.acl: {
218
- return <Acl additionalTenantInfo={props.additionalTenantInfo} />;
230
+ switch (summaryTab) {
231
+ case TENANT_SUMMARY_TABS_IDS.acl: {
232
+ return <Acl />;
219
233
  }
220
- case TenantInfoTabsIds.schema: {
234
+ case TENANT_SUMMARY_TABS_IDS.schema: {
221
235
  return loadingSchema ? (
222
236
  renderLoader()
223
237
  ) : (
224
238
  <div className={b('schema')}>
225
- <SchemaViewer data={schema} />
239
+ <SchemaViewer keyColumnIds={keyColumnIds} columns={columns} type={type} />
226
240
  </div>
227
241
  );
228
242
  }
@@ -232,18 +246,10 @@ function ObjectSummary(props: ObjectSummaryProps) {
232
246
  }
233
247
  };
234
248
 
235
- const renderLoader = () => {
236
- return (
237
- <div className={b('loader')}>
238
- <Loader size="m" />
239
- </div>
240
- );
241
- };
242
-
243
249
  const renderTree = () => {
244
250
  return (
245
251
  <div className={b('tree-wrapper')}>
246
- <div className={b('tree-header')}>Navigation</div>
252
+ <div className={b('tree-header')}>{i18n('summary.navigation')}</div>
247
253
  <div className={b('tree')}>
248
254
  {pathData && (
249
255
  <SchemaTree
@@ -278,15 +284,24 @@ function ObjectSummary(props: ObjectSummaryProps) {
278
284
  };
279
285
 
280
286
  const renderCommonInfoControls = () => {
281
- const showPreview = isTableType(props.type) && !isIndexTable(props.subType);
287
+ const showPreview = isTableType(type) && !isIndexTable(subType);
282
288
  return (
283
289
  <React.Fragment>
284
290
  {showPreview && (
285
- <Button view="flat-secondary" onClick={onOpenPreview} title="Show preview">
291
+ <Button
292
+ view="flat-secondary"
293
+ onClick={onOpenPreview}
294
+ title={i18n('summary.showPreview')}
295
+ >
286
296
  <Icon name="tablePreview" viewBox={'0 0 16 16'} height={16} width={16} />
287
297
  </Button>
288
298
  )}
289
- <CopyToClipboard text={currentSchemaPath} title="Copy schema path" />
299
+ {currentSchemaPath && (
300
+ <CopyToClipboard
301
+ text={currentSchemaPath}
302
+ title={i18n('summary.copySchemaPath')}
303
+ />
304
+ )}
290
305
  <PaneVisibilityToggleButtons
291
306
  onCollapse={onCollapseInfoHandler}
292
307
  onExpand={onExpandInfoHandler}
@@ -298,7 +313,6 @@ function ObjectSummary(props: ObjectSummaryProps) {
298
313
  };
299
314
 
300
315
  const renderEntityTypeBadge = () => {
301
- const {type} = props;
302
316
  const {Status, Reason} = currentItem;
303
317
 
304
318
  let message;
@@ -321,7 +335,7 @@ function ObjectSummary(props: ObjectSummaryProps) {
321
335
  }
322
336
  return (
323
337
  <div className={b()}>
324
- <div className={b({hidden: props.isCollapsed})}>
338
+ <div className={b({hidden: isCollapsed})}>
325
339
  <SplitPane
326
340
  direction="vertical"
327
341
  defaultSizePaneKey={DEFAULT_SIZE_TENANT_SUMMARY_KEY}
@@ -350,9 +364,9 @@ function ObjectSummary(props: ObjectSummaryProps) {
350
364
  </SplitPane>
351
365
  </div>
352
366
  <PaneVisibilityToggleButtons
353
- onCollapse={props.onCollapseSummary}
354
- onExpand={props.onExpandSummary}
355
- isCollapsed={props.isCollapsed}
367
+ onCollapse={onCollapseSummary}
368
+ onExpand={onExpandSummary}
369
+ isCollapsed={isCollapsed}
356
370
  initialDirection="left"
357
371
  className={b('action-button')}
358
372
  />
@@ -362,5 +376,3 @@ function ObjectSummary(props: ObjectSummaryProps) {
362
376
 
363
377
  return renderContent();
364
378
  }
365
-
366
- export default ObjectSummary;
@@ -4,33 +4,31 @@ import cn from 'bem-cn-lite';
4
4
  import {Button, Icon, ArrowToggle} from '@gravity-ui/uikit';
5
5
  import ShortyString from '../../../../components/ShortyString/ShortyString';
6
6
 
7
- import {IssueType, SEVERITY, getSeverity} from './models';
7
+ import type {ErrorResponse, IssueMessage} from '../../../../types/api/query';
8
8
 
9
9
  import fatalIcon from '../../../../assets/icons/circle-xmark.svg';
10
10
  import errorIcon from '../../../../assets/icons/triangle-exclamation.svg';
11
11
  import warningIcon from '../../../../assets/icons/circle-exclamation.svg';
12
12
  import infoIcon from '../../../../assets/icons/circle-info.svg';
13
13
 
14
+ import {SEVERITY, getSeverity} from './models';
15
+
14
16
  import './Issues.scss';
15
17
 
16
18
  const blockWrapper = cn('kv-result-issues');
17
19
  const blockIssues = cn('kv-issues');
18
20
  const blockIssue = cn('kv-issue');
19
21
 
20
- type DataIssues = {
21
- error: IssueType;
22
- issues?: IssueType[];
23
- };
24
-
25
22
  interface ResultIssuesProps {
26
- data: DataIssues | string;
23
+ data: ErrorResponse | string;
27
24
  className: string;
28
25
  }
29
26
 
30
27
  export default function ResultIssues({data, className}: ResultIssuesProps) {
31
28
  const [showIssues, setShowIssues] = React.useState(false);
32
29
 
33
- const hasIssues = typeof data === 'string' ? false : Array.isArray(data?.issues);
30
+ const issues = typeof data === 'string' ? undefined : data?.issues;
31
+ const hasIssues = Array.isArray(issues) && issues.length > 0;
34
32
 
35
33
  const renderTitle = () => {
36
34
  let content;
@@ -61,37 +59,37 @@ export default function ResultIssues({data, className}: ResultIssuesProps) {
61
59
  </Button>
62
60
  )}
63
61
  </div>
64
- {hasIssues && showIssues && (
65
- <Issues issues={(data as DataIssues).issues!} className={className} />
66
- )}
62
+ {hasIssues && showIssues && <Issues issues={issues} className={className} />}
67
63
  </div>
68
64
  );
69
65
  }
70
66
 
71
67
  interface IssuesProps {
72
68
  className?: string;
73
- issues: IssueType[];
69
+ issues: IssueMessage[] | null | undefined;
74
70
  }
75
71
  export function Issues({issues, className}: IssuesProps) {
76
- const mostSevereIssue = issues.reduce((result, issue) => {
72
+ const mostSevereIssue = issues?.reduce((result, issue) => {
77
73
  const severity = issue.severity ?? 10;
78
74
  return Math.min(result, severity);
79
75
  }, 10);
80
76
  return (
81
77
  <div className={blockIssues(null, className)}>
82
- {issues.map((issue, index) => (
78
+ {issues?.map((issue, index) => (
83
79
  <Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
84
80
  ))}
85
81
  </div>
86
82
  );
87
83
  }
88
84
 
89
- function Issue({issue, level = 0}: {issue: IssueType; expanded?: boolean; level?: number}) {
85
+ function Issue({issue, level = 0}: {issue: IssueMessage; expanded?: boolean; level?: number}) {
90
86
  const [isExpand, setIsExpand] = React.useState(true);
91
87
  const severity = getSeverity(issue.severity);
92
- const hasIssues = Array.isArray(issue.issues) && issue.issues.length > 0;
93
88
  const position = getIssuePosition(issue);
94
89
 
90
+ const issues = issue.issues;
91
+ const hasIssues = Array.isArray(issues) && issues.length > 0;
92
+
95
93
  const arrowDirection = isExpand ? 'bottom' : 'right';
96
94
 
97
95
  return (
@@ -123,18 +121,20 @@ function Issue({issue, level = 0}: {issue: IssueType; expanded?: boolean; level?
123
121
  <ShortyString value={issue.message} expandLabel={'Show full message'} />
124
122
  </div>
125
123
  </span>
126
- {issue.code ? <span className={blockIssue('code')}>Code: {issue.code}</span> : null}
124
+ {issue.issue_code ? (
125
+ <span className={blockIssue('code')}>Code: {issue.issue_code}</span>
126
+ ) : null}
127
127
  </div>
128
128
  {hasIssues && isExpand && (
129
129
  <div className={blockIssue('issues')}>
130
- <IssueList issues={issue.issues!} level={level + 1} expanded={isExpand} />
130
+ <IssueList issues={issues} level={level + 1} expanded={isExpand} />
131
131
  </div>
132
132
  )}
133
133
  </div>
134
134
  );
135
135
  }
136
136
 
137
- function IssueList(props: {issues: IssueType[]; expanded: boolean; level: number}) {
137
+ function IssueList(props: {issues: IssueMessage[]; expanded: boolean; level: number}) {
138
138
  const {issues, level, expanded} = props;
139
139
  return (
140
140
  <div className={blockIssue('list')}>
@@ -145,7 +145,7 @@ function IssueList(props: {issues: IssueType[]; expanded: boolean; level: number
145
145
  );
146
146
  }
147
147
 
148
- const severityIcons: Record<SEVERITY, any> = {
148
+ const severityIcons: Record<SEVERITY, string> = {
149
149
  S_INFO: infoIcon,
150
150
  S_WARNING: warningIcon,
151
151
  S_ERROR: errorIcon,
@@ -162,10 +162,14 @@ function IssueSeverity({severity}: {severity: SEVERITY}) {
162
162
  );
163
163
  }
164
164
 
165
- function getIssuePosition(issue: IssueType) {
166
- const {file, position} = issue;
165
+ function getIssuePosition(issue: IssueMessage) {
166
+ const {position = {}} = issue;
167
+
167
168
  if (!position) {
168
169
  return false;
169
170
  }
170
- return `${file ? 'file:' : ''}${position.row}:${position.column}`;
171
+
172
+ const {file, row, column} = position;
173
+
174
+ return `${file ? 'file:' : ''}${row}:${column}`;
171
175
  }
@@ -1,14 +1,3 @@
1
- export interface IssueType {
2
- file?: string;
3
- position?: {row: number; column: number};
4
- // eslint-disable-next-line camelcase
5
- end_position?: {row: number; column: number};
6
- message?: string;
7
- code?: number;
8
- severity?: number;
9
- issues?: IssueType[];
10
- }
11
-
12
1
  export const SEVERITY_LIST = ['S_FATAL', 'S_ERROR', 'S_WARNING', 'S_INFO'] as const;
13
2
 
14
3
  export type SEVERITY = typeof SEVERITY_LIST[number];
@@ -15,7 +15,7 @@ import Fullscreen from '../../../../components/Fullscreen/Fullscreen';
15
15
  import {QueryResultTable} from '../../../../components/QueryResultTable';
16
16
  import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
17
17
 
18
- import {isTableType} from '../../utils/schema';
18
+ import {isExternalTable, isTableType} from '../../utils/schema';
19
19
 
20
20
  import i18n from '../i18n';
21
21
 
@@ -56,7 +56,7 @@ export const Preview = ({database, type}: PreviewProps) => {
56
56
  sendQuery({
57
57
  query,
58
58
  database,
59
- action: 'execute-scan',
59
+ action: isExternalTable(type) ? 'execute-query' : 'execute-scan',
60
60
  }),
61
61
  );
62
62
  },
@@ -103,7 +103,7 @@ export const Preview = ({database, type}: PreviewProps) => {
103
103
  if (!isTableType(type)) {
104
104
  message = <div className={b('message-container')}>{i18n('preview.not-available')}</div>;
105
105
  } else if (error) {
106
- message = <div className={b('message-container')}>{prepareQueryError(error)}</div>;
106
+ message = <div className={b('message-container', 'error')}>{prepareQueryError(error)}</div>;
107
107
  }
108
108
 
109
109
  const content = message ?? (
@@ -13,7 +13,7 @@ interface SchemaTreeProps {
13
13
  rootPath: string;
14
14
  rootName: string;
15
15
  rootType?: EPathType;
16
- currentPath: string;
16
+ currentPath?: string;
17
17
  }
18
18
 
19
19
  export function SchemaTree(props: SchemaTreeProps) {
@@ -57,7 +57,7 @@ export function SchemaTree(props: SchemaTreeProps) {
57
57
 
58
58
  useEffect(() => {
59
59
  // if the cached path is not in the current tree, show root
60
- if (!currentPath.startsWith(rootPath)) {
60
+ if (!currentPath?.startsWith(rootPath)) {
61
61
  handleActivePathUpdate(rootPath);
62
62
  }
63
63
  }, []);
@@ -0,0 +1,99 @@
1
+ import cn from 'bem-cn-lite';
2
+
3
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
4
+
5
+ import type {EPathType, TColumnDescription} from '../../../../types/api/schema';
6
+ import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
7
+
8
+ import {Icon} from '../../../../components/Icon';
9
+
10
+ import {isExternalTable} from '../../utils/schema';
11
+
12
+ import './SchemaViewer.scss';
13
+
14
+ const b = cn('schema-viewer');
15
+
16
+ const SchemaViewerColumns = {
17
+ id: 'Id',
18
+ name: 'Name',
19
+ key: 'Key',
20
+ type: 'Type',
21
+ notNull: 'NotNull',
22
+ };
23
+
24
+ interface SchemaViewerProps {
25
+ keyColumnIds?: number[];
26
+ columns?: TColumnDescription[];
27
+ type?: EPathType;
28
+ }
29
+
30
+ export const SchemaViewer = ({keyColumnIds = [], columns = [], type}: SchemaViewerProps) => {
31
+ let dataTableColumns: Column<TColumnDescription>[] = [
32
+ {
33
+ name: SchemaViewerColumns.id,
34
+ width: 40,
35
+ },
36
+ {
37
+ name: SchemaViewerColumns.key,
38
+ width: 40,
39
+ sortAccessor: (row) => {
40
+ return row.Id && keyColumnIds.includes(row.Id) ? 1 : 0;
41
+ },
42
+ render: ({row}) => {
43
+ return row.Id && keyColumnIds.includes(row.Id) ? (
44
+ <div className={b('key-icon')}>
45
+ <Icon name="key" viewBox="0 0 12 7" width={12} height={7} />
46
+ </div>
47
+ ) : null;
48
+ },
49
+ },
50
+ {
51
+ name: SchemaViewerColumns.name,
52
+ width: 100,
53
+ },
54
+ {
55
+ name: SchemaViewerColumns.type,
56
+ width: 100,
57
+ },
58
+ {
59
+ name: SchemaViewerColumns.notNull,
60
+ width: 100,
61
+ render: ({row}) => {
62
+ if (row.NotNull) {
63
+ return '\u2713';
64
+ }
65
+
66
+ return undefined;
67
+ },
68
+ },
69
+ ];
70
+
71
+ if (isExternalTable(type)) {
72
+ // External tables don't have key columns
73
+ dataTableColumns = dataTableColumns.filter(
74
+ (column) => column.name !== SchemaViewerColumns.key,
75
+ );
76
+ }
77
+
78
+ // Display key columns first
79
+ const tableData = columns.sort((column) => {
80
+ if (column.Id && keyColumnIds.includes(column.Id)) {
81
+ return 1;
82
+ }
83
+ return -1;
84
+ });
85
+
86
+ return (
87
+ <div className={b()}>
88
+ <DataTable
89
+ theme="yandex-cloud"
90
+ data={tableData}
91
+ columns={dataTableColumns}
92
+ settings={DEFAULT_TABLE_SETTINGS}
93
+ initialSortOrder={{columnId: SchemaViewerColumns.key, order: DataTable.DESCENDING}}
94
+ />
95
+ </div>
96
+ );
97
+ };
98
+
99
+ export default SchemaViewer;