From fe0c5fcccaeddedeec1c50196de217e1da5baf3c Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 13:56:03 -0700 Subject: [PATCH 1/9] pass dependencies to isEnabled func Signed-off-by: Joshua Li --- .../public/ui/search_bar_extensions/search_bar_extension.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx index 88a3fcdfbb08..d2a97332291c 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx @@ -34,7 +34,7 @@ export interface SearchBarExtensionConfig { * A function that determines if the search bar extension is enabled and should be rendered on UI. * @returns whether the extension is enabled. */ - isEnabled: () => Promise; + isEnabled: (dependencies: SearchBarExtensionDependencies) => Promise; /** * A function that returns the mount point for the search bar extension. * @param dependencies - The dependencies required for the extension. @@ -52,7 +52,7 @@ export const SearchBarExtension: React.FC = (props) => ]); useEffect(() => { - props.config.isEnabled().then(setIsEnabled); + props.config.isEnabled(props.dependencies).then(setIsEnabled); }, [props.dependencies, props.config]); if (!isEnabled) return null; From c0d3bddc566ad2f3629a7f384858aec80c065c12 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 14:03:01 -0700 Subject: [PATCH 2/9] add lazy and memo to search bar extensions Signed-off-by: Joshua Li --- .../ui/query_editor/query_editor_top_row.tsx | 4 +- .../public/ui/search_bar_extensions/index.ts | 7 ---- .../public/ui/search_bar_extensions/index.tsx | 17 ++++++++ .../search_bar_extension.test.tsx | 22 ++-------- .../search_bar_extensions.test.tsx | 42 +++++++++---------- .../search_bar_extensions.tsx | 32 +++++++------- 6 files changed, 61 insertions(+), 63 deletions(-) delete mode 100644 src/plugins/data/public/ui/search_bar_extensions/index.ts create mode 100644 src/plugins/data/public/ui/search_bar_extensions/index.tsx diff --git a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx index 268a2986e176..56047a3de0d3 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx @@ -251,8 +251,8 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { return ( ); } diff --git a/src/plugins/data/public/ui/search_bar_extensions/index.ts b/src/plugins/data/public/ui/search_bar_extensions/index.ts deleted file mode 100644 index d14971c671e3..000000000000 --- a/src/plugins/data/public/ui/search_bar_extensions/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { SearchBarExtensionConfig } from './search_bar_extension'; -export { SearchBarExtensions } from './search_bar_extensions'; diff --git a/src/plugins/data/public/ui/search_bar_extensions/index.tsx b/src/plugins/data/public/ui/search_bar_extensions/index.tsx new file mode 100644 index 000000000000..ab790aa655c9 --- /dev/null +++ b/src/plugins/data/public/ui/search_bar_extensions/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { ComponentProps } from 'react'; + +const Fallback = () =>
; + +const LazySearchBarExtensions = React.lazy(() => import('./search_bar_extensions')); +export const SearchBarExtensions = (props: ComponentProps) => ( + }> + + +); + +export { SearchBarExtensionConfig } from './search_bar_extension'; diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.test.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.test.tsx index 5abbe0200f27..194b92ca9bfa 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.test.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.test.tsx @@ -8,10 +8,9 @@ import React, { ComponentProps } from 'react'; import { IIndexPattern } from '../../../common'; import { SearchBarExtension } from './search_bar_extension'; -jest.mock('@elastic/eui', () => ({ - ...jest.requireActual('@elastic/eui'), - EuiPortal: jest.fn(({ children }) =>
{children}
), - EuiErrorBoundary: jest.fn(({ children }) =>
{children}
), +jest.mock('react-dom', () => ({ + ...jest.requireActual('react-dom'), + createPortal: jest.fn((element) => element), })); type SearchBarExtensionProps = ComponentProps; @@ -45,7 +44,7 @@ describe('SearchBarExtension', () => { dependencies: { indexPatterns: [mockIndexPattern], }, - portalInsert: { sibling: document.createElement('div'), position: 'after' }, + portalContainer: document.createElement('div'), }; beforeEach(() => { @@ -78,17 +77,4 @@ describe('SearchBarExtension', () => { expect(isEnabledMock).toHaveBeenCalled(); }); - - it('calls isEnabled and getComponent correctly', async () => { - isEnabledMock.mockResolvedValue(true); - getComponentMock.mockReturnValue(
Test Component
); - - render(); - - await waitFor(() => { - expect(isEnabledMock).toHaveBeenCalled(); - }); - - expect(getComponentMock).toHaveBeenCalledWith(defaultProps.dependencies); - }); }); diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.test.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.test.tsx index 52b3b87dc419..2c11db0a56f8 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.test.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.test.tsx @@ -6,7 +6,7 @@ import { render, waitFor } from '@testing-library/react'; import React, { ComponentProps } from 'react'; import { SearchBarExtension } from './search_bar_extension'; -import { SearchBarExtensions } from './search_bar_extensions'; +import SearchBarExtensions from './search_bar_extensions'; type SearchBarExtensionProps = ComponentProps; type SearchBarExtensionsProps = ComponentProps; @@ -15,32 +15,30 @@ jest.mock('./search_bar_extension', () => ({ SearchBarExtension: jest.fn(({ config, dependencies }: SearchBarExtensionProps) => (
Mocked SearchBarExtension {config.id} with{' '} - {dependencies.indexPatterns?.map((i) => i.title).join(', ')} + {dependencies.indexPatterns?.map((i) => (typeof i === 'string' ? i : i.title)).join(', ')}
)), })); describe('SearchBarExtensions', () => { const defaultProps: SearchBarExtensionsProps = { - dependencies: { - indexPatterns: [ - { - id: '1234', - title: 'logstash-*', - fields: [ - { - name: 'response', - type: 'number', - esTypes: ['integer'], - aggregatable: true, - filterable: true, - searchable: true, - }, - ], - }, - ], - }, - portalInsert: { sibling: document.createElement('div'), position: 'after' }, + indexPatterns: [ + { + id: '1234', + title: 'logstash-*', + fields: [ + { + name: 'response', + type: 'number', + esTypes: ['integer'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], + }, + ], + portalContainer: document.createElement('div'), }; beforeEach(() => { @@ -89,7 +87,7 @@ describe('SearchBarExtensions', () => { expect(SearchBarExtension).toHaveBeenCalledWith( expect.objectContaining({ - dependencies: defaultProps.dependencies, + dependencies: { indexPatterns: defaultProps.indexPatterns }, }), expect.anything() ); diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx index cef34e46fa28..c18563ec0d4d 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiPortalProps } from '@elastic/eui'; import React, { useMemo } from 'react'; import { SearchBarExtension, @@ -11,37 +10,42 @@ import { SearchBarExtensionDependencies, } from './search_bar_extension'; -interface SearchBarExtensionsProps { +interface SearchBarExtensionsProps extends SearchBarExtensionDependencies { configs?: SearchBarExtensionConfig[]; - dependencies: SearchBarExtensionDependencies; - portalInsert: EuiPortalProps['insert']; + portalSibling: NonNullable['sibling']; } -export const SearchBarExtensions: React.FC = (props) => { - const configs = useMemo(() => { - if (!props.configs) return []; +const SearchBarExtensions: React.FC = React.memo((props) => { + const { configs, portalSibling, ...dependencies } = props; + + const sortedConfigs = useMemo(() => { + if (!configs) return []; const seenIds = new Set(); - props.configs.forEach((config) => { + configs.forEach((config) => { if (seenIds.has(config.id)) { throw new Error(`Duplicate search bar extension id '${config.id}' found.`); } seenIds.add(config.id); }); - return [...props.configs].sort((a, b) => a.order - b.order); - }, [props.configs]); + return [...configs].sort((a, b) => a.order - b.order); + }, [configs]); return ( <> - {configs.map((config) => ( + {sortedConfigs.map((config) => ( ))} ); -}; +}); + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default SearchBarExtensions; From 1bcc4858979a9da17cfa0e21c9c3af4243698c13 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 14:20:59 -0700 Subject: [PATCH 3/9] move ppl specific string out from query assist Signed-off-by: Joshua Li --- .../query_enhancements/public/plugin.tsx | 2 +- .../query_assist/components/call_outs.tsx | 24 +++++++++---------- .../components/query_assist_bar.tsx | 21 +++++++--------- .../query_assist/utils/create_extension.tsx | 13 ++++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins-extra/query_enhancements/public/plugin.tsx b/plugins-extra/query_enhancements/public/plugin.tsx index 256142023c38..5dba1c768926 100644 --- a/plugins-extra/query_enhancements/public/plugin.tsx +++ b/plugins-extra/query_enhancements/public/plugin.tsx @@ -52,7 +52,7 @@ export class QueryEnhancementsPlugin initialTo: moment().add(2, 'days').toISOString(), }, showFilterBar: false, - extensions: [createQueryAssistExtension(core.http)], + extensions: [createQueryAssistExtension(core.http, 'PPL')], }, fields: { visualizable: false, diff --git a/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx b/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx index f98569f42572..21d931c213c2 100644 --- a/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx +++ b/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx @@ -1,8 +1,8 @@ import { EuiCallOut, EuiCallOutProps } from '@elastic/eui'; import React from 'react'; -type CalloutDismiss = Required>; -interface QueryAssistCallOutProps extends CalloutDismiss { +interface QueryAssistCallOutProps extends Required> { + language: string; type: QueryAssistCallOutType; } @@ -14,7 +14,7 @@ export type QueryAssistCallOutType = | 'empty_index' | 'query_generated'; -const EmptyIndexCallOut: React.FC = (props) => ( +const EmptyIndexCallOut: React.FC = (props) => ( = (props) => ( /> ); -const ProhibitedQueryCallOut: React.FC = (props) => ( +const ProhibitedQueryCallOut: React.FC = (props) => ( = (props) => ( /> ); -const EmptyQueryCallOut: React.FC = (props) => ( +const EmptyQueryCallOut: React.FC = (props) => ( = (props) => ( /> ); -const PPLGeneratedCallOut: React.FC = (props) => ( +const QueryGeneratedCallOut: React.FC = (props) => ( = (props) => ( export const QueryAssistCallOut: React.FC = (props) => { switch (props.type) { case 'empty_query': - return ; + return ; case 'empty_index': - return ; + return ; case 'invalid_query': - return ; + return ; case 'query_generated': - return ; + return ; default: break; } diff --git a/plugins-extra/query_enhancements/public/query_assist/components/query_assist_bar.tsx b/plugins-extra/query_enhancements/public/query_assist/components/query_assist_bar.tsx index c80fe5d09377..904382c1899e 100644 --- a/plugins-extra/query_enhancements/public/query_assist/components/query_assist_bar.tsx +++ b/plugins-extra/query_enhancements/public/query_assist/components/query_assist_bar.tsx @@ -1,5 +1,5 @@ import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow } from '@elastic/eui'; -import React, { SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react'; +import React, { SyntheticEvent, useMemo, useRef, useState } from 'react'; import { IDataPluginServices, PersistedLog } from '../../../../../src/plugins/data/public'; import { SearchBarExtensionDependencies } from '../../../../../src/plugins/data/public/ui/search_bar_extensions/search_bar_extension'; import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public'; @@ -11,6 +11,7 @@ import { QueryAssistInput } from './query_assist_input'; import { QueryAssistSubmitButton } from './submit_button'; interface QueryAssistInputProps { + language: string; dependencies: SearchBarExtensionDependencies; } @@ -25,17 +26,12 @@ export const QueryAssistBar: React.FC = (props) => { const { generateQuery, loading } = useGenerateQuery(); const [callOutType, setCallOutType] = useState(); const dismissCallout = () => setCallOutType(undefined); - const mounted = useRef(false); - const selectedIndex = props.dependencies.indexPatterns?.at(0)?.title; + const selectedIndexPattern = props.dependencies.indexPatterns?.at(0); + const selectedIndex = + selectedIndexPattern && + (typeof selectedIndexPattern === 'string' ? selectedIndexPattern : selectedIndexPattern.title); const previousQuestionRef = useRef(); - useEffect(() => { - mounted.current = true; - return () => { - mounted.current = false; - }; - }, []); - const onSubmit = async (e: SyntheticEvent) => { e.preventDefault(); if (!inputRef.current?.value) { @@ -52,10 +48,9 @@ export const QueryAssistBar: React.FC = (props) => { const params = { question: inputRef.current.value, index: selectedIndex, - language: 'PPL', + language: props.language, }; const { response, error } = await generateQuery(params); - if (!mounted.current) return; if (error) { if (error instanceof ProhibitedQueryError) { setCallOutType('invalid_query'); @@ -89,7 +84,7 @@ export const QueryAssistBar: React.FC = (props) => { - + ); }; diff --git a/plugins-extra/query_enhancements/public/query_assist/utils/create_extension.tsx b/plugins-extra/query_enhancements/public/query_assist/utils/create_extension.tsx index f2f046d11f61..ef3bcb7fc335 100644 --- a/plugins-extra/query_enhancements/public/query_assist/utils/create_extension.tsx +++ b/plugins-extra/query_enhancements/public/query_assist/utils/create_extension.tsx @@ -3,22 +3,27 @@ import { HttpSetup } from 'opensearch-dashboards/public'; import { QueryAssistBar } from '../components'; import { SearchBarExtensionConfig } from '../../../../../src/plugins/data/public/ui/search_bar_extensions'; -export const createQueryAssistExtension = (http: HttpSetup): SearchBarExtensionConfig => { +export const createQueryAssistExtension = ( + http: HttpSetup, + language: string +): SearchBarExtensionConfig => { return { - id: 'query-assist-ppl', + id: 'query-assist', order: 1000, isEnabled: (() => { let agentConfigured: boolean; return async () => { if (agentConfigured === undefined) { agentConfigured = await http - .get<{ configured: boolean }>('/api/ql/query_assist/configured/PPL') + .get<{ configured: boolean }>(`/api/ql/query_assist/configured/${language}`) .then((response) => response.configured) .catch(() => false); } return agentConfigured; }; })(), - getComponent: (dependencies) => , + getComponent: (dependencies) => ( + + ), }; }; From 17e698f45327756f3b09bc371db7aa0550b0dfb1 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 14:21:47 -0700 Subject: [PATCH 4/9] prevent setstate after hook unmounts Signed-off-by: Joshua Li --- .../public/query_assist/hooks/use_generate.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/plugins-extra/query_enhancements/public/query_assist/hooks/use_generate.ts b/plugins-extra/query_enhancements/public/query_assist/hooks/use_generate.ts index 0d23114fbd34..5365a7ca18f9 100644 --- a/plugins-extra/query_enhancements/public/query_assist/hooks/use_generate.ts +++ b/plugins-extra/query_enhancements/public/query_assist/hooks/use_generate.ts @@ -5,15 +5,26 @@ import { QueryAssistParameters, QueryAssistResponse } from '../../../common/quer import { formatError } from '../utils'; export const useGenerateQuery = () => { + const mounted = useRef(false); const [loading, setLoading] = useState(false); const abortControllerRef = useRef(); const { services } = useOpenSearchDashboards(); - useEffect(() => () => abortControllerRef.current?.abort(), []); + useEffect(() => { + mounted.current = true; + return () => { + mounted.current = false; + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = undefined; + } + }; + }, []); const generateQuery = async ( params: QueryAssistParameters ): Promise<{ response?: QueryAssistResponse; error?: Error }> => { + abortControllerRef.current?.abort(); abortControllerRef.current = new AbortController(); setLoading(true); try { @@ -24,12 +35,13 @@ export const useGenerateQuery = () => { signal: abortControllerRef.current?.signal, } ); - return { response }; + if (mounted.current) return { response }; } catch (error) { - return { error: formatError(error) }; + if (mounted.current) return { error: formatError(error) }; } finally { - setLoading(false); + if (mounted.current) setLoading(false); } + return {}; }; return { generateQuery, loading, abortControllerRef }; From d56cc8882ac906c88fa6b7b6f5d6f76633683464 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 14:44:06 -0700 Subject: [PATCH 5/9] add max-height to search bar extensions Signed-off-by: Joshua Li --- .../data/public/ui/query_editor/_query_editor.scss | 7 +++++++ .../data/public/ui/query_editor/query_editor.tsx | 8 +++++++- .../public/ui/query_editor/query_editor_top_row.tsx | 2 +- .../search_bar_extensions/search_bar_extension.tsx | 13 ++++++------- .../search_bar_extensions/search_bar_extensions.tsx | 6 +++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/plugins/data/public/ui/query_editor/_query_editor.scss b/src/plugins/data/public/ui/query_editor/_query_editor.scss index 96d1d5c04264..1103762e0593 100644 --- a/src/plugins/data/public/ui/query_editor/_query_editor.scss +++ b/src/plugins/data/public/ui/query_editor/_query_editor.scss @@ -8,6 +8,13 @@ width: 500px; } +.osdQueryEditorHeader { + max-height: 400px; + + // TODO fix styling: with "overflow: auto" the scroll bar appears although the content is below max-height + // overflow: auto; +} + @include euiBreakpoint("xs", "s") { .osdQueryEditor--withDatePicker { > :first-child { diff --git a/src/plugins/data/public/ui/query_editor/query_editor.tsx b/src/plugins/data/public/ui/query_editor/query_editor.tsx index 88211841f335..dc428cb3fe78 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -55,6 +55,7 @@ export interface QueryEditorProps { className?: string; isInvalid?: boolean; queryEditorHeaderRef: React.RefObject; + queryEditorHeaderClassName?: string; } interface Props extends QueryEditorProps { @@ -492,6 +493,11 @@ export default class QueryEditorUI extends Component { // ); const className = classNames(this.props.className); + const queryEditorHeaderClassName = classNames( + 'osdQueryEditorHeader', + this.props.queryEditorHeaderClassName + ); + const queryLanguageSwitcher = ( { -
+
); diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx index d2a97332291c..f7649ba0d84e 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx @@ -3,15 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiErrorBoundary, EuiPortal } from '@elastic/eui'; -import { EuiPortalProps } from '@opensearch-project/oui'; +import { EuiErrorBoundary } from '@elastic/eui'; import React, { useEffect, useMemo, useState } from 'react'; +import ReactDOM from 'react-dom'; import { IIndexPattern } from '../../../common'; interface SearchBarExtensionProps { config: SearchBarExtensionConfig; dependencies: SearchBarExtensionDependencies; - portalInsert: EuiPortalProps['insert']; + portalContainer: Element; } export interface SearchBarExtensionDependencies { @@ -57,9 +57,8 @@ export const SearchBarExtension: React.FC = (props) => if (!isEnabled) return null; - return ( - - {component} - + return ReactDOM.createPortal( + {component}, + props.portalContainer ); }; diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx index c18563ec0d4d..2ae444c334d1 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx @@ -12,11 +12,11 @@ import { interface SearchBarExtensionsProps extends SearchBarExtensionDependencies { configs?: SearchBarExtensionConfig[]; - portalSibling: NonNullable['sibling']; + portalContainer: Element; } const SearchBarExtensions: React.FC = React.memo((props) => { - const { configs, portalSibling, ...dependencies } = props; + const { configs, portalContainer, ...dependencies } = props; const sortedConfigs = useMemo(() => { if (!configs) return []; @@ -39,7 +39,7 @@ const SearchBarExtensions: React.FC = React.memo((prop key={config.id} config={config} dependencies={dependencies} - portalInsert={{ sibling: portalSibling, position: 'before' }} + portalContainer={portalContainer} /> ))} From 744afa227a5e44e0964195bafc2818424eb457d9 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 15:21:47 -0700 Subject: [PATCH 6/9] prevent setstate after component unmounts Signed-off-by: Joshua Li --- .../search_bar_extensions/search_bar_extension.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx index f7649ba0d84e..505846c66b08 100644 --- a/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx +++ b/src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx @@ -4,7 +4,7 @@ */ import { EuiErrorBoundary } from '@elastic/eui'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; import { IIndexPattern } from '../../../common'; @@ -45,6 +45,7 @@ export interface SearchBarExtensionConfig { export const SearchBarExtension: React.FC = (props) => { const [isEnabled, setIsEnabled] = useState(false); + const isMounted = useRef(true); const component = useMemo(() => props.config.getComponent(props.dependencies), [ props.config, @@ -52,7 +53,16 @@ export const SearchBarExtension: React.FC = (props) => ]); useEffect(() => { - props.config.isEnabled(props.dependencies).then(setIsEnabled); + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + useEffect(() => { + props.config.isEnabled(props.dependencies).then((enabled) => { + if (isMounted.current) setIsEnabled(enabled); + }); }, [props.dependencies, props.config]); if (!isEnabled) return null; From c671c084be71ab355b116afd38868ed6fe458a89 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 15:06:39 -0700 Subject: [PATCH 7/9] move ml-commons API to common/index.ts Signed-off-by: Joshua Li --- plugins-extra/query_enhancements/common/index.ts | 2 ++ .../query_enhancements/server/routes/query_assist/agents.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins-extra/query_enhancements/common/index.ts b/plugins-extra/query_enhancements/common/index.ts index 1d434bcc74d8..a191a2e35d45 100644 --- a/plugins-extra/query_enhancements/common/index.ts +++ b/plugins-extra/query_enhancements/common/index.ts @@ -18,4 +18,6 @@ export const OPENSEARCH_DATACONNECTIONS_API = { export const JOBS_ENDPOINT_BASE = '/_plugins/_async_query'; +export const BASE_ML_COMMONS_URI = '/_plugins/_ml'; + export * from './utils'; diff --git a/plugins-extra/query_enhancements/server/routes/query_assist/agents.ts b/plugins-extra/query_enhancements/server/routes/query_assist/agents.ts index 676ea01bab5c..f0a8dcfe382f 100644 --- a/plugins-extra/query_enhancements/server/routes/query_assist/agents.ts +++ b/plugins-extra/query_enhancements/server/routes/query_assist/agents.ts @@ -1,8 +1,8 @@ import { ApiResponse } from '@opensearch-project/opensearch'; import { RequestBody, TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport'; import { RequestHandlerContext } from 'src/core/server'; +import { BASE_ML_COMMONS_URI } from '../../../common'; -const ML_COMMONS_API_PREFIX = '/_plugins/_ml'; const AGENT_REQUEST_OPTIONS = { /** * It is time-consuming for LLM to generate final answer @@ -30,7 +30,7 @@ export const getAgentIdByConfig = async ( try { const response = (await client.transport.request({ method: 'GET', - path: `${ML_COMMONS_API_PREFIX}/config/${configName}`, + path: `${BASE_ML_COMMONS_URI}/config/${configName}`, })) as ApiResponse<{ type: string; configuration: { agent_id?: string } }>; if (!response || response.body.configuration.agent_id === undefined) { @@ -54,7 +54,7 @@ export const requestAgentByConfig = async (options: { return client.transport.request( { method: 'POST', - path: `${ML_COMMONS_API_PREFIX}/agents/${agentId}/_execute`, + path: `${BASE_ML_COMMONS_URI}/agents/${agentId}/_execute`, body, }, AGENT_REQUEST_OPTIONS From ccf9962bf6088269af9ff035044ec6b9f11b2c8a Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 15:13:17 -0700 Subject: [PATCH 8/9] improve i18n and accessibility usages Signed-off-by: Joshua Li --- .../query_assist/components/call_outs.tsx | 18 ++++++++++++++---- .../query_assist/components/submit_button.tsx | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx b/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx index 21d931c213c2..fdbe901a18cc 100644 --- a/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx +++ b/plugins-extra/query_enhancements/public/query_assist/components/call_outs.tsx @@ -1,4 +1,5 @@ import { EuiCallOut, EuiCallOutProps } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; import React from 'react'; interface QueryAssistCallOutProps extends Required> { @@ -17,7 +18,9 @@ export type QueryAssistCallOutType = const EmptyIndexCallOut: React.FC = (props) => ( = (props) => ( const ProhibitedQueryCallOut: React.FC = (props) => ( = (props) => ( const EmptyQueryCallOut: React.FC = (props) => ( = (props) => ( const QueryGeneratedCallOut: React.FC = (props) => ( = (props) => { isDisabled={props.isDisabled} size="s" type="submit" - aria-label="submit-question" + aria-label="Submit question to query assistant" /> ); }; From caee5909cc80dba445c32171d9383abfe789afd3 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Wed, 5 Jun 2024 15:38:07 -0700 Subject: [PATCH 9/9] add hard-coded suggestions for sample data indices Signed-off-by: Joshua Li --- .../components/query_assist_input.tsx | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/plugins-extra/query_enhancements/public/query_assist/components/query_assist_input.tsx b/plugins-extra/query_enhancements/public/query_assist/components/query_assist_input.tsx index 735ff555372a..476e2d1b7f57 100644 --- a/plugins-extra/query_enhancements/public/query_assist/components/query_assist_input.tsx +++ b/plugins-extra/query_enhancements/public/query_assist/components/query_assist_input.tsx @@ -20,18 +20,49 @@ export const QueryAssistInput: React.FC = (props) => { const [suggestionIndex, setSuggestionIndex] = useState(null); const [value, setValue] = useState(props.initialValue ?? ''); - const recentSearchSuggestions = useMemo(() => { + const sampleDataSuggestions = useMemo(() => { + switch (props.selectedIndex) { + case 'opensearch_dashboards_sample_data_ecommerce': + return [ + 'How many unique customers placed orders this week?', + 'Count the number of orders grouped by manufacturer and category', + 'find customers with first names like Eddie', + ]; + + case 'opensearch_dashboards_sample_data_logs': + return [ + 'Are there any errors in my logs?', + 'How many requests were there grouped by response code last week?', + "What's the average request size by week?", + ]; + + case 'opensearch_dashboards_sample_data_flights': + return [ + 'how many flights were there this week grouped by destination country?', + 'what were the longest flight delays this week?', + 'what carriers have the furthest flights?', + ]; + + default: + return []; + } + }, [props.selectedIndex]); + + const suggestions = useMemo(() => { if (!props.persistedLog) return []; return props.persistedLog .get() - .filter((recentSearch) => recentSearch.includes(value)) - .map((recentSearch) => ({ + .concat(sampleDataSuggestions) + .filter( + (suggestion, i, array) => array.indexOf(suggestion) === i && suggestion.includes(value) + ) + .map((suggestion) => ({ type: QuerySuggestionTypes.RecentSearch, - text: recentSearch, + text: suggestion, start: 0, end: value.length, })); - }, [props.persistedLog, value]); + }, [props.persistedLog, value, sampleDataSuggestions]); return ( setIsSuggestionsVisible(false)}> @@ -54,7 +85,7 @@ export const QueryAssistInput: React.FC = (props) => { { if (!props.inputRef.current) return;