Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added a new API to allow adding additional editor controls #7936

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/plugins/data/public/ui/query_editor/query_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '@elastic/eui';
import classNames from 'classnames';
import { isEqual } from 'lodash';
import { BehaviorSubject, Subscription } from 'rxjs';
import React, { Component, createRef, RefObject } from 'react';
import { monaco } from '@osd/monaco';
import {
Expand All @@ -37,6 +38,7 @@ import { QueryControls } from '../../query/query_string/language_service/get_que
import { RecentQueriesTable } from '../../query/query_string/language_service/recent_query';
import { DefaultInputProps } from './editors';
import { MonacoCompatibleQuerySuggestion } from '../../autocomplete/providers/query_suggestion_provider';
import { SearchBarControl } from '../types';

export interface QueryEditorProps {
query: Query;
Expand All @@ -61,6 +63,7 @@ export interface QueryEditorProps {
filterBar?: any;
prepend?: React.ComponentProps<typeof EuiCompressedFieldText>['prepend'];
savedQueryManagement?: any;
additionalControls$?: BehaviorSubject<SearchBarControl[]>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this to be an observable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not be a hard requirement now, but I was thinking, it might make sense to make it an observable so that the controls can be added on the fly, similar to dynamic action triggers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 problems I see with this approach.

  1. Whats the usecase for such dynamic controls? Without a use I don't want to add more complexity to the query bar. It think it's more useful to pass the current app state to determine if the controls are needed rather than having an observable.
  2. Right now you can only add controls but not remove them. But this is an easy fix compared to the first one though

}

interface Props extends QueryEditorProps {
Expand All @@ -76,6 +79,7 @@ interface State {
timeStamp: IFieldType | null;
lineCount: number | undefined;
isRecentQueryVisible: boolean;
additionalControls?: SearchBarControl[];
}

// Needed for React.lazy
Expand All @@ -91,6 +95,7 @@ export default class QueryEditorUI extends Component<Props, State> {
timeStamp: null,
lineCount: undefined,
isRecentQueryVisible: false,
additionalControls: [],
};

public inputRef: monaco.editor.IStandaloneCodeEditor | null = null;
Expand All @@ -104,6 +109,7 @@ export default class QueryEditorUI extends Component<Props, State> {
private headerRef: RefObject<HTMLDivElement> = createRef();
private bannerRef: RefObject<HTMLDivElement> = createRef();
private extensionMap = this.languageManager.getQueryEditorExtensionMap();
private additionalControlsSubscription?: Subscription;

private getQueryString = () => {
return toUser(this.props.query.query);
Expand Down Expand Up @@ -228,6 +234,12 @@ export default class QueryEditorUI extends Component<Props, State> {
}

this.initPersistedLog();

if (this.props.additionalControls$) {
this.additionalControlsSubscription = this.props.additionalControls$.subscribe((controls) => {
this.setState({ additionalControls: controls });
});
}
}

public componentDidUpdate(prevProps: Props) {
Expand All @@ -254,6 +266,7 @@ export default class QueryEditorUI extends Component<Props, State> {

public componentWillUnmount() {
if (this.abortController) this.abortController.abort();
if (this.additionalControlsSubscription) this.additionalControlsSubscription.unsubscribe();
}

handleOnFocus = () => {
Expand Down Expand Up @@ -330,6 +343,18 @@ export default class QueryEditorUI extends Component<Props, State> {
return <QueryControls queryControls={queryControls} />;
};

private renderAdditionalControls = () => {
if (!this.state.additionalControls) return null;
const sorted = this.state.additionalControls.sort((c1, c2) => c1.order - c2.order);
return (
<>
{sorted.map((control, i) => {
return <React.Fragment key={i}>{control.render()}</React.Fragment>;
})}
</>
);
};

public render() {
const className = classNames(this.props.className);

Expand Down Expand Up @@ -446,6 +471,7 @@ export default class QueryEditorUI extends Component<Props, State> {
<div className="osdQueryEditor__querycontrols">
<EuiFlexGroup responsive={false} gutterSize="s" alignItems="center">
{this.renderQueryControls(languageEditor.TopBar.Controls)}
{this.renderAdditionalControls()}
{!languageEditor.TopBar.Expanded && this.renderToggleIcon()}
{this.props.savedQueryManagement}
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import classNames from 'classnames';
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
import { BehaviorSubject } from 'rxjs';
import { IDataPluginServices, IIndexPattern, Query, TimeHistoryContract, TimeRange } from '../..';
import {
useOpenSearchDashboards,
Expand All @@ -24,6 +25,7 @@ import { UI_SETTINGS } from '../../../common';
import { getQueryLog, PersistedLog } from '../../query';
import { NoDataPopover } from './no_data_popover';
import QueryEditorUI from './query_editor';
import { SearchBarControl } from '../types';

const QueryEditor = withOpenSearchDashboards(QueryEditorUI);

Expand Down Expand Up @@ -54,6 +56,7 @@ export interface QueryEditorTopRowProps {
indicateNoData?: boolean;
datePickerRef?: React.RefObject<HTMLDivElement>;
savedQueryManagement?: any;
additionalControls$?: BehaviorSubject<SearchBarControl[]>;
}

// Needed for React.lazy
Expand Down Expand Up @@ -186,6 +189,7 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) {
dataTestSubj={props.dataTestSubj}
filterBar={props.filterBar}
savedQueryManagement={props.savedQueryManagement}
additionalControls$={props.additionalControls$}
/>
</EuiFlexItem>
);
Expand Down
11 changes: 10 additions & 1 deletion src/plugins/data/public/ui/search_bar/create_search_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import _ from 'lodash';
import React, { useEffect, useRef } from 'react';
import { BehaviorSubject } from 'rxjs';
import { CoreStart } from 'src/core/public';
import { OpenSearchDashboardsContextProvider } from '../../../../opensearch_dashboards_react/public';
import { QueryStart, SavedQuery } from '../../query';
Expand All @@ -40,11 +41,13 @@ import { useSavedQuery } from './lib/use_saved_query';
import { DataPublicPluginStart } from '../../types';
import { DataStorage, Filter, Query, TimeRange } from '../../../common';
import { useQueryStringManager } from './lib/use_query_string_manager';
import { SearchBarControl } from '../types';

interface StatefulSearchBarDeps {
core: CoreStart;
data: Omit<DataPublicPluginStart, 'ui'>;
storage: DataStorage;
searchBarControls$: BehaviorSubject<SearchBarControl[]>;
}

export type StatefulSearchBarProps = SearchBarOwnProps & {
Expand Down Expand Up @@ -129,7 +132,12 @@ const overrideDefaultBehaviors = (props: StatefulSearchBarProps) => {
return props.useDefaultBehaviors ? {} : props;
};

export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) {
export function createSearchBar({
core,
storage,
data,
searchBarControls$,
}: 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.
return (props: StatefulSearchBarProps) => {
Expand Down Expand Up @@ -210,6 +218,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps)
onSaved={defaultOnSavedQueryUpdated(props, setSavedQuery)}
datePickerRef={props.datePickerRef}
isFilterBarPortable={props.isFilterBarPortable}
searchBarControls$={searchBarControls$}
{...overrideDefaultBehaviors(props)}
/>
</OpenSearchDashboardsContextProvider>
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/data/public/ui/search_bar/search_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { InjectedIntl, injectI18n } from '@osd/i18n/react';
import classNames from 'classnames';
import { compact, get, isEqual } from 'lodash';
import React, { Component } from 'react';
import { BehaviorSubject } from 'rxjs';
import ResizeObserver from 'resize-observer-polyfill';
import {
OpenSearchDashboardsReactContextValue,
Expand All @@ -45,6 +46,7 @@ import { QueryEditorTopRow } from '../query_editor';
import QueryBarTopRow from '../query_string_input/query_bar_top_row';
import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form';
import { FilterOptions } from '../filter_bar/filter_options';
import { SearchBarControl } from '../types';

interface SearchBarInjectedDeps {
opensearchDashboards: OpenSearchDashboardsReactContextValue<IDataPluginServices>;
Expand Down Expand Up @@ -91,6 +93,7 @@ export interface SearchBarOwnProps {
onClearSavedQuery?: () => void;

onRefresh?: (payload: { dateRange: TimeRange }) => void;
searchBarControls$?: BehaviorSubject<SearchBarControl[]>;
indicateNoData?: boolean;
}

Expand Down Expand Up @@ -550,6 +553,7 @@ class SearchBarUI extends Component<SearchBarProps, State> {
indicateNoData={this.props.indicateNoData}
datePickerRef={this.props.datePickerRef}
savedQueryManagement={searchBarMenu(false, true)}
additionalControls$={this.props.searchBarControls$}
/>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/plugins/data/public/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { IndexPatternSelectProps } from './index_pattern_select';
import { StatefulSearchBarProps } from './search_bar';
import { SuggestionsComponentProps } from './typeahead/suggestions_component';

export interface SearchBarControl {
order: number;
render: () => React.ReactNode;
}

/**
* The setup contract exposed by the Search plugin exposes the search strategy extension
* point.
Expand All @@ -21,4 +27,5 @@ export interface IUiStart {
IndexPatternSelect: React.ComponentType<IndexPatternSelectProps>;
SearchBar: React.ComponentType<StatefulSearchBarProps>;
SuggestionsComponent: React.ComponentType<SuggestionsComponentProps>;
addSearchBarControl: (control: SearchBarControl) => void;
}
12 changes: 11 additions & 1 deletion src/plugins/data/public/ui/ui_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { BehaviorSubject } from 'rxjs';
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
import { ConfigSchema } from '../../config';
import { DataPublicPluginStart } from '../types';
import { createIndexPatternSelect } from './index_pattern_select';
import { createSearchBar } from './search_bar/create_search_bar';
import { SuggestionsComponent } from './typeahead';
import { IUiSetup, IUiStart } from './types';
import { IUiSetup, IUiStart, SearchBarControl } from './types';
import { DataStorage } from '../../common';

/** @internal */
Expand All @@ -24,13 +25,20 @@

export class UiService implements Plugin<IUiSetup, IUiStart> {
enhancementsConfig: ConfigSchema['enhancements'];
searchBarControls$: BehaviorSubject<SearchBarControl[]> = new BehaviorSubject(

Check warning on line 28 in src/plugins/data/public/ui/ui_service.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data/public/ui/ui_service.ts#L28

Added line #L28 was not covered by tests
[] as SearchBarControl[]
);

constructor(initializerContext: PluginInitializerContext<ConfigSchema>) {
const { enhancements } = initializerContext.config.get<ConfigSchema>();

this.enhancementsConfig = enhancements;
}

addSearchBarControl = (control: SearchBarControl) => {
this.searchBarControls$.next([...this.searchBarControls$.value, control]);

Check warning on line 39 in src/plugins/data/public/ui/ui_service.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data/public/ui/ui_service.ts#L38-L39

Added lines #L38 - L39 were not covered by tests
};

public setup(core: CoreSetup, {}: UiServiceSetupDependencies): IUiSetup {
return {};
}
Expand All @@ -40,12 +48,14 @@
core,
data: dataServices,
storage,
searchBarControls$: this.searchBarControls$,
});

return {
IndexPatternSelect: createIndexPatternSelect(core.savedObjects.client),
SearchBar,
SuggestionsComponent,
addSearchBarControl: this.addSearchBarControl,
};
}

Expand Down
Loading