diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md new file mode 100644 index 00000000000000..00a56c6fe9c31d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) > [isPartial](./kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md) + +## IEsSearchResponse.isPartial property + +Indicates whether the results returned are complete or partial + +Signature: + +```typescript +isPartial?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md new file mode 100644 index 00000000000000..56fb1a7519811e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) > [isRunning](./kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md) + +## IEsSearchResponse.isRunning property + +Indicates whether async search is still in flight + +Signature: + +```typescript +isRunning?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md index ea7e2aef00d6ef..041d79de3282ec 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md @@ -14,5 +14,7 @@ export interface IEsSearchResponse extends IKibanaSearchResponse | Property | Type | Description | | --- | --- | --- | +| [isPartial](./kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | +| [isRunning](./kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | | [rawResponse](./kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md) | SearchResponse<any> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md index cdf19cd15a298a..3bd6a398c8df54 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ISearchGeneric = (request: IEsSearchRequest, options?: IStrategyOptions) => Observable; +export declare type ISearchGeneric = (request: IEsSearchRequest, options?: ISearchOptions) => Observable; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 05b72526062896..3eb38dc7d52e08 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -15,4 +15,5 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [signal](./kibana-plugin-plugins-data-public.isearchoptions.signal.md) | AbortSignal | | +| [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md new file mode 100644 index 00000000000000..df7e050691a8f3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.strategy.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) + +## ISearchOptions.strategy property + +Signature: + +```typescript +strategy?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md index 30e980b5ffc544..b3b7da05326d06 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md @@ -35,7 +35,7 @@ export declare class SearchInterceptor | Method | Modifiers | Description | | --- | --- | --- | -| [runSearch(request, signal)](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) | | | +| [runSearch(request, signal, strategy)](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) | | | | [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when cancelPending is called, when the request times out, or when the original AbortSignal is aborted. Updates the pendingCount when the request is started/finalized. | | [setupTimers(options)](./kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md index 3601a00c48cf31..ad1d1dcb59d7b8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md @@ -7,7 +7,7 @@ Signature: ```typescript -protected runSearch(request: IEsSearchRequest, signal: AbortSignal): Observable; +protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; ``` ## Parameters @@ -16,6 +16,7 @@ protected runSearch(request: IEsSearchRequest, signal: AbortSignal): Observable< | --- | --- | --- | | request | IEsSearchRequest | | | signal | AbortSignal | | +| strategy | string | | Returns: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md new file mode 100644 index 00000000000000..aaf4e55ee007b4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) > [indexType](./kibana-plugin-plugins-data-server.iessearchrequest.indextype.md) + +## IEsSearchRequest.indexType property + +Signature: + +```typescript +indexType?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md new file mode 100644 index 00000000000000..0dfa23eb64c1b2 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) + +## IEsSearchRequest interface + +Signature: + +```typescript +export interface IEsSearchRequest extends IKibanaSearchRequest +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [indexType](./kibana-plugin-plugins-data-server.iessearchrequest.indextype.md) | string | | +| [params](./kibana-plugin-plugins-data-server.iessearchrequest.params.md) | ISearchRequestParams | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md new file mode 100644 index 00000000000000..d65281973c951f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.params.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) > [params](./kibana-plugin-plugins-data-server.iessearchrequest.params.md) + +## IEsSearchRequest.params property + +Signature: + +```typescript +params?: ISearchRequestParams; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md new file mode 100644 index 00000000000000..fbddfc1cd9fc40 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [isPartial](./kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md) + +## IEsSearchResponse.isPartial property + +Indicates whether the results returned are complete or partial + +Signature: + +```typescript +isPartial?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md new file mode 100644 index 00000000000000..01f3982957d5c3 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [isRunning](./kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md) + +## IEsSearchResponse.isRunning property + +Indicates whether async search is still in flight + +Signature: + +```typescript +isRunning?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md new file mode 100644 index 00000000000000..0407dce5fe4181 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) + +## IEsSearchResponse interface + +Signature: + +```typescript +export interface IEsSearchResponse extends IKibanaSearchResponse +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [isPartial](./kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | +| [isRunning](./kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | +| [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) | SearchResponse<any> | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md new file mode 100644 index 00000000000000..0ee1691d0f697c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) > [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) + +## IEsSearchResponse.rawResponse property + +Signature: + +```typescript +rawResponse: SearchResponse; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 1bcd575803f880..f472064c877555 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -36,6 +36,8 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | | [Filter](./kibana-plugin-plugins-data-server.filter.md) | | +| [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | +| [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | | [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | | diff --git a/examples/search_examples/README.md b/examples/search_examples/README.md new file mode 100644 index 00000000000000..bcc17bf7f33338 --- /dev/null +++ b/examples/search_examples/README.md @@ -0,0 +1,9 @@ +# search_examples + +> An awesome Kibana plugin + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/examples/search_examples/common/index.ts b/examples/search_examples/common/index.ts new file mode 100644 index 00000000000000..e1e7f6389ae831 --- /dev/null +++ b/examples/search_examples/common/index.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IEsSearchResponse, IEsSearchRequest } from '../../../src/plugins/data/common'; + +export const PLUGIN_ID = 'searchExamples'; +export const PLUGIN_NAME = 'Search Examples'; + +export interface IMyStrategyRequest extends IEsSearchRequest { + get_cool: boolean; +} +export interface IMyStrategyResponse extends IEsSearchResponse { + cool: string; +} + +export const SERVER_SEARCH_ROUTE_PATH = '/api/examples/search'; diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json new file mode 100644 index 00000000000000..7e392b84173607 --- /dev/null +++ b/examples/search_examples/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "searchExamples", + "version": "8.0.0", + "server": true, + "ui": true, + "requiredPlugins": ["navigation", "data", "developerExamples"], + "optionalPlugins": [], + "requiredBundles": [] +} diff --git a/examples/search_examples/public/application.tsx b/examples/search_examples/public/application.tsx new file mode 100644 index 00000000000000..81e4ee8514c11f --- /dev/null +++ b/examples/search_examples/public/application.tsx @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters, CoreStart } from '../../../src/core/public'; +import { AppPluginStartDependencies } from './types'; +import { SearchExamplesApp } from './components/app'; + +export const renderApp = ( + { notifications, savedObjects, http }: CoreStart, + { navigation, data }: AppPluginStartDependencies, + { appBasePath, element }: AppMountParameters +) => { + ReactDOM.render( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx new file mode 100644 index 00000000000000..31cc5e420da11b --- /dev/null +++ b/examples/search_examples/public/components/app.tsx @@ -0,0 +1,341 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { + EuiButton, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiTitle, + EuiText, + EuiFlexGrid, + EuiFlexItem, + EuiCheckbox, + EuiSpacer, + EuiCode, + EuiComboBox, + EuiFormLabel, +} from '@elastic/eui'; + +import { CoreStart } from '../../../../src/core/public'; +import { mountReactNode } from '../../../../src/core/public/utils'; +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; + +import { + PLUGIN_ID, + PLUGIN_NAME, + IMyStrategyRequest, + IMyStrategyResponse, + SERVER_SEARCH_ROUTE_PATH, +} from '../../common'; + +import { + DataPublicPluginStart, + IndexPatternSelect, + IndexPattern, + IndexPatternField, +} from '../../../../src/plugins/data/public'; + +interface SearchExamplesAppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + savedObjectsClient: CoreStart['savedObjects']['client']; + navigation: NavigationPublicPluginStart; + data: DataPublicPluginStart; +} + +function formatFieldToComboBox(field?: IndexPatternField | null) { + if (!field) return []; + return formatFieldsToComboBox([field]); +} + +function formatFieldsToComboBox(fields?: IndexPatternField[]) { + if (!fields) return []; + + return fields?.map((field) => { + return { + label: field.displayName || field.name, + }; + }); +} + +export const SearchExamplesApp = ({ + http, + basename, + notifications, + savedObjectsClient, + navigation, + data, +}: SearchExamplesAppDeps) => { + const [getCool, setGetCool] = useState(false); + const [timeTook, setTimeTook] = useState(); + const [indexPattern, setIndexPattern] = useState(); + const [numericFields, setNumericFields] = useState(); + const [selectedField, setSelectedField] = useState(); + + // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. + useEffect(() => { + const setDefaultIndexPattern = async () => { + const defaultIndexPattern = await data.indexPatterns.getDefault(); + setIndexPattern(defaultIndexPattern); + }; + + setDefaultIndexPattern(); + }, [data]); + + // Update the fields list every time the index pattern is modified. + useEffect(() => { + const fields = indexPattern?.fields.filter( + (field) => field.type === 'number' && field.aggregatable + ); + setNumericFields(fields); + setSelectedField(fields?.length ? fields[0] : null); + }, [indexPattern]); + + const doAsyncSearch = async (strategy?: string) => { + if (!indexPattern || !selectedField) return; + + // Constuct the query portion of the search request + const query = data.query.getEsQuery(indexPattern); + + // Constuct the aggregations portion of the search request by using the `data.search.aggs` service. + const aggs = [{ type: 'avg', params: { field: selectedField.name } }]; + const aggsDsl = data.search.aggs.createAggConfigs(indexPattern, aggs).toDsl(); + + const request = { + params: { + index: indexPattern.title, + body: { + aggs: aggsDsl, + query, + }, + }, + }; + + if (strategy) { + // Add a custom request parameter to be consumed by `MyStrategy`. + (request as IMyStrategyRequest).get_cool = getCool; + } + + // Submit the search request using the `data.search` service. + const searchSubscription$ = data.search + .search(request, { + strategy, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + setTimeTook(response.rawResponse.took); + const avgResult: number | undefined = response.rawResponse.aggregations + ? response.rawResponse.aggregations[1].value + : undefined; + const message = ( + + Searched {response.rawResponse.hits.total} documents.
+ The average of {selectedField.name} is {avgResult ? Math.floor(avgResult) : 0}. +
+ Is it Cool? {String((response as IMyStrategyResponse).cool)} +
+ ); + notifications.toasts.addSuccess({ + title: 'Query result', + text: mountReactNode(message), + }); + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + // TODO: Make response error status clearer + notifications.toasts.addWarning('An error has occurred'); + searchSubscription$.unsubscribe(); + } + }, + error: () => { + notifications.toasts.addDanger('Failed to run search'); + }, + }); + }; + + const onClickHandler = () => { + doAsyncSearch(); + }; + + const onMyStrategyClickHandler = () => { + doAsyncSearch('myStrategy'); + }; + + const onServerClickHandler = async () => { + if (!indexPattern || !selectedField) return; + try { + const response = await http.get(SERVER_SEARCH_ROUTE_PATH, { + query: { + index: indexPattern.title, + field: selectedField.name, + }, + }); + + notifications.toasts.addSuccess(`Server returned ${JSON.stringify(response)}`); + } catch (e) { + notifications.toasts.addDanger('Failed to run search'); + } + }; + + if (!indexPattern) return null; + + return ( + + + <> + + + + + +

+ +

+
+
+ + + + + + Index Pattern + { + const newIndexPattern = await data.indexPatterns.get(newIndexPatternId); + setIndexPattern(newIndexPattern); + }} + isClearable={false} + /> + + + Numeric Fields + { + const field = indexPattern.getFieldByName(option[0].label); + setSelectedField(field || null); + }} + sortMatchesBy="startsWith" + /> + + + + + + + + +

+ Searching Elasticsearch using data.search +

+
+ + If you want to fetch data from Elasticsearch, you can use the different services + provided by the data plugin. These help you get the index + pattern and search bar configuration, format them into a DSL query and send it + to Elasticsearch. + + + + + + + +

Writing a custom search strategy

+
+ + If you want to do some pre or post processing on the server, you might want to + create a custom search strategy. This example uses such a strategy, passing in + custom input and receiving custom output back. + + + } + checked={getCool} + onChange={(event) => setGetCool(event.target.checked)} + /> + + + + + + +

Using search on the server

+
+ + You can also run your search request from the server, without registering a + search strategy. This request does not take the configuration of{' '} + TopNavMenu into account, but you could pass those down to the + server as well. + + + + +
+
+
+
+ +
+
+ ); +}; diff --git a/examples/search_examples/public/index.scss b/examples/search_examples/public/index.scss new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/examples/search_examples/public/index.ts b/examples/search_examples/public/index.ts new file mode 100644 index 00000000000000..cd3208aa0c0884 --- /dev/null +++ b/examples/search_examples/public/index.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './index.scss'; + +import { SearchExamplesPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new SearchExamplesPlugin(); +} +export { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; diff --git a/examples/search_examples/public/plugin.ts b/examples/search_examples/public/plugin.ts new file mode 100644 index 00000000000000..436304e9d5ff61 --- /dev/null +++ b/examples/search_examples/public/plugin.ts @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, + AppNavLinkStatus, +} from '../../../src/core/public'; +import { + SearchExamplesPluginSetup, + SearchExamplesPluginStart, + AppPluginSetupDependencies, + AppPluginStartDependencies, +} from './types'; +import { PLUGIN_NAME } from '../common'; + +export class SearchExamplesPlugin + implements + Plugin< + SearchExamplesPluginSetup, + SearchExamplesPluginStart, + AppPluginSetupDependencies, + AppPluginStartDependencies + > { + public setup( + core: CoreSetup, + { developerExamples }: AppPluginSetupDependencies + ): SearchExamplesPluginSetup { + // Register an application into the side navigation menu + core.application.register({ + id: 'searchExamples', + title: PLUGIN_NAME, + navLinkStatus: AppNavLinkStatus.hidden, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart, params); + }, + }); + + developerExamples.register({ + appId: 'searchExamples', + title: 'Search Examples', + description: `Search Examples`, + }); + + return {}; + } + + public start(core: CoreStart): SearchExamplesPluginStart { + return {}; + } + + public stop() {} +} diff --git a/examples/search_examples/public/types.ts b/examples/search_examples/public/types.ts new file mode 100644 index 00000000000000..0670cc5907449c --- /dev/null +++ b/examples/search_examples/public/types.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; +import { DataPublicPluginStart } from '../../../src/plugins/data/public'; +import { DeveloperExamplesSetup } from '../../developer_examples/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchExamplesPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchExamplesPluginStart {} + +export interface AppPluginSetupDependencies { + developerExamples: DeveloperExamplesSetup; +} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart; + data: DataPublicPluginStart; +} diff --git a/examples/search_examples/server/index.ts b/examples/search_examples/server/index.ts new file mode 100644 index 00000000000000..06d41490af57ac --- /dev/null +++ b/examples/search_examples/server/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../src/core/server'; +import { SearchExamplesPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SearchExamplesPlugin(initializerContext); +} + +export { SearchExamplesPluginSetup, SearchExamplesPluginStart } from './types'; diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts new file mode 100644 index 00000000000000..a1116ddbd759b9 --- /dev/null +++ b/examples/search_examples/server/my_strategy.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ISearchStrategy, PluginStart } from '../../../src/plugins/data/server'; +import { IMyStrategyResponse, IMyStrategyRequest } from '../common'; + +export const mySearchStrategyProvider = (data: PluginStart): ISearchStrategy => { + const es = data.search.getSearchStrategy('es'); + return { + search: async (context, request, options): Promise => { + request.debug = true; + const esSearchRes = await es.search(context, request, options); + return { + ...esSearchRes, + cool: (request as IMyStrategyRequest).get_cool ? 'YES' : 'NOPE', + }; + }, + cancel: async (context, id) => { + if (es.cancel) { + es.cancel(context, id); + } + }, + }; +}; diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts new file mode 100644 index 00000000000000..deef5a53b2b731 --- /dev/null +++ b/examples/search_examples/server/plugin.ts @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../src/core/server'; + +import { + SearchExamplesPluginSetup, + SearchExamplesPluginStart, + SearchExamplesPluginSetupDeps, + SearchExamplesPluginStartDeps, +} from './types'; +import { mySearchStrategyProvider } from './my_strategy'; +import { registerRoutes } from './routes'; + +export class SearchExamplesPlugin + implements + Plugin< + SearchExamplesPluginSetup, + SearchExamplesPluginStart, + SearchExamplesPluginSetupDeps, + SearchExamplesPluginStartDeps + > { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup( + core: CoreSetup, + deps: SearchExamplesPluginSetupDeps + ) { + this.logger.debug('search_examples: Setup'); + const router = core.http.createRouter(); + + core.getStartServices().then(([_, depsStart]) => { + const myStrategy = mySearchStrategyProvider(depsStart.data); + deps.data.search.registerSearchStrategy('myStrategy', myStrategy); + registerRoutes(router, depsStart.data); + }); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('search_examples: Started'); + return {}; + } + + public stop() {} +} diff --git a/examples/search_examples/server/routes/index.ts b/examples/search_examples/server/routes/index.ts new file mode 100644 index 00000000000000..ea575cf371bb7d --- /dev/null +++ b/examples/search_examples/server/routes/index.ts @@ -0,0 +1,19 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export { registerRoutes } from './register_routes'; diff --git a/examples/search_examples/server/routes/register_routes.ts b/examples/search_examples/server/routes/register_routes.ts new file mode 100644 index 00000000000000..ac3ea28d89d26e --- /dev/null +++ b/examples/search_examples/server/routes/register_routes.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IRouter } from 'kibana/server'; +import { PluginStart as DataPluginStart } from 'src/plugins/data/server'; +import { registerServerSearchRoute } from './server_search_route'; + +export function registerRoutes(router: IRouter, data: DataPluginStart) { + registerServerSearchRoute(router, data); +} diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts new file mode 100644 index 00000000000000..6eb21cf34b4a31 --- /dev/null +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginStart as DataPluginStart, IEsSearchRequest } from 'src/plugins/data/server'; +import { schema } from '@kbn/config-schema'; +import { IEsSearchResponse } from 'src/plugins/data/common'; +import { IRouter } from '../../../../src/core/server'; +import { SERVER_SEARCH_ROUTE_PATH } from '../../common'; + +export function registerServerSearchRoute(router: IRouter, data: DataPluginStart) { + router.get( + { + path: SERVER_SEARCH_ROUTE_PATH, + validate: { + query: schema.object({ + index: schema.maybe(schema.string()), + field: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, response) => { + const { index, field } = request.query; + // Run a synchronous search server side, by enforcing a high keepalive and waiting for completion. + // If you wish to run the search with polling (in basic+), you'd have to poll on the search API. + // Please reach out to the @app-arch-team if you need this to be implemented. + const res = await data.search.search( + context, + { + params: { + index, + body: { + aggs: { + '1': { + avg: { + field, + }, + }, + }, + }, + waitForCompletionTimeout: '5m', + keepAlive: '5m', + }, + } as IEsSearchRequest, + {} + ); + + return response.ok({ + body: { + aggs: (res as IEsSearchResponse).rawResponse.aggregations, + }, + }); + } + ); +} diff --git a/examples/search_examples/server/types.ts b/examples/search_examples/server/types.ts new file mode 100644 index 00000000000000..da0f24a3012bb3 --- /dev/null +++ b/examples/search_examples/server/types.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Rename PluginStart to something better +import { PluginSetup, PluginStart } from '../../../src/plugins/data/server'; + +export interface SearchExamplesPluginSetupDeps { + data: PluginSetup; +} + +export interface SearchExamplesPluginStartDeps { + data: PluginStart; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchExamplesPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchExamplesPluginStart {} diff --git a/examples/search_examples/tsconfig.json b/examples/search_examples/tsconfig.json new file mode 100644 index 00000000000000..8a3ced743d0fa3 --- /dev/null +++ b/examples/search_examples/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index db2e31706e95ce..6fc09237687037 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -31,5 +31,13 @@ export interface IEsSearchRequest extends IKibanaSearchRequest { } export interface IEsSearchResponse extends IKibanaSearchResponse { + /** + * Indicates whether async search is still in flight + */ + isRunning?: boolean; + /** + * Indicates whether the results returned are complete or partial + */ + isPartial?: boolean; rawResponse: SearchResponse; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 0f8801c77d033e..a05f61b5c5adfb 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -770,6 +770,8 @@ export interface IEsSearchRequest extends IKibanaSearchRequest { // // @public (undocumented) export interface IEsSearchResponse extends IKibanaSearchResponse { + isPartial?: boolean; + isRunning?: boolean; // (undocumented) rawResponse: SearchResponse_2; } @@ -1223,11 +1225,10 @@ export type InputTimeRange = TimeRange | { // @public (undocumented) export type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) => Observable; -// Warning: (ae-forgotten-export) The symbol "IStrategyOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ISearchGeneric" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ISearchGeneric = (request: IEsSearchRequest, options?: IStrategyOptions) => Observable; +export type ISearchGeneric = (request: IEsSearchRequest, options?: ISearchOptions) => Observable; // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1235,6 +1236,8 @@ export type ISearchGeneric = (request: IEsSearchRequest, options?: IStrategyOpti export interface ISearchOptions { // (undocumented) signal?: AbortSignal; + // (undocumented) + strategy?: string; } // Warning: (ae-forgotten-export) The symbol "SearchSource" needs to be exported by the entry point index.d.ts @@ -1733,7 +1736,7 @@ export class SearchInterceptor { // (undocumented) protected readonly requestTimeout?: number | undefined; // (undocumented) - protected runSearch(request: IEsSearchRequest, signal: AbortSignal): Observable; + protected runSearch(request: IEsSearchRequest, signal: AbortSignal, strategy?: string): Observable; search(request: IEsSearchRequest, options?: ISearchOptions): Observable; // (undocumented) protected setupTimers(options?: ISearchOptions): { diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 8c15d9d6d0152e..53c177de0fa394 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -44,6 +44,7 @@ const createStartContractMock = () => { savedQueries: jest.fn() as any, state$: new Observable(), timefilter: timefilterServiceMock.createStartContract(), + getEsQuery: jest.fn(), }; return startContract; diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index da514c0e24ea44..fe7fdcbb1d113c 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -26,6 +26,9 @@ import { TimefilterService, TimefilterSetup } from './timefilter'; import { createSavedQueryService } from './saved_query/saved_query_service'; import { createQueryStateObservable } from './state_sync/create_global_query_observable'; import { QueryStringManager, QueryStringContract } from './query_string'; +import { buildEsQuery, getEsQueryConfig } from '../../common'; +import { getUiSettings } from '../services'; +import { IndexPattern } from '..'; /** * Query Service @@ -86,6 +89,16 @@ export class QueryService { savedQueries: createSavedQueryService(savedObjectsClient), state$: this.state$, timefilter: this.timefilter, + getEsQuery: (indexPattern: IndexPattern) => { + const timeFilter = this.timefilter.timefilter.createFilter(indexPattern); + + return buildEsQuery( + indexPattern, + this.queryStringManager.getQuery(), + [...this.filterManager.getFilters(), ...(timeFilter ? [timeFilter] : [])], + getEsQueryConfig(getUiSettings()) + ); + }, }; } diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 677ad0ccea6772..d6fcde8e986f36 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -17,11 +17,12 @@ * under the License. */ +import { trimEnd } from 'lodash'; import { BehaviorSubject, throwError, timer, Subscription, defer, from, Observable } from 'rxjs'; import { finalize, filter } from 'rxjs/operators'; import { ApplicationStart, Toast, ToastsStart, CoreStart } from 'kibana/public'; import { getCombinedSignal, AbortError } from '../../common/utils'; -import { IEsSearchRequest, IEsSearchResponse } from '../../common/search'; +import { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY } from '../../common/search'; import { ISearchOptions } from './types'; import { getLongQueryNotification } from './long_query_notification'; import { SearchUsageCollector } from './collectors'; @@ -92,14 +93,20 @@ export class SearchInterceptor { protected runSearch( request: IEsSearchRequest, - signal: AbortSignal + signal: AbortSignal, + strategy?: string ): Observable { const { id, ...searchRequest } = request; - const path = id != null ? `/internal/search/es/${id}` : '/internal/search/es'; - const method = 'POST'; + const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); const body = JSON.stringify(id != null ? {} : searchRequest); - const response = this.deps.http.fetch({ path, method, body, signal }); - return from(response); + return from( + this.deps.http.fetch({ + method: 'POST', + path, + body, + signal, + }) + ); } /** @@ -120,7 +127,7 @@ export class SearchInterceptor { const { combinedSignal, cleanup } = this.setupTimers(options); this.pendingCount$.next(++this.pendingCount); - return this.runSearch(request, combinedSignal).pipe( + return this.runSearch(request, combinedSignal, options?.strategy).pipe( finalize(() => { this.pendingCount$.next(--this.pendingCount); cleanup(); diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index ec74275f35c041..f80a13d048a684 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -37,6 +37,7 @@ import { GetInternalStartServicesFn } from '../types'; export interface ISearchOptions { signal?: AbortSignal; + strategy?: string; } export type ISearch = ( @@ -44,14 +45,9 @@ export type ISearch = ( options?: ISearchOptions ) => Observable; -// Service API types -export interface IStrategyOptions extends ISearchOptions { - strategy?: string; -} - export type ISearchGeneric = ( request: IEsSearchRequest, - options?: IStrategyOptions + options?: ISearchOptions ) => Observable; export interface ISearchStartLegacy { diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 1f3d7fbcb9f0f0..73ed88850d7874 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -161,7 +161,12 @@ import { toAbsoluteDates, } from '../common'; -export { EsaggsExpressionFunctionDefinition, ParsedInterval } from '../common'; +export { + EsaggsExpressionFunctionDefinition, + ParsedInterval, + IEsSearchRequest, + IEsSearchResponse, +} from '../common'; export { ISearchStrategy, diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index bc59bdee6a40a0..2888f9d9d20b5d 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -78,7 +78,7 @@ describe('ES search strategy', () => { }); }); - it('returns total, loaded, and raw response', async () => { + it('has all response parameters', async () => { const params = { index: 'logstash-*' }; const esSearch = await esSearchStrategyProvider(mockConfig$, mockLogger); @@ -86,7 +86,8 @@ describe('ES search strategy', () => { params, }); - expect(response).toHaveProperty('total'); + expect(response.isRunning).toBe(false); + expect(response.isPartial).toBe(false); expect(response).toHaveProperty('loaded'); expect(response).toHaveProperty('rawResponse'); }); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 78ead6df1a44e6..234c30376d6db4 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -30,7 +30,7 @@ export const esSearchStrategyProvider = ( ): ISearchStrategy => { return { search: async (context, request, options) => { - logger.info(`search ${JSON.stringify(request.params)}`); + logger.info(`search ${request.params?.index}`); const config = await config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); @@ -56,7 +56,12 @@ export const esSearchStrategyProvider = ( // The above query will either complete or timeout and throw an error. // There is no progress indication on this api. - return { rawResponse, ...getTotalLoaded(rawResponse._shards) }; + return { + isPartial: false, + isRunning: false, + rawResponse, + ...getTotalLoaded(rawResponse._shards), + }; } catch (e) { if (usage) usage.trackError(); throw e; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index e1ce877ef21ea9..7f26df57666205 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -369,6 +369,30 @@ export function getTotalLoaded({ total, failed, successful }: ShardsResponse): { loaded: number; }; +// Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IEsSearchRequest extends IKibanaSearchRequest { + // (undocumented) + indexType?: string; + // Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts + // + // (undocumented) + params?: ISearchRequestParams; +} + +// Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IEsSearchResponse extends IKibanaSearchResponse { + isPartial?: boolean; + isRunning?: boolean; + // (undocumented) + rawResponse: SearchResponse; +} + // Warning: (ae-missing-release-tag) "IFieldFormatsRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -549,8 +573,6 @@ export interface ISearchSetup { export interface ISearchStart { getSearchStrategy: (name: string) => ISearchStrategy; // Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts // // (undocumented) search: (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise; @@ -562,9 +584,6 @@ export interface ISearchStart { export interface ISearchStrategy { // (undocumented) cancel?: (context: RequestHandlerContext, id: string) => Promise; - // Warning: (ae-forgotten-export) The symbol "IEsSearchRequest" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "IEsSearchResponse" needs to be exported by the entry point index.d.ts - // // (undocumented) search: (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise; } @@ -819,13 +838,13 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:180:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:181:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:182:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:183:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 4d38e5486907c8..1f1cd938c97d1d 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - EnhancedSearchParams, - IEnhancedEsSearchRequest, - IAsyncSearchRequest, - IAsyncSearchResponse, -} from './search'; +export { EnhancedSearchParams, IEnhancedEsSearchRequest, IAsyncSearchRequest } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index 9137e551adeb2d..129e412a47ccf1 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - EnhancedSearchParams, - IEnhancedEsSearchRequest, - IAsyncSearchRequest, - IAsyncSearchResponse, -} from './types'; +export { EnhancedSearchParams, IEnhancedEsSearchRequest, IAsyncSearchRequest } from './types'; diff --git a/x-pack/plugins/data_enhanced/common/search/types.ts b/x-pack/plugins/data_enhanced/common/search/types.ts index c29deee5e4cb31..a5d7d326cecd5b 100644 --- a/x-pack/plugins/data_enhanced/common/search/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/types.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IEsSearchRequest, - IEsSearchResponse, - ISearchRequestParams, -} from '../../../../../src/plugins/data/common'; +import { IEsSearchRequest, ISearchRequestParams } from '../../../../../src/plugins/data/common'; export interface EnhancedSearchParams extends ISearchRequestParams { ignoreThrottled: boolean; @@ -23,17 +19,6 @@ export interface IAsyncSearchRequest extends IEsSearchRequest { params?: EnhancedSearchParams; } -export interface IAsyncSearchResponse extends IEsSearchResponse { - /** - * Indicates whether async search is still in flight - */ - is_running?: boolean; - /** - * Indicates whether the results returned are complete or partial - */ - is_partial?: boolean; -} - export interface IEnhancedEsSearchRequest extends IEsSearchRequest { /** * Used to determine whether to use the _rollups_search or a regular search endpoint. diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index 639f56d0cafca7..d004511fa46749 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -72,8 +72,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, rawResponse: { took: 1, @@ -99,8 +99,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: true, + isPartial: false, + isRunning: true, id: 1, rawResponse: { took: 1, @@ -110,8 +110,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 20, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, rawResponse: { took: 1, @@ -144,8 +144,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: true, - is_running: false, + isPartial: true, + isRunning: false, id: 1, }, }, @@ -168,8 +168,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 500, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, @@ -194,16 +194,16 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: true, + isPartial: false, + isRunning: true, id: 1, }, }, { time: 300, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, @@ -238,8 +238,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 2000, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, @@ -262,16 +262,16 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: true, + isPartial: false, + isRunning: true, id: 1, }, }, { time: 2000, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, @@ -302,8 +302,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: true, + isPartial: false, + isRunning: true, id: 1, }, }, @@ -311,8 +311,8 @@ describe('EnhancedSearchInterceptor', () => { time: 10, value: { error: 'oh no', - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, isError: true, @@ -346,16 +346,16 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, { time: 20, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, }, }, @@ -380,8 +380,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 250, value: { - is_partial: true, - is_running: true, + isPartial: true, + isRunning: true, id: 1, rawResponse: { took: 1, @@ -391,8 +391,8 @@ describe('EnhancedSearchInterceptor', () => { { time: 2000, value: { - is_partial: false, - is_running: false, + isPartial: false, + isRunning: false, id: 1, rawResponse: { took: 1, diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index 9662b9e17248b5..bff9e2cb9048c8 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable, throwError, EMPTY, timer, from } from 'rxjs'; +import { throwError, EMPTY, timer, from } from 'rxjs'; import { mergeMap, expand, takeUntil, finalize, tap } from 'rxjs/operators'; import { getLongQueryNotification } from './long_query_notification'; import { @@ -14,7 +14,7 @@ import { } from '../../../../../src/plugins/data/public'; import { AbortError, toPromise } from '../../../../../src/plugins/data/common'; import { IAsyncSearchOptions } from '.'; -import { IAsyncSearchRequest, IAsyncSearchResponse } from '../../common'; +import { IAsyncSearchRequest } from '../../common'; export class EnhancedSearchInterceptor extends SearchInterceptor { /** @@ -67,7 +67,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { public search( request: IAsyncSearchRequest, { pollInterval = 1000, ...options }: IAsyncSearchOptions = {} - ): Observable { + ) { let { id } = request; request.params = { @@ -80,15 +80,15 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { this.pendingCount$.next(++this.pendingCount); - return (this.runSearch(request, combinedSignal) as Observable).pipe( - expand((response: IAsyncSearchResponse) => { + return this.runSearch(request, combinedSignal, options?.strategy).pipe( + expand((response) => { // If the response indicates of an error, stop polling and complete the observable - if (!response || (!response.is_running && response.is_partial)) { + if (!response || (!response.isRunning && response.isPartial)) { return throwError(new AbortError()); } // If the response indicates it is complete, stop polling and complete the observable - if (!response.is_running) { + if (!response.isRunning) { return EMPTY; } @@ -97,7 +97,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { return timer(pollInterval).pipe( // Send future requests using just the ID from the response mergeMap(() => { - return this.runSearch({ id }, combinedSignal) as Observable; + return this.runSearch({ id }, combinedSignal, options?.strategy); }) ); }), diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 0ed5485cfb6c9c..95f1285363f494 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -128,8 +128,8 @@ async function asyncSearch( return { id, - is_partial, - is_running, + isPartial: is_partial, + isRunning: is_running, rawResponse: shimHitsTotal(response), ...getTotalLoaded(response._shards), };