Skip to content

Commit 4c4e684

Browse files
fix(Query): process null issues error (#480)
1 parent cefb272 commit 4c4e684

File tree

16 files changed

+78
-81
lines changed

16 files changed

+78
-81
lines changed

src/.eslintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
2+
"parser": "@typescript-eslint/parser",
23
"extends": "@gravity-ui/eslint-config/client",
4+
"plugins": ["@typescript-eslint"],
35
"rules": {
46
"react/jsx-uses-react": "off",
57
"react/react-in-jsx-scope": "off",

src/containers/Heatmap/Heatmap.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import cn from 'bem-cn-lite';
44

55
import {Checkbox, Select} from '@gravity-ui/uikit';
66

7+
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
78
import {getTabletsInfo, setHeatmapOptions} from '../../store/reducers/heatmap';
89
import {showTooltip, hideTooltip} from '../../store/reducers/tooltip';
910
import {formatNumber} from '../../utils';
10-
import {prepareQueryError} from '../../utils/query';
1111
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
12+
1213
import {Loader} from '../../components/Loader';
13-
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
14+
import {ResponseError} from '../../components/Errors/ResponseError';
1415

1516
import {COLORS_RANGE_SIZE, getColorRange, getColorIndex, getCurrentMetricLimits} from './util';
1617
import {HeatmapCanvas} from './HeatmapCanvas/HeatmapCanvas';
@@ -196,7 +197,7 @@ export const Heatmap = ({path}: HeatmapProps) => {
196197
}
197198

198199
if (error) {
199-
return <div>{prepareQueryError(error)}</div>;
200+
return <ResponseError error={error} />;
200201
}
201202

202203
return renderContent();

src/containers/Tenant/Diagnostics/Describe/Describe.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import JSONTree from 'react-json-inspector';
66
import 'react-json-inspector/json-inspector.css';
77

88
import {Loader} from '../../../../components/Loader';
9+
import {ResponseError} from '../../../../components/Errors/ResponseError';
910

10-
import {prepareQueryError} from '../../../../utils/query';
1111
import {useAutofetcher, useTypedSelector} from '../../../../utils/hooks';
1212
import {
1313
getDescribe,
@@ -86,7 +86,7 @@ const Describe = ({tenant, type}: IDescribeProps) => {
8686
}
8787

8888
if (error) {
89-
return <div className={b('message-container', 'error')}>{prepareQueryError(error)}</div>;
89+
return <ResponseError error={error} className={b('message-container')} />;
9090
}
9191

9292
if (!loading && !preparedDescribeData) {

src/containers/Tenant/Diagnostics/HotKeys/HotKeys.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {Loader} from '@gravity-ui/uikit';
55
import DataTable from '@gravity-ui/react-data-table';
66

77
import {Icon} from '../../../../components/Icon';
8+
import {ResponseError} from '../../../../components/Errors/ResponseError';
89

910
import {AutoFetcher} from '../../../../utils/autofetcher';
1011
import {getHotKeys, setHotKeysOptions} from '../../../../store/reducers/hotKeys';
11-
import {prepareQueryError} from '../../../../utils/query';
1212

1313
import {isColumnEntityType, isTableType} from '../../utils/schema';
1414

@@ -86,7 +86,7 @@ function HotKeys({
8686
sortable: false,
8787
align: DataTable.RIGHT,
8888
},
89-
...keyColumnsIds?.map((col, index) => ({
89+
...keyColumnsIds.map((col, index) => ({
9090
name: col,
9191
header: (
9292
<div className={b('primary-key-column')}>
@@ -107,7 +107,7 @@ function HotKeys({
107107

108108
const renderContent = () => {
109109
if (error) {
110-
return prepareQueryError(error);
110+
return <ResponseError error={error} />;
111111
}
112112
return data !== null ? (
113113
<div className={b('table-content')}>

src/containers/Tenant/Query/Issues/Issues.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,31 @@ import cn from 'bem-cn-lite';
44
import {Button, Icon, ArrowToggle} from '@gravity-ui/uikit';
55
import ShortyString from '../../../../components/ShortyString/ShortyString';
66

7-
import {IssueType, SEVERITY, getSeverity} from './models';
7+
import type {ErrorResponse, IssueMessage} from '../../../../types/api/query';
88

99
import fatalIcon from '../../../../assets/icons/circle-xmark.svg';
1010
import errorIcon from '../../../../assets/icons/triangle-exclamation.svg';
1111
import warningIcon from '../../../../assets/icons/circle-exclamation.svg';
1212
import infoIcon from '../../../../assets/icons/circle-info.svg';
1313

14+
import {SEVERITY, getSeverity} from './models';
15+
1416
import './Issues.scss';
1517

1618
const blockWrapper = cn('kv-result-issues');
1719
const blockIssues = cn('kv-issues');
1820
const blockIssue = cn('kv-issue');
1921

20-
type DataIssues = {
21-
error: IssueType;
22-
issues?: IssueType[];
23-
};
24-
2522
interface ResultIssuesProps {
26-
data: DataIssues | string;
23+
data: ErrorResponse | string;
2724
className: string;
2825
}
2926

3027
export default function ResultIssues({data, className}: ResultIssuesProps) {
3128
const [showIssues, setShowIssues] = React.useState(false);
3229

33-
const hasIssues = typeof data === 'string' ? false : Array.isArray(data?.issues);
30+
const issues = typeof data === 'string' ? undefined : data?.issues;
31+
const hasIssues = Array.isArray(issues) && issues.length > 0;
3432

3533
const renderTitle = () => {
3634
let content;
@@ -61,37 +59,37 @@ export default function ResultIssues({data, className}: ResultIssuesProps) {
6159
</Button>
6260
)}
6361
</div>
64-
{hasIssues && showIssues && (
65-
<Issues issues={(data as DataIssues).issues!} className={className} />
66-
)}
62+
{hasIssues && showIssues && <Issues issues={issues} className={className} />}
6763
</div>
6864
);
6965
}
7066

7167
interface IssuesProps {
7268
className?: string;
73-
issues: IssueType[];
69+
issues: IssueMessage[] | null | undefined;
7470
}
7571
export function Issues({issues, className}: IssuesProps) {
76-
const mostSevereIssue = issues.reduce((result, issue) => {
72+
const mostSevereIssue = issues?.reduce((result, issue) => {
7773
const severity = issue.severity ?? 10;
7874
return Math.min(result, severity);
7975
}, 10);
8076
return (
8177
<div className={blockIssues(null, className)}>
82-
{issues.map((issue, index) => (
78+
{issues?.map((issue, index) => (
8379
<Issue key={index} issue={issue} expanded={issue === mostSevereIssue} />
8480
))}
8581
</div>
8682
);
8783
}
8884

89-
function Issue({issue, level = 0}: {issue: IssueType; expanded?: boolean; level?: number}) {
85+
function Issue({issue, level = 0}: {issue: IssueMessage; expanded?: boolean; level?: number}) {
9086
const [isExpand, setIsExpand] = React.useState(true);
9187
const severity = getSeverity(issue.severity);
92-
const hasIssues = Array.isArray(issue.issues) && issue.issues.length > 0;
9388
const position = getIssuePosition(issue);
9489

90+
const issues = issue.issues;
91+
const hasIssues = Array.isArray(issues) && issues.length > 0;
92+
9593
const arrowDirection = isExpand ? 'bottom' : 'right';
9694

9795
return (
@@ -123,18 +121,20 @@ function Issue({issue, level = 0}: {issue: IssueType; expanded?: boolean; level?
123121
<ShortyString value={issue.message} expandLabel={'Show full message'} />
124122
</div>
125123
</span>
126-
{issue.code ? <span className={blockIssue('code')}>Code: {issue.code}</span> : null}
124+
{issue.issue_code ? (
125+
<span className={blockIssue('code')}>Code: {issue.issue_code}</span>
126+
) : null}
127127
</div>
128128
{hasIssues && isExpand && (
129129
<div className={blockIssue('issues')}>
130-
<IssueList issues={issue.issues!} level={level + 1} expanded={isExpand} />
130+
<IssueList issues={issues} level={level + 1} expanded={isExpand} />
131131
</div>
132132
)}
133133
</div>
134134
);
135135
}
136136

137-
function IssueList(props: {issues: IssueType[]; expanded: boolean; level: number}) {
137+
function IssueList(props: {issues: IssueMessage[]; expanded: boolean; level: number}) {
138138
const {issues, level, expanded} = props;
139139
return (
140140
<div className={blockIssue('list')}>
@@ -145,7 +145,7 @@ function IssueList(props: {issues: IssueType[]; expanded: boolean; level: number
145145
);
146146
}
147147

148-
const severityIcons: Record<SEVERITY, any> = {
148+
const severityIcons: Record<SEVERITY, string> = {
149149
S_INFO: infoIcon,
150150
S_WARNING: warningIcon,
151151
S_ERROR: errorIcon,
@@ -162,10 +162,14 @@ function IssueSeverity({severity}: {severity: SEVERITY}) {
162162
);
163163
}
164164

165-
function getIssuePosition(issue: IssueType) {
166-
const {file, position} = issue;
165+
function getIssuePosition(issue: IssueMessage) {
166+
const {position = {}} = issue;
167+
167168
if (!position) {
168169
return false;
169170
}
170-
return `${file ? 'file:' : ''}${position.row}:${position.column}`;
171+
172+
const {file, row, column} = position;
173+
174+
return `${file ? 'file:' : ''}${row}:${column}`;
171175
}

src/containers/Tenant/Query/Issues/models.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
121
export const SEVERITY_LIST = ['S_FATAL', 'S_ERROR', 'S_WARNING', 'S_INFO'] as const;
132

143
export type SEVERITY = typeof SEVERITY_LIST[number];

src/store/reducers/preview.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import '../../services/api';
22

3-
import type {ErrorResponse, ExecuteActions} from '../../types/api/query';
4-
import type {IQueryResult} from '../../types/store/query';
3+
import type {ExecuteActions} from '../../types/api/query';
4+
import type {IQueryResult, QueryErrorResponse} from '../../types/store/query';
55
import {parseQueryAPIExecuteResponse} from '../../utils/query';
66

77
import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
@@ -16,7 +16,9 @@ const initialState = {
1616

1717
const preview = (
1818
state = initialState,
19-
action: ApiRequestAction<typeof SEND_QUERY, IQueryResult, ErrorResponse> | ReturnType<typeof setQueryOptions>,
19+
action:
20+
| ApiRequestAction<typeof SEND_QUERY, IQueryResult, QueryErrorResponse>
21+
| ReturnType<typeof setQueryOptions>,
2022
) => {
2123
switch (action.type) {
2224
case SEND_QUERY.REQUEST: {
@@ -57,7 +59,7 @@ interface SendQueryParams {
5759
query?: string;
5860
database?: string;
5961
action?: ExecuteActions;
60-
};
62+
}
6163

6264
export const sendQuery = ({query, database, action}: SendQueryParams) => {
6365
return createApiRequest({

src/types/api/error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export interface IResponseError {
2-
data?: unknown;
1+
export interface IResponseError<T = unknown> {
2+
data?: T;
33
status?: number;
44
statusText?: string;
55
isCancelled?: boolean;

src/types/api/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export type Actions = ExecuteActions | ExplainActions;
205205

206206
export interface ErrorResponse {
207207
error?: IssueMessage;
208-
issues?: IssueMessage[];
208+
issues?: IssueMessage[] | null;
209209
}
210210

211211
// ==== Explain Responses ====

src/types/store/executeQuery.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import {
99
MONACO_HOT_KEY_ACTIONS,
1010
} from '../../store/reducers/executeQuery';
1111
import type {ApiRequestAction} from '../../store/utils';
12-
import type {ErrorResponse} from '../api/query';
1312
import type {ValueOf} from '../common';
14-
import type {IQueryResult, QueryError} from './query';
13+
import type {IQueryResult, QueryError, QueryErrorResponse} from './query';
1514

1615
export type MonacoHotKeyAction = ValueOf<typeof MONACO_HOT_KEY_ACTIONS>;
1716

@@ -26,7 +25,7 @@ export interface ExecuteQueryState {
2625
tenantPath?: string;
2726
data?: IQueryResult;
2827
stats?: IQueryResult['stats'];
29-
error?: string | ErrorResponse;
28+
error?: string | QueryErrorResponse;
3029
}
3130

3231
type SendQueryAction = ApiRequestAction<typeof SEND_QUERY, IQueryResult, QueryError>;

src/types/store/executeTopQueries.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import {FETCH_TOP_QUERIES, setTopQueriesState, setTopQueriesFilters} from '../../store/reducers/executeTopQueries';
1+
import {
2+
FETCH_TOP_QUERIES,
3+
setTopQueriesState,
4+
setTopQueriesFilters,
5+
} from '../../store/reducers/executeTopQueries';
26
import type {ApiRequestAction} from '../../store/utils';
3-
import type {IResponseError} from '../api/error';
4-
import type {IQueryResult} from './query';
7+
import type {IQueryResult, QueryErrorResponse} from './query';
58

69
export interface ITopQueriesFilters {
710
/** ms from epoch */
@@ -15,12 +18,12 @@ export interface ITopQueriesState {
1518
loading: boolean;
1619
wasLoaded: boolean;
1720
data?: IQueryResult;
18-
error?: IResponseError;
21+
error?: QueryErrorResponse;
1922
filters: ITopQueriesFilters;
2023
}
2124

2225
export type ITopQueriesAction =
23-
| ApiRequestAction<typeof FETCH_TOP_QUERIES, IQueryResult, IResponseError>
26+
| ApiRequestAction<typeof FETCH_TOP_QUERIES, IQueryResult, QueryErrorResponse>
2427
| ReturnType<typeof setTopQueriesState>
2528
| ReturnType<typeof setTopQueriesFilters>;
2629

src/types/store/explainQuery.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid';
22

33
import {GET_EXPLAIN_QUERY, GET_EXPLAIN_QUERY_AST} from '../../store/reducers/explainQuery';
44
import type {ApiRequestAction} from '../../store/utils';
5-
import type {PlanTable, ErrorResponse, QueryPlan, ScriptPlan} from '../api/query';
6-
import type {IQueryResult, QueryError} from './query';
5+
import type {PlanTable, QueryPlan, ScriptPlan} from '../api/query';
6+
import type {IQueryResult, QueryError, QueryErrorResponse} from './query';
77

88
export interface PreparedExplainResponse {
99
plan?: {
@@ -20,8 +20,8 @@ export interface ExplainQueryState {
2020
loading: boolean;
2121
data?: PreparedExplainResponse['plan'];
2222
dataAst?: PreparedExplainResponse['ast'];
23-
error?: string | ErrorResponse;
24-
errorAst?: string | ErrorResponse;
23+
error?: string | QueryErrorResponse;
24+
errorAst?: string | QueryErrorResponse;
2525
}
2626

2727
type GetExplainQueryAstAction = ApiRequestAction<

src/types/store/query.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import {QUERY_ACTIONS, QUERY_MODES} from '../../utils/query';
22

3-
import type {NetworkError} from '../api/error';
3+
import type {IResponseError, NetworkError} from '../api/error';
44
import type {
55
KeyValueRow,
66
ColumnType,
7-
ErrorResponse,
7+
ErrorResponse as QueryErrorResponseData,
88
ScriptPlan,
99
QueryPlan,
1010
TKqpStatsQuery,
@@ -24,7 +24,8 @@ export interface QueryRequestParams {
2424
query: string;
2525
}
2626

27-
export type QueryError = NetworkError | ErrorResponse;
27+
export type QueryErrorResponse = IResponseError<QueryErrorResponseData>;
28+
export type QueryError = NetworkError | QueryErrorResponse;
2829

2930
export type QueryAction = ValueOf<typeof QUERY_ACTIONS>;
3031
export type QueryMode = ValueOf<typeof QUERY_MODES>;

0 commit comments

Comments
 (0)