ydb-embedded-ui 1.5.1 → 1.6.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 +37 -0
- package/dist/assets/icons/circle-exclamation.svg +1 -0
- package/dist/assets/icons/circle-info.svg +1 -0
- package/dist/assets/icons/circle-xmark.svg +1 -0
- package/dist/assets/icons/triangle-exclamation.svg +1 -0
- package/dist/components/AsideNavigation/Settings/Settings.scss +4 -4
- package/dist/components/Breadcrumbs/Breadcrumbs.scss +1 -1
- package/dist/components/Collapse/Collapse.scss +8 -8
- package/dist/components/EmptyState/EmptyState.scss +6 -6
- package/dist/components/EntityStatus/EntityStatus.scss +6 -6
- package/dist/components/FullNodeViewer/FullNodeViewer.scss +7 -7
- package/dist/components/GroupViewer/GroupViewer.scss +1 -1
- package/dist/components/InfoViewer/InfoViewer.scss +4 -4
- package/dist/components/PDiskViewer/PDiskViewer.scss +1 -1
- package/dist/components/PoolUsage/PoolUsage.scss +5 -5
- package/dist/components/ProgressViewer/ProgressViewer.scss +9 -9
- package/dist/components/ShortyString/ShortyString.scss +7 -0
- package/dist/components/ShortyString/ShortyString.tsx +53 -0
- package/dist/components/TableSkeleton/TableSkeleton.scss +1 -1
- package/dist/components/TabletsViewer/TabletsViewer.scss +2 -2
- package/dist/containers/App/App.js +2 -1
- package/dist/containers/App/App.scss +6 -0
- package/dist/containers/App/TipPopup/TipPopup.js +1 -1
- package/dist/containers/AsideNavigation/AsideNavigation.scss +1 -1
- package/dist/containers/Authentication/Authentication.scss +2 -2
- package/dist/containers/Header/Header.scss +2 -2
- package/dist/containers/Header/Host/Host.scss +6 -6
- package/dist/containers/Heatmap/Heatmap.js +1 -2
- package/dist/containers/Heatmap/Heatmap.scss +2 -2
- package/dist/containers/Node/Node.scss +0 -1
- package/dist/containers/Node/Node.tsx +1 -0
- package/dist/containers/Node/NodeStructure/NodeStructure.scss +3 -3
- package/dist/containers/Node/NodeStructure/Pdisk.tsx +6 -6
- package/dist/containers/Pdisk/Pdisk.scss +2 -2
- package/dist/containers/Pool/Pool.scss +3 -3
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +2 -2
- package/dist/containers/Storage/Storage.scss +3 -3
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +4 -4
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +4 -4
- package/dist/containers/Tablet/Tablet.scss +4 -4
- package/dist/containers/Tablets/Tablets.js +1 -2
- package/dist/containers/TabletsFilters/TabletsFilters.js +1 -2
- package/dist/containers/TabletsFilters/TabletsFilters.scss +2 -2
- package/dist/containers/Tenant/Acl/Acl.scss +2 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +15 -21
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -2
- package/dist/containers/Tenant/Diagnostics/Network/Network.scss +6 -6
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.scss +4 -4
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +5 -4
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +2 -2
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +8 -7
- package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.scss +7 -8
- package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +2 -12
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +5 -8
- package/dist/containers/Tenant/Preview/Preview.js +2 -2
- package/dist/containers/Tenant/QueryEditor/Issues/Issues.scss +124 -0
- package/dist/containers/Tenant/QueryEditor/Issues/Issues.tsx +171 -0
- package/dist/containers/Tenant/QueryEditor/Issues/models.ts +27 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +38 -52
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +4 -1
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +32 -5
- package/dist/containers/Tenant/QueryEditor/SaveQuery/SaveQuery.scss +1 -1
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -3
- package/dist/containers/Tenant/Tenant.scss +2 -2
- package/dist/containers/Tenant/utils/schemaActions.ts +3 -18
- package/dist/containers/Vdisk/Vdisk.scss +2 -2
- package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +2 -2
- package/dist/store/reducers/tenant.js +30 -0
- package/dist/store/state-url-mapping.js +6 -0
- package/dist/store/utils.js +1 -1
- package/dist/styles/mixins.scss +13 -13
- package/dist/utils/getNodesColumns.js +2 -3
- package/dist/utils/index.js +4 -0
- package/package.json +6 -7
@@ -0,0 +1,124 @@
|
|
1
|
+
.kv-result-issues {
|
2
|
+
overflow: auto;
|
3
|
+
|
4
|
+
height: 100%;
|
5
|
+
padding: 0 10px;
|
6
|
+
&__error-message {
|
7
|
+
position: sticky;
|
8
|
+
z-index: 2;
|
9
|
+
top: 0;
|
10
|
+
left: 0;
|
11
|
+
|
12
|
+
display: flex;
|
13
|
+
align-items: center;
|
14
|
+
|
15
|
+
padding: 10px 0;
|
16
|
+
|
17
|
+
background-color: var(--yc-color-base-background);
|
18
|
+
}
|
19
|
+
&__error-message-text {
|
20
|
+
margin: 0 10px;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
.kv-issues {
|
25
|
+
position: relative;
|
26
|
+
}
|
27
|
+
|
28
|
+
.kv-issue {
|
29
|
+
&_leaf {
|
30
|
+
margin-left: 31px;
|
31
|
+
}
|
32
|
+
|
33
|
+
&__issues {
|
34
|
+
padding-left: 24px;
|
35
|
+
}
|
36
|
+
|
37
|
+
&__line {
|
38
|
+
display: flex;
|
39
|
+
align-items: flex-start;
|
40
|
+
|
41
|
+
margin: 0 0 10px;
|
42
|
+
padding: 0 10px 0 0;
|
43
|
+
}
|
44
|
+
|
45
|
+
&__place-text {
|
46
|
+
display: inline-block;
|
47
|
+
|
48
|
+
padding-right: 10px;
|
49
|
+
|
50
|
+
text-align: left;
|
51
|
+
|
52
|
+
color: var(--yc-color-text-secondary);
|
53
|
+
}
|
54
|
+
|
55
|
+
&__message {
|
56
|
+
display: flex;
|
57
|
+
|
58
|
+
margin-right: auto;
|
59
|
+
margin-left: 10px;
|
60
|
+
|
61
|
+
font-family: var(--yc-font-family-monospace);
|
62
|
+
font-size: var(--yc-text-code-2-font-size);
|
63
|
+
line-height: var(--yc-text-header-2-line-height);
|
64
|
+
}
|
65
|
+
|
66
|
+
&__message-text {
|
67
|
+
flex: 1 1 auto;
|
68
|
+
|
69
|
+
min-width: 240px;
|
70
|
+
|
71
|
+
white-space: pre-wrap;
|
72
|
+
word-break: break-word;
|
73
|
+
}
|
74
|
+
|
75
|
+
&__code {
|
76
|
+
flex: 0 0 auto;
|
77
|
+
|
78
|
+
margin-left: 1.5em;
|
79
|
+
padding: 3px 0;
|
80
|
+
|
81
|
+
font-size: 12px;
|
82
|
+
|
83
|
+
color: var(--yc-color-text-complementary);
|
84
|
+
}
|
85
|
+
|
86
|
+
&__arrow-toggle {
|
87
|
+
margin-right: 5px;
|
88
|
+
.yc-button__text {
|
89
|
+
margin: 0 5px;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
.yql-issue-severity {
|
95
|
+
display: flex;
|
96
|
+
align-items: center;
|
97
|
+
|
98
|
+
line-height: 28px;
|
99
|
+
white-space: nowrap;
|
100
|
+
|
101
|
+
&_severity_fatal &__icon {
|
102
|
+
color: var(--yc-color-text-danger);
|
103
|
+
}
|
104
|
+
|
105
|
+
&_severity_error &__icon {
|
106
|
+
color: var(--yc-color-text-danger);
|
107
|
+
}
|
108
|
+
|
109
|
+
&_severity_warning &__icon {
|
110
|
+
color: var(--yc-color-text-warning-medium);
|
111
|
+
}
|
112
|
+
|
113
|
+
&_severity_info &__icon {
|
114
|
+
color: var(--yc-color-text-info);
|
115
|
+
}
|
116
|
+
|
117
|
+
&__title {
|
118
|
+
margin-left: 4px;
|
119
|
+
|
120
|
+
text-transform: capitalize;
|
121
|
+
|
122
|
+
color: var(--yc-color-text-complementary);
|
123
|
+
}
|
124
|
+
}
|
@@ -0,0 +1,171 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import {Button, Icon, ArrowToggle} from '@yandex-cloud/uikit';
|
5
|
+
import ShortyString from '../../../../components/ShortyString/ShortyString';
|
6
|
+
|
7
|
+
import {IssueType, SEVERITY, getSeverity} from './models';
|
8
|
+
|
9
|
+
import fatalIcon from '../../../../assets/icons/circle-xmark.svg';
|
10
|
+
import errorIcon from '../../../../assets/icons/triangle-exclamation.svg';
|
11
|
+
import warningIcon from '../../../../assets/icons/circle-exclamation.svg';
|
12
|
+
import infoIcon from '../../../../assets/icons/circle-info.svg';
|
13
|
+
|
14
|
+
import './Issues.scss';
|
15
|
+
|
16
|
+
const blockWrapper = cn('kv-result-issues');
|
17
|
+
const blockIssues = cn('kv-issues');
|
18
|
+
const blockIssue = cn('kv-issue');
|
19
|
+
|
20
|
+
type DataIssues = {
|
21
|
+
error: IssueType;
|
22
|
+
issues?: IssueType[];
|
23
|
+
};
|
24
|
+
|
25
|
+
interface ResultIssuesProps {
|
26
|
+
data: DataIssues | string;
|
27
|
+
className: string;
|
28
|
+
}
|
29
|
+
|
30
|
+
export default function ResultIssues({data, className}: ResultIssuesProps) {
|
31
|
+
const [showIssues, setShowIssues] = React.useState(false);
|
32
|
+
|
33
|
+
const hasIssues = typeof data === 'string' ? false : Array.isArray(data?.issues);
|
34
|
+
|
35
|
+
const renderTitle = () => {
|
36
|
+
let content;
|
37
|
+
if (typeof data === 'string') {
|
38
|
+
content = data;
|
39
|
+
} else {
|
40
|
+
const severity = getSeverity(data?.error?.severity);
|
41
|
+
content = (
|
42
|
+
<React.Fragment>
|
43
|
+
<IssueSeverity severity={severity} />{' '}
|
44
|
+
<span className={blockWrapper('error-message-text')}>
|
45
|
+
{data?.error?.message}
|
46
|
+
</span>
|
47
|
+
</React.Fragment>
|
48
|
+
);
|
49
|
+
}
|
50
|
+
|
51
|
+
return content;
|
52
|
+
};
|
53
|
+
|
54
|
+
return (
|
55
|
+
<div className={blockWrapper()}>
|
56
|
+
<div className={blockWrapper('error-message')}>
|
57
|
+
{renderTitle()}
|
58
|
+
{hasIssues && (
|
59
|
+
<Button view="normal" onClick={() => setShowIssues(!showIssues)}>
|
60
|
+
{showIssues ? 'Hide details' : 'Show details'}
|
61
|
+
</Button>
|
62
|
+
)}
|
63
|
+
</div>
|
64
|
+
{hasIssues && showIssues && (
|
65
|
+
<Issues issues={(data as DataIssues).issues!} className={className} />
|
66
|
+
)}
|
67
|
+
</div>
|
68
|
+
);
|
69
|
+
}
|
70
|
+
|
71
|
+
interface IssuesProps {
|
72
|
+
className?: string;
|
73
|
+
issues: IssueType[];
|
74
|
+
}
|
75
|
+
export function Issues({issues, className}: IssuesProps) {
|
76
|
+
const mostSevereIssue = issues.reduce((result, issue) => {
|
77
|
+
const severity = issue.severity ?? 10;
|
78
|
+
return Math.min(result, severity);
|
79
|
+
}, 10);
|
80
|
+
return (
|
81
|
+
<div className={blockIssues(null, className)}>
|
82
|
+
{issues.map((issue, index) => (
|
83
|
+
<Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
|
84
|
+
))}
|
85
|
+
</div>
|
86
|
+
);
|
87
|
+
}
|
88
|
+
|
89
|
+
function Issue({issue, level = 0}: {issue: IssueType; expanded?: boolean; level?: number}) {
|
90
|
+
const [isExpand, setIsExpand] = React.useState(true);
|
91
|
+
const severity = getSeverity(issue.severity);
|
92
|
+
const hasIssues = Array.isArray(issue.issues) && issue.issues.length > 0;
|
93
|
+
const position = getIssuePosition(issue);
|
94
|
+
|
95
|
+
const arrowDirection = isExpand ? 'bottom' : 'right';
|
96
|
+
|
97
|
+
return (
|
98
|
+
<div
|
99
|
+
className={blockIssue({
|
100
|
+
leaf: !hasIssues,
|
101
|
+
'has-issues': hasIssues,
|
102
|
+
})}
|
103
|
+
>
|
104
|
+
<div className={blockIssue('line')}>
|
105
|
+
{hasIssues && (
|
106
|
+
<Button
|
107
|
+
view="flat-secondary"
|
108
|
+
onClick={() => setIsExpand(!isExpand)}
|
109
|
+
className={blockIssue('arrow-toggle')}
|
110
|
+
>
|
111
|
+
<ArrowToggle direction={arrowDirection} size={16} />
|
112
|
+
</Button>
|
113
|
+
)}
|
114
|
+
<IssueSeverity severity={severity} />
|
115
|
+
|
116
|
+
<span className={blockIssue('message')}>
|
117
|
+
{position && (
|
118
|
+
<span className={blockIssue('place-text')} title="Position">
|
119
|
+
{position}
|
120
|
+
</span>
|
121
|
+
)}
|
122
|
+
<div className={blockIssue('message-text')}>
|
123
|
+
<ShortyString value={issue.message} expandLabel={'Show full message'} />
|
124
|
+
</div>
|
125
|
+
</span>
|
126
|
+
{issue.code ? <span className={blockIssue('code')}>Code: {issue.code}</span> : null}
|
127
|
+
</div>
|
128
|
+
{hasIssues && isExpand && (
|
129
|
+
<div className={blockIssue('issues')}>
|
130
|
+
<IssueList issues={issue.issues!} level={level + 1} expanded={isExpand} />
|
131
|
+
</div>
|
132
|
+
)}
|
133
|
+
</div>
|
134
|
+
);
|
135
|
+
}
|
136
|
+
|
137
|
+
function IssueList(props: {issues: IssueType[]; expanded: boolean; level: number}) {
|
138
|
+
const {issues, level, expanded} = props;
|
139
|
+
return (
|
140
|
+
<div className={blockIssue('list')}>
|
141
|
+
{issues.map((issue, index) => (
|
142
|
+
<Issue key={index} issue={issue} level={level} expanded={expanded} />
|
143
|
+
))}
|
144
|
+
</div>
|
145
|
+
);
|
146
|
+
}
|
147
|
+
|
148
|
+
const severityIcons: Record<SEVERITY, any> = {
|
149
|
+
S_INFO: infoIcon,
|
150
|
+
S_WARNING: warningIcon,
|
151
|
+
S_ERROR: errorIcon,
|
152
|
+
S_FATAL: fatalIcon,
|
153
|
+
};
|
154
|
+
const blockIssueSeverity = cn('yql-issue-severity');
|
155
|
+
function IssueSeverity({severity}: {severity: SEVERITY}) {
|
156
|
+
const shortenSeverity = severity.slice(2).toLowerCase();
|
157
|
+
return (
|
158
|
+
<span className={blockIssueSeverity({severity: shortenSeverity})}>
|
159
|
+
<Icon className={blockIssueSeverity('icon')} data={severityIcons[severity]} size={16} />
|
160
|
+
<span className={blockIssueSeverity('title')}>{shortenSeverity}</span>
|
161
|
+
</span>
|
162
|
+
);
|
163
|
+
}
|
164
|
+
|
165
|
+
function getIssuePosition(issue: IssueType) {
|
166
|
+
const {file, position} = issue;
|
167
|
+
if (!position) {
|
168
|
+
return false;
|
169
|
+
}
|
170
|
+
return `${file ? 'file:' : ''}${position.row}:${position.column}`;
|
171
|
+
}
|
@@ -0,0 +1,27 @@
|
|
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
|
+
export const SEVERITY_LIST = ['S_FATAL', 'S_ERROR', 'S_WARNING', 'S_INFO'] as const;
|
13
|
+
|
14
|
+
export type SEVERITY = typeof SEVERITY_LIST[number];
|
15
|
+
|
16
|
+
// Severity values from ydb/library/yql/public/issue/protos/issue_severity.proto
|
17
|
+
// FATAL = 0;
|
18
|
+
// ERROR = 1;
|
19
|
+
// WARNING = 2;
|
20
|
+
// INFO = 3;
|
21
|
+
export function isSeverity(value: number | undefined) {
|
22
|
+
return value ? SEVERITY_LIST[value] !== undefined : false;
|
23
|
+
}
|
24
|
+
|
25
|
+
export function getSeverity(value: number | undefined) {
|
26
|
+
return isSeverity(value) ? SEVERITY_LIST[value!] : 'S_INFO';
|
27
|
+
}
|
@@ -307,58 +307,56 @@ function QueryEditor(props) {
|
|
307
307
|
executeQuery: {data, error, stats},
|
308
308
|
showTooltip,
|
309
309
|
} = props;
|
310
|
-
const result = getExecuteResult();
|
311
|
-
const shouldRenderAnswer = result.length || error;
|
312
310
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
311
|
+
let content;
|
312
|
+
if (data) {
|
313
|
+
let columns = [];
|
314
|
+
if (data.length > 0) {
|
315
|
+
columns = Object.keys(data[0]).map((key) => ({
|
316
|
+
name: key,
|
317
|
+
render: ({value}) => {
|
318
|
+
return (
|
319
|
+
<span
|
320
|
+
className={b('cell')}
|
321
|
+
onClick={(e) => showTooltip(e.target, value, 'cell')}
|
322
|
+
>
|
323
|
+
{value}
|
324
|
+
</span>
|
325
|
+
);
|
326
|
+
},
|
327
|
+
}));
|
328
|
+
const preparedData = prepareQueryResponse(data);
|
329
|
+
|
330
|
+
content = columns.length ? (
|
331
|
+
<DataTable
|
332
|
+
columns={columns}
|
333
|
+
data={preparedData}
|
334
|
+
settings={TABLE_SETTINGS}
|
335
|
+
theme="yandex-cloud"
|
336
|
+
rowKey={(_, index) => index}
|
337
|
+
/>
|
338
|
+
) : (
|
339
|
+
<div>{data}</div>
|
340
|
+
);
|
341
|
+
}
|
332
342
|
}
|
343
|
+
const textResults = getPreparedResult();
|
344
|
+
const disabled = !textResults.length || resultType !== RESULT_TYPES.EXECUTE;
|
333
345
|
|
334
|
-
|
335
|
-
|
336
|
-
const content = columns.length ? (
|
337
|
-
<DataTable
|
338
|
-
columns={columns}
|
339
|
-
data={preparedData}
|
340
|
-
settings={TABLE_SETTINGS}
|
341
|
-
theme="yandex-cloud"
|
342
|
-
rowKey={(_, index) => index}
|
343
|
-
/>
|
344
|
-
) : (
|
345
|
-
<div>{result}</div>
|
346
|
-
);
|
347
|
-
const results = getPreparedResult();
|
348
|
-
const disabled = !results.length || resultType !== RESULT_TYPES.EXECUTE;
|
349
|
-
return (
|
346
|
+
return data || error ? (
|
350
347
|
<QueryResult
|
351
348
|
result={content}
|
352
349
|
stats={stats}
|
353
|
-
error={
|
354
|
-
textResults={
|
350
|
+
error={error}
|
351
|
+
textResults={textResults}
|
355
352
|
copyDisabled={disabled}
|
356
353
|
isResultsCollapsed={resultVisibilityState.collapsed}
|
357
354
|
onExpandResults={onExpandResultHandler}
|
358
355
|
onCollapseResults={onCollapseResultHandler}
|
359
356
|
/>
|
360
|
-
);
|
357
|
+
) : null;
|
361
358
|
};
|
359
|
+
|
362
360
|
const renderExplainQuery = () => {
|
363
361
|
const {
|
364
362
|
explainQuery: {data, dataAst, error, loading, loadingAst},
|
@@ -463,18 +461,6 @@ function QueryEditor(props) {
|
|
463
461
|
);
|
464
462
|
};
|
465
463
|
|
466
|
-
const getExecuteResult = () => {
|
467
|
-
const {data = [], error} = props.executeQuery;
|
468
|
-
|
469
|
-
if (error) {
|
470
|
-
return error.data || error;
|
471
|
-
} else if (data.length > 0) {
|
472
|
-
return data;
|
473
|
-
} else {
|
474
|
-
return '';
|
475
|
-
}
|
476
|
-
};
|
477
|
-
|
478
464
|
const getPreparedResult = () => {
|
479
465
|
const {
|
480
466
|
executeQuery: {data},
|
@@ -234,7 +234,10 @@ function QueryExplain(props) {
|
|
234
234
|
}
|
235
235
|
|
236
236
|
if (error) {
|
237
|
-
|
237
|
+
if (error.data) {
|
238
|
+
return typeof error.data === 'string' ? error.data : error.data.error?.message;
|
239
|
+
}
|
240
|
+
return error;
|
238
241
|
}
|
239
242
|
|
240
243
|
switch (activeOption) {
|
@@ -13,6 +13,7 @@ import './QueryResult.scss';
|
|
13
13
|
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
|
14
14
|
import QueryExecutionStatus from '../../../../components/QueryExecutionStatus/QueryExecutionStatus';
|
15
15
|
import EnableFullscreenButton from '../../../../components/EnableFullscreenButton/EnableFullscreenButton';
|
16
|
+
import ResultIssues from '../Issues/Issues';
|
16
17
|
|
17
18
|
const b = cn('kv-query-result');
|
18
19
|
|
@@ -27,7 +28,9 @@ const resultOptions = [
|
|
27
28
|
];
|
28
29
|
|
29
30
|
function QueryResult(props) {
|
30
|
-
const [activeSection, setActiveSection] = useState(
|
31
|
+
const [activeSection, setActiveSection] = useState(
|
32
|
+
props.result ? resultOptionsIds.result : resultOptionsIds.stats,
|
33
|
+
);
|
31
34
|
const isFullscreen = useSelector((state) => state.fullscreen);
|
32
35
|
const dispatch = useDispatch();
|
33
36
|
|
@@ -83,11 +86,34 @@ function QueryResult(props) {
|
|
83
86
|
return (
|
84
87
|
<React.Fragment>
|
85
88
|
{result}
|
86
|
-
{isFullscreen &&
|
89
|
+
{isFullscreen && (
|
90
|
+
<Fullscreen>
|
91
|
+
<div className={b('result', {fullscreen: true})}>{result}</div>
|
92
|
+
</Fullscreen>
|
93
|
+
)}
|
87
94
|
</React.Fragment>
|
88
95
|
);
|
89
96
|
};
|
90
97
|
|
98
|
+
const renderIssues = () => {
|
99
|
+
const error = props.error?.data;
|
100
|
+
|
101
|
+
const hasIssues = error?.issues && Array.isArray(error.issues);
|
102
|
+
|
103
|
+
return hasIssues ? (
|
104
|
+
<React.Fragment>
|
105
|
+
<ResultIssues data={error} />
|
106
|
+
{isFullscreen && (
|
107
|
+
<Fullscreen>
|
108
|
+
<div className={b('result', {fullscreen: true})}>
|
109
|
+
<ResultIssues data={error} />
|
110
|
+
</div>
|
111
|
+
</Fullscreen>
|
112
|
+
)}
|
113
|
+
</React.Fragment>
|
114
|
+
) : null;
|
115
|
+
};
|
116
|
+
|
91
117
|
return (
|
92
118
|
<React.Fragment>
|
93
119
|
<div className={b('controls')}>
|
@@ -107,7 +133,7 @@ function QueryResult(props) {
|
|
107
133
|
</div>
|
108
134
|
<div className={b('controls-left')}>
|
109
135
|
{renderClipboardButton()}
|
110
|
-
<EnableFullscreenButton
|
136
|
+
<EnableFullscreenButton />
|
111
137
|
<PaneVisibilityToggleButtons
|
112
138
|
onCollapse={props.onCollapseResults}
|
113
139
|
onExpand={props.onExpandResults}
|
@@ -117,8 +143,9 @@ function QueryResult(props) {
|
|
117
143
|
</div>
|
118
144
|
</div>
|
119
145
|
<div className={b('result')}>
|
120
|
-
{activeSection === resultOptionsIds.result && renderResult()}
|
121
|
-
{activeSection === resultOptionsIds.stats && renderStats()}
|
146
|
+
{activeSection === resultOptionsIds.result && !props.error && renderResult()}
|
147
|
+
{activeSection === resultOptionsIds.stats && !props.error && renderStats()}
|
148
|
+
{renderIssues()}
|
122
149
|
</div>
|
123
150
|
</React.Fragment>
|
124
151
|
);
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import {useDispatch} from 'react-redux';
|
2
|
-
import {useHistory} from 'react-router';
|
3
2
|
|
4
3
|
import {NavigationTree} from 'ydb-ui-components';
|
5
4
|
|
@@ -26,7 +25,6 @@ export function SchemaTree(props: SchemaTreeProps) {
|
|
26
25
|
} = props;
|
27
26
|
|
28
27
|
const dispatch = useDispatch();
|
29
|
-
const history = useHistory();
|
30
28
|
|
31
29
|
const fetchPath = (path: string) => window.api.getSchema(
|
32
30
|
{path},
|
@@ -55,7 +53,7 @@ export function SchemaTree(props: SchemaTreeProps) {
|
|
55
53
|
collapsed: false,
|
56
54
|
}}
|
57
55
|
fetchPath={fetchPath}
|
58
|
-
getActions={getActions(dispatch,
|
56
|
+
getActions={getActions(dispatch, handleActivePathUpdate)}
|
59
57
|
activePath={currentPath}
|
60
58
|
onActivePathUpdate={handleActivePathUpdate}
|
61
59
|
cache={false}
|
@@ -3,8 +3,8 @@
|
|
3
3
|
.tenant-page {
|
4
4
|
overflow: hidden;
|
5
5
|
|
6
|
-
font-size: var(--yc-text-
|
7
|
-
line-height: var(--yc-text-
|
6
|
+
font-size: var(--yc-text-body-2-font-size);
|
7
|
+
line-height: var(--yc-text-body-2-line-height);
|
8
8
|
@include flex-container();
|
9
9
|
|
10
10
|
.yc-tabs {
|
@@ -1,13 +1,11 @@
|
|
1
|
-
import qs from 'qs';
|
2
1
|
import {Dispatch} from 'react';
|
3
|
-
import {History} from 'history';
|
4
2
|
import type {NavigationTreeNodeType} from 'ydb-ui-components';
|
5
3
|
|
6
|
-
import routes, {createHref} from '../../../routes';
|
7
4
|
import {changeUserInput} from '../../../store/reducers/executeQuery';
|
8
5
|
import {setShowPreview} from '../../../store/reducers/schema';
|
6
|
+
import {setTopLevelTab} from '../../../store/reducers/tenant';
|
9
7
|
import createToast from '../../../utils/createToast';
|
10
|
-
import {TenantGeneralTabsIds
|
8
|
+
import {TenantGeneralTabsIds} from '../TenantPages';
|
11
9
|
|
12
10
|
const createTableTemplate = (path: string) => {
|
13
11
|
return `CREATE TABLE \`${path}/my_table\`
|
@@ -36,29 +34,16 @@ VALUES ( );`;
|
|
36
34
|
|
37
35
|
export const getActions = (
|
38
36
|
dispatch: Dispatch<any>,
|
39
|
-
history: History<unknown>,
|
40
37
|
setActivePath: (path: string) => void,
|
41
38
|
) =>
|
42
39
|
(path: string, type: NavigationTreeNodeType) => {
|
43
|
-
const queryParams = qs.parse(location.search, {
|
44
|
-
ignoreQueryPrefix: true,
|
45
|
-
});
|
46
|
-
|
47
40
|
const switchTabToQuery = () => {
|
48
|
-
|
49
|
-
createHref(routes.tenant, undefined, {
|
50
|
-
...queryParams,
|
51
|
-
[TenantTabsGroups.general]: TenantGeneralTabsIds.query,
|
52
|
-
}),
|
53
|
-
);
|
41
|
+
dispatch(setTopLevelTab(TenantGeneralTabsIds.query));
|
54
42
|
};
|
55
43
|
|
56
44
|
const onCreateTableClick = () => {
|
57
45
|
dispatch(changeUserInput({input: createTableTemplate(path)}));
|
58
46
|
switchTabToQuery();
|
59
|
-
// here and in the other handlers this should be called after switching tab:
|
60
|
-
// redux-location-state catches the history.push event from the tab switching
|
61
|
-
// before active path updates in url, preventing its update at all
|
62
47
|
setActivePath(path);
|
63
48
|
};
|
64
49
|
|
@@ -20,9 +20,9 @@
|
|
20
20
|
&__title {
|
21
21
|
margin-right: 16px;
|
22
22
|
|
23
|
-
font-size: var(--yc-text-
|
23
|
+
font-size: var(--yc-text-body-2-font-size);
|
24
24
|
font-weight: 500;
|
25
|
-
line-height: var(--yc-text-
|
25
|
+
line-height: var(--yc-text-body-2-line-height);
|
26
26
|
text-transform: uppercase;
|
27
27
|
}
|
28
28
|
|
@@ -27,9 +27,9 @@
|
|
27
27
|
&__title {
|
28
28
|
margin-right: 16px;
|
29
29
|
|
30
|
-
font-size: var(--yc-text-
|
30
|
+
font-size: var(--yc-text-body-2-font-size);
|
31
31
|
font-weight: 500;
|
32
|
-
line-height: var(--yc-text-
|
32
|
+
line-height: var(--yc-text-body-2-line-height);
|
33
33
|
text-transform: uppercase;
|
34
34
|
}
|
35
35
|
|