Skip to content

Commit 1477024

Browse files
Fix: Correctly show error message in DQL and PPL query editor (opensearch-project#9586)
* Fix: Correctly show error message in DQL and PPL query editor Signed-off-by: Joey Liu <jiyili@amazon.com> * Changeset file for PR opensearch-project#9586 created/updated * Update query status interface Signed-off-by: Joey Liu <jiyili@amazon.com> * Add unit test Signed-off-by: Joey Liu <jiyili@amazon.com> --------- Signed-off-by: Joey Liu <jiyili@amazon.com> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
1 parent 2b0ee3e commit 1477024

File tree

8 files changed

+202
-8
lines changed

8 files changed

+202
-8
lines changed

changelogs/fragments/9586.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fix:
2+
- Correctly show error message in DQL and PPL query editor ([#9586](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/9586))

src/plugins/data/public/query/query_string/language_service/lib/__snapshots__/query_result.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/data/public/query/query_string/language_service/lib/query_result.test.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import React from 'react';
77
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
88
import { QueryResult } from './query_result';
9+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
910

1011
enum ResultStatus {
1112
UNINITIALIZED = 'uninitialized',
@@ -117,4 +118,79 @@ describe('Query Result', () => {
117118
const component = shallowWithIntl(<QueryResult {...props} />);
118119
expect(component).toEqual({});
119120
});
121+
122+
it('should render error message correctly with normal search strategy', async () => {
123+
const props = {
124+
queryStatus: {
125+
status: ResultStatus.ERROR,
126+
body: {
127+
error: {
128+
error: 'error',
129+
statusCode: 400,
130+
message: {
131+
error: {
132+
reason: 'error reason',
133+
details: 'error details',
134+
type: 'error type',
135+
},
136+
status: 400,
137+
},
138+
},
139+
},
140+
},
141+
};
142+
143+
render(<QueryResult {...props} />);
144+
145+
await fireEvent.click(screen.getByText('Error'));
146+
147+
await waitFor(() => {
148+
expect(screen.getByText('error details')).toBeInTheDocument();
149+
});
150+
});
151+
152+
it('should render error message correctly with async search strategy', async () => {
153+
const props = {
154+
queryStatus: {
155+
status: ResultStatus.ERROR,
156+
body: {
157+
error: {
158+
error: 'error',
159+
statusCode: 400,
160+
message: {
161+
error: 'error message',
162+
status: 400,
163+
},
164+
},
165+
},
166+
},
167+
};
168+
169+
render(<QueryResult {...props} />);
170+
171+
await fireEvent.click(screen.getByText('Error'));
172+
173+
await waitFor(() => {
174+
expect(screen.getByText('error message')).toBeInTheDocument();
175+
});
176+
});
177+
178+
it('should render error message with flexible error format', async () => {
179+
const props = {
180+
queryStatus: {
181+
status: ResultStatus.ERROR,
182+
body: {
183+
error: 'error message',
184+
},
185+
},
186+
};
187+
188+
render(<QueryResult {...props} />);
189+
190+
await fireEvent.click(screen.getByText('Error'));
191+
192+
await waitFor(() => {
193+
expect(screen.getByText('error message')).toBeInTheDocument();
194+
});
195+
});
120196
});

src/plugins/data/public/query/query_string/language_service/lib/query_result.tsx

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { i18n } from '@osd/i18n';
88
import './_recent_query.scss';
99
import { EuiButtonEmpty, EuiPopover, EuiText, EuiPopoverTitle } from '@elastic/eui';
1010

11-
import React, { useEffect, useState } from 'react';
11+
import React, { useEffect, useMemo, useState } from 'react';
1212

1313
export enum ResultStatus {
1414
UNINITIALIZED = 'uninitialized',
@@ -22,8 +22,18 @@ export interface QueryStatus {
2222
status: ResultStatus;
2323
body?: {
2424
error?: {
25+
error?: string;
26+
message?: {
27+
error?:
28+
| string
29+
| {
30+
reason?: string;
31+
details: string;
32+
type?: string;
33+
};
34+
status?: number;
35+
};
2536
statusCode?: number;
26-
message?: string;
2737
};
2838
};
2939
elapsedMs?: number;
@@ -58,6 +68,43 @@ export function QueryResult(props: { queryStatus: QueryStatus }) {
5868
};
5969
}, [props.queryStatus.startTime]);
6070

71+
const displayErrorMessage = useMemo(() => {
72+
const error = props.queryStatus.body?.error;
73+
const message = error?.message;
74+
75+
if (message == null) {
76+
if (typeof error === 'string') {
77+
return error;
78+
}
79+
80+
if (typeof error === 'object') {
81+
return JSON.stringify(error);
82+
}
83+
84+
return `Unknown Error: ${String(error)}`;
85+
}
86+
87+
// For async search strategy, expecting message.error to be string
88+
if (typeof message.error === 'string') {
89+
return message.error;
90+
}
91+
92+
// For normal search strategy, expecting message.error to be object
93+
if (message.error?.details) {
94+
return message.error.details;
95+
}
96+
97+
if (typeof message === 'string') {
98+
return message;
99+
}
100+
101+
if (typeof message === 'object') {
102+
return JSON.stringify(message);
103+
}
104+
105+
return `Unknown Error: ${String(message)}`;
106+
}, [props.queryStatus.body?.error]);
107+
61108
if (props.queryStatus.status === ResultStatus.LOADING) {
62109
const time = Math.floor(elapsedTime / 1000);
63110
const loadingText =
@@ -166,7 +213,7 @@ export function QueryResult(props: { queryStatus: QueryStatus }) {
166213
defaultMessage: `Message:`,
167214
})}
168215
</strong>{' '}
169-
{props.queryStatus.body.error.message}
216+
{displayErrorMessage}
170217
</p>
171218
</EuiText>
172219
</div>

src/plugins/discover/public/application/view_components/canvas/top_nav.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ export const TopNav = ({ opts, showSaveQuery, isEnhancementsEnabled }: TopNavPro
9393
};
9494
setQueryStatus(result);
9595
});
96+
97+
return () => {
98+
subscription.unsubscribe();
99+
};
96100
}, [data$]);
97101

98102
useEffect(() => {

src/plugins/discover/public/application/view_components/utils/use_search.test.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Subject } from 'rxjs';
1212
import { createDataExplorerServicesMock } from '../../../../../data_explorer/public/utils/mocks';
1313
import { DiscoverViewServices } from '../../../build_services';
1414
import { discoverPluginMock } from '../../../mocks';
15-
import { ResultStatus, useSearch } from './use_search';
15+
import { ResultStatus, safeJSONParse, useSearch } from './use_search';
1616
import { Filter, ISearchSource, UI_SETTINGS } from '../../../../../data/common';
1717
import { opensearchFilters } from 'src/plugins/data/public';
1818

@@ -418,3 +418,12 @@ describe('useSearch', () => {
418418
});
419419
});
420420
});
421+
422+
describe('safeJSONParse', () => {
423+
it('should covert string to JSON if possible', () => {
424+
const validJSONString = '{"result":true, "count":42}';
425+
const invalidJSONString = 'invalidJSON';
426+
expect(safeJSONParse(validJSONString)).toEqual(JSON.parse(validJSONString));
427+
expect(safeJSONParse(invalidJSONString)).toEqual(invalidJSONString);
428+
});
429+
});

src/plugins/discover/public/application/view_components/utils/use_search.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ import { useSelector } from '../../utils/state_management';
3434
import { SEARCH_ON_PAGE_LOAD_SETTING } from '../../../../common';
3535
import { trackQueryMetric } from '../../../ui_metric';
3636

37+
export function safeJSONParse(text: any) {
38+
try {
39+
return JSON.parse(text);
40+
} catch (error) {
41+
return text;
42+
}
43+
}
44+
3745
export enum ResultStatus {
3846
UNINITIALIZED = 'uninitialized',
3947
LOADING = 'loading', // initial data load
@@ -54,10 +62,19 @@ export interface SearchData {
5462
queryStatus?: {
5563
body?: {
5664
error?: {
57-
reason?: string;
58-
details: string;
65+
error?: string;
66+
message?: {
67+
error?:
68+
| string
69+
| {
70+
reason?: string;
71+
details: string;
72+
type?: string;
73+
};
74+
status?: number;
75+
};
76+
statusCode?: number;
5977
};
60-
statusCode?: number;
6178
};
6279
elapsedMs?: number;
6380
startTime?: number;
@@ -318,17 +335,55 @@ export const useSearch = (services: DiscoverViewServices) => {
318335
data.search.showError(error as Error);
319336
return;
320337
}
338+
// TODO: Create a unify error response at server side
321339
let errorBody;
322340
try {
341+
// Normal search strategy failed query, return HttpFetchError
342+
/*
343+
@type {HttpFetchError}
344+
{
345+
body: {
346+
error: string,
347+
statusCode: number,
348+
message: JSONstring,
349+
},
350+
...
351+
}
352+
*/
323353
errorBody = JSON.parse(error.body);
324354
} catch (e) {
325355
if (error.body) {
326356
errorBody = error.body;
327357
} else {
358+
// Async search strategy failed query, return Error
359+
/*
360+
@type {Error}
361+
{
362+
message: string,
363+
stack: string,
364+
}
365+
*/
328366
errorBody = error;
329367
}
330368
}
331369

370+
// Error message can be sent as encoded JSON string, which requires extra parsing
371+
/*
372+
errorBody: {
373+
error: string,
374+
statusCode: number,
375+
message: {
376+
error: {
377+
reason: string;
378+
details: string;
379+
type: string;
380+
};
381+
status: number;
382+
}
383+
}
384+
*/
385+
errorBody.message = safeJSONParse(errorBody.message);
386+
332387
data$.next({
333388
status: ResultStatus.ERROR,
334389
queryStatus: {

src/plugins/query_enhancements/common/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export const fetch = (context: EnhancedFetchContext, query: Query, aggConfig?: Q
8383
// eslint-disable-next-line no-console
8484
console.error('Failed to cancel query:', cancelError);
8585
}
86-
throw error;
8786
}
87+
throw error;
8888
})
8989
);
9090
};

0 commit comments

Comments
 (0)