ydb-embedded-ui 4.10.1 → 4.11.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 (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;