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 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,7 @@
1
+ .kv-shorty-string {
2
+ &__toggle {
3
+ margin-left: 2em;
4
+
5
+ font-size: 0.85em;
6
+ }
7
+ }
@@ -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.data;
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 { OLAP_STORE_TYPE, OLAP_TABLE_TYPE } from '../../Tenant';
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 {Loader} from '@yandex-cloud/uikit';
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.data || error).slice(0, 300);
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 InternalLink from '../../../../components/InternalLink/InternalLink';
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.data || 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.data || error}</div>;
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
- if (!shouldRenderAnswer) {
314
- return null;
315
- }
316
-
317
- let columns = [];
318
- if (data && data.length > 0) {
319
- columns = Object.keys(data[0]).map((key) => ({
320
- name: key,
321
- render: ({value}) => {
322
- return (
323
- <span
324
- className={b('cell')}
325
- onClick={(e) => showTooltip(e.target, value, 'cell')}
326
- >
327
- {value}
328
- </span>
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
- const preparedData = prepareQueryResponse(data);
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={Boolean(error)}
354
- textResults={results}
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
- return error.data ? error.data : error;
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 && <Fullscreen><div className={b('result', {fullscreen: true})}>{result}</div></Fullscreen>}
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 disabled={Boolean(props.error)}/>
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
  );
@@ -201,7 +201,7 @@ class Tenants extends React.Component {
201
201
  },
202
202
  {
203
203
  name: 'State',
204
- width: 80,
204
+ width: 90,
205
205
  render: ({value}) => (value ? value.toLowerCase() : '—'),
206
206
  customStyle: () => ({textTransform: 'capitalize'}),
207
207
  },
@@ -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',
@@ -138,3 +138,7 @@ export const prepareQueryResponse = (data) => {
138
138
  return formattedData;
139
139
  });
140
140
  };
141
+
142
+ export function prepareQueryError(error) {
143
+ return error.data?.error?.message || error.data || error
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "1.5.3",
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": "npm run typecheck:server && npm run typecheck:ui",
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": {