ydb-embedded-ui 1.5.3 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +28 -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/ShortyString/ShortyString.scss +7 -0
- package/dist/components/ShortyString/ShortyString.tsx +53 -0
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +3 -2
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +5 -4
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +8 -7
- 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 +31 -4
- package/dist/containers/Tenants/Tenants.js +1 -1
- package/dist/store/utils.js +1 -1
- package/dist/utils/index.js +4 -0
- package/package.json +2 -4
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.6.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.6.1...v1.6.2) (2022-06-07)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* shouls always select result tab ([98d4bcb](https://github.com/ydb-platform/ydb-embedded-ui/commit/98d4bcbc94bc2b9db9fb9b9cd5aced9f079ecdae))
|
9
|
+
|
10
|
+
## [1.6.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.6.0...v1.6.1) (2022-06-07)
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* should show Pending instead of Pendin ([0b93f80](https://github.com/ydb-platform/ydb-embedded-ui/commit/0b93f8000dffca27cd26321eb86f41e4f458faa6))
|
16
|
+
* should show query error even if no issues ([708bac5](https://github.com/ydb-platform/ydb-embedded-ui/commit/708bac56c2e671ec23e23c5055d0c0a9d419cd86))
|
17
|
+
|
18
|
+
## [1.6.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.5.3...v1.6.0) (2022-06-06)
|
19
|
+
|
20
|
+
|
21
|
+
### Features
|
22
|
+
|
23
|
+
* query issues displaying ([3ba4c25](https://github.com/ydb-platform/ydb-embedded-ui/commit/3ba4c2591542ef902eba4f7c44550f3c59618575))
|
24
|
+
|
25
|
+
|
26
|
+
### Bug Fixes
|
27
|
+
|
28
|
+
* code-review ([742c58a](https://github.com/ydb-platform/ydb-embedded-ui/commit/742c58a9bc4fa0dd0b24aa0119b7352e2be6fc8e))
|
29
|
+
* **package.json:** typecheck script ([111b525](https://github.com/ydb-platform/ydb-embedded-ui/commit/111b525f51a050010bbc03a3d0990be00c18ccd8))
|
30
|
+
|
3
31
|
### [1.5.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.5.2...v1.5.3) (2022-05-26)
|
4
32
|
|
5
33
|
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM232 152C232 138.8 242.8 128 256 128s24 10.75 24 24v128c0 13.25-10.75 24-24 24S232 293.3 232 280V152zM256 400c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 385.9 273.4 400 256 400z"/></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM175 208.1L222.1 255.1L175 303C165.7 312.4 165.7 327.6 175 336.1C184.4 346.3 199.6 346.3 208.1 336.1L255.1 289.9L303 336.1C312.4 346.3 327.6 346.3 336.1 336.1C346.3 327.6 346.3 312.4 336.1 303L289.9 255.1L336.1 208.1C346.3 199.6 346.3 184.4 336.1 175C327.6 165.7 312.4 165.7 303 175L255.1 222.1L208.1 175C199.6 165.7 184.4 165.7 175 175C165.7 184.4 165.7 199.6 175 208.1V208.1z"/></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/></svg>
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import {Link} from '@yandex-cloud/uikit';
|
5
|
+
|
6
|
+
import './ShortyString.scss';
|
7
|
+
|
8
|
+
const block = cn('kv-shorty-string');
|
9
|
+
|
10
|
+
type Props = {
|
11
|
+
value?: string;
|
12
|
+
limit?: number;
|
13
|
+
displayLength?: boolean;
|
14
|
+
render?: (value: string) => React.ReactNode;
|
15
|
+
onToggle?: () => void;
|
16
|
+
expandLabel?: string;
|
17
|
+
collapseLabel?: string;
|
18
|
+
};
|
19
|
+
|
20
|
+
export default function ShortyString({
|
21
|
+
value = '',
|
22
|
+
limit = 200,
|
23
|
+
displayLength = true,
|
24
|
+
render = (v: string) => v,
|
25
|
+
onToggle,
|
26
|
+
expandLabel = 'Show more',
|
27
|
+
collapseLabel = 'Show less',
|
28
|
+
}: Props) {
|
29
|
+
const [expanded, setExpanded] = React.useState(false);
|
30
|
+
const hasToggle = value.length > limit;
|
31
|
+
const length =
|
32
|
+
displayLength && !expanded ? `(${value.length} symbols)` : undefined;
|
33
|
+
|
34
|
+
const text = expanded || value.length <= limit ? value : value.slice(0, limit - 4) + '\u00a0...';
|
35
|
+
const label = expanded ? collapseLabel : expandLabel;
|
36
|
+
return (
|
37
|
+
<div className={block()}>
|
38
|
+
{render(text)}
|
39
|
+
{hasToggle ? (
|
40
|
+
<Link
|
41
|
+
className={block('toggle')}
|
42
|
+
onClick={(e) => {
|
43
|
+
e.stopPropagation();
|
44
|
+
setExpanded((v) => !v);
|
45
|
+
onToggle?.();
|
46
|
+
}}
|
47
|
+
>
|
48
|
+
{label} {length}
|
49
|
+
</Link>
|
50
|
+
) : null}
|
51
|
+
</div>
|
52
|
+
);
|
53
|
+
}
|
@@ -8,9 +8,10 @@ import Icon from '../../../../components/Icon/Icon';
|
|
8
8
|
|
9
9
|
import {AutoFetcher} from '../../../../utils/autofetcher';
|
10
10
|
import {getHotKeys, setHotKeysOptions} from '../../../../store/reducers/hotKeys';
|
11
|
+
import {TABLE_TYPE} from '../../Tenant';
|
12
|
+
import {prepareQueryError} from '../../../../utils';
|
11
13
|
|
12
14
|
import './HotKeys.scss';
|
13
|
-
import {TABLE_TYPE} from '../../Tenant';
|
14
15
|
|
15
16
|
const b = cn('hot-keys');
|
16
17
|
|
@@ -105,7 +106,7 @@ function HotKeys({
|
|
105
106
|
|
106
107
|
const renderContent = () => {
|
107
108
|
if (error) {
|
108
|
-
return error
|
109
|
+
return prepareQueryError(error);
|
109
110
|
}
|
110
111
|
return data !== null ? (
|
111
112
|
<div className={b('table-content')}>
|
@@ -3,18 +3,19 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import cn from 'bem-cn-lite';
|
4
4
|
import {connect} from 'react-redux';
|
5
5
|
import DataTable from '@yandex-cloud/react-data-table';
|
6
|
+
import {Loader} from '@yandex-cloud/uikit';
|
6
7
|
|
7
8
|
import {changeUserInput} from '../../../../store/reducers/executeQuery';
|
8
9
|
import {sendQuery, setQueryOptions} from '../../../../store/reducers/executeTopQueries';
|
9
10
|
import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
|
10
11
|
import {AutoFetcher} from '../../../../utils/autofetcher';
|
11
|
-
import {
|
12
|
+
import {OLAP_STORE_TYPE, OLAP_TABLE_TYPE} from '../../Tenant';
|
12
13
|
|
13
|
-
import './TopQueries.scss';
|
14
14
|
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
|
15
15
|
import {TenantGeneralTabsIds} from '../../TenantPages';
|
16
|
-
import {
|
16
|
+
import {prepareQueryError} from '../../../../utils';
|
17
17
|
|
18
|
+
import './TopQueries.scss';
|
18
19
|
|
19
20
|
const b = cn('kv-top-queries');
|
20
21
|
|
@@ -151,7 +152,7 @@ class TopQueries extends React.Component {
|
|
151
152
|
if (type === OLAP_STORE_TYPE || type === OLAP_TABLE_TYPE) {
|
152
153
|
message = 'No data';
|
153
154
|
} else if (error && !error.isCancelled) {
|
154
|
-
message = (error
|
155
|
+
message = prepareQueryError(error).slice(0, 300);
|
155
156
|
} else if (!loading && !data) {
|
156
157
|
message = 'No data';
|
157
158
|
}
|
@@ -1,20 +1,21 @@
|
|
1
1
|
import {useContext, useEffect, useMemo} from 'react';
|
2
2
|
import cn from 'bem-cn-lite';
|
3
3
|
import {connect} from 'react-redux';
|
4
|
-
import
|
4
|
+
import {Loader} from '@yandex-cloud/uikit';
|
5
5
|
import DataTable from '@yandex-cloud/react-data-table';
|
6
|
-
import routes, {createHref} from '../../../../routes';
|
7
6
|
|
7
|
+
import InternalLink from '../../../../components/InternalLink/InternalLink';
|
8
|
+
|
9
|
+
import routes, {createHref} from '../../../../routes';
|
8
10
|
import {sendShardQuery, setShardQueryOptions} from '../../../../store/reducers/shardsWorkload';
|
9
11
|
import {setCurrentSchemaPath, getSchema} from '../../../../store/reducers/schema';
|
10
12
|
import {AutoFetcher} from '../../../../utils/autofetcher';
|
11
|
-
|
12
13
|
import HistoryContext from '../../../../contexts/HistoryContext';
|
14
|
+
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
|
15
|
+
import {OLAP_STORE_TYPE, OLAP_TABLE_TYPE} from '../../Tenant';
|
16
|
+
import {prepareQueryError} from '../../../../utils';
|
13
17
|
|
14
18
|
import './TopShards.scss';
|
15
|
-
import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants';
|
16
|
-
import {Loader} from '@yandex-cloud/uikit';
|
17
|
-
import { OLAP_STORE_TYPE, OLAP_TABLE_TYPE } from '../../Tenant';
|
18
19
|
|
19
20
|
const b = cn('top-shards');
|
20
21
|
const bLink = cn('yc-link');
|
@@ -123,7 +124,7 @@ function TopShards({
|
|
123
124
|
return 'No data';
|
124
125
|
}
|
125
126
|
if (error) {
|
126
|
-
return error
|
127
|
+
return prepareQueryError(error);
|
127
128
|
}
|
128
129
|
|
129
130
|
return data && data.length > 0 ? (
|
@@ -11,7 +11,7 @@ import Fullscreen from '../../../components/Fullscreen/Fullscreen';
|
|
11
11
|
|
12
12
|
import {sendQuery, setQueryOptions} from '../../../store/reducers/preview';
|
13
13
|
import {showTooltip, hideTooltip} from '../../../store/reducers/tooltip';
|
14
|
-
import {prepareQueryResponse} from '../../../utils/index';
|
14
|
+
import {prepareQueryError, prepareQueryResponse} from '../../../utils/index';
|
15
15
|
|
16
16
|
import {OLAP_TABLE_TYPE, TABLE_TYPE} from '../Tenant';
|
17
17
|
import {AutoFetcher} from '../../../utils/autofetcher';
|
@@ -163,7 +163,7 @@ class Preview extends React.Component {
|
|
163
163
|
}
|
164
164
|
|
165
165
|
if (error) {
|
166
|
-
message = <div className={b('message-container')}>{error
|
166
|
+
message = <div className={b('message-container')}>{prepareQueryError(error)}</div>;
|
167
167
|
}
|
168
168
|
|
169
169
|
if (!loading && data.length === 0) {
|
@@ -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
|
|
@@ -83,8 +84,33 @@ function QueryResult(props) {
|
|
83
84
|
return (
|
84
85
|
<React.Fragment>
|
85
86
|
{result}
|
86
|
-
{isFullscreen &&
|
87
|
+
{isFullscreen && (
|
88
|
+
<Fullscreen>
|
89
|
+
<div className={b('result', {fullscreen: true})}>{result}</div>
|
90
|
+
</Fullscreen>
|
91
|
+
)}
|
92
|
+
</React.Fragment>
|
93
|
+
);
|
94
|
+
};
|
95
|
+
|
96
|
+
const renderIssues = () => {
|
97
|
+
const error = props.error?.data;
|
98
|
+
|
99
|
+
const hasIssues = error?.issues && Array.isArray(error.issues);
|
100
|
+
|
101
|
+
return hasIssues ? (
|
102
|
+
<React.Fragment>
|
103
|
+
<ResultIssues data={error} />
|
104
|
+
{isFullscreen && (
|
105
|
+
<Fullscreen>
|
106
|
+
<div className={b('result', {fullscreen: true})}>
|
107
|
+
<ResultIssues data={error} />
|
108
|
+
</div>
|
109
|
+
</Fullscreen>
|
110
|
+
)}
|
87
111
|
</React.Fragment>
|
112
|
+
) : (
|
113
|
+
<span>{error?.data ?? error}</span>
|
88
114
|
);
|
89
115
|
};
|
90
116
|
|
@@ -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
|
);
|
package/dist/store/utils.js
CHANGED
@@ -32,7 +32,7 @@ export function createApiRequest({actions, request, dataHandler = nop}) {
|
|
32
32
|
dispatch({
|
33
33
|
type: SET_UNAUTHENTICATED.SUCCESS,
|
34
34
|
});
|
35
|
-
} else if (error && error.status && error.statusText) {
|
35
|
+
} else if (error && Number(error.status) >= 500 && error.statusText) {
|
36
36
|
createToast({
|
37
37
|
name: 'Request failure',
|
38
38
|
title: 'Request failure',
|
package/dist/utils/index.js
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ydb-embedded-ui",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.6.2",
|
4
4
|
"files": [
|
5
5
|
"dist"
|
6
6
|
],
|
@@ -52,9 +52,7 @@
|
|
52
52
|
"test": "react-app-rewired test",
|
53
53
|
"eject": "react-scripts eject",
|
54
54
|
"prepublishOnly": "npm run package",
|
55
|
-
"typecheck": "
|
56
|
-
"typecheck:server": "tsc -p src/server --noEmit",
|
57
|
-
"typecheck:ui": "tsc -p src/ui --noEmit",
|
55
|
+
"typecheck": "tsc --noEmit",
|
58
56
|
"prepare": "husky install"
|
59
57
|
},
|
60
58
|
"lint-staged": {
|