diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror._constructor_.md index 051414eac7585..5f43f8477cb9f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `PainlessError` class Signature: ```typescript -constructor(err: IEsError, request: IKibanaSearchRequest); +constructor(err: IEsError); ``` ## Parameters @@ -17,5 +17,4 @@ constructor(err: IEsError, request: IKibanaSearchRequest); | Parameter | Type | Description | | --- | --- | --- | | err | IEsError | | -| request | IKibanaSearchRequest | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror.md index 6ab32f3fb1dfa..c77b8b259136b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.painlesserror.md @@ -14,7 +14,7 @@ export declare class PainlessError extends EsError | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(err, request)](./kibana-plugin-plugins-data-public.painlesserror._constructor_.md) | | Constructs a new instance of the PainlessError class | +| [(constructor)(err)](./kibana-plugin-plugins-data-public.painlesserror._constructor_.md) | | Constructs a new instance of the PainlessError class | ## Properties diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md index 1c8b6eb41a72e..b5ac4a4e53887 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md @@ -7,7 +7,7 @@ Signature: ```typescript -protected handleSearchError(e: any, request: IKibanaSearchRequest, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; +protected handleSearchError(e: any, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; ``` ## Parameters @@ -15,7 +15,6 @@ protected handleSearchError(e: any, request: IKibanaSearchRequest, timeoutSignal | Parameter | Type | Description | | --- | --- | --- | | e | any | | -| request | IKibanaSearchRequest | | | timeoutSignal | AbortSignal | | | options | ISearchOptions | | 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 40c7055e4c059..5f266e7d8bd8c 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 @@ -27,7 +27,7 @@ export declare class SearchInterceptor | Method | Modifiers | Description | | --- | --- | --- | | [getTimeoutMode()](./kibana-plugin-plugins-data-public.searchinterceptor.gettimeoutmode.md) | | | -| [handleSearchError(e, request, timeoutSignal, options)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md) | | | +| [handleSearchError(e, timeoutSignal, options)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.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 pendingCount$ when the request is started/finalized. | | [showError(e)](./kibana-plugin-plugins-data-public.searchinterceptor.showerror.md) | | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md index 3d9191196aaf0..19a4bbbbef86c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getdefaultsearchparams.md @@ -7,11 +7,7 @@ Signature: ```typescript -export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<{ - maxConcurrentShardRequests: number | undefined; - ignoreUnavailable: boolean; - trackTotalHits: boolean; -}>; +export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise>; ``` ## Parameters @@ -22,9 +18,5 @@ export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClie Returns: -`Promise<{ - maxConcurrentShardRequests: number | undefined; - ignoreUnavailable: boolean; - trackTotalHits: boolean; -}>` +`Promise>` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md index d7e2a597ff33d..87aa32608eb14 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.getshardtimeout.md @@ -7,11 +7,7 @@ Signature: ```typescript -export declare function getShardTimeout(config: SharedGlobalConfig): { - timeout: string; -} | { - timeout?: undefined; -}; +export declare function getShardTimeout(config: SharedGlobalConfig): Pick; ``` ## Parameters @@ -22,9 +18,5 @@ export declare function getShardTimeout(config: SharedGlobalConfig): { Returns: -`{ - timeout: string; -} | { - timeout?: undefined; -}` +`Pick` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md deleted file mode 100644 index 8e1d5d01bb664..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md) > [id](./kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md) - -## IEsRawSearchResponse.id property - -Signature: - -```typescript -id?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md deleted file mode 100644 index da2a57a84ab2f..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md) > [is\_partial](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md) - -## IEsRawSearchResponse.is\_partial property - -Signature: - -```typescript -is_partial?: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md deleted file mode 100644 index 78b9e07b77890..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md) > [is\_running](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md) - -## IEsRawSearchResponse.is\_running property - -Signature: - -```typescript -is_running?: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.md deleted file mode 100644 index 306c18dea9b0d..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iesrawsearchresponse.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md) - -## IEsRawSearchResponse interface - -Signature: - -```typescript -export interface IEsRawSearchResponse extends SearchResponse -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [id](./kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md) | string | | -| [is\_partial](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md) | boolean | | -| [is\_running](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md) | boolean | | - 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 d9f14950be0e8..c85f294d162bc 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 @@ -34,6 +34,7 @@ | [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | +| [searchUsageObserver(logger, usage)](./kibana-plugin-plugins-data-server.searchusageobserver.md) | Rxjs observer for easily doing tap(searchUsageObserver(logger, usage)) in an rxjs chain. | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | | [usageProvider(core)](./kibana-plugin-plugins-data-server.usageprovider.md) | | @@ -45,7 +46,6 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | -| [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md index 77abcacd7704a..4f8a0beefa421 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.search.md @@ -8,24 +8,6 @@ ```typescript search: { - esSearch: { - utils: { - doSearch: (searchMethod: () => Promise, abortSignal?: AbortSignal | undefined) => import("rxjs").Observable; - shimAbortSignal: >(promise: T, signal: AbortSignal | undefined) => T; - trackSearchStatus: = import("./search").IEsSearchResponse>>(logger: import("src/core/server").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction, import("rxjs").Observable>; - includeTotalLoaded: () => import("rxjs").OperatorFunction>, { - total: number; - loaded: number; - id?: string | undefined; - isRunning?: boolean | undefined; - isPartial?: boolean | undefined; - rawResponse: import("elasticsearch").SearchResponse; - }>; - toKibanaSearchResponse: = import("../common").IEsRawSearchResponse, KibanaResponse_1 extends import("../common").IKibanaSearchResponse = import("../common").IKibanaSearchResponse>() => import("rxjs").OperatorFunction, KibanaResponse_1>; - getTotalLoaded: typeof getTotalLoaded; - toSnakeCase: typeof toSnakeCase; - }; - }; aggs: { CidrMask: typeof CidrMask; dateHistogramInterval: typeof dateHistogramInterval; diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md new file mode 100644 index 0000000000000..5e03bb381527e --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchusageobserver.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [searchUsageObserver](./kibana-plugin-plugins-data-server.searchusageobserver.md) + +## searchUsageObserver() function + +Rxjs observer for easily doing `tap(searchUsageObserver(logger, usage))` in an rxjs chain. + +Signature: + +```typescript +export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage): { + next(response: IEsSearchResponse): void; + error(): void; +}; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| logger | Logger | | +| usage | SearchUsage | | + +Returns: + +`{ + next(response: IEsSearchResponse): void; + error(): void; +}` + diff --git a/src/plugins/data/common/search/es_search/es_search_rxjs_utils.ts b/src/plugins/data/common/search/es_search/es_search_rxjs_utils.ts deleted file mode 100644 index e3238ea62db57..0000000000000 --- a/src/plugins/data/common/search/es_search/es_search_rxjs_utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { from } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import type { SearchResponse } from 'elasticsearch'; -import type { ApiResponse } from '@elastic/elasticsearch'; - -import { shimAbortSignal } from './shim_abort_signal'; -import { getTotalLoaded } from './get_total_loaded'; - -import type { IEsRawSearchResponse } from './types'; -import type { IKibanaSearchResponse } from '../types'; - -export const doSearch = ( - searchMethod: () => Promise, - abortSignal?: AbortSignal -) => from(shimAbortSignal(searchMethod(), abortSignal)); - -export const toKibanaSearchResponse = < - SearchResponse extends IEsRawSearchResponse = IEsRawSearchResponse, - KibanaResponse extends IKibanaSearchResponse = IKibanaSearchResponse ->() => - map, KibanaResponse>( - (response) => - ({ - id: response.body.id, - isPartial: response.body.is_partial || false, - isRunning: response.body.is_running || false, - rawResponse: response.body, - } as KibanaResponse) - ); - -export const includeTotalLoaded = () => - map((response: IKibanaSearchResponse>) => ({ - ...response, - ...getTotalLoaded(response.rawResponse._shards), - })); diff --git a/src/plugins/data/common/search/es_search/get_total_loaded.test.ts b/src/plugins/data/common/search/es_search/get_total_loaded.test.ts deleted file mode 100644 index 74e2873ede762..0000000000000 --- a/src/plugins/data/common/search/es_search/get_total_loaded.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { getTotalLoaded } from './get_total_loaded'; - -describe('getTotalLoaded', () => { - it('returns the total/loaded, not including skipped', () => { - const result = getTotalLoaded({ - successful: 10, - failed: 5, - skipped: 5, - total: 100, - }); - - expect(result).toEqual({ - total: 100, - loaded: 15, - }); - }); -}); diff --git a/src/plugins/data/common/search/es_search/index.ts b/src/plugins/data/common/search/es_search/index.ts index 555667a9f5300..d8f7b5091eb8f 100644 --- a/src/plugins/data/common/search/es_search/index.ts +++ b/src/plugins/data/common/search/es_search/index.ts @@ -18,8 +18,3 @@ */ export * from './types'; -export * from './utils'; -export * from './es_search_rxjs_utils'; -export * from './shim_abort_signal'; -export * from './to_snake_case'; -export * from './get_total_loaded'; diff --git a/src/plugins/data/common/search/es_search/shim_abort_signal.test.ts b/src/plugins/data/common/search/es_search/shim_abort_signal.test.ts deleted file mode 100644 index 61af8b4c782ae..0000000000000 --- a/src/plugins/data/common/search/es_search/shim_abort_signal.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 { shimAbortSignal } from './shim_abort_signal'; - -const createSuccessTransportRequestPromise = ( - body: any, - { statusCode = 200 }: { statusCode?: number } = {} -) => { - const promise = Promise.resolve({ body, statusCode }) as any; - promise.abort = jest.fn(); - - return promise; -}; - -describe('shimAbortSignal', () => { - test('aborts the promise if the signal is aborted', () => { - const promise = createSuccessTransportRequestPromise({ - success: true, - }); - const controller = new AbortController(); - shimAbortSignal(promise, controller.signal); - controller.abort(); - - expect(promise.abort).toHaveBeenCalled(); - }); - - test('returns the original promise', async () => { - const promise = createSuccessTransportRequestPromise({ - success: true, - }); - const controller = new AbortController(); - const response = await shimAbortSignal(promise, controller.signal); - - expect(response).toEqual(expect.objectContaining({ body: { success: true } })); - }); - - test('allows the promise to be aborted manually', () => { - const promise = createSuccessTransportRequestPromise({ - success: true, - }); - const controller = new AbortController(); - const enhancedPromise = shimAbortSignal(promise, controller.signal); - - enhancedPromise.abort(); - expect(promise.abort).toHaveBeenCalled(); - }); -}); diff --git a/src/plugins/data/common/search/es_search/shim_abort_signal.ts b/src/plugins/data/common/search/es_search/shim_abort_signal.ts deleted file mode 100644 index 554a24e268815..0000000000000 --- a/src/plugins/data/common/search/es_search/shim_abort_signal.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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. - */ - -/** - * @internal - * TransportRequestPromise extends base Promise with an "abort" method - */ -export interface TransportRequestPromise extends Promise { - abort?: () => void; -} - -/** - * - * @internal - * NOTE: Temporary workaround until https://github.com/elastic/elasticsearch-js/issues/1297 - * is resolved - * - * @param promise a TransportRequestPromise - * @param signal optional AbortSignal - * - * @returns a TransportRequestPromise that will be aborted if the signal is aborted - */ - -export const shimAbortSignal = >( - promise: T, - signal: AbortSignal | undefined -): T => { - if (signal) { - signal.addEventListener('abort', () => promise.abort && promise.abort()); - } - return promise; -}; diff --git a/src/plugins/data/common/search/es_search/to_snake_case.ts b/src/plugins/data/common/search/es_search/to_snake_case.ts deleted file mode 100644 index b222a56fbf602..0000000000000 --- a/src/plugins/data/common/search/es_search/to_snake_case.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { mapKeys, snakeCase } from 'lodash'; - -export function toSnakeCase(obj: Record): Record { - return mapKeys(obj, (value, key) => snakeCase(key)); -} diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 7d81cf42e1866..7dbbd01d2cdad 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -30,10 +30,4 @@ export interface IEsSearchRequest extends IKibanaSearchRequest extends SearchResponse { - id?: string; - is_partial?: boolean; - is_running?: boolean; -} - export type IEsSearchResponse = IKibanaSearchResponse>; diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index e650cf10db87c..01944d6e37aaf 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -24,3 +24,4 @@ export * from './search_source'; export * from './tabify'; export * from './types'; export * from './session'; +export * from './utils'; diff --git a/src/plugins/data/common/search/utils.test.ts b/src/plugins/data/common/search/utils.test.ts new file mode 100644 index 0000000000000..94f7b14de4bc3 --- /dev/null +++ b/src/plugins/data/common/search/utils.test.ts @@ -0,0 +1,106 @@ +/* + * 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 { isErrorResponse, isCompleteResponse, isPartialResponse } from './utils'; + +describe('utils', () => { + describe('isErrorResponse', () => { + it('returns `true` if the response is undefined', () => { + const isError = isErrorResponse(); + expect(isError).toBe(true); + }); + + it('returns `true` if the response is not running and partial', () => { + const isError = isErrorResponse({ + isPartial: true, + isRunning: false, + rawResponse: {}, + }); + expect(isError).toBe(true); + }); + + it('returns `false` if the response is running and partial', () => { + const isError = isErrorResponse({ + isPartial: true, + isRunning: true, + rawResponse: {}, + }); + expect(isError).toBe(false); + }); + + it('returns `false` if the response is complete', () => { + const isError = isErrorResponse({ + isPartial: false, + isRunning: false, + rawResponse: {}, + }); + expect(isError).toBe(false); + }); + }); + + describe('isCompleteResponse', () => { + it('returns `false` if the response is undefined', () => { + const isError = isCompleteResponse(); + expect(isError).toBe(false); + }); + + it('returns `false` if the response is running and partial', () => { + const isError = isCompleteResponse({ + isPartial: true, + isRunning: true, + rawResponse: {}, + }); + expect(isError).toBe(false); + }); + + it('returns `true` if the response is complete', () => { + const isError = isCompleteResponse({ + isPartial: false, + isRunning: false, + rawResponse: {}, + }); + expect(isError).toBe(true); + }); + }); + + describe('isPartialResponse', () => { + it('returns `false` if the response is undefined', () => { + const isError = isPartialResponse(); + expect(isError).toBe(false); + }); + + it('returns `true` if the response is running and partial', () => { + const isError = isPartialResponse({ + isPartial: true, + isRunning: true, + rawResponse: {}, + }); + expect(isError).toBe(true); + }); + + it('returns `false` if the response is complete', () => { + const isError = isPartialResponse({ + isPartial: false, + isRunning: false, + rawResponse: {}, + }); + expect(isError).toBe(false); + }); + }); +}); diff --git a/src/plugins/data/common/search/es_search/utils.ts b/src/plugins/data/common/search/utils.ts similarity index 96% rename from src/plugins/data/common/search/es_search/utils.ts rename to src/plugins/data/common/search/utils.ts index 6ed222ab0830c..0d544a51c2d45 100644 --- a/src/plugins/data/common/search/es_search/utils.ts +++ b/src/plugins/data/common/search/utils.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { IKibanaSearchResponse } from '../types'; +import type { IKibanaSearchResponse } from './types'; /** * @returns true if response had an error while executing in ES diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 2c47ecb27184d..12d6fc5ad32c7 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1686,7 +1686,7 @@ export interface OptionedValueProp { // @public (undocumented) export class PainlessError extends EsError { // Warning: (ae-forgotten-export) The symbol "IEsError" needs to be exported by the entry point index.d.ts - constructor(err: IEsError, request: IKibanaSearchRequest); + constructor(err: IEsError); // (undocumented) getErrorMessage(application: ApplicationStart): JSX.Element; // (undocumented) @@ -2090,7 +2090,7 @@ export class SearchInterceptor { // (undocumented) protected getTimeoutMode(): TimeoutErrorMode; // (undocumented) - protected handleSearchError(e: any, request: IKibanaSearchRequest, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; + protected handleSearchError(e: any, timeoutSignal: AbortSignal, options?: ISearchOptions): Error; // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) diff --git a/src/plugins/data/public/search/errors/painless_error.tsx b/src/plugins/data/public/search/errors/painless_error.tsx index 282a602d358c7..3cfe9f4278ba0 100644 --- a/src/plugins/data/public/search/errors/painless_error.tsx +++ b/src/plugins/data/public/search/errors/painless_error.tsx @@ -25,11 +25,10 @@ import { ApplicationStart } from 'kibana/public'; import { IEsError, isEsError } from './types'; import { EsError } from './es_error'; import { getRootCause } from './utils'; -import { IKibanaSearchRequest } from '..'; export class PainlessError extends EsError { painlessStack?: string; - constructor(err: IEsError, request: IKibanaSearchRequest) { + constructor(err: IEsError) { super(err); } diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index 60274261da25f..6e75f6e5eef9e 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -65,20 +65,17 @@ describe('SearchInterceptor', () => { test('Renders a PainlessError', async () => { searchInterceptor.showError( - new PainlessError( - { - body: { - attributes: { - error: { - failed_shards: { - reason: 'bananas', - }, + new PainlessError({ + body: { + attributes: { + error: { + failed_shards: { + reason: 'bananas', }, }, - } as any, - }, - {} as any - ) + }, + } as any, + }) ); expect(mockCoreSetup.notifications.toasts.addDanger).toBeCalledTimes(1); expect(mockCoreSetup.notifications.toasts.addError).not.toBeCalled(); diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 3fadb723b27cd..e5abac0d48fef 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -93,12 +93,7 @@ export class SearchInterceptor { * @returns `Error` a search service specific error or the original error, if a specific error can't be recognized. * @internal */ - protected handleSearchError( - e: any, - request: IKibanaSearchRequest, - timeoutSignal: AbortSignal, - options?: ISearchOptions - ): Error { + protected handleSearchError(e: any, timeoutSignal: AbortSignal, options?: ISearchOptions): Error { if (timeoutSignal.aborted || get(e, 'body.message') === 'Request timed out') { // Handle a client or a server side timeout const err = new SearchTimeoutError(e, this.getTimeoutMode()); @@ -112,7 +107,7 @@ export class SearchInterceptor { return e; } else if (isEsError(e)) { if (isPainlessError(e)) { - return new PainlessError(e, request); + return new PainlessError(e); } else { return new EsError(e); } @@ -244,7 +239,7 @@ export class SearchInterceptor { this.pendingCount$.next(this.pendingCount$.getValue() + 1); return from(this.runSearch(request, { ...options, abortSignal: combinedSignal })).pipe( catchError((e: Error) => { - return throwError(this.handleSearchError(e, request, timeoutSignal, options)); + return throwError(this.handleSearchError(e, timeoutSignal, options)); }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index b3fe412152c9d..a233447cdf438 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -156,7 +156,6 @@ export { IndexPatternAttributes, UI_SETTINGS, IndexPattern, - IEsRawSearchResponse, } from '../common'; /** @@ -189,13 +188,6 @@ import { // tabify tabifyAggResponse, tabifyGetColumns, - // search - toSnakeCase, - shimAbortSignal, - doSearch, - includeTotalLoaded, - toKibanaSearchResponse, - getTotalLoaded, calcAutoIntervalLessThan, } from '../common'; @@ -243,27 +235,17 @@ export { SearchStrategyDependencies, getDefaultSearchParams, getShardTimeout, + getTotalLoaded, + toKibanaSearchResponse, shimHitsTotal, usageProvider, + searchUsageObserver, + shimAbortSignal, SearchUsage, } from './search'; -import { trackSearchStatus } from './search'; - // Search namespace export const search = { - esSearch: { - utils: { - doSearch, - shimAbortSignal, - trackSearchStatus, - includeTotalLoaded, - toKibanaSearchResponse, - // utils: - getTotalLoaded, - toSnakeCase, - }, - }, aggs: { CidrMask, dateHistogramInterval, diff --git a/src/plugins/data/server/search/collectors/index.ts b/src/plugins/data/server/search/collectors/index.ts index 417dc1c2012d3..8ad6501d505eb 100644 --- a/src/plugins/data/server/search/collectors/index.ts +++ b/src/plugins/data/server/search/collectors/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { usageProvider, SearchUsage } from './usage'; +export type { SearchUsage } from './usage'; +export { usageProvider, searchUsageObserver } from './usage'; diff --git a/src/plugins/data/server/search/collectors/usage.ts b/src/plugins/data/server/search/collectors/usage.ts index e1be92aa13c37..948175a41cb6b 100644 --- a/src/plugins/data/server/search/collectors/usage.ts +++ b/src/plugins/data/server/search/collectors/usage.ts @@ -17,8 +17,9 @@ * under the License. */ -import { CoreSetup } from 'kibana/server'; -import { Usage } from './register'; +import type { CoreSetup, Logger } from 'kibana/server'; +import type { IEsSearchResponse } from '../../../common'; +import type { Usage } from './register'; const SAVED_OBJECT_ID = 'search-telemetry'; @@ -74,3 +75,19 @@ export function usageProvider(core: CoreSetup): SearchUsage { trackSuccess: getTracker('successCount'), }; } + +/** + * Rxjs observer for easily doing `tap(searchUsageObserver(logger, usage))` in an rxjs chain. + */ +export function searchUsageObserver(logger: Logger, usage?: SearchUsage) { + return { + next(response: IEsSearchResponse) { + logger.debug(`trackSearchStatus:next ${response.rawResponse.took}`); + usage?.trackSuccess(response.rawResponse.took); + }, + error() { + logger.debug(`trackSearchStatus:error`); + usage?.trackError(); + }, + }; +} diff --git a/src/plugins/data/server/search/es_search/es_search_rxjs_utils.ts b/src/plugins/data/server/search/es_search/es_search_rxjs_utils.ts deleted file mode 100644 index 3ba2f9c4b2698..0000000000000 --- a/src/plugins/data/server/search/es_search/es_search_rxjs_utils.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { pipe } from 'rxjs'; -import { tap } from 'rxjs/operators'; - -import type { Logger, SearchResponse } from 'kibana/server'; -import type { SearchUsage } from '../collectors'; -import type { IEsSearchResponse, IKibanaSearchResponse } from '../../../common/search'; - -/** - * trackSearchStatus is a custom rxjs operator that can be used to track the progress of a search. - * @param Logger - * @param SearchUsage - */ -export const trackSearchStatus = < - KibanaResponse extends IKibanaSearchResponse = IEsSearchResponse> ->( - logger: Logger, - usage?: SearchUsage -) => { - return pipe( - tap( - (response: KibanaResponse) => { - const trackSuccessData = response.rawResponse.took; - - if (trackSuccessData !== undefined) { - logger.debug(`trackSearchStatus:next ${trackSuccessData}`); - usage?.trackSuccess(trackSuccessData); - } - }, - (err: any) => { - logger.debug(`trackSearchStatus:error ${err}`); - usage?.trackError(); - } - ) - ); -}; 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 3e2d415eac16f..620df9c8edcb0 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 @@ -16,20 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; - -import type { Logger } from 'kibana/server'; -import type { ApiResponse } from '@elastic/elasticsearch'; -import type { SharedGlobalConfig } from 'kibana/server'; - -import { doSearch, includeTotalLoaded, toKibanaSearchResponse, toSnakeCase } from '../../../common'; -import { trackSearchStatus } from './es_search_rxjs_utils'; -import { getDefaultSearchParams, getShardTimeout } from '../es_search'; - +import { from, Observable } from 'rxjs'; +import { first, tap } from 'rxjs/operators'; +import type { SearchResponse } from 'elasticsearch'; +import type { Logger, SharedGlobalConfig } from 'kibana/server'; import type { ISearchStrategy } from '../types'; -import type { SearchUsage } from '../collectors/usage'; -import type { IEsRawSearchResponse } from '../../../common'; +import type { SearchUsage } from '../collectors'; +import { getDefaultSearchParams, getShardTimeout, shimAbortSignal } from './request_utils'; +import { toKibanaSearchResponse } from './response_utils'; +import { searchUsageObserver } from '../collectors/usage'; export const esSearchStrategyProvider = ( config$: Observable, @@ -43,19 +38,18 @@ export const esSearchStrategyProvider = ( throw new Error(`Unsupported index pattern type ${request.indexType}`); } - return doSearch>(async () => { + const search = async () => { const config = await config$.pipe(first()).toPromise(); - const params = toSnakeCase({ + const params = { ...(await getDefaultSearchParams(uiSettingsClient)), ...getShardTimeout(config), ...request.params, - }); + }; + const promise = esClient.asCurrentUser.search>(params); + const { body } = await shimAbortSignal(promise, abortSignal); + return toKibanaSearchResponse(body); + }; - return esClient.asCurrentUser.search(params); - }, abortSignal).pipe( - toKibanaSearchResponse(), - trackSearchStatus(logger, usage), - includeTotalLoaded() - ); + return from(search()).pipe(tap(searchUsageObserver(logger, usage))); }, }); diff --git a/src/plugins/data/server/search/es_search/get_default_search_params.ts b/src/plugins/data/server/search/es_search/get_default_search_params.ts deleted file mode 100644 index a01b0885abf3b..0000000000000 --- a/src/plugins/data/server/search/es_search/get_default_search_params.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { UI_SETTINGS } from '../../../common/constants'; -import type { SharedGlobalConfig, IUiSettingsClient } from '../../../../../core/server'; - -export function getShardTimeout(config: SharedGlobalConfig) { - const timeout = config.elasticsearch.shardTimeout.asMilliseconds(); - return timeout - ? { - timeout: `${timeout}ms`, - } - : {}; -} - -export async function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient) { - const maxConcurrentShardRequests = await uiSettingsClient.get( - UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS - ); - return { - maxConcurrentShardRequests: - maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined, - ignoreUnavailable: true, // Don't fail if the index/indices don't exist - trackTotalHits: true, - }; -} diff --git a/src/plugins/data/server/search/es_search/index.ts b/src/plugins/data/server/search/es_search/index.ts index 14e8a4e1b0245..f6487e3ef84f5 100644 --- a/src/plugins/data/server/search/es_search/index.ts +++ b/src/plugins/data/server/search/es_search/index.ts @@ -18,7 +18,6 @@ */ export { esSearchStrategyProvider } from './es_search_strategy'; -export * from './get_default_search_params'; -export * from './es_search_rxjs_utils'; - +export * from './request_utils'; +export * from './response_utils'; export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../common'; diff --git a/src/plugins/data/server/search/es_search/request_utils.test.ts b/src/plugins/data/server/search/es_search/request_utils.test.ts new file mode 100644 index 0000000000000..b63a6b3ae7e9b --- /dev/null +++ b/src/plugins/data/server/search/es_search/request_utils.test.ts @@ -0,0 +1,148 @@ +/* + * 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 { getShardTimeout, getDefaultSearchParams, shimAbortSignal } from './request_utils'; +import { IUiSettingsClient, SharedGlobalConfig } from 'kibana/server'; + +const createSuccessTransportRequestPromise = ( + body: any, + { statusCode = 200 }: { statusCode?: number } = {} +) => { + const promise = Promise.resolve({ body, statusCode }) as any; + promise.abort = jest.fn(); + + return promise; +}; + +describe('request utils', () => { + describe('getShardTimeout', () => { + test('returns an empty object if the config does not contain a value', () => { + const result = getShardTimeout(({ + elasticsearch: { + shardTimeout: { + asMilliseconds: jest.fn(), + }, + }, + } as unknown) as SharedGlobalConfig); + expect(result).toEqual({}); + }); + + test('returns an empty object if the config contains 0', () => { + const result = getShardTimeout(({ + elasticsearch: { + shardTimeout: { + asMilliseconds: jest.fn().mockReturnValue(0), + }, + }, + } as unknown) as SharedGlobalConfig); + expect(result).toEqual({}); + }); + + test('returns a duration if the config >= 0', () => { + const result = getShardTimeout(({ + elasticsearch: { + shardTimeout: { + asMilliseconds: jest.fn().mockReturnValue(10), + }, + }, + } as unknown) as SharedGlobalConfig); + expect(result).toEqual({ timeout: '10ms' }); + }); + }); + + describe('getDefaultSearchParams', () => { + describe('max_concurrent_shard_requests', () => { + test('returns value if > 0', async () => { + const result = await getDefaultSearchParams(({ + get: jest.fn().mockResolvedValue(1), + } as unknown) as IUiSettingsClient); + expect(result).toHaveProperty('max_concurrent_shard_requests', 1); + }); + + test('returns undefined if === 0', async () => { + const result = await getDefaultSearchParams(({ + get: jest.fn().mockResolvedValue(0), + } as unknown) as IUiSettingsClient); + expect(result.max_concurrent_shard_requests).toBe(undefined); + }); + + test('returns undefined if undefined', async () => { + const result = await getDefaultSearchParams(({ + get: jest.fn(), + } as unknown) as IUiSettingsClient); + expect(result.max_concurrent_shard_requests).toBe(undefined); + }); + }); + + describe('other defaults', () => { + test('returns ignore_unavailable and track_total_hits', async () => { + const result = await getDefaultSearchParams(({ + get: jest.fn(), + } as unknown) as IUiSettingsClient); + expect(result).toHaveProperty('ignore_unavailable', true); + expect(result).toHaveProperty('track_total_hits', true); + }); + }); + }); + + describe('shimAbortSignal', () => { + test('aborts the promise if the signal is already aborted', async () => { + const promise = createSuccessTransportRequestPromise({ + success: true, + }); + const controller = new AbortController(); + controller.abort(); + shimAbortSignal(promise, controller.signal); + + expect(promise.abort).toHaveBeenCalled(); + }); + + test('aborts the promise if the signal is aborted', () => { + const promise = createSuccessTransportRequestPromise({ + success: true, + }); + const controller = new AbortController(); + shimAbortSignal(promise, controller.signal); + controller.abort(); + + expect(promise.abort).toHaveBeenCalled(); + }); + + test('returns the original promise', async () => { + const promise = createSuccessTransportRequestPromise({ + success: true, + }); + const controller = new AbortController(); + const response = await shimAbortSignal(promise, controller.signal); + + expect(response).toEqual(expect.objectContaining({ body: { success: true } })); + }); + + test('allows the promise to be aborted manually', () => { + const promise = createSuccessTransportRequestPromise({ + success: true, + }); + const controller = new AbortController(); + const enhancedPromise = shimAbortSignal(promise, controller.signal); + + enhancedPromise.abort(); + expect(promise.abort).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/plugins/data/server/search/es_search/request_utils.ts b/src/plugins/data/server/search/es_search/request_utils.ts new file mode 100644 index 0000000000000..03b7db7da8ffe --- /dev/null +++ b/src/plugins/data/server/search/es_search/request_utils.ts @@ -0,0 +1,66 @@ +/* + * 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 type { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import type { Search } from '@elastic/elasticsearch/api/requestParams'; +import type { IUiSettingsClient, SharedGlobalConfig } from 'kibana/server'; +import { UI_SETTINGS } from '../../../common'; + +export function getShardTimeout(config: SharedGlobalConfig): Pick { + const timeout = config.elasticsearch.shardTimeout.asMilliseconds(); + return timeout ? { timeout: `${timeout}ms` } : {}; +} + +export async function getDefaultSearchParams( + uiSettingsClient: IUiSettingsClient +): Promise< + Pick +> { + const maxConcurrentShardRequests = await uiSettingsClient.get( + UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS + ); + return { + max_concurrent_shard_requests: + maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined, + ignore_unavailable: true, // Don't fail if the index/indices don't exist + track_total_hits: true, + }; +} + +/** + * Temporary workaround until https://github.com/elastic/elasticsearch-js/issues/1297 is resolved. + * Shims the `AbortSignal` behavior so that, if the given `signal` aborts, the `abort` method on the + * `TransportRequestPromise` is called, actually performing the cancellation. + * @internal + */ +export const shimAbortSignal = (promise: TransportRequestPromise, signal?: AbortSignal) => { + if (!signal) return promise; + const abortHandler = () => { + promise.abort(); + cleanup(); + }; + const cleanup = () => signal.removeEventListener('abort', abortHandler); + if (signal.aborted) { + promise.abort(); + } else { + signal.addEventListener('abort', abortHandler); + promise.then(cleanup, cleanup); + } + return promise; +}; diff --git a/src/plugins/data/server/search/es_search/response_utils.test.ts b/src/plugins/data/server/search/es_search/response_utils.test.ts new file mode 100644 index 0000000000000..f93625980a69c --- /dev/null +++ b/src/plugins/data/server/search/es_search/response_utils.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { getTotalLoaded, toKibanaSearchResponse } from './response_utils'; +import { SearchResponse } from 'elasticsearch'; + +describe('response utils', () => { + describe('getTotalLoaded', () => { + it('returns the total/loaded, not including skipped', () => { + const result = getTotalLoaded(({ + _shards: { + successful: 10, + failed: 5, + skipped: 5, + total: 100, + }, + } as unknown) as SearchResponse); + + expect(result).toEqual({ + total: 100, + loaded: 15, + }); + }); + }); + + describe('toKibanaSearchResponse', () => { + it('returns rawResponse, isPartial, isRunning, total, and loaded', () => { + const result = toKibanaSearchResponse(({ + _shards: { + successful: 10, + failed: 5, + skipped: 5, + total: 100, + }, + } as unknown) as SearchResponse); + + expect(result).toEqual({ + rawResponse: { + _shards: { + successful: 10, + failed: 5, + skipped: 5, + total: 100, + }, + }, + isRunning: false, + isPartial: false, + total: 100, + loaded: 15, + }); + }); + }); +}); diff --git a/src/plugins/data/common/search/es_search/get_total_loaded.ts b/src/plugins/data/server/search/es_search/response_utils.ts similarity index 69% rename from src/plugins/data/common/search/es_search/get_total_loaded.ts rename to src/plugins/data/server/search/es_search/response_utils.ts index 233bcf8186666..2f502f55057b8 100644 --- a/src/plugins/data/common/search/es_search/get_total_loaded.ts +++ b/src/plugins/data/server/search/es_search/response_utils.ts @@ -17,14 +17,28 @@ * under the License. */ -import type { ShardsResponse } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; /** * Get the `total`/`loaded` for this response (see `IKibanaSearchResponse`). Note that `skipped` is * not included as it is already included in `successful`. * @internal */ -export function getTotalLoaded({ total, failed, successful }: ShardsResponse) { +export function getTotalLoaded(response: SearchResponse) { + const { total, failed, successful } = response._shards; const loaded = failed + successful; return { total, loaded }; } + +/** + * Get the Kibana representation of this response (see `IKibanaSearchResponse`). + * @internal + */ +export function toKibanaSearchResponse(rawResponse: SearchResponse) { + return { + rawResponse, + isPartial: false, + isRunning: false, + ...getTotalLoaded(rawResponse), + }; +} diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 1be641401b29c..3001bbe3c2f38 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -19,6 +19,6 @@ export * from './types'; export * from './es_search'; -export { usageProvider, SearchUsage } from './collectors'; +export { usageProvider, SearchUsage, searchUsageObserver } from './collectors'; export * from './aggs'; export { shimHitsTotal } from './routes'; diff --git a/src/plugins/data/server/search/routes/call_msearch.ts b/src/plugins/data/server/search/routes/call_msearch.ts index 603b3ed867b23..923369297889b 100644 --- a/src/plugins/data/server/search/routes/call_msearch.ts +++ b/src/plugins/data/server/search/routes/call_msearch.ts @@ -24,9 +24,8 @@ import { SearchResponse } from 'elasticsearch'; import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server'; import type { MsearchRequestBody, MsearchResponse } from '../../../common/search/search_source'; -import { toSnakeCase, shimAbortSignal } from '../../../common/search/es_search'; import { shimHitsTotal } from './shim_hits_total'; -import { getShardTimeout, getDefaultSearchParams } from '..'; +import { getShardTimeout, getDefaultSearchParams, shimAbortSignal } from '..'; /** @internal */ export function convertRequestBody( @@ -71,7 +70,7 @@ export function getCallMsearch(dependencies: CallMsearchDependencies) { const timeout = getShardTimeout(config); // trackTotalHits is not supported by msearch - const { trackTotalHits, ...defaultParams } = await getDefaultSearchParams(uiSettings); + const { track_total_hits: _, ...defaultParams } = await getDefaultSearchParams(uiSettings); const body = convertRequestBody(params.body, timeout); @@ -81,7 +80,7 @@ export function getCallMsearch(dependencies: CallMsearchDependencies) { body, }, { - querystring: toSnakeCase(defaultParams), + querystring: defaultParams, } ), params.signal diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 6870ad5e2402f..73e2a68cb9667 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -34,6 +34,7 @@ import { IScopedClusterClient } from 'src/core/server'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'src/core/server'; +import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/server'; import { KibanaRequest } from 'src/core/server'; import { LegacyAPICaller } from 'src/core/server'; import { Logger } from 'src/core/server'; @@ -58,8 +59,9 @@ import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'kiba import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; -import { ShardsResponse } from 'elasticsearch'; +import { SharedGlobalConfig as SharedGlobalConfig_2 } from 'kibana/server'; import { ToastInputFields } from 'src/core/public/notifications'; +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UiStatsMetricType } from '@kbn/analytics'; @@ -410,25 +412,15 @@ export function getCapabilitiesForRollupIndices(indices: { [key: string]: any; }; -// Warning: (ae-forgotten-export) The symbol "IUiSettingsClient" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getDefaultSearchParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient_2): Promise<{ - maxConcurrentShardRequests: number | undefined; - ignoreUnavailable: boolean; - trackTotalHits: boolean; -}>; +export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient_3): Promise>; -// Warning: (ae-forgotten-export) The symbol "SharedGlobalConfig" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getShardTimeout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function getShardTimeout(config: SharedGlobalConfig): { - timeout: string; -} | { - timeout?: undefined; -}; +export function getShardTimeout(config: SharedGlobalConfig_2): Pick; // Warning: (ae-forgotten-export) The symbol "IIndexPattern" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -439,6 +431,12 @@ export function getTime(indexPattern: IIndexPattern | undefined, timeRange: Time fieldName?: string; }): import("../..").RangeFilter | undefined; +// @internal +export function getTotalLoaded(response: SearchResponse): { + total: number; + loaded: number; +}; + // Warning: (ae-missing-release-tag) "IAggConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -455,18 +453,6 @@ export type IAggConfigs = AggConfigs; // @public (undocumented) export type IAggType = AggType; -// Warning: (ae-missing-release-tag) "IEsRawSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface IEsRawSearchResponse extends SearchResponse { - // (undocumented) - id?: string; - // (undocumented) - is_partial?: boolean; - // (undocumented) - is_running?: boolean; -} - // Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" 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) @@ -1040,24 +1026,6 @@ export interface RefreshInterval { // // @public (undocumented) export const search: { - esSearch: { - utils: { - doSearch: (searchMethod: () => Promise, abortSignal?: AbortSignal | undefined) => import("rxjs").Observable; - shimAbortSignal: >(promise: T, signal: AbortSignal | undefined) => T; - trackSearchStatus: = import("./search").IEsSearchResponse>>(logger: import("src/core/server").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction, import("rxjs").Observable>; - includeTotalLoaded: () => import("rxjs").OperatorFunction>, { - total: number; - loaded: number; - id?: string | undefined; - isRunning?: boolean | undefined; - isPartial?: boolean | undefined; - rawResponse: import("elasticsearch").SearchResponse; - }>; - toKibanaSearchResponse: = import("../common").IEsRawSearchResponse, KibanaResponse_1 extends import("../common").IKibanaSearchResponse = import("../common").IKibanaSearchResponse>() => import("rxjs").OperatorFunction, KibanaResponse_1>; - getTotalLoaded: typeof getTotalLoaded; - toSnakeCase: typeof toSnakeCase; - }; - }; aggs: { CidrMask: typeof CidrMask; dateHistogramInterval: typeof dateHistogramInterval; @@ -1114,6 +1082,17 @@ export interface SearchUsage { trackSuccess(duration: number): Promise; } +// Warning: (ae-missing-release-tag) "searchUsageObserver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage): { + next(response: IEsSearchResponse): void; + error(): void; +}; + +// @internal +export const shimAbortSignal: (promise: TransportRequestPromise, signal?: AbortSignal | undefined) => TransportRequestPromise; + // @internal export function shimHitsTotal(response: SearchResponse): { hits: { @@ -1176,6 +1155,15 @@ export type TimeRange = { mode?: 'absolute' | 'relative'; }; +// @internal +export function toKibanaSearchResponse(rawResponse: SearchResponse): { + total: number; + loaded: number; + rawResponse: SearchResponse; + isPartial: boolean; + isRunning: boolean; +}; + // Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1247,22 +1235,20 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:111:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:137:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:137:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:269:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:270:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:274:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:275:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:284:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:285:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:286:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:290:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:291:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:295:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:298:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:299:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:250:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:260:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:274:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:275:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:58:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts // src/plugins/data/server/search/types.ts:104:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 61767af030803..dd1a2d39ab5d1 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -10,9 +10,6 @@ export { EqlRequestParams, EqlSearchStrategyRequest, EqlSearchStrategyResponse, - IAsyncSearchRequest, - IEnhancedEsSearchRequest, IAsyncSearchOptions, - doPartialSearch, - throwOnEsError, + pollSearch, } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts b/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts deleted file mode 100644 index 8b25a59ed857a..0000000000000 --- a/x-pack/plugins/data_enhanced/common/search/es_search/es_search_rxjs_utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { of, merge, timer, throwError } from 'rxjs'; -import { map, takeWhile, switchMap, expand, mergeMap, tap } from 'rxjs/operators'; -import { ApiResponse } from '@elastic/elasticsearch'; - -import { - doSearch, - IKibanaSearchResponse, - isErrorResponse, -} from '../../../../../../src/plugins/data/common'; -import { AbortError } from '../../../../../../src/plugins/kibana_utils/common'; -import type { IKibanaSearchRequest } from '../../../../../../src/plugins/data/common'; -import type { IAsyncSearchOptions } from '../../../common/search/types'; - -const DEFAULT_POLLING_INTERVAL = 1000; - -export const doPartialSearch = ( - searchMethod: () => Promise, - partialSearchMethod: (id: IKibanaSearchRequest['id']) => Promise, - isCompleteResponse: (response: SearchResponse) => boolean, - getId: (response: SearchResponse) => IKibanaSearchRequest['id'], - requestId: IKibanaSearchRequest['id'], - { abortSignal, pollInterval = DEFAULT_POLLING_INTERVAL }: IAsyncSearchOptions -) => - doSearch( - requestId ? () => partialSearchMethod(requestId) : searchMethod, - abortSignal - ).pipe( - tap((response) => (requestId = getId(response))), - expand(() => timer(pollInterval).pipe(switchMap(() => partialSearchMethod(requestId)))), - takeWhile((response) => !isCompleteResponse(response), true) - ); - -export const normalizeEqlResponse = () => - map((eqlResponse) => ({ - ...eqlResponse, - body: { - ...eqlResponse.body, - ...eqlResponse, - }, - })); - -export const throwOnEsError = () => - mergeMap((r: IKibanaSearchResponse) => - isErrorResponse(r) ? merge(of(r), throwError(new AbortError())) : of(r) - ); diff --git a/x-pack/plugins/data_enhanced/common/search/es_search/index.ts b/x-pack/plugins/data_enhanced/common/search/es_search/index.ts deleted file mode 100644 index bbf9f14ba63c2..0000000000000 --- a/x-pack/plugins/data_enhanced/common/search/es_search/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './es_search_rxjs_utils'; diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index 44f82386e35c3..34bb21cb91af1 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -5,4 +5,4 @@ */ export * from './types'; -export * from './es_search'; +export * from './poll_search'; diff --git a/x-pack/plugins/data_enhanced/common/search/poll_search.ts b/x-pack/plugins/data_enhanced/common/search/poll_search.ts new file mode 100644 index 0000000000000..c0e289c691cfd --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/poll_search.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { from, NEVER, Observable, timer } from 'rxjs'; +import { expand, finalize, switchMap, takeUntil, takeWhile, tap } from 'rxjs/operators'; +import type { IKibanaSearchResponse } from '../../../../../src/plugins/data/common'; +import { isErrorResponse, isPartialResponse } from '../../../../../src/plugins/data/common'; +import { AbortError, abortSignalToPromise } from '../../../../../src/plugins/kibana_utils/common'; +import type { IAsyncSearchOptions } from './types'; + +export const pollSearch = ( + search: () => Promise, + { pollInterval = 1000, ...options }: IAsyncSearchOptions = {} +): Observable => { + const aborted = options?.abortSignal + ? abortSignalToPromise(options?.abortSignal) + : { promise: NEVER, cleanup: () => {} }; + + return from(search()).pipe( + expand(() => timer(pollInterval).pipe(switchMap(search))), + tap((response) => { + if (isErrorResponse(response)) throw new AbortError(); + }), + takeWhile(isPartialResponse, true), + takeUntil(from(aborted.promise)), + finalize(aborted.cleanup) + ); +}; diff --git a/x-pack/plugins/data_enhanced/common/search/types.ts b/x-pack/plugins/data_enhanced/common/search/types.ts index 4abf8351114f8..f017462d4050b 100644 --- a/x-pack/plugins/data_enhanced/common/search/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/types.ts @@ -9,27 +9,12 @@ import { ApiResponse, TransportRequestOptions } from '@elastic/elasticsearch/lib import { ISearchOptions, - IEsSearchRequest, IKibanaSearchRequest, IKibanaSearchResponse, } from '../../../../../src/plugins/data/common'; export const ENHANCED_ES_SEARCH_STRATEGY = 'ese'; -export interface IAsyncSearchRequest extends IEsSearchRequest { - /** - * The ID received from the response from the initial request - */ - id?: string; -} - -export interface IEnhancedEsSearchRequest extends IEsSearchRequest { - /** - * Used to determine whether to use the _rollups_search or a regular search endpoint. - */ - isRollup?: boolean; -} - export const EQL_SEARCH_STRATEGY = 'eql'; export type EqlRequestParams = EqlSearch>; 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 044489d58eb0e..3f1cfc7a010c7 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 @@ -117,7 +117,7 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - isPartial: false, + isPartial: true, isRunning: true, id: 1, rawResponse: { @@ -175,8 +175,6 @@ describe('EnhancedSearchInterceptor', () => { await timeTravel(10); - expect(next).toHaveBeenCalled(); - expect(next.mock.calls[0][0]).toStrictEqual(responses[0].value); expect(error).toHaveBeenCalled(); expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError); }); @@ -212,7 +210,7 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - isPartial: false, + isPartial: true, isRunning: true, id: 1, }, @@ -280,7 +278,7 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - isPartial: false, + isPartial: true, isRunning: true, id: 1, }, @@ -320,7 +318,7 @@ describe('EnhancedSearchInterceptor', () => { { time: 10, value: { - isPartial: false, + isPartial: true, isRunning: true, id: 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 e1bd71caddb4d..9aa35b460b1e8 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -4,24 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { throwError, from, Subscription } from 'rxjs'; -import { tap, takeUntil, finalize, catchError } from 'rxjs/operators'; +import { throwError, Subscription } from 'rxjs'; +import { tap, finalize, catchError } from 'rxjs/operators'; import { TimeoutErrorMode, - IEsSearchResponse, SearchInterceptor, SearchInterceptorDeps, UI_SETTINGS, + IKibanaSearchRequest, } from '../../../../../src/plugins/data/public'; -import { AbortError, abortSignalToPromise } from '../../../../../src/plugins/kibana_utils/public'; - -import { - IAsyncSearchRequest, - ENHANCED_ES_SEARCH_STRATEGY, - IAsyncSearchOptions, - doPartialSearch, - throwOnEsError, -} from '../../common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common'; export class EnhancedSearchInterceptor extends SearchInterceptor { private uiSettingsSub: Subscription; @@ -60,49 +53,26 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { if (this.deps.usageCollector) this.deps.usageCollector.trackQueriesCancelled(); }; - public search( - request: IAsyncSearchRequest, - { pollInterval = 1000, ...options }: IAsyncSearchOptions = {} - ) { - let { id } = request; - + public search({ id, ...request }: IKibanaSearchRequest, options: IAsyncSearchOptions = {}) { const { combinedSignal, timeoutSignal, cleanup } = this.setupAbortSignal({ abortSignal: options.abortSignal, timeout: this.searchTimeout, }); - const abortedPromise = abortSignalToPromise(combinedSignal); const strategy = options?.strategy ?? ENHANCED_ES_SEARCH_STRATEGY; + const searchOptions = { ...options, strategy, abortSignal: combinedSignal }; + const search = () => this.runSearch({ id, ...request }, searchOptions); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - return doPartialSearch( - () => this.runSearch(request, { ...options, strategy, abortSignal: combinedSignal }), - (requestId) => - this.runSearch( - { ...request, id: requestId }, - { ...options, strategy, abortSignal: combinedSignal } - ), - (r) => !r.isRunning, - (response) => response.id, - id, - { pollInterval } - ).pipe( - tap((r) => { - id = r.id ?? id; - }), - throwOnEsError(), - takeUntil(from(abortedPromise.promise)), + return pollSearch(search, { ...options, abortSignal: combinedSignal }).pipe( + tap((response) => (id = response.id)), catchError((e: AbortError) => { - if (id) { - this.deps.http.delete(`/internal/search/${strategy}/${id}`); - } - - return throwError(this.handleSearchError(e, request, timeoutSignal, options)); + if (id) this.deps.http.delete(`/internal/search/${strategy}/${id}`); + return throwError(this.handleSearchError(e, timeoutSignal, options)); }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); cleanup(); - abortedPromise.cleanup(); }) ); } diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts index cd94d91db8c5e..f2d7725954a26 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts @@ -178,7 +178,7 @@ describe('EQL search strategy', () => { expect(requestOptions).toEqual( expect.objectContaining({ - max_retries: 2, + maxRetries: 2, ignore: [300], }) ); diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts index 7b3d0db450b04..26325afc378f7 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts @@ -4,21 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { tap } from 'rxjs/operators'; import type { Logger } from 'kibana/server'; -import type { ApiResponse } from '@elastic/elasticsearch'; - -import { search } from '../../../../../src/plugins/data/server'; -import { - doPartialSearch, - normalizeEqlResponse, -} from '../../common/search/es_search/es_search_rxjs_utils'; -import { getAsyncOptions, getDefaultSearchParams } from './get_default_search_params'; - -import type { ISearchStrategy, IEsRawSearchResponse } from '../../../../../src/plugins/data/server'; +import type { ISearchStrategy } from '../../../../../src/plugins/data/server'; import type { EqlSearchStrategyRequest, EqlSearchStrategyResponse, -} from '../../common/search/types'; + IAsyncSearchOptions, +} from '../../common'; +import { getDefaultSearchParams, shimAbortSignal } from '../../../../../src/plugins/data/server'; +import { pollSearch } from '../../common'; +import { getDefaultAsyncGetParams, getIgnoreThrottled } from './request_utils'; +import { toEqlKibanaSearchResponse } from './response_utils'; +import { EqlSearchResponse } from './types'; export const eqlSearchStrategyProvider = ( logger: Logger @@ -26,48 +24,37 @@ export const eqlSearchStrategyProvider = ( return { cancel: async (id, options, { esClient }) => { logger.debug(`_eql/delete ${id}`); - await esClient.asCurrentUser.eql.delete({ - id, - }); + await esClient.asCurrentUser.eql.delete({ id }); }, - search: (request, options, { esClient, uiSettingsClient }) => { - logger.debug(`_eql/search ${JSON.stringify(request.params) || request.id}`); + search: ({ id, ...request }, options: IAsyncSearchOptions, { esClient, uiSettingsClient }) => { + logger.debug(`_eql/search ${JSON.stringify(request.params) || id}`); - const { utils } = search.esSearch; - const asyncOptions = getAsyncOptions(); - const requestOptions = utils.toSnakeCase({ ...request.options }); const client = esClient.asCurrentUser.eql; - return doPartialSearch>( - async () => { - const { ignoreThrottled, ignoreUnavailable } = await getDefaultSearchParams( - uiSettingsClient - ); - - return client.search( - utils.toSnakeCase({ - ignoreThrottled, - ignoreUnavailable, - ...asyncOptions, + const search = async () => { + const { track_total_hits: _, ...defaultParams } = await getDefaultSearchParams( + uiSettingsClient + ); + const params = id + ? getDefaultAsyncGetParams() + : { + ...(await getIgnoreThrottled(uiSettingsClient)), + ...defaultParams, + ...getDefaultAsyncGetParams(), ...request.params, - }) as EqlSearchStrategyRequest['params'], - requestOptions - ); - }, - (id) => - client.get( - { - id: id!, - ...utils.toSnakeCase(asyncOptions), - }, - requestOptions - ), - (response) => !response.body.is_running, - (response) => response.body.id, - request.id, - options - ).pipe(normalizeEqlResponse(), utils.toKibanaSearchResponse()); + }; + const promise = id + ? client.get({ ...params, id }, request.options) + : client.search( + params as EqlSearchStrategyRequest['params'], + request.options + ); + const response = await shimAbortSignal(promise, options.abortSignal); + return toEqlKibanaSearchResponse(response); + }; + + return pollSearch(search, options).pipe(tap((response) => (id = response.id))); }, }; }; 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 2070610ceb20e..e1c7d7b5fc22e 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 @@ -4,86 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ +import type { Observable } from 'rxjs'; +import type { Logger, SharedGlobalConfig } from 'kibana/server'; +import { first, tap } from 'rxjs/operators'; +import { SearchResponse } from 'elasticsearch'; import { from } from 'rxjs'; -import { first, map } from 'rxjs/operators'; -import { Observable } from 'rxjs'; - -import type { SearchResponse } from 'elasticsearch'; -import type { ApiResponse } from '@elastic/elasticsearch'; - -import { - getShardTimeout, - shimHitsTotal, - search, - SearchStrategyDependencies, -} from '../../../../../src/plugins/data/server'; -import { doPartialSearch } from '../../common/search/es_search/es_search_rxjs_utils'; -import { getDefaultSearchParams, getAsyncOptions } from './get_default_search_params'; - -import type { SharedGlobalConfig, Logger } from '../../../../../src/core/server'; - import type { + IEsSearchRequest, + IEsSearchResponse, + ISearchOptions, ISearchStrategy, + SearchStrategyDependencies, SearchUsage, - IEsRawSearchResponse, - ISearchOptions, - IEsSearchResponse, } from '../../../../../src/plugins/data/server'; - -import type { IEnhancedEsSearchRequest } from '../../common'; - -const { utils } = search.esSearch; - -interface IEsRawAsyncSearchResponse extends IEsRawSearchResponse { - response: SearchResponse; -} +import { + getDefaultSearchParams, + getShardTimeout, + getTotalLoaded, + searchUsageObserver, + shimAbortSignal, +} from '../../../../../src/plugins/data/server'; +import type { IAsyncSearchOptions } from '../../common'; +import { pollSearch } from '../../common'; +import { + getDefaultAsyncGetParams, + getDefaultAsyncSubmitParams, + getIgnoreThrottled, +} from './request_utils'; +import { toAsyncKibanaSearchResponse } from './response_utils'; +import { AsyncSearchResponse } from './types'; export const enhancedEsSearchStrategyProvider = ( config$: Observable, logger: Logger, usage?: SearchUsage -): ISearchStrategy => { +): ISearchStrategy => { function asyncSearch( - request: IEnhancedEsSearchRequest, - options: ISearchOptions, + { id, ...request }: IEsSearchRequest, + options: IAsyncSearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ) { - const asyncOptions = getAsyncOptions(); const client = esClient.asCurrentUser.asyncSearch; - return doPartialSearch>( - async () => - client.submit( - utils.toSnakeCase({ - ...(await getDefaultSearchParams(uiSettingsClient)), - batchedReduceSize: 64, - keepOnCompletion: !!options.sessionId, // Always return an ID, even if the request completes quickly - ...asyncOptions, - ...request.params, - }) - ), - (id) => - client.get({ - id: id!, - ...utils.toSnakeCase({ ...asyncOptions }), - }), - (response) => !response.body.is_running, - (response) => response.body.id, - request.id, - options - ).pipe( - utils.toKibanaSearchResponse(), - map((response) => ({ - ...response, - rawResponse: shimHitsTotal(response.rawResponse.response!), - })), - utils.trackSearchStatus(logger, usage), - utils.includeTotalLoaded() + const search = async () => { + const params = id + ? getDefaultAsyncGetParams() + : { ...(await getDefaultAsyncSubmitParams(uiSettingsClient, options)), ...request.params }; + const promise = id + ? client.get({ ...params, id }) + : client.submit(params); + const { body } = await shimAbortSignal(promise, options.abortSignal); + return toAsyncKibanaSearchResponse(body); + }; + + return pollSearch(search, options).pipe( + tap((response) => (id = response.id)), + tap(searchUsageObserver(logger, usage)) ); } async function rollupSearch( - request: IEnhancedEsSearchRequest, + request: IEsSearchRequest, options: ISearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ): Promise { @@ -91,11 +72,12 @@ export const enhancedEsSearchStrategyProvider = ( const { body, index, ...params } = request.params!; const method = 'POST'; const path = encodeURI(`/${index}/_rollup_search`); - const querystring = utils.toSnakeCase({ + const querystring = { ...getShardTimeout(config), + ...(await getIgnoreThrottled(uiSettingsClient)), ...(await getDefaultSearchParams(uiSettingsClient)), ...params, - }); + }; const promise = esClient.asCurrentUser.transport.request({ method, @@ -104,17 +86,16 @@ export const enhancedEsSearchStrategyProvider = ( querystring, }); - const esResponse = await utils.shimAbortSignal(promise, options?.abortSignal); - + const esResponse = await shimAbortSignal(promise, options?.abortSignal); const response = esResponse.body as SearchResponse; return { rawResponse: response, - ...utils.getTotalLoaded(response._shards), + ...getTotalLoaded(response), }; } return { - search: (request, options, deps) => { + search: (request, options: IAsyncSearchOptions, deps) => { logger.debug(`search ${JSON.stringify(request.params) || request.id}`); return request.indexType !== 'rollup' diff --git a/x-pack/plugins/data_enhanced/server/search/get_default_search_params.ts b/x-pack/plugins/data_enhanced/server/search/get_default_search_params.ts deleted file mode 100644 index fdda78798808f..0000000000000 --- a/x-pack/plugins/data_enhanced/server/search/get_default_search_params.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { IUiSettingsClient } from 'src/core/server'; -import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; - -import { getDefaultSearchParams as getBaseSearchParams } from '../../../../../src/plugins/data/server'; - -/** - @internal - */ -export async function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient) { - const ignoreThrottled = !(await uiSettingsClient.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN)); - - return { - ignoreThrottled, - ...(await getBaseSearchParams(uiSettingsClient)), - }; -} - -/** - @internal - */ -export const getAsyncOptions = (): { - waitForCompletionTimeout: string; - keepAlive: string; -} => ({ - waitForCompletionTimeout: '100ms', // Wait up to 100ms for the response to return - keepAlive: '1m', // Extend the TTL for this search request by one minute, -}); diff --git a/x-pack/plugins/data_enhanced/server/search/request_utils.ts b/x-pack/plugins/data_enhanced/server/search/request_utils.ts new file mode 100644 index 0000000000000..f54ab2199c905 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/request_utils.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IUiSettingsClient } from 'kibana/server'; +import { + AsyncSearchGet, + AsyncSearchSubmit, + Search, +} from '@elastic/elasticsearch/api/requestParams'; +import { ISearchOptions, UI_SETTINGS } from '../../../../../src/plugins/data/common'; +import { getDefaultSearchParams } from '../../../../../src/plugins/data/server'; + +/** + * @internal + */ +export async function getIgnoreThrottled( + uiSettingsClient: IUiSettingsClient +): Promise> { + const includeFrozen = await uiSettingsClient.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); + return { ignore_throttled: !includeFrozen }; +} + +/** + @internal + */ +export async function getDefaultAsyncSubmitParams( + uiSettingsClient: IUiSettingsClient, + options: ISearchOptions +): Promise< + Pick< + AsyncSearchSubmit, + | 'batched_reduce_size' + | 'keep_alive' + | 'wait_for_completion_timeout' + | 'ignore_throttled' + | 'max_concurrent_shard_requests' + | 'ignore_unavailable' + | 'track_total_hits' + | 'keep_on_completion' + > +> { + return { + batched_reduce_size: 64, + keep_on_completion: !!options.sessionId, // Always return an ID, even if the request completes quickly + ...getDefaultAsyncGetParams(), + ...(await getIgnoreThrottled(uiSettingsClient)), + ...(await getDefaultSearchParams(uiSettingsClient)), + }; +} + +/** + @internal + */ +export function getDefaultAsyncGetParams(): Pick< + AsyncSearchGet, + 'keep_alive' | 'wait_for_completion_timeout' +> { + return { + keep_alive: '1m', // Extend the TTL for this search request by one minute + wait_for_completion_timeout: '100ms', // Wait up to 100ms for the response to return + }; +} diff --git a/x-pack/plugins/data_enhanced/server/search/response_utils.ts b/x-pack/plugins/data_enhanced/server/search/response_utils.ts new file mode 100644 index 0000000000000..716e7d72d80e7 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/response_utils.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ApiResponse } from '@elastic/elasticsearch'; +import { getTotalLoaded } from '../../../../../src/plugins/data/server'; +import { AsyncSearchResponse, EqlSearchResponse } from './types'; +import { EqlSearchStrategyResponse } from '../../common/search'; + +/** + * Get the Kibana representation of an async search response (see `IKibanaSearchResponse`). + */ +export function toAsyncKibanaSearchResponse(response: AsyncSearchResponse) { + return { + id: response.id, + rawResponse: response.response, + isPartial: response.is_partial, + isRunning: response.is_running, + ...getTotalLoaded(response.response), + }; +} + +/** + * Get the Kibana representation of an EQL search response (see `IKibanaSearchResponse`). + * (EQL does not provide _shard info, so total/loaded cannot be calculated.) + */ +export function toEqlKibanaSearchResponse( + response: ApiResponse +): EqlSearchStrategyResponse { + return { + id: response.body.id, + rawResponse: response, + isPartial: response.body.is_partial, + isRunning: response.body.is_running, + }; +} diff --git a/x-pack/plugins/data_enhanced/server/search/types.ts b/x-pack/plugins/data_enhanced/server/search/types.ts new file mode 100644 index 0000000000000..f01ac51a1516e --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; + +export interface AsyncSearchResponse { + id?: string; + response: SearchResponse; + is_partial: boolean; + is_running: boolean; +} + +export interface EqlSearchResponse extends SearchResponse { + id?: string; + is_partial: boolean; + is_running: boolean; +} diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index baacad65e140f..8b2cce01cf07a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mergeMap } from 'rxjs/operators'; -import { ISearchStrategy, PluginStart } from '../../../../../../src/plugins/data/server'; +import { map, mergeMap } from 'rxjs/operators'; +import { + ISearchStrategy, + PluginStart, + shimHitsTotal, +} from '../../../../../../src/plugins/data/server'; import { ENHANCED_ES_SEARCH_STRATEGY } from '../../../../data_enhanced/common'; import { FactoryQueryTypes, @@ -28,9 +32,17 @@ export const securitySolutionSearchStrategyProvider = = securitySolutionFactory[request.factoryQueryType]; const dsl = queryFactory.buildDsl(request); - return es - .search({ ...request, params: dsl }, options, deps) - .pipe(mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes))); + return es.search({ ...request, params: dsl }, options, deps).pipe( + map((response) => { + return { + ...response, + ...{ + rawResponse: shimHitsTotal(response.rawResponse), + }, + }; + }), + mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes)) + ); }, cancel: async (id, options, deps) => { if (es.cancel) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts index 29ad37e76264f..5ad00a727c3b6 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mergeMap } from 'rxjs/operators'; -import { ISearchStrategy, PluginStart } from '../../../../../../src/plugins/data/server'; +import { map, mergeMap } from 'rxjs/operators'; +import { + ISearchStrategy, + PluginStart, + shimHitsTotal, +} from '../../../../../../src/plugins/data/server'; import { ENHANCED_ES_SEARCH_STRATEGY } from '../../../../data_enhanced/common'; import { TimelineFactoryQueryTypes, @@ -29,9 +33,17 @@ export const securitySolutionTimelineSearchStrategyProvider = queryFactory.parse(request, esSearchRes))); + return es.search({ ...request, params: dsl }, options, deps).pipe( + map((response) => { + return { + ...response, + ...{ + rawResponse: shimHitsTotal(response.rawResponse), + }, + }; + }), + mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes)) + ); }, cancel: async (id, options, deps) => { if (es.cancel) {