Skip to content

Commit b0053b9

Browse files
committed
[Discover-next] Address comments for search bar extensions and query assist (opensearch-project#6933)
* pass dependencies to isEnabled func Signed-off-by: Joshua Li <joshuali925@gmail.com> * add lazy and memo to search bar extensions Signed-off-by: Joshua Li <joshuali925@gmail.com> * move ppl specific string out from query assist Signed-off-by: Joshua Li <joshuali925@gmail.com> * prevent setstate after hook unmounts Signed-off-by: Joshua Li <joshuali925@gmail.com> * add max-height to search bar extensions Signed-off-by: Joshua Li <joshuali925@gmail.com> * prevent setstate after component unmounts Signed-off-by: Joshua Li <joshuali925@gmail.com> * move ml-commons API to common/index.ts Signed-off-by: Joshua Li <joshuali925@gmail.com> * improve i18n and accessibility usages Signed-off-by: Joshua Li <joshuali925@gmail.com> * add hard-coded suggestions for sample data indices Signed-off-by: Joshua Li <joshuali925@gmail.com> --------- Signed-off-by: Joshua Li <joshuali925@gmail.com> (cherry picked from commit 4aade0f)
1 parent b6d54a5 commit b0053b9

File tree

8 files changed

+100
-47
lines changed

8 files changed

+100
-47
lines changed

public/plugin.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class QueryEnhancementsPlugin
5555
initialTo: moment().add(2, 'days').toISOString(),
5656
},
5757
showFilterBar: false,
58-
extensions: [createQueryAssistExtension(core.http)],
58+
extensions: [createQueryAssistExtension(core.http, 'PPL')],
5959
},
6060
fields: {
6161
filterable: false,

public/query_assist/components/call_outs.tsx

+25-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { EuiCallOut, EuiCallOutProps } from '@elastic/eui';
2+
import { i18n } from '@osd/i18n';
23
import React from 'react';
34

4-
type CalloutDismiss = Required<Pick<EuiCallOutProps, 'onDismiss'>>;
5-
interface QueryAssistCallOutProps extends CalloutDismiss {
5+
interface QueryAssistCallOutProps extends Required<Pick<EuiCallOutProps, 'onDismiss'>> {
6+
language: string;
67
type: QueryAssistCallOutType;
78
}
89

@@ -14,10 +15,12 @@ export type QueryAssistCallOutType =
1415
| 'empty_index'
1516
| 'query_generated';
1617

17-
const EmptyIndexCallOut: React.FC<CalloutDismiss> = (props) => (
18+
const EmptyIndexCallOut: React.FC<QueryAssistCallOutProps> = (props) => (
1819
<EuiCallOut
1920
data-test-subj="query-assist-empty-index-callout"
20-
title="Select a data source or index to ask a question."
21+
title={i18n.translate('queryAssist.callOut.emptyIndex.title', {
22+
defaultMessage: 'Select a data source or index to ask a question.',
23+
})}
2124
size="s"
2225
color="warning"
2326
iconType="iInCircle"
@@ -26,10 +29,12 @@ const EmptyIndexCallOut: React.FC<CalloutDismiss> = (props) => (
2629
/>
2730
);
2831

29-
const ProhibitedQueryCallOut: React.FC<CalloutDismiss> = (props) => (
32+
const ProhibitedQueryCallOut: React.FC<QueryAssistCallOutProps> = (props) => (
3033
<EuiCallOut
3134
data-test-subj="query-assist-guard-callout"
32-
title="I am unable to respond to this query. Try another question."
35+
title={i18n.translate('queryAssist.callOut.prohibitedQuery.title', {
36+
defaultMessage: 'I am unable to respond to this query. Try another question.',
37+
})}
3338
size="s"
3439
color="danger"
3540
iconType="alert"
@@ -38,10 +43,13 @@ const ProhibitedQueryCallOut: React.FC<CalloutDismiss> = (props) => (
3843
/>
3944
);
4045

41-
const EmptyQueryCallOut: React.FC<CalloutDismiss> = (props) => (
46+
const EmptyQueryCallOut: React.FC<QueryAssistCallOutProps> = (props) => (
4247
<EuiCallOut
4348
data-test-subj="query-assist-empty-query-callout"
44-
title="Enter a natural language question to automatically generate a query to view results."
49+
title={i18n.translate('queryAssist.callOut.emptyQuery.title', {
50+
defaultMessage:
51+
'Enter a natural language question to automatically generate a query to view results.',
52+
})}
4553
size="s"
4654
color="warning"
4755
iconType="iInCircle"
@@ -50,10 +58,12 @@ const EmptyQueryCallOut: React.FC<CalloutDismiss> = (props) => (
5058
/>
5159
);
5260

53-
const PPLGeneratedCallOut: React.FC<CalloutDismiss> = (props) => (
61+
const QueryGeneratedCallOut: React.FC<QueryAssistCallOutProps> = (props) => (
5462
<EuiCallOut
55-
data-test-subj="query-assist-ppl-callout"
56-
title="PPL query generated"
63+
data-test-subj="query-assist-query-generated-callout"
64+
title={`${props.language} ${i18n.translate('queryAssist.callOut.queryGenerated.title', {
65+
defaultMessage: `query generated. If there are any issues with the response, try adding more context to the question or a new question to submit.`,
66+
})}`}
5767
size="s"
5868
color="success"
5969
iconType="check"
@@ -65,13 +75,13 @@ const PPLGeneratedCallOut: React.FC<CalloutDismiss> = (props) => (
6575
export const QueryAssistCallOut: React.FC<QueryAssistCallOutProps> = (props) => {
6676
switch (props.type) {
6777
case 'empty_query':
68-
return <EmptyQueryCallOut onDismiss={props.onDismiss} />;
78+
return <EmptyQueryCallOut {...props} />;
6979
case 'empty_index':
70-
return <EmptyIndexCallOut onDismiss={props.onDismiss} />;
80+
return <EmptyIndexCallOut {...props} />;
7181
case 'invalid_query':
72-
return <ProhibitedQueryCallOut onDismiss={props.onDismiss} />;
82+
return <ProhibitedQueryCallOut {...props} />;
7383
case 'query_generated':
74-
return <PPLGeneratedCallOut onDismiss={props.onDismiss} />;
84+
return <QueryGeneratedCallOut {...props} />;
7585
default:
7686
break;
7787
}

public/query_assist/components/query_assist_bar.tsx

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow } from '@elastic/eui';
2-
import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react';
2+
import React, { SyntheticEvent, useMemo, useRef, useState } from 'react';
33
import { IDataPluginServices, PersistedLog } from '../../../../../src/plugins/data/public';
44
import { SearchBarExtensionDependencies } from '../../../../../src/plugins/data/public/ui/search_bar_extensions/search_bar_extension';
55
import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public';
@@ -11,6 +11,7 @@ import { QueryAssistInput } from './query_assist_input';
1111
import { QueryAssistSubmitButton } from './submit_button';
1212

1313
interface QueryAssistInputProps {
14+
language: string;
1415
dependencies: SearchBarExtensionDependencies;
1516
}
1617

@@ -25,17 +26,12 @@ export const QueryAssistBar: React.FC<QueryAssistInputProps> = (props) => {
2526
const { generateQuery, loading } = useGenerateQuery();
2627
const [callOutType, setCallOutType] = useState<QueryAssistCallOutType>();
2728
const dismissCallout = () => setCallOutType(undefined);
28-
const mounted = useRef(false);
29-
const selectedIndex = props.dependencies.indexPatterns?.at(0)?.title;
29+
const selectedIndexPattern = props.dependencies.indexPatterns?.at(0);
30+
const selectedIndex =
31+
selectedIndexPattern &&
32+
(typeof selectedIndexPattern === 'string' ? selectedIndexPattern : selectedIndexPattern.title);
3033
const previousQuestionRef = useRef<string>();
3134

32-
useEffect(() => {
33-
mounted.current = true;
34-
return () => {
35-
mounted.current = false;
36-
};
37-
}, []);
38-
3935
const onSubmit = async (e: SyntheticEvent) => {
4036
e.preventDefault();
4137
if (!inputRef.current?.value) {
@@ -52,10 +48,9 @@ export const QueryAssistBar: React.FC<QueryAssistInputProps> = (props) => {
5248
const params = {
5349
question: inputRef.current.value,
5450
index: selectedIndex,
55-
language: 'PPL',
51+
language: props.language,
5652
};
5753
const { response, error } = await generateQuery(params);
58-
if (!mounted.current) return;
5954
if (error) {
6055
if (error instanceof ProhibitedQueryError) {
6156
setCallOutType('invalid_query');
@@ -89,7 +84,7 @@ export const QueryAssistBar: React.FC<QueryAssistInputProps> = (props) => {
8984
</EuiFlexItem>
9085
</EuiFlexGroup>
9186
</EuiFormRow>
92-
<QueryAssistCallOut type={callOutType} onDismiss={dismissCallout} />
87+
<QueryAssistCallOut language={props.language} type={callOutType} onDismiss={dismissCallout} />
9388
</EuiForm>
9489
);
9590
};

public/query_assist/components/query_assist_input.tsx

+37-6
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,49 @@ export const QueryAssistInput: React.FC<QueryAssistInputProps> = (props) => {
2020
const [suggestionIndex, setSuggestionIndex] = useState<number | null>(null);
2121
const [value, setValue] = useState(props.initialValue ?? '');
2222

23-
const recentSearchSuggestions = useMemo(() => {
23+
const sampleDataSuggestions = useMemo(() => {
24+
switch (props.selectedIndex) {
25+
case 'opensearch_dashboards_sample_data_ecommerce':
26+
return [
27+
'How many unique customers placed orders this week?',
28+
'Count the number of orders grouped by manufacturer and category',
29+
'find customers with first names like Eddie',
30+
];
31+
32+
case 'opensearch_dashboards_sample_data_logs':
33+
return [
34+
'Are there any errors in my logs?',
35+
'How many requests were there grouped by response code last week?',
36+
"What's the average request size by week?",
37+
];
38+
39+
case 'opensearch_dashboards_sample_data_flights':
40+
return [
41+
'how many flights were there this week grouped by destination country?',
42+
'what were the longest flight delays this week?',
43+
'what carriers have the furthest flights?',
44+
];
45+
46+
default:
47+
return [];
48+
}
49+
}, [props.selectedIndex]);
50+
51+
const suggestions = useMemo(() => {
2452
if (!props.persistedLog) return [];
2553
return props.persistedLog
2654
.get()
27-
.filter((recentSearch) => recentSearch.includes(value))
28-
.map((recentSearch) => ({
55+
.concat(sampleDataSuggestions)
56+
.filter(
57+
(suggestion, i, array) => array.indexOf(suggestion) === i && suggestion.includes(value)
58+
)
59+
.map((suggestion) => ({
2960
type: QuerySuggestionTypes.RecentSearch,
30-
text: recentSearch,
61+
text: suggestion,
3162
start: 0,
3263
end: value.length,
3364
}));
34-
}, [props.persistedLog, value]);
65+
}, [props.persistedLog, value, sampleDataSuggestions]);
3566

3667
return (
3768
<EuiOutsideClickDetector onOutsideClick={() => setIsSuggestionsVisible(false)}>
@@ -54,7 +85,7 @@ export const QueryAssistInput: React.FC<QueryAssistInputProps> = (props) => {
5485
<EuiPortal>
5586
<SuggestionsComponent
5687
show={isSuggestionsVisible}
57-
suggestions={recentSearchSuggestions}
88+
suggestions={suggestions}
5889
index={suggestionIndex}
5990
onClick={(suggestion) => {
6091
if (!props.inputRef.current) return;

public/query_assist/components/submit_button.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const QueryAssistSubmitButton: React.FC<SubmitButtonProps> = (props) => {
1313
isDisabled={props.isDisabled}
1414
size="s"
1515
type="submit"
16-
aria-label="submit-question"
16+
aria-label="Submit question to query assistant"
1717
/>
1818
);
1919
};

public/query_assist/hooks/use_generate.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,26 @@ import { QueryAssistParameters, QueryAssistResponse } from '../../../common/quer
55
import { formatError } from '../utils';
66

77
export const useGenerateQuery = () => {
8+
const mounted = useRef(false);
89
const [loading, setLoading] = useState(false);
910
const abortControllerRef = useRef<AbortController>();
1011
const { services } = useOpenSearchDashboards<IDataPluginServices>();
1112

12-
useEffect(() => () => abortControllerRef.current?.abort(), []);
13+
useEffect(() => {
14+
mounted.current = true;
15+
return () => {
16+
mounted.current = false;
17+
if (abortControllerRef.current) {
18+
abortControllerRef.current.abort();
19+
abortControllerRef.current = undefined;
20+
}
21+
};
22+
}, []);
1323

1424
const generateQuery = async (
1525
params: QueryAssistParameters
1626
): Promise<{ response?: QueryAssistResponse; error?: Error }> => {
27+
abortControllerRef.current?.abort();
1728
abortControllerRef.current = new AbortController();
1829
setLoading(true);
1930
try {
@@ -24,12 +35,13 @@ export const useGenerateQuery = () => {
2435
signal: abortControllerRef.current?.signal,
2536
}
2637
);
27-
return { response };
38+
if (mounted.current) return { response };
2839
} catch (error) {
29-
return { error: formatError(error) };
40+
if (mounted.current) return { error: formatError(error) };
3041
} finally {
31-
setLoading(false);
42+
if (mounted.current) setLoading(false);
3243
}
44+
return {};
3345
};
3446

3547
return { generateQuery, loading, abortControllerRef };

public/query_assist/utils/create_extension.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ import { HttpSetup } from 'opensearch-dashboards/public';
33
import { QueryAssistBar } from '../components';
44
import { SearchBarExtensionConfig } from '../../../../../src/plugins/data/public/ui/search_bar_extensions';
55

6-
export const createQueryAssistExtension = (http: HttpSetup): SearchBarExtensionConfig => {
6+
export const createQueryAssistExtension = (
7+
http: HttpSetup,
8+
language: string
9+
): SearchBarExtensionConfig => {
710
return {
8-
id: 'query-assist-ppl',
11+
id: 'query-assist',
912
order: 1000,
1013
isEnabled: (() => {
1114
let agentConfigured: boolean;
1215
return async () => {
1316
if (agentConfigured === undefined) {
1417
agentConfigured = await http
15-
.get<{ configured: boolean }>('/api/ql/query_assist/configured/PPL')
18+
.get<{ configured: boolean }>(`/api/ql/query_assist/configured/${language}`)
1619
.then((response) => response.configured)
1720
.catch(() => false);
1821
}
1922
return agentConfigured;
2023
};
2124
})(),
22-
getComponent: (dependencies) => <QueryAssistBar dependencies={dependencies} />,
25+
getComponent: (dependencies) => (
26+
<QueryAssistBar language={language} dependencies={dependencies} />
27+
),
2328
};
2429
};

server/routes/query_assist/agents.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { ApiResponse } from '@opensearch-project/opensearch';
22
import { RequestBody, TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport';
33
import { RequestHandlerContext } from 'src/core/server';
4+
import { URI } from '../../../common';
45

5-
const ML_COMMONS_API_PREFIX = '/_plugins/_ml';
66
const AGENT_REQUEST_OPTIONS = {
77
/**
88
* It is time-consuming for LLM to generate final answer
@@ -30,7 +30,7 @@ export const getAgentIdByConfig = async (
3030
try {
3131
const response = (await client.transport.request({
3232
method: 'GET',
33-
path: `${ML_COMMONS_API_PREFIX}/config/${configName}`,
33+
path: `${URI.ML}/config/${configName}`,
3434
})) as ApiResponse<{ type: string; configuration: { agent_id?: string } }>;
3535

3636
if (!response || response.body.configuration.agent_id === undefined) {
@@ -54,7 +54,7 @@ export const requestAgentByConfig = async (options: {
5454
return client.transport.request(
5555
{
5656
method: 'POST',
57-
path: `${ML_COMMONS_API_PREFIX}/agents/${agentId}/_execute`,
57+
path: `${URI.ML}/agents/${agentId}/_execute`,
5858
body,
5959
},
6060
AGENT_REQUEST_OPTIONS

0 commit comments

Comments
 (0)