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 80ddd42fede5..fd11e76ffaa4 100644 --- a/src/plugins/data/public/ui/query_editor/_query_editor.scss +++ b/src/plugins/data/public/ui/query_editor/_query_editor.scss @@ -15,6 +15,10 @@ // overflow: auto; } +.osdQueryEditorFooterHide { + display: none +} + .osdQueryEditor__languageWrapper { :first-child { box-shadow: none !important; @@ -42,6 +46,7 @@ .osdQueryEditor__dataSetWrapper { .dataExplorerDSSelect { border-bottom: $euiBorderThin !important; + max-width: 375px; div:is([class$="--group"]) { padding: 0 !important; diff --git a/src/plugins/data/public/ui/query_editor/collapsed_query_bar.tsx b/src/plugins/data/public/ui/query_editor/collapsed_query_bar.tsx new file mode 100644 index 000000000000..1d0622f3dc38 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/collapsed_query_bar.tsx @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +import React, { RefObject, createRef, useState } from 'react'; +import { i18n } from '@osd/i18n'; + +import { EuiFieldText, EuiOutsideClickDetector, EuiPortal, Query } from '@elastic/eui'; +import { SuggestionsComponent } from '../typeahead'; + +export interface CollapsedQueryBarInputProps { + initialValue: string; + onChange?: (query: string) => void; +} + +export const CollapsedQueryBarInput: React.FC = (props) => { + const [isSuggestionsVisible, setIsSuggestionsVisible] = useState(false); + const [suggestionIndex, setSuggestionIndex] = useState(null); + const [value, setValue] = useState(props.initialValue ?? ''); + + return ( + setIsSuggestionsVisible(false)}> +
+ setIsSuggestionsVisible(true)} + onChange={(e) => setValue(e.target.value)} + onKeyDown={() => setIsSuggestionsVisible(true)} + placeholder={''} + fullWidth + /> + + { + return; + }} + onMouseEnter={(i) => setSuggestionIndex(i)} + loadMore={() => {}} + size="s" + /> + +
+
+ ); +}; diff --git a/src/plugins/data/public/ui/query_editor/components/query_editor_body.tsx b/src/plugins/data/public/ui/query_editor/components/query_editor_body.tsx new file mode 100644 index 000000000000..efb80834e0f4 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/components/query_editor_body.tsx @@ -0,0 +1,89 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiComboBox, EuiComboBoxOptionOption, PopoverAnchorPosition } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import { getUiService } from '../../services'; + +interface Props { + language: string; + onSelectLanguage: (newLanguage: string) => void; + anchorPosition?: PopoverAnchorPosition; + appName?: string; +} + +const mapExternalLanguageToOptions = (language: string) => { + return { + label: language, + value: language, + }; +}; + +// code editor(PPL or SQL), or nothing(DQL or Lucene), filter bar +export const QueryEditorBody = (props: Props) => { + const dqlLabel = i18n.translate('data.query.queryEditor.dqlLanguageName', { + defaultMessage: 'DQL', + }); + const luceneLabel = i18n.translate('data.query.queryEditor.luceneLanguageName', { + defaultMessage: 'Lucene', + }); + + const languageOptions: EuiComboBoxOptionOption[] = [ + { + label: dqlLabel, + value: 'kuery', + }, + { + label: luceneLabel, + value: 'lucene', + }, + ]; + + const uiService = getUiService(); + + const queryEnhancements = uiService.Settings.getAllQueryEnhancements(); + queryEnhancements.forEach((enhancement) => { + if ( + (enhancement.supportedAppNames && + props.appName && + !enhancement.supportedAppNames.includes(props.appName)) || + uiService.Settings.getUserQueryLanguageBlocklist().includes( + enhancement.language.toLowerCase() + ) + ) + return; + languageOptions.unshift(mapExternalLanguageToOptions(enhancement.language)); + }); + + const selectedLanguage = { + label: + (languageOptions.find( + (option) => (option.value as string).toLowerCase() === props.language.toLowerCase() + )?.label as string) ?? languageOptions[0].label, + }; + + const handleLanguageChange = (newLanguage: EuiComboBoxOptionOption[]) => { + const queryLanguage = newLanguage[0].value as string; + props.onSelectLanguage(queryLanguage); + uiService.Settings.setUserQueryLanguage(queryLanguage); + }; + + uiService.Settings.setUserQueryLanguage(props.language); + + return ( + + ); +}; diff --git a/src/plugins/data/public/ui/query_editor/components/query_editor_footer.tsx b/src/plugins/data/public/ui/query_editor/components/query_editor_footer.tsx new file mode 100644 index 000000000000..e2582204c024 --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/components/query_editor_footer.tsx @@ -0,0 +1,93 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiComboBox, EuiComboBoxOptionOption, PopoverAnchorPosition } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import { getUiService } from '../../services'; + +interface Props { + language: string; + onSelectLanguage: (newLanguage: string) => void; + anchorPosition?: PopoverAnchorPosition; + appName?: string; +} + +const mapExternalLanguageToOptions = (language: string) => { + return { + label: language, + value: language, + }; +}; + +// footer container ref: language selector, line count, timestamp fields, +// errors, feedbacks(ref from query enhancement plugin), shortcuts +// all the above are registerable by language +export const QueryEditorFooter = (props: Props) => { + const dqlLabel = i18n.translate('data.query.queryEditor.dqlLanguageName', { + defaultMessage: 'DQL', + }); + const luceneLabel = i18n.translate('data.query.queryEditor.luceneLanguageName', { + defaultMessage: 'Lucene', + }); + + const languageOptions: EuiComboBoxOptionOption[] = [ + { + label: dqlLabel, + value: 'kuery', + }, + { + label: luceneLabel, + value: 'lucene', + }, + ]; + + const uiService = getUiService(); + + const queryEnhancements = uiService.Settings.getAllQueryEnhancements(); + queryEnhancements.forEach((enhancement) => { + if ( + (enhancement.supportedAppNames && + props.appName && + !enhancement.supportedAppNames.includes(props.appName)) || + uiService.Settings.getUserQueryLanguageBlocklist().includes( + enhancement.language.toLowerCase() + ) + ) + return; + languageOptions.unshift(mapExternalLanguageToOptions(enhancement.language)); + }); + + const selectedLanguage = { + label: + (languageOptions.find( + (option) => (option.value as string).toLowerCase() === props.language.toLowerCase() + )?.label as string) ?? languageOptions[0].label, + }; + + const handleLanguageChange = (newLanguage: EuiComboBoxOptionOption[]) => { + const queryLanguage = newLanguage[0].value as string; + props.onSelectLanguage(queryLanguage); + uiService.Settings.setUserQueryLanguage(queryLanguage); + }; + + uiService.Settings.setUserQueryLanguage(props.language); + + return ( +
+ +
+ ); +}; diff --git a/src/plugins/data/public/ui/query_editor/components/query_editor_header.tsx b/src/plugins/data/public/ui/query_editor/components/query_editor_header.tsx new file mode 100644 index 000000000000..86a0b38e4f0a --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/components/query_editor_header.tsx @@ -0,0 +1,90 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiComboBox, EuiComboBoxOptionOption, PopoverAnchorPosition } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import { getUiService } from '../../services'; + +interface Props { + language: string; + onSelectLanguage: (newLanguage: string) => void; + anchorPosition?: PopoverAnchorPosition; + appName?: string; +} + +const mapExternalLanguageToOptions = (language: string) => { + return { + label: language, + value: language, + }; +}; + +// This is the expansion button, and containerRef(dataset selector), +// language actions, query actions +export const QueryEditorHeader = (props: Props) => { + const dqlLabel = i18n.translate('data.query.queryEditor.dqlLanguageName', { + defaultMessage: 'DQL', + }); + const luceneLabel = i18n.translate('data.query.queryEditor.luceneLanguageName', { + defaultMessage: 'Lucene', + }); + + const languageOptions: EuiComboBoxOptionOption[] = [ + { + label: dqlLabel, + value: 'kuery', + }, + { + label: luceneLabel, + value: 'lucene', + }, + ]; + + const uiService = getUiService(); + + const queryEnhancements = uiService.Settings.getAllQueryEnhancements(); + queryEnhancements.forEach((enhancement) => { + if ( + (enhancement.supportedAppNames && + props.appName && + !enhancement.supportedAppNames.includes(props.appName)) || + uiService.Settings.getUserQueryLanguageBlocklist().includes( + enhancement.language.toLowerCase() + ) + ) + return; + languageOptions.unshift(mapExternalLanguageToOptions(enhancement.language)); + }); + + const selectedLanguage = { + label: + (languageOptions.find( + (option) => (option.value as string).toLowerCase() === props.language.toLowerCase() + )?.label as string) ?? languageOptions[0].label, + }; + + const handleLanguageChange = (newLanguage: EuiComboBoxOptionOption[]) => { + const queryLanguage = newLanguage[0].value as string; + props.onSelectLanguage(queryLanguage); + uiService.Settings.setUserQueryLanguage(queryLanguage); + }; + + uiService.Settings.setUserQueryLanguage(props.language); + + return ( + + ); +}; diff --git a/src/plugins/data/public/ui/query_editor/index.tsx b/src/plugins/data/public/ui/query_editor/index.tsx index bddef49af1d4..cd5924acc81f 100644 --- a/src/plugins/data/public/ui/query_editor/index.tsx +++ b/src/plugins/data/public/ui/query_editor/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { withOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import type { QueryEditorTopRowProps } from './query_editor_top_row'; import type { QueryEditorProps } from './query_editor'; +import { QueryLanguageSelectorProps } from './language_selector'; const Fallback = () =>
; @@ -26,3 +27,10 @@ export const QueryEditor = (props: QueryEditorProps) => ( export type { QueryEditorProps }; export { QueryEditorExtensions, QueryEditorExtensionConfig } from './query_editor_extensions'; + +const LazyQueryLanguageSelector = React.lazy(() => import('./language_selector')); +export const QueryLanguageSelector = (props: QueryLanguageSelectorProps) => ( + }> + + +); diff --git a/src/plugins/data/public/ui/query_editor/language_selector.tsx b/src/plugins/data/public/ui/query_editor/language_selector.tsx index 7ea82fe2b24e..bc2fc34c7670 100644 --- a/src/plugins/data/public/ui/query_editor/language_selector.tsx +++ b/src/plugins/data/public/ui/query_editor/language_selector.tsx @@ -8,11 +8,12 @@ import { i18n } from '@osd/i18n'; import React from 'react'; import { getUiService } from '../../services'; -interface Props { +export interface QueryLanguageSelectorProps { language: string; onSelectLanguage: (newLanguage: string) => void; anchorPosition?: PopoverAnchorPosition; appName?: string; + languageSelectorContainerRef?: React.RefCallback; } const mapExternalLanguageToOptions = (language: string) => { @@ -22,7 +23,8 @@ const mapExternalLanguageToOptions = (language: string) => { }; }; -export const QueryLanguageSelector = (props: Props) => { +export const QueryLanguageSelector = (props: QueryLanguageSelectorProps) => { + const ref = React.createRef(); const dqlLabel = i18n.translate('data.query.queryEditor.dqlLanguageName', { defaultMessage: 'DQL', }); @@ -73,16 +75,22 @@ export const QueryLanguageSelector = (props: Props) => { uiService.Settings.setUserQueryLanguage(props.language); return ( - +
+ +
); }; + +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default QueryLanguageSelector; 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 19c4c527038c..e09e7d097728 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor.tsx @@ -3,12 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiFlexGroup, EuiFlexItem, htmlIdGenerator, PopoverAnchorPosition } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + htmlIdGenerator, + PopoverAnchorPosition, +} from '@elastic/eui'; import classNames from 'classnames'; -import { isEqual } from 'lodash'; -import React, { Component, createRef, RefObject } from 'react'; +import { debounce, isEqual } from 'lodash'; +import React, { Component, createRef, RefObject, useRef } from 'react'; +import { monaco } from 'packages/osd-monaco/target'; import { Settings } from '..'; -import { DataSource, IDataPluginServices, IIndexPattern, Query, TimeRange } from '../..'; +import { + DataSource, + IDataPluginServices, + IFieldType, + IIndexPattern, + Query, + TimeRange, +} from '../..'; import { CodeEditor, OpenSearchDashboardsReactContextValue, @@ -20,13 +35,16 @@ import { DataSettings } from '../types'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSelector } from './language_selector'; import { QueryEditorExtensions } from './query_editor_extensions'; +import { QueryEditorBtnCollapse } from './query_editor_btn_collapse'; export interface QueryEditorProps { indexPatterns: Array; dataSource?: DataSource; query: Query; + container?: HTMLDivElement; dataSourceContainerRef?: React.RefCallback; containerRef?: React.RefCallback; + languageSelectorContainerRef?: React.RefCallback; settings: Settings; disableAutoFocus?: boolean; screenTitle?: string; @@ -47,6 +65,8 @@ export interface QueryEditorProps { queryLanguage?: string; headerClassName?: string; bannerClassName?: string; + footerClassName?: string; + filterBar?: any; } interface Props extends QueryEditorProps { @@ -60,6 +80,9 @@ interface State { index: number | null; suggestions: QuerySuggestion[]; indexPatterns: IIndexPattern[]; + isCollapsed: boolean; + timeStamp: IFieldType | null; + lineCount: number | undefined; } const KEY_CODES = { @@ -85,9 +108,12 @@ export default class QueryEditorUI extends Component { index: null, suggestions: [], indexPatterns: [], + isCollapsed: true, + timeStamp: null, + lineCount: undefined, }; - public inputRef: HTMLElement | null = null; + public inputRef: monaco.editor.IStandaloneCodeEditor | null = null; private persistedLog: PersistedLog | undefined; private abortController?: AbortController; @@ -95,6 +121,8 @@ export default class QueryEditorUI extends Component { private componentIsUnmounting = false; private headerRef: RefObject = createRef(); private bannerRef: RefObject = createRef(); + private footerRef: RefObject = createRef(); + //private codeEditorRef: RefObject = createRef(); private extensionMap = this.props.settings?.getQueryEditorExtensionMap(); private getQueryString = () => { @@ -125,6 +153,10 @@ export default class QueryEditorUI extends Component { }; private renderQueryEditorExtensions() { + console.log('i am here!', this.headerRef.current); + console.log('i am here!', this.bannerRef.current); + console.log('i am here!', this.footerRef.current); + if ( !( this.headerRef.current && @@ -175,6 +207,19 @@ export default class QueryEditorUI extends Component { private onInputChange = (value: string) => { this.onQueryStringChange(value); + + if (!this.inputRef) return; + + const currentLineCount = this.inputRef.getModel()?.getLineCount(); + if (this.state.lineCount === currentLineCount) return; + this.setState({ lineCount: currentLineCount }); + }; + + private onSingleLineInputChange = (value: string) => { + // Replace new lines with an empty string to prevent multi-line input + this.onQueryStringChange(value.replace(/[\r\n]+/gm, '')); + + this.setState({ lineCount: undefined }); }; private onClickInput = (event: React.MouseEvent) => { @@ -259,7 +304,8 @@ export default class QueryEditorUI extends Component { } this.initPersistedLog(); - // this.fetchIndexPatterns().then(this.updateSuggestions); + //this.fetchIndexPatterns(); + //this.fetchIndexPatterns().then(this.updateTimeStampField); this.initDataSourcesVisibility(); this.initDataSetsVisibility(); } @@ -284,10 +330,29 @@ export default class QueryEditorUI extends Component { } }; + editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => { + this.setState({ lineCount: editor.getModel()?.getLineCount() }); + this.inputRef = editor; + }; + public render() { const className = classNames(this.props.className); const headerClassName = classNames('osdQueryEditorHeader', this.props.headerClassName); const bannerClassName = classNames('osdQueryEditorBanner', this.props.bannerClassName); + const footerClassName = classNames('osdQueryEditorFooter', this.props.footerClassName); + + const useQueryEditor = + this.props.query.language === 'SQLAsync' || + this.props.query.language === 'SQL' || + this.props.query.language === 'PPL'; + + // console.log('this.state.isDataSourcesVisible', this.state.isDataSourcesVisible); + // console.log('this.state.isDataSetsVisible', this.state.isDataSetsVisible); + + // console.log('this.props.dataSourceContainerRef', this.props.dataSourceContainerRef); + // console.log('this.props.containerRef', this.props.containerRef); + console.log('index patterns', this.props.indexPatterns); + console.log('timestamp', this.state.indexPatterns[0]?.timeFieldName); return (
@@ -295,48 +360,122 @@ export default class QueryEditorUI extends Component { - {this.props.prepend} + + this.setState({ isCollapsed: !this.state.isCollapsed })} + isCollapsed={this.state.isCollapsed} + /> + {this.state.isDataSourcesVisible && ( - +
)} - - - + {this.state.isDataSetsVisible && ( - +
)} + {(!this.state.isCollapsed || !useQueryEditor) && ( + + {/* */} + + + + )} + {!useQueryEditor && ( + + + + )} + {this.props.prepend} +
- + {this.state.isCollapsed && useQueryEditor && ( + + )} + +
+ + + + + + + + {this.state.lineCount} + + {typeof this.props.indexPatterns[0] !== 'string' && + this.props.indexPatterns[0].timeFieldName} + + + + +
+ + {this.state.isCollapsed && {this.props.filterBar}} {this.renderQueryEditorExtensions()}
diff --git a/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx b/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx new file mode 100644 index 000000000000..b61ed566b08a --- /dev/null +++ b/src/plugins/data/public/ui/query_editor/query_editor_btn_collapse.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; + +export interface Props { + onClick: () => void; + isCollapsed: boolean; +} + +export function QueryEditorBtnCollapse({ onClick, isCollapsed }: Props) { + const label = i18n.translate('queryEditor.collapse', { + defaultMessage: 'Toggle query editor', + }); + return ( + + + + ); +} diff --git a/src/plugins/data/public/ui/query_editor/query_editor_extensions/query_editor_extension.tsx b/src/plugins/data/public/ui/query_editor/query_editor_extensions/query_editor_extension.tsx index f684aebea1d9..c72828b9d81d 100644 --- a/src/plugins/data/public/ui/query_editor/query_editor_extensions/query_editor_extension.tsx +++ b/src/plugins/data/public/ui/query_editor/query_editor_extensions/query_editor_extension.tsx @@ -60,8 +60,9 @@ export interface QueryEditorExtensionConfig { * @returns The component the query editor extension. */ getBanner?: (dependencies: QueryEditorExtensionDependencies) => React.ReactElement | null; -} + getFooter?: (dependencies: QueryEditorExtensionDependencies) => React.ReactElement | null; +} const QueryEditorExtensionPortal: React.FC<{ container: Element }> = (props) => { if (!props.children) return null; @@ -85,6 +86,11 @@ export const QueryEditorExtension: React.FC = (props) props.dependencies, ]); + const footer = useMemo(() => props.config.getFooter?.(props.dependencies), [ + props.config, + props.dependencies, + ]); + useEffect(() => { isMounted.current = true; return () => { @@ -99,6 +105,10 @@ export const QueryEditorExtension: React.FC = (props) return () => subscription.unsubscribe(); }, [props.dependencies, props.config]); + console.log('isEnabled', isEnabled); + console.log('props.config', props.config); + console.log('banner', props.bannerContainer, banner); + console.log('component', props.componentContainer, component); if (!isEnabled) return null; return ( 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 3ea715418ab1..97cd16cbe3c8 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 @@ -40,6 +40,7 @@ export interface QueryEditorTopRowProps { query?: Query; dataSourceContainerRef?: React.RefCallback; containerRef?: React.RefCallback; + languageSelectorContainerRef?: React.RefCallback; settings?: Settings; onSubmit: (payload: { dateRange: TimeRange; query?: Query }) => void; onChange: (payload: { dateRange: TimeRange; query?: Query }) => void; @@ -237,6 +238,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { query={parsedQuery} dataSourceContainerRef={props.dataSourceContainerRef} containerRef={props.containerRef} + languageSelectorContainerRef={props.languageSelectorContainerRef} settings={props.settings!} screenTitle={props.screenTitle} onChange={onQueryChange} @@ -247,6 +249,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { className="osdQueryEditor" dataTestSubj={props.dataTestSubj} queryLanguage={queryLanguage} + filterBar={props.filterBar} />
); @@ -375,12 +378,11 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) { direction="column" justifyContent="flexEnd" > + {renderUpdateButton()} {renderQueryEditor()} - - {props.filterBar} + {renderSharingMetaFields()} - {renderUpdateButton()} diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index da7c25254adb..ebd76ff5b756 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -182,7 +182,6 @@ export function SavedQueryManagementComponent({ data-test-subj="saved-query-management-popover-button" > - ); diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 244f4296216c..b1191d3ad1e9 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -50,6 +50,7 @@ interface StatefulSearchBarDeps { settings: Settings; setDataSourceContainerRef: (ref: HTMLDivElement | null) => void; setContainerRef: (ref: HTMLDivElement | null) => void; + setLanguageSelectorContainerRef: (ref: HTMLDivElement | undefined) => void; } export type StatefulSearchBarProps = SearchBarOwnProps & { @@ -57,6 +58,7 @@ export type StatefulSearchBarProps = SearchBarOwnProps & { useDefaultBehaviors?: boolean; savedQueryId?: string; onSavedQueryIdChange?: (savedQueryId?: string) => void; + queryEditorComponent?: HTMLDivElement; }; // Respond to user changing the filters @@ -141,6 +143,7 @@ export function createSearchBar({ settings, setDataSourceContainerRef, setContainerRef, + setLanguageSelectorContainerRef, }: StatefulSearchBarDeps) { // App name should come from the core application service. // Until it's available, we'll ask the user to provide it for the pre-wired component. @@ -182,6 +185,12 @@ export function createSearchBar({ } }, []); + const languageSelectorContainerRef = useCallback((node) => { + if (node) { + setLanguageSelectorContainerRef(node); + } + }, []); + const containerRef = useCallback((node) => { if (node) { setContainerRef(node); @@ -230,6 +239,7 @@ export function createSearchBar({ settings={settings} dataSourceContainerRef={dataSourceContainerRef} containerRef={containerRef} + languageSelectorContainerRef={languageSelectorContainerRef} onFiltersUpdated={defaultFiltersUpdated(data.query)} onRefreshChange={defaultOnRefreshChange(data.query)} savedQuery={savedQuery} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 11914f134443..0e0462bcd5dd 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -82,6 +82,7 @@ export interface SearchBarOwnProps { settings?: Settings; dataSourceContainerRef?: React.RefCallback; containerRef?: React.RefCallback; + languageSelectorContainerRef?: React.RefCallback; // Show when user has privileges to save showSaveQuery?: boolean; savedQuery?: SavedQuery; @@ -96,6 +97,7 @@ export interface SearchBarOwnProps { onRefresh?: (payload: { dateRange: TimeRange }) => void; indicateNoData?: boolean; + queryEditorComponent?: HTMLDivElement; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -493,6 +495,7 @@ class SearchBarUI extends Component { timeHistory={this.props.timeHistory} dataSourceContainerRef={this.props.dataSourceContainerRef} containerRef={this.props.containerRef} + languageSelectorContainerRef={this.props.languageSelectorContainerRef} settings={this.props.settings} query={this.state.query} screenTitle={this.props.screenTitle} diff --git a/src/plugins/data/public/ui/types.ts b/src/plugins/data/public/ui/types.ts index afa0c8130504..1efedad89d5c 100644 --- a/src/plugins/data/public/ui/types.ts +++ b/src/plugins/data/public/ui/types.ts @@ -10,6 +10,7 @@ import { StatefulSearchBarProps } from './search_bar'; import { QueryEditorExtensionConfig } from './query_editor/query_editor_extensions'; import { Settings } from './settings'; import { SuggestionsComponentProps } from './typeahead/suggestions_component'; +import { QueryLanguageSelectorProps } from './query_editor/language_selector'; export * from './settings'; @@ -66,7 +67,10 @@ export interface IUiStart { IndexPatternSelect: React.ComponentType; SearchBar: React.ComponentType; SuggestionsComponent: React.ComponentType; + QueryLanguageSelector: React.ComponentType; + languageSelectorContainer$: Observable; Settings: Settings; dataSourceContainer$: Observable; + dataSourceFooter$: Observable; container$: Observable; } diff --git a/src/plugins/data/public/ui/ui_service.ts b/src/plugins/data/public/ui/ui_service.ts index 243490dc8201..7559504f969e 100644 --- a/src/plugins/data/public/ui/ui_service.ts +++ b/src/plugins/data/public/ui/ui_service.ts @@ -9,7 +9,7 @@ import { IStorageWrapper } from '../../../opensearch_dashboards_utils/public'; import { ConfigSchema } from '../../config'; import { DataPublicPluginStart } from '../types'; import { createIndexPatternSelect } from './index_pattern_select'; -import { QueryEditorExtensionConfig } from './query_editor'; +import { QueryEditorExtensionConfig, QueryLanguageSelector } from './query_editor'; import { createSearchBar } from './search_bar/create_search_bar'; import { createSettings } from './settings'; import { SuggestionsComponent } from './typeahead'; @@ -30,7 +30,9 @@ export class UiService implements Plugin { private queryEnhancements: Map = new Map(); private queryEditorExtensionMap: Record = {}; private dataSourceContainer$ = new BehaviorSubject(null); + private dataSourceFooter$ = new BehaviorSubject(null); private container$ = new BehaviorSubject(null); + private languageSelectorContainer$ = new BehaviorSubject(null); constructor(initializerContext: PluginInitializerContext) { const { enhancements } = initializerContext.config.get(); @@ -71,6 +73,10 @@ export class UiService implements Plugin { this.container$.next(ref); }; + const setLanguageSelectorContainerRef = (ref: HTMLDivElement | null) => { + this.languageSelectorContainer$.next(ref); + }; + const SearchBar = createSearchBar({ core, data: dataServices, @@ -78,14 +84,18 @@ export class UiService implements Plugin { settings: Settings, setDataSourceContainerRef, setContainerRef, + setLanguageSelectorContainerRef, }); return { IndexPatternSelect: createIndexPatternSelect(core.savedObjects.client), SearchBar, SuggestionsComponent, + QueryLanguageSelector, + languageSelectorContainer$: this.languageSelectorContainer$, Settings, dataSourceContainer$: this.dataSourceContainer$, + dataSourceFooter$: this.dataSourceFooter$, container$: this.container$, }; }