Skip to content

Commit

Permalink
Update UI styles for query assist (#1523) (#1538)
Browse files Browse the repository at this point in the history
* update ui for query assist



* mock prohibited query when generating ppl



* update tests



* move guardrails check to server side



* focus input when selecting from suggestion



* update styles of run buttons



* hide patterns table by default



* show loading state for ppl generation



---------


(cherry picked from commit 9a4e532)

Signed-off-by: Joshua Li <[email protected]>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 0a03c16 commit 0d770f7
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 229 deletions.
2 changes: 2 additions & 0 deletions common/constants/query_assist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' };
8 changes: 8 additions & 0 deletions public/components/common/search/query_area.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

.ppl-query-accordion {
padding: $euiSizeS;
}
101 changes: 59 additions & 42 deletions public/components/common/search/query_area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -17,7 +18,7 @@ export function QueryArea({
runQuery,
tempQuery,
setNeedsUpdate,
setFillRun,
runChanges,
selectedIndex,
nlqInput,
setNlqInput,
Expand All @@ -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 = (
<EuiCodeEditor
theme="textmate"
width="100%"
height="4rem"
showPrintMargin={false}
setOptions={{
fontSize: '14px',
}}
aria-label="Code Editor"
onChange={(query) => {
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 <EuiPanel paddingSize="m">{queryEditor}</EuiPanel>;
}

return (
<EuiPanel paddingSize="m">
<EuiFlexGroup gutterSize="m" direction="column">
<EuiFlexItem>
<EuiCodeEditor
theme="textmate"
width="100%"
height="4rem"
showPrintMargin={false}
setOptions={{
fontSize: '14px',
}}
aria-label="Code Editor"
onChange={(query) => {
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}
/>
</EuiFlexItem>
{coreRefs.queryAssistEnabled && (
<EuiFlexItem>
<QueryAssistInput
tabId={tabId}
handleTimePickerChange={handleTimePickerChange}
handleQueryChange={handleQueryChange}
handleTimeRangePickerRefresh={handleTimeRangePickerRefresh}
setNeedsUpdate={setNeedsUpdate}
selectedIndex={selectedIndex}
nlqInput={nlqInput}
setNlqInput={setNlqInput}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiPanel paddingSize="none">
<EuiAccordion
id="ppl-query-accordion"
buttonContent="PPL Query"
initialIsOpen
className="ppl-query-accordion"
// this paddingSize is for accordion children
paddingSize="none"
>
<>
<EuiSpacer size="s" />
<QueryAssistInput
tabId={tabId}
handleTimePickerChange={handleTimePickerChange}
handleQueryChange={handleQueryChange}
handleTimeRangePickerRefresh={handleTimeRangePickerRefresh}
setNeedsUpdate={setNeedsUpdate}
selectedIndex={selectedIndex}
nlqInput={nlqInput}
setNlqInput={setNlqInput}
lastFocusedInput={lastFocusedInput}
setLastFocusedInput={setLastFocusedInput}
runChanges={runChanges}
>
{queryEditor}
</QueryAssistInput>
</>
</EuiAccordion>
</EuiPanel>
);
}
47 changes: 24 additions & 23 deletions public/components/common/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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('');
Expand Down Expand Up @@ -475,19 +474,21 @@ export const Search = (props: any) => {
/>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip position="bottom" content={needsUpdate ? 'Click to apply' : false}>
<EuiButton
color={needsUpdate ? 'success' : 'primary'}
iconType={needsUpdate ? 'kqlFunction' : 'play'}
fill={!showQueryArea || fillRun} // keep fill on all the time if not using query assistant
onClick={runChanges}
data-test-subj="superDatePickerApplyTimeButton" // mimic actual timepicker button
>
{needsUpdate ? 'Update' : 'Run'}
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
{!showQueryArea && (
<EuiFlexItem grow={false}>
<EuiToolTip position="bottom" content={needsUpdate ? 'Click to apply' : false}>
<EuiButton
color={needsUpdate ? 'success' : 'primary'}
iconType={needsUpdate ? 'kqlFunction' : 'play'}
fill
onClick={runChanges}
data-test-subj="superDatePickerApplyTimeButton" // mimic actual timepicker button
>
{needsUpdate ? 'Update' : 'Run'}
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
)}
{!showQueryArea && showSaveButton && !showSavePanelOptionsList && (
<EuiFlexItem className="euiFlexItem--flexGrowZero live-tail">
<EuiPopover
Expand Down Expand Up @@ -573,11 +574,11 @@ export const Search = (props: any) => {
runQuery={query}
tempQuery={tempQuery}
setNeedsUpdate={setNeedsUpdate}
setFillRun={setFillRun}
selectedIndex={selectedIndex}
nlqInput={nlqInput}
setNlqInput={setNlqInput}
pplService={pplService}
runChanges={runChanges}
/>
</EuiFlexItem>
{(queryAssistantSummarization?.summary?.length > 0 ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -39,7 +38,7 @@ const EventPatterns = ({
const dispatch = useDispatch();
const { tabId, pplService, notifications } = useContext<any>(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({
Expand Down
21 changes: 15 additions & 6 deletions public/components/event_analytics/explorer/no_results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<EuiPage paddingSize="s">
{coreRefs.queryAssistEnabled ? (
<>
{/* check to see if the rawQuery is empty or not */}
{queryInfo?.rawQuery ? (
{queryAssistLoading ? (
<EuiEmptyPrompt
title={<EuiLoadingSpinner size="xl" />}
body={<p>Loading results...</p>}
/>
) : queryInfo?.rawQuery ? (
<EuiFlexGroup justifyContent="center" direction="column">
<EuiFlexItem grow={false}>
<EuiCallOut
Expand Down Expand Up @@ -62,8 +71,8 @@ export const NoResults = ({ tabId }: any) => {
title={<h2>Get started</h2>}
body={
<p>
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.
</p>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,6 +29,7 @@ const renderQueryAssistInput = (
selectedIndex: [{ label: 'selected-test-index' }],
nlqInput: 'test-input',
setNlqInput: jest.fn(),
handleTimePickerChange: jest.fn(),
},
overrideProps
);
Expand Down Expand Up @@ -59,7 +60,8 @@ describe('<QueryAssistInput /> 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, {
Expand All @@ -73,6 +75,8 @@ describe('<QueryAssistInput /> 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'));
});

Expand All @@ -91,7 +95,8 @@ describe('<QueryAssistInput /> 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, {
Expand All @@ -116,15 +121,34 @@ describe('<QueryAssistInput /> 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, {
body: '{"question":"test-input","index":"selected-test-index"}',
});
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();
});
});
Loading

0 comments on commit 0d770f7

Please sign in to comment.