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.
- package/CHANGELOG.md +17 -0
- package/dist/components/QueryResultTable/Cell/Cell.tsx +8 -8
- package/dist/components/QueryResultTable/i18n/en.json +1 -1
- package/dist/components/QueryResultTable/i18n/ru.json +1 -1
- package/dist/components/ShortyString/ShortyString.tsx +3 -6
- package/dist/components/ShortyString/i18n/en.json +8 -8
- package/dist/components/ShortyString/i18n/ru.json +8 -8
- package/dist/components/SpeedMultiMeter/i18n/index.ts +0 -2
- package/dist/components/Stack/Stack.tsx +16 -16
- package/dist/components/TableSkeleton/TableSkeleton.tsx +3 -3
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -3
- package/dist/containers/Header/Header.tsx +2 -2
- package/dist/containers/Header/breadcrumbs.ts +2 -1
- package/dist/containers/Heatmap/Heatmap.tsx +4 -3
- package/dist/containers/Node/NodeStructure/PDiskTitleBadge.tsx +2 -8
- package/dist/containers/Nodes/Nodes.tsx +1 -1
- package/dist/containers/Storage/EmptyFilter/i18n/en.json +2 -2
- package/dist/containers/Storage/EmptyFilter/i18n/ru.json +2 -2
- package/dist/containers/Storage/StorageGroups/i18n/en.json +5 -5
- package/dist/containers/Storage/StorageGroups/i18n/ru.json +5 -5
- package/dist/containers/Storage/UsageFilter/i18n/en.json +3 -8
- package/dist/containers/Storage/UsageFilter/i18n/ru.json +3 -8
- package/dist/containers/Tablet/Tablet.tsx +2 -2
- package/dist/containers/Tenant/Acl/Acl.scss +1 -9
- package/dist/containers/Tenant/Acl/Acl.tsx +137 -0
- package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -0
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -3
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +2 -0
- package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +2 -3
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +0 -6
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +95 -83
- package/dist/containers/Tenant/Query/Issues/Issues.tsx +27 -23
- package/dist/containers/Tenant/Query/Issues/models.ts +0 -11
- package/dist/containers/Tenant/Query/Preview/Preview.tsx +3 -3
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +2 -2
- package/dist/containers/Tenant/Schema/SchemaViewer/SchemaViewer.tsx +99 -0
- package/dist/containers/Tenant/Tenant.tsx +1 -5
- package/dist/containers/Tenant/TenantPages.tsx +9 -14
- package/dist/containers/Tenant/i18n/en.json +11 -0
- package/dist/containers/Tenant/i18n/index.ts +11 -0
- package/dist/containers/Tenant/i18n/ru.json +11 -0
- package/dist/containers/Tenant/utils/schema.ts +24 -0
- package/dist/containers/Tenant/utils/schemaActions.ts +28 -24
- package/dist/containers/Tenants/Tenants.tsx +1 -4
- package/dist/services/api.ts +6 -7
- package/dist/store/index.js +1 -1
- package/dist/store/reducers/nodes/nodes.ts +14 -5
- package/dist/store/reducers/nodes/types.ts +22 -3
- package/dist/store/reducers/nodes/utils.ts +23 -10
- package/dist/store/reducers/preview.ts +6 -4
- package/dist/store/reducers/schemaAcl/schemaAcl.ts +17 -0
- package/dist/store/reducers/schemaAcl/types.ts +9 -7
- package/dist/store/reducers/tenant/constants.ts +6 -0
- package/dist/store/reducers/tenant/tenant.ts +15 -0
- package/dist/store/reducers/tenant/types.ts +18 -3
- package/dist/store/state-url-mapping.js +3 -0
- package/dist/types/api/cluster.ts +1 -1
- package/dist/types/api/compute.ts +11 -11
- package/dist/types/api/error.ts +2 -2
- package/dist/types/api/netInfo.ts +3 -3
- package/dist/types/api/nodes.ts +9 -8
- package/dist/types/api/query.ts +1 -1
- package/dist/types/api/schema/schema.ts +3 -0
- package/dist/types/api/schema/shared.ts +3 -3
- package/dist/types/api/schema/table.ts +22 -22
- package/dist/types/api/storage.ts +1 -1
- package/dist/types/assets.d.ts +1 -2
- package/dist/types/store/executeQuery.ts +2 -3
- package/dist/types/store/executeTopQueries.ts +8 -5
- package/dist/types/store/explainQuery.ts +4 -4
- package/dist/types/store/query.ts +4 -3
- package/dist/types/store/shardsWorkload.ts +8 -5
- package/dist/utils/constants.ts +4 -1
- package/dist/utils/error.ts +2 -3
- package/dist/utils/query.ts +3 -9
- package/dist/utils/tests/providers.tsx +6 -9
- package/package.json +6 -2
- package/dist/containers/Tenant/Acl/Acl.js +0 -153
- 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} =
|
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,
|
@@ -1,17 +1,13 @@
|
|
1
1
|
import React, {ReactNode, useEffect, useReducer} from 'react';
|
2
|
-
import {useDispatch
|
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
|
6
|
+
import cn from 'bem-cn-lite';
|
8
7
|
|
9
|
-
import {Button, HelpPopover,
|
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 {
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
import
|
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 {
|
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(
|
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
|
-
} =
|
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
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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 ===
|
144
|
-
|
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
|
-
}, [
|
154
|
+
}, [dispatch, type, summaryTab]);
|
150
155
|
|
151
156
|
const renderTabs = () => {
|
152
|
-
const isTable = isTableType(
|
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={
|
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.
|
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={
|
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 (
|
217
|
-
case
|
218
|
-
return <Acl
|
230
|
+
switch (summaryTab) {
|
231
|
+
case TENANT_SUMMARY_TABS_IDS.acl: {
|
232
|
+
return <Acl />;
|
219
233
|
}
|
220
|
-
case
|
234
|
+
case TENANT_SUMMARY_TABS_IDS.schema: {
|
221
235
|
return loadingSchema ? (
|
222
236
|
renderLoader()
|
223
237
|
) : (
|
224
238
|
<div className={b('schema')}>
|
225
|
-
<SchemaViewer
|
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')}>
|
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(
|
287
|
+
const showPreview = isTableType(type) && !isIndexTable(subType);
|
282
288
|
return (
|
283
289
|
<React.Fragment>
|
284
290
|
{showPreview && (
|
285
|
-
<Button
|
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
|
-
|
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:
|
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={
|
354
|
-
onExpand={
|
355
|
-
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 {
|
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:
|
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
|
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:
|
69
|
+
issues: IssueMessage[] | null | undefined;
|
74
70
|
}
|
75
71
|
export function Issues({issues, className}: IssuesProps) {
|
76
|
-
const mostSevereIssue = issues
|
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
|
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:
|
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.
|
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={
|
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:
|
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,
|
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:
|
166
|
-
const {
|
165
|
+
function getIssuePosition(issue: IssueMessage) {
|
166
|
+
const {position = {}} = issue;
|
167
|
+
|
167
168
|
if (!position) {
|
168
169
|
return false;
|
169
170
|
}
|
170
|
-
|
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
|
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
|
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;
|