diff --git a/common/constants/query_assist.ts b/common/constants/query_assist.ts index e0b3bb29e6..8de10583d6 100644 --- a/common/constants/query_assist.ts +++ b/common/constants/query_assist.ts @@ -11,3 +11,5 @@ export const QUERY_ASSIST_API = { }; export const ML_COMMONS_API_PREFIX = '/_plugins/_ml'; + +export const ERROR_DETAILS = { GUARDRAILS_TRIGGERED: 'guardrail triggered' }; diff --git a/public/components/common/search/query_area.scss b/public/components/common/search/query_area.scss new file mode 100644 index 0000000000..3d5d31463e --- /dev/null +++ b/public/components/common/search/query_area.scss @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.ppl-query-accordion { + padding: $euiSizeS; +} diff --git a/public/components/common/search/query_area.tsx b/public/components/common/search/query_area.tsx index a298e546ae..16d44e0cbd 100644 --- a/public/components/common/search/query_area.tsx +++ b/public/components/common/search/query_area.tsx @@ -3,11 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiCodeEditor, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import React, { useEffect, useMemo } from 'react'; +import { EuiAccordion, EuiCodeEditor, EuiPanel, EuiSpacer } from '@elastic/eui'; +import React, { useEffect, useMemo, useState } from 'react'; import { coreRefs } from '../../../framework/core_refs'; import { QueryAssistInput } from '../../event_analytics/explorer/query_assist/input'; import { useFetchEvents } from '../../event_analytics/hooks/use_fetch_events'; +import './query_area.scss'; export function QueryArea({ tabId, @@ -17,7 +18,7 @@ export function QueryArea({ runQuery, tempQuery, setNeedsUpdate, - setFillRun, + runChanges, selectedIndex, nlqInput, setNlqInput, @@ -37,47 +38,63 @@ export function QueryArea({ memoizedHandleQueryChange(indexQuery); memoizedGetAvailableFields(indexQuery); }, [selectedIndex, memoizedGetAvailableFields, memoizedHandleQueryChange]); + const [lastFocusedInput, setLastFocusedInput] = useState<'query_area' | 'nlq_input'>('nlq_input'); + + const queryEditor = ( + { + handleQueryChange(query); + // query is considered updated when the last run query is not the same as whats in the editor + // setUpdatedQuery(runQuery !== query); + setNeedsUpdate(runQuery !== query); + }} + onFocus={() => setLastFocusedInput('query_area')} + value={tempQuery} + wrapEnabled={true} + /> + ); + + if (!coreRefs.queryAssistEnabled) { + return {queryEditor}; + } return ( - - - - { - handleQueryChange(query); - // query is considered updated when the last run query is not the same as whats in the editor - // setUpdatedQuery(runQuery !== query); - setNeedsUpdate(runQuery !== query); - }} - onFocus={() => setFillRun(true)} - onBlur={() => setFillRun(false)} - value={tempQuery} - wrapEnabled={true} - /> - - {coreRefs.queryAssistEnabled && ( - - - - )} - + + + <> + + + {queryEditor} + + + ); } diff --git a/public/components/common/search/search.tsx b/public/components/common/search/search.tsx index ec78d9f342..f434dae917 100644 --- a/public/components/common/search/search.tsx +++ b/public/components/common/search/search.tsx @@ -10,20 +10,20 @@ import { EuiButtonEmpty, EuiComboBox, EuiComboBoxOptionOption, + EuiContextMenuItem, EuiContextMenuPanel, EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, EuiPopover, EuiPopoverFooter, EuiText, EuiToolTip, - EuiContextMenuItem, - EuiModal, - EuiModalHeader, - EuiModalBody, - EuiModalHeaderTitle, - EuiModalFooter, } from '@elastic/eui'; import { isEqual } from 'lodash'; import React, { useEffect, useState } from 'react'; @@ -59,11 +59,11 @@ import { import { update as updateSearchMetaData } from '../../event_analytics/redux/slices/search_meta_data_slice'; import { PPLReferenceFlyout } from '../helpers'; import { LiveTailButton, StopLiveButton } from '../live_tail/live_tail_button'; +import { Autocomplete } from './autocomplete'; import { DatePicker } from './date_picker'; import { QueryArea } from './query_area'; -import './search.scss'; import { QueryAssistSummarization } from './query_assist_summarization'; -import { Autocomplete } from './autocomplete'; +import './search.scss'; export interface IQueryBarProps { query: string; @@ -136,7 +136,6 @@ export const Search = (props: any) => { const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [queryLang, setQueryLang] = useState(QUERY_LANGUAGE.PPL); const [needsUpdate, setNeedsUpdate] = useState(false); - const [fillRun, setFillRun] = useState(false); const sqlService = new SQLService(coreRefs.http); const { application } = coreRefs; const [nlqInput, setNlqInput] = useState(''); @@ -475,19 +474,21 @@ export const Search = (props: any) => { /> )} - - - - {needsUpdate ? 'Update' : 'Run'} - - - + {!showQueryArea && ( + + + + {needsUpdate ? 'Update' : 'Run'} + + + + )} {!showQueryArea && showSaveButton && !showSavePanelOptionsList && ( { runQuery={query} tempQuery={tempQuery} setNeedsUpdate={setNeedsUpdate} - setFillRun={setFillRun} selectedIndex={selectedIndex} nlqInput={nlqInput} setNlqInput={setNlqInput} pplService={pplService} + runChanges={runChanges} /> {(queryAssistantSummarization?.summary?.length > 0 || diff --git a/public/components/event_analytics/explorer/log_patterns/log_patterns.tsx b/public/components/event_analytics/explorer/log_patterns/log_patterns.tsx index 2ed2d408fe..6a81597d68 100644 --- a/public/components/event_analytics/explorer/log_patterns/log_patterns.tsx +++ b/public/components/event_analytics/explorer/log_patterns/log_patterns.tsx @@ -10,7 +10,6 @@ import { FILTERED_PATTERN, PATTERN_REGEX, PPL_DEFAULT_PATTERN_REGEX_FILETER, - RAW_QUERY, } from '../../../../../common/constants/explorer'; import { PatternTableData, Query as IQuery } from '../../../../../common/types/explorer'; import { TabContext, useFetchPatterns } from '../../hooks'; @@ -39,7 +38,7 @@ const EventPatterns = ({ const dispatch = useDispatch(); const { tabId, pplService, notifications } = useContext(TabContext); const patternsData = patterns[tabId]; - const [viewLogPatterns, setViewLogPatterns] = useState(true); + const [viewLogPatterns, setViewLogPatterns] = useState(false); const [isPatternConfigPopoverOpen, setIsPatternConfigPopoverOpen] = useState(false); const [patternRegexInput, setPatternRegexInput] = useState(PPL_DEFAULT_PATTERN_REGEX_FILETER); const { isEventsLoading: isPatternLoading, getPatterns } = useFetchPatterns({ diff --git a/public/components/event_analytics/explorer/no_results.tsx b/public/components/event_analytics/explorer/no_results.tsx index c4ce3a9b09..272417f7f2 100644 --- a/public/components/event_analytics/explorer/no_results.tsx +++ b/public/components/event_analytics/explorer/no_results.tsx @@ -3,31 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; -import { FormattedMessage } from '@osd/i18n/react'; import { EuiCallOut, + EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, EuiPage, EuiSpacer, EuiText, - EuiEmptyPrompt, } from '@elastic/eui'; +import { FormattedMessage } from '@osd/i18n/react'; +import React from 'react'; import { useSelector } from 'react-redux'; import { coreRefs } from '../../../framework/core_refs'; +import { selectQueryAssistantSummarization } from '../redux/slices/query_assistant_summarization_slice'; import { selectQueries } from '../redux/slices/query_slice'; export const NoResults = ({ tabId }: any) => { // get the queries isLoaded, if it exists AND is true = show no res const queryInfo = useSelector(selectQueries)[tabId]; + const summaryData = useSelector(selectQueryAssistantSummarization)[tabId]; + const queryAssistLoading = summaryData.loading; return ( {coreRefs.queryAssistEnabled ? ( <> {/* check to see if the rawQuery is empty or not */} - {queryInfo?.rawQuery ? ( + {queryAssistLoading ? ( + } + body={

Loading results...

} + /> + ) : queryInfo?.rawQuery ? ( { title={

Get started

} body={

- Run a query to view results, or use the Query Assistant to automatically generate - complex queries using simple conversational prompts. + Run a query to view results, or use the Natural Language Query Generator to + automatically generate complex queries using simple conversational prompts.

} /> diff --git a/public/components/event_analytics/explorer/query_assist/__tests__/input.test.tsx b/public/components/event_analytics/explorer/query_assist/__tests__/input.test.tsx index 1a95a5e379..31eb1c872c 100644 --- a/public/components/event_analytics/explorer/query_assist/__tests__/input.test.tsx +++ b/public/components/event_analytics/explorer/query_assist/__tests__/input.test.tsx @@ -8,7 +8,7 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import React, { ComponentProps } from 'react'; import { Provider } from 'react-redux'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; -import { QUERY_ASSIST_API } from '../../../../../../common/constants/query_assist'; +import { ERROR_DETAILS, QUERY_ASSIST_API } from '../../../../../../common/constants/query_assist'; import * as coreServices from '../../../../../../common/utils/core_services'; import { coreRefs } from '../../../../../framework/core_refs'; import { rootReducer } from '../../../../../framework/redux/reducers'; @@ -29,6 +29,7 @@ const renderQueryAssistInput = ( selectedIndex: [{ label: 'selected-test-index' }], nlqInput: 'test-input', setNlqInput: jest.fn(), + handleTimePickerChange: jest.fn(), }, overrideProps ); @@ -59,7 +60,8 @@ describe(' spec', () => { const { component, props } = renderQueryAssistInput(); await waitFor(() => { - fireEvent.click(component.getByTestId('query-assist-generate-and-run-button')); + // splitbutton data-test-subj doesn't work in Oui 1.5, this should be query-assist-generate-and-run-button + fireEvent.click(component.getByText('Generate and run')); }); expect(httpMock.post).toBeCalledWith(QUERY_ASSIST_API.GENERATE_PPL, { @@ -73,6 +75,8 @@ describe(' spec', () => { const { component } = renderQueryAssistInput(); await waitFor(() => { + // splitbutton dropdown buttons don't support custom test id in Oui 1.5 + fireEvent.click(component.getByTestId('splitButton--dropdown')); fireEvent.click(component.getByTestId('query-assist-generate-button')); }); @@ -91,7 +95,8 @@ describe(' spec', () => { const { component } = renderQueryAssistInput(); await waitFor(() => { - fireEvent.click(component.getByTestId('query-assist-generate-and-run-button')); + // splitbutton data-test-subj doesn't work in Oui 1.5, this should be query-assist-generate-and-run-button + fireEvent.click(component.getByText('Generate and run')); }); expect(httpMock.post).toBeCalledWith(QUERY_ASSIST_API.GENERATE_PPL, { @@ -116,7 +121,8 @@ describe(' spec', () => { const { component } = renderQueryAssistInput(); await waitFor(() => { - fireEvent.click(component.getByTestId('query-assist-generate-and-run-button')); + // splitbutton data-test-subj doesn't work in Oui 1.5, this should be query-assist-generate-and-run-button + fireEvent.click(component.getByText('Generate and run')); }); expect(httpMock.post).toBeCalledWith(QUERY_ASSIST_API.GENERATE_PPL, { @@ -124,7 +130,25 @@ describe(' spec', () => { }); expect(httpMock.post).toBeCalledWith(QUERY_ASSIST_API.SUMMARIZE, { body: - '{"question":"test-input","index":"selected-test-index","isError":true,"query":"","response":"{\\"statusCode\\":429}"}', + '{"question":"test-input","index":"selected-test-index","isError":true,"query":"","response":"{\\"statusCode\\":429,\\"message\\":\\"Request is throttled. Try again later or contact your administrator\\"}"}', }); }); + + it('should display callout when response returned 400 with guardrails denied', async () => { + coreRefs.summarizeEnabled = true; + httpMock.post.mockRejectedValueOnce({ + body: { statusCode: 400, message: ERROR_DETAILS.GUARDRAILS_TRIGGERED }, + }); + + const { component } = renderQueryAssistInput(); + await waitFor(() => { + // splitbutton data-test-subj doesn't work in Oui 1.5, this should be query-assist-generate-and-run-button + fireEvent.click(component.getByText('Generate and run')); + }); + + expect(httpMock.post).toBeCalledWith(QUERY_ASSIST_API.GENERATE_PPL, { + body: '{"question":"test-input","index":"selected-test-index"}', + }); + expect(component.getByTestId('query-assist-guard-callout')).toBeInTheDocument(); + }); }); diff --git a/public/components/event_analytics/explorer/query_assist/input.tsx b/public/components/event_analytics/explorer/query_assist/input.tsx index fba8aa72e3..e6b18df585 100644 --- a/public/components/event_analytics/explorer/query_assist/input.tsx +++ b/public/components/event_analytics/explorer/query_assist/input.tsx @@ -4,26 +4,26 @@ */ import { - EuiBadge, EuiButton, + EuiButtonIcon, + EuiCallOut, EuiComboBoxOptionOption, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiForm, EuiIcon, EuiInputPopover, - EuiLink, EuiListGroup, EuiListGroupItem, - EuiPanel, + EuiSpacer, + EuiSplitButton, EuiText, } from '@elastic/eui'; import { ResponseError } from '@opensearch-project/opensearch/lib/errors'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RAW_QUERY } from '../../../../../common/constants/explorer'; -import { QUERY_ASSIST_API } from '../../../../../common/constants/query_assist'; +import { ERROR_DETAILS, QUERY_ASSIST_API } from '../../../../../common/constants/query_assist'; import { QUERY_ASSIST_START_TIME } from '../../../../../common/constants/shared'; import { getOSDHttp } from '../../../../../common/utils'; import { coreRefs } from '../../../../framework/core_refs'; @@ -32,11 +32,18 @@ import { changeSummary, resetSummary, selectQueryAssistantSummarization, + setLoading, setResponseForSummaryStatus, } from '../../redux/slices/query_assistant_summarization_slice'; import { reset, selectQueryResult } from '../../redux/slices/query_result_slice'; import { changeQuery, selectQueries } from '../../redux/slices/query_slice'; +class ProhibitedQueryError extends Error { + constructor(message?: string) { + super(message); + } +} + interface SummarizationContext { question: string; query?: string; @@ -54,6 +61,9 @@ interface Props { selectedIndex: Array>; nlqInput: string; setNlqInput: React.Dispatch>; + lastFocusedInput: 'query_area' | 'nlq_input'; + setLastFocusedInput: React.Dispatch>; + runChanges: () => void; } const HARDCODED_SUGGESTIONS: Record = { @@ -79,13 +89,35 @@ const HARDCODED_SUGGESTIONS: Record = { ], }; -export const QueryAssistInput: React.FC = (props) => { +const prohibitedQueryCallOut = ( + +); + +const emptyQueryCallOut = ( + +); + +export const QueryAssistInput: React.FC> = (props) => { // @ts-ignore const queryRedux = useSelector(selectQueries)[props.tabId]; // @ts-ignore const explorerData = useSelector(selectQueryResult)[props.tabId]; // @ts-ignore const summaryData = useSelector(selectQueryAssistantSummarization)[props.tabId]; + const loading = summaryData.loading; + const inputRef = useRef(null); useEffect(() => { if ( @@ -108,15 +140,12 @@ export const QueryAssistInput: React.FC = (props) => { })(); }, [summaryData.responseForSummaryStatus]); - const [barSelected, setBarSelected] = useState(false); - const dispatch = useDispatch(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [generating, setGenerating] = useState(false); - const [generatingOrRunning, setGeneratingOrRunning] = useState(false); // below is only used for url redirection const [autoRun, setAutoRun] = useState(false); + const [callOut, setCallOut] = useState(null); useEffect(() => { if (autoRun) { @@ -149,13 +178,18 @@ export const QueryAssistInput: React.FC = (props) => { ); return generatedPPL; }; - const formatError = (error: ResponseError): Error => { - if (error.body) { + const formatError = (error: ResponseError | Error): Error => { + if ('body' in error) { if (error.body.statusCode === 429) return { ...error.body, message: 'Request is throttled. Try again later or contact your administrator', } as Error; + if ( + error.body.statusCode === 400 && + error.body.message === ERROR_DETAILS.GUARDRAILS_TRIGGERED + ) + return new ProhibitedQueryError(error.body.message); return error.body as Error; } return error; @@ -165,15 +199,23 @@ export const QueryAssistInput: React.FC = (props) => { dispatch(reset({ tabId: props.tabId })); dispatch(resetSummary({ tabId: props.tabId })); if (!props.selectedIndex.length) return; + if (props.nlqInput.trim().length === 0) { + setCallOut(emptyQueryCallOut); + return; + } try { - setGenerating(true); + dispatch(setLoading({ tabId: props.tabId, loading: true })); + setCallOut(null); await request(); - } catch (error) { - coreRefs.toasts?.addError(formatError(error as ResponseError), { - title: 'Failed to generate results', - }); + } catch (err) { + const error = formatError(err); + if (error instanceof ProhibitedQueryError) { + setCallOut(prohibitedQueryCallOut); + return; + } + coreRefs.toasts?.addError(error, { title: 'Failed to generate results' }); } finally { - setGenerating(false); + dispatch(setLoading({ tabId: props.tabId, loading: false })); } }; const generateSummary = async (context?: Partial) => { @@ -219,10 +261,13 @@ export const QueryAssistInput: React.FC = (props) => { }, }) ); - } catch (error) { - coreRefs.toasts?.addError(formatError(error as ResponseError), { - title: 'Failed to summarize results', - }); + } catch (err) { + const error = formatError(err); + if (error instanceof ProhibitedQueryError) { + setCallOut(prohibitedQueryCallOut); + return; + } + coreRefs.toasts?.addError(error, { title: 'Failed to summarize results' }); } finally { await dispatch( changeSummary({ @@ -245,137 +290,127 @@ export const QueryAssistInput: React.FC = (props) => { dispatch(reset({ tabId: props.tabId })); dispatch(resetSummary({ tabId: props.tabId })); if (!props.selectedIndex.length) return; + if (props.nlqInput.trim().length === 0) { + setCallOut(emptyQueryCallOut); + return; + } try { - setGeneratingOrRunning(true); + dispatch(setLoading({ tabId: props.tabId, loading: true })); + setCallOut(null); await request(); await props.handleTimePickerChange([QUERY_ASSIST_START_TIME, 'now']); await props.handleTimeRangePickerRefresh(undefined, true); - } catch (error) { + } catch (err) { + const error = formatError(err); + if (error instanceof ProhibitedQueryError) { + setCallOut(prohibitedQueryCallOut); + return; + } if (coreRefs.summarizeEnabled) { - generateSummary({ isError: true, response: JSON.stringify((error as ResponseError).body) }); + generateSummary({ isError: true, response: JSON.stringify(error) }); } else { - coreRefs.toasts?.addError(formatError(error as ResponseError), { - title: 'Failed to generate results', - }); + coreRefs.toasts?.addError(error, { title: 'Failed to generate results' }); } } finally { - setGeneratingOrRunning(false); + dispatch(setLoading({ tabId: props.tabId, loading: false })); } }; return ( <> - - { - e.preventDefault(); - request(); - }} + + + props.setNlqInput(e.target.value)} + onKeyDown={(e) => { + // listen to enter key manually. the cursor jumps to CodeEditor with EuiForm's onSubmit + if (e.key === 'Enter') runAndSummarize(); + }} + prepend={} + fullWidth + onFocus={() => { + props.setNeedsUpdate(false); + props.setLastFocusedInput('nlq_input'); + if (props.nlqInput.length === 0) setIsPopoverOpen(true); + }} + /> + } + disableFocusTrap + fullWidth={true} + isOpen={isPopoverOpen} + closePopover={() => { + setIsPopoverOpen(false); + }} + > + + {HARDCODED_SUGGESTIONS[props.selectedIndex[0]?.label]?.map((question) => ( + { + props.setNlqInput(question); + inputRef.current?.focus(); + setIsPopoverOpen(false); + }} + label={question} + /> + ))} + + + + + + + + {callOut} + {props.children && } + {props.children} + + {props.lastFocusedInput === 'query_area' ? ( + + Run + + ) : ( + Generate query + ), + onClick: generatePPL, + }, + ]} + onClick={runAndSummarize} > - - - - - - - - Query Assistant - - - New! - - - props.setNlqInput(e.target.value)} - fullWidth - onFocus={() => { - setBarSelected(true); - props.setNeedsUpdate(false); - if (props.nlqInput.length === 0) setIsPopoverOpen(true); - }} - onBlur={() => setBarSelected(false)} - /> - } - disableFocusTrap - fullWidth={true} - isOpen={isPopoverOpen} - closePopover={() => { - setIsPopoverOpen(false); - }} - > - - {HARDCODED_SUGGESTIONS[props.selectedIndex[0]?.label]?.map((question) => ( - { - props.setNlqInput(question); - setIsPopoverOpen(false); - }} - label={question} - /> - ))} - - - - - - - - - - - Share feedback via{' '} - - Forum - {' '} - or{' '} - - Slack - - - - - - - Generate query - - - - - Generate and run - - - - - - - + {loading ? 'Running...' : 'Generate and run'} + + )} ); }; diff --git a/public/components/event_analytics/redux/slices/query_assistant_summarization_slice.ts b/public/components/event_analytics/redux/slices/query_assistant_summarization_slice.ts index e25ebab022..286f87fdd0 100644 --- a/public/components/event_analytics/redux/slices/query_assistant_summarization_slice.ts +++ b/public/components/event_analytics/redux/slices/query_assistant_summarization_slice.ts @@ -8,6 +8,7 @@ import { initialTabId } from '../../../../framework/redux/store/shared_state'; const initialState = { [initialTabId]: { + loading: false, responseForSummaryStatus: 'false' as 'false' | 'success' | 'failure', }, }; @@ -30,9 +31,16 @@ export const summarizationSlice = createSlice({ }, resetSummary: (state, { payload }) => { state[payload.tabId] = { + loading: false, responseForSummaryStatus: initialState[initialTabId].responseForSummaryStatus, }; }, + setLoading: (state, { payload }) => { + state[payload.tabId] = { + ...state[payload.tabId], + loading: payload.loading, + }; + }, }, }); @@ -40,6 +48,7 @@ export const { setResponseForSummaryStatus, changeSummary, resetSummary, + setLoading, } = summarizationSlice.actions; export const selectQueryAssistantSummarization = createSelector( diff --git a/server/routes/query_assist/routes.ts b/server/routes/query_assist/routes.ts index 3ec92777d0..5969ddf7e8 100644 --- a/server/routes/query_assist/routes.ts +++ b/server/routes/query_assist/routes.ts @@ -10,7 +10,7 @@ import { ResponseError, } from '../../../../../src/core/server'; import { isResponseError } from '../../../../../src/core/server/opensearch/client/errors'; -import { QUERY_ASSIST_API } from '../../../common/constants/query_assist'; +import { ERROR_DETAILS, QUERY_ASSIST_API } from '../../../common/constants/query_assist'; import { generateFieldContext } from '../../common/helpers/query_assist/generate_field_context'; import { getAgentIdByConfig, getAgentIdAndRequest } from './utils/agents'; import { AGENT_CONFIGS } from './utils/constants'; @@ -81,11 +81,12 @@ export function registerQueryAssistRoutes(router: IRouter) { .replace(/\bSPAN\(/g, 'span('); // https://github.com/opensearch-project/dashboards-observability/issues/759 return response.ok({ body: ppl }); } catch (error) { - // parse PPL query from error response if exists - // TODO remove after https://github.com/opensearch-project/skills/issues/138 - if (isResponseError(error) && error.body.error?.reason) { - const pplMatch = error.body.error.reason.match(/execute ppl:(.+), get error:/); - if (pplMatch[1]) return response.ok({ body: pplMatch[1] }); + if ( + isResponseError(error) && + error.statusCode === 400 && + error.body.error.details === ERROR_DETAILS.GUARDRAILS_TRIGGERED + ) { + return response.badRequest({ body: ERROR_DETAILS.GUARDRAILS_TRIGGERED }); } return response.custom({ statusCode: error.statusCode || 500, body: error.message }); } @@ -152,10 +153,14 @@ export function registerQueryAssistRoutes(router: IRouter) { }, }); } catch (error) { - return response.custom({ - statusCode: error.statusCode || 500, - body: error.message, - }); + if ( + isResponseError(error) && + error.statusCode === 400 && + error.body.error.details === ERROR_DETAILS.GUARDRAILS_TRIGGERED + ) { + return response.badRequest({ body: ERROR_DETAILS.GUARDRAILS_TRIGGERED }); + } + return response.custom({ statusCode: error.statusCode || 500, body: error.message }); } } );