mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[data.search] Simplify poll logic and improve types (#82545)
* [Search] Add request context and asScoped pattern
* Update docs
* Unify interface for getting search client
* Update examples/search_examples/server/my_strategy.ts
Co-authored-by: Anton Dosov <dosantappdev@gmail.com>
* Review feedback
* Fix checks
* Fix CI
* Fix security search
* Fix test
* Fix test for reals
* Fix types
* [data.search] Refactor search polling and improve types
* Fix & update tests & types
* eql totals
* doc
* Revert "eql totals"
This reverts commit 01e8a06847
.
* lint
* response type
* shim inside strategies
* shim for security
* fix eql params
Co-authored-by: Anton Dosov <dosantappdev@gmail.com>
Co-authored-by: Liza K <liza.katz@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
dc15aa8ea2
commit
f80da6cc39
58 changed files with 843 additions and 856 deletions
|
@ -9,7 +9,7 @@ Constructs a new instance of the `PainlessError` class
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor(err: IEsError, request: IKibanaSearchRequest);
|
||||
constructor(err: IEsError);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -17,5 +17,4 @@ constructor(err: IEsError, request: IKibanaSearchRequest);
|
|||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>IEsError</code> | |
|
||||
| request | <code>IKibanaSearchRequest</code> | |
|
||||
|
||||
|
|
|
@ -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 <code>PainlessError</code> class |
|
||||
| [(constructor)(err)](./kibana-plugin-plugins-data-public.painlesserror._constructor_.md) | | Constructs a new instance of the <code>PainlessError</code> class |
|
||||
|
||||
## Properties
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```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 | <code>any</code> | |
|
||||
| request | <code>IKibanaSearchRequest</code> | |
|
||||
| timeoutSignal | <code>AbortSignal</code> | |
|
||||
| options | <code>ISearchOptions</code> | |
|
||||
|
||||
|
|
|
@ -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 <code>search</code> method. Overrides the <code>AbortSignal</code> with one that will abort either when <code>cancelPending</code> is called, when the request times out, or when the original <code>AbortSignal</code> is aborted. Updates <code>pendingCount$</code> when the request is started/finalized. |
|
||||
| [showError(e)](./kibana-plugin-plugins-data-public.searchinterceptor.showerror.md) | | |
|
||||
|
||||
|
|
|
@ -7,11 +7,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<{
|
||||
maxConcurrentShardRequests: number | undefined;
|
||||
ignoreUnavailable: boolean;
|
||||
trackTotalHits: boolean;
|
||||
}>;
|
||||
export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<Pick<Search, 'max_concurrent_shard_requests' | 'ignore_unavailable' | 'track_total_hits'>>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -22,9 +18,5 @@ export declare function getDefaultSearchParams(uiSettingsClient: IUiSettingsClie
|
|||
|
||||
<b>Returns:</b>
|
||||
|
||||
`Promise<{
|
||||
maxConcurrentShardRequests: number | undefined;
|
||||
ignoreUnavailable: boolean;
|
||||
trackTotalHits: boolean;
|
||||
}>`
|
||||
`Promise<Pick<Search, 'max_concurrent_shard_requests' | 'ignore_unavailable' | 'track_total_hits'>>`
|
||||
|
||||
|
|
|
@ -7,11 +7,7 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function getShardTimeout(config: SharedGlobalConfig): {
|
||||
timeout: string;
|
||||
} | {
|
||||
timeout?: undefined;
|
||||
};
|
||||
export declare function getShardTimeout(config: SharedGlobalConfig): Pick<Search, 'timeout'>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -22,9 +18,5 @@ export declare function getShardTimeout(config: SharedGlobalConfig): {
|
|||
|
||||
<b>Returns:</b>
|
||||
|
||||
`{
|
||||
timeout: string;
|
||||
} | {
|
||||
timeout?: undefined;
|
||||
}`
|
||||
`Pick<Search, 'timeout'>`
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
id?: string;
|
||||
```
|
|
@ -1,11 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
is_partial?: boolean;
|
||||
```
|
|
@ -1,11 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
is_running?: boolean;
|
||||
```
|
|
@ -1,20 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsRawSearchResponse](./kibana-plugin-plugins-data-server.iesrawsearchresponse.md)
|
||||
|
||||
## IEsRawSearchResponse interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface IEsRawSearchResponse<Source = any> extends SearchResponse<Source>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [id](./kibana-plugin-plugins-data-server.iesrawsearchresponse.id.md) | <code>string</code> | |
|
||||
| [is\_partial](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_partial.md) | <code>boolean</code> | |
|
||||
| [is\_running](./kibana-plugin-plugins-data-server.iesrawsearchresponse.is_running.md) | <code>boolean</code> | |
|
||||
|
|
@ -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 <code>tap(searchUsageObserver(logger, usage))</code> 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) | |
|
||||
|
|
|
@ -8,24 +8,6 @@
|
|||
|
||||
```typescript
|
||||
search: {
|
||||
esSearch: {
|
||||
utils: {
|
||||
doSearch: <SearchResponse = any>(searchMethod: () => Promise<SearchResponse>, abortSignal?: AbortSignal | undefined) => import("rxjs").Observable<SearchResponse>;
|
||||
shimAbortSignal: <T extends import("../common").TransportRequestPromise<unknown>>(promise: T, signal: AbortSignal | undefined) => T;
|
||||
trackSearchStatus: <KibanaResponse extends import("../common").IKibanaSearchResponse<any> = import("./search").IEsSearchResponse<import("src/core/server").SearchResponse<unknown>>>(logger: import("src/core/server").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction<import("rxjs").Observable<KibanaResponse>, import("rxjs").Observable<KibanaResponse>>;
|
||||
includeTotalLoaded: () => import("rxjs").OperatorFunction<import("../common").IKibanaSearchResponse<import("elasticsearch").SearchResponse<unknown>>, {
|
||||
total: number;
|
||||
loaded: number;
|
||||
id?: string | undefined;
|
||||
isRunning?: boolean | undefined;
|
||||
isPartial?: boolean | undefined;
|
||||
rawResponse: import("elasticsearch").SearchResponse<unknown>;
|
||||
}>;
|
||||
toKibanaSearchResponse: <SearchResponse_1 extends import("../common").IEsRawSearchResponse<any> = import("../common").IEsRawSearchResponse<any>, KibanaResponse_1 extends import("../common").IKibanaSearchResponse<any> = import("../common").IKibanaSearchResponse<SearchResponse_1>>() => import("rxjs").OperatorFunction<import("@elastic/elasticsearch").ApiResponse<SearchResponse_1, import("@elastic/elasticsearch/lib/Transport").Context>, KibanaResponse_1>;
|
||||
getTotalLoaded: typeof getTotalLoaded;
|
||||
toSnakeCase: typeof toSnakeCase;
|
||||
};
|
||||
};
|
||||
aggs: {
|
||||
CidrMask: typeof CidrMask;
|
||||
dateHistogramInterval: typeof dateHistogramInterval;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function searchUsageObserver(logger: Logger, usage?: SearchUsage): {
|
||||
next(response: IEsSearchResponse): void;
|
||||
error(): void;
|
||||
};
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| logger | <code>Logger</code> | |
|
||||
| usage | <code>SearchUsage</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`{
|
||||
next(response: IEsSearchResponse): void;
|
||||
error(): void;
|
||||
}`
|
||||
|
|
@ -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 = <SearchResponse = any>(
|
||||
searchMethod: () => Promise<SearchResponse>,
|
||||
abortSignal?: AbortSignal
|
||||
) => from(shimAbortSignal(searchMethod(), abortSignal));
|
||||
|
||||
export const toKibanaSearchResponse = <
|
||||
SearchResponse extends IEsRawSearchResponse = IEsRawSearchResponse,
|
||||
KibanaResponse extends IKibanaSearchResponse = IKibanaSearchResponse<SearchResponse>
|
||||
>() =>
|
||||
map<ApiResponse<SearchResponse>, 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<SearchResponse<unknown>>) => ({
|
||||
...response,
|
||||
...getTotalLoaded(response.rawResponse._shards),
|
||||
}));
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<T> extends Promise<T> {
|
||||
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 = <T extends TransportRequestPromise<unknown>>(
|
||||
promise: T,
|
||||
signal: AbortSignal | undefined
|
||||
): T => {
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => promise.abort && promise.abort());
|
||||
}
|
||||
return promise;
|
||||
};
|
|
@ -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<string, any>): Record<string, any> {
|
||||
return mapKeys(obj, (value, key) => snakeCase(key));
|
||||
}
|
|
@ -30,10 +30,4 @@ export interface IEsSearchRequest extends IKibanaSearchRequest<ISearchRequestPar
|
|||
indexType?: string;
|
||||
}
|
||||
|
||||
export interface IEsRawSearchResponse<Source = any> extends SearchResponse<Source> {
|
||||
id?: string;
|
||||
is_partial?: boolean;
|
||||
is_running?: boolean;
|
||||
}
|
||||
|
||||
export type IEsSearchResponse<Source = any> = IKibanaSearchResponse<SearchResponse<Source>>;
|
||||
|
|
|
@ -24,3 +24,4 @@ export * from './search_source';
|
|||
export * from './tabify';
|
||||
export * from './types';
|
||||
export * from './session';
|
||||
export * from './utils';
|
||||
|
|
106
src/plugins/data/common/search/utils.test.ts
Normal file
106
src/plugins/data/common/search/utils.test.ts
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
|
@ -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<number>;
|
||||
// @internal (undocumented)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { usageProvider, SearchUsage } from './usage';
|
||||
export type { SearchUsage } from './usage';
|
||||
export { usageProvider, searchUsageObserver } from './usage';
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<SearchResponse<unknown>>
|
||||
>(
|
||||
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();
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
|
@ -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<SharedGlobalConfig>,
|
||||
|
@ -43,19 +38,18 @@ export const esSearchStrategyProvider = (
|
|||
throw new Error(`Unsupported index pattern type ${request.indexType}`);
|
||||
}
|
||||
|
||||
return doSearch<ApiResponse<IEsRawSearchResponse>>(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<SearchResponse<unknown>>(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)));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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<number>(
|
||||
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,
|
||||
};
|
||||
}
|
|
@ -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';
|
||||
|
|
148
src/plugins/data/server/search/es_search/request_utils.test.ts
Normal file
148
src/plugins/data/server/search/es_search/request_utils.test.ts
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
66
src/plugins/data/server/search/es_search/request_utils.ts
Normal file
66
src/plugins/data/server/search/es_search/request_utils.ts
Normal file
|
@ -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<Search, 'timeout'> {
|
||||
const timeout = config.elasticsearch.shardTimeout.asMilliseconds();
|
||||
return timeout ? { timeout: `${timeout}ms` } : {};
|
||||
}
|
||||
|
||||
export async function getDefaultSearchParams(
|
||||
uiSettingsClient: IUiSettingsClient
|
||||
): Promise<
|
||||
Pick<Search, 'max_concurrent_shard_requests' | 'ignore_unavailable' | 'track_total_hits'>
|
||||
> {
|
||||
const maxConcurrentShardRequests = await uiSettingsClient.get<number>(
|
||||
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 = <T>(promise: TransportRequestPromise<T>, 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;
|
||||
};
|
|
@ -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<unknown>);
|
||||
|
||||
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<unknown>);
|
||||
|
||||
expect(result).toEqual({
|
||||
rawResponse: {
|
||||
_shards: {
|
||||
successful: 10,
|
||||
failed: 5,
|
||||
skipped: 5,
|
||||
total: 100,
|
||||
},
|
||||
},
|
||||
isRunning: false,
|
||||
isPartial: false,
|
||||
total: 100,
|
||||
loaded: 15,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<unknown>) {
|
||||
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<unknown>) {
|
||||
return {
|
||||
rawResponse,
|
||||
isPartial: false,
|
||||
isRunning: false,
|
||||
...getTotalLoaded(rawResponse),
|
||||
};
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Pick<Search, 'max_concurrent_shard_requests' | 'ignore_unavailable' | 'track_total_hits'>>;
|
||||
|
||||
// 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<Search, 'timeout'>;
|
||||
|
||||
// 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<unknown>): {
|
||||
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<Source = any> extends SearchResponse<Source> {
|
||||
// (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: <SearchResponse = any>(searchMethod: () => Promise<SearchResponse>, abortSignal?: AbortSignal | undefined) => import("rxjs").Observable<SearchResponse>;
|
||||
shimAbortSignal: <T extends import("../common").TransportRequestPromise<unknown>>(promise: T, signal: AbortSignal | undefined) => T;
|
||||
trackSearchStatus: <KibanaResponse extends import("../common").IKibanaSearchResponse<any> = import("./search").IEsSearchResponse<import("src/core/server").SearchResponse<unknown>>>(logger: import("src/core/server").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction<import("rxjs").Observable<KibanaResponse>, import("rxjs").Observable<KibanaResponse>>;
|
||||
includeTotalLoaded: () => import("rxjs").OperatorFunction<import("../common").IKibanaSearchResponse<import("elasticsearch").SearchResponse<unknown>>, {
|
||||
total: number;
|
||||
loaded: number;
|
||||
id?: string | undefined;
|
||||
isRunning?: boolean | undefined;
|
||||
isPartial?: boolean | undefined;
|
||||
rawResponse: import("elasticsearch").SearchResponse<unknown>;
|
||||
}>;
|
||||
toKibanaSearchResponse: <SearchResponse_1 extends import("../common").IEsRawSearchResponse<any> = import("../common").IEsRawSearchResponse<any>, KibanaResponse_1 extends import("../common").IKibanaSearchResponse<any> = import("../common").IKibanaSearchResponse<SearchResponse_1>>() => import("rxjs").OperatorFunction<import("@elastic/elasticsearch").ApiResponse<SearchResponse_1, import("@elastic/elasticsearch/lib/Transport").Context>, 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<void>;
|
||||
}
|
||||
|
||||
// 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: <T>(promise: TransportRequestPromise<T>, signal?: AbortSignal | undefined) => TransportRequestPromise<T>;
|
||||
|
||||
// @internal
|
||||
export function shimHitsTotal(response: SearchResponse<any>): {
|
||||
hits: {
|
||||
|
@ -1176,6 +1155,15 @@ export type TimeRange = {
|
|||
mode?: 'absolute' | 'relative';
|
||||
};
|
||||
|
||||
// @internal
|
||||
export function toKibanaSearchResponse(rawResponse: SearchResponse<unknown>): {
|
||||
total: number;
|
||||
loaded: number;
|
||||
rawResponse: SearchResponse<unknown>;
|
||||
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
|
||||
|
|
|
@ -10,9 +10,6 @@ export {
|
|||
EqlRequestParams,
|
||||
EqlSearchStrategyRequest,
|
||||
EqlSearchStrategyResponse,
|
||||
IAsyncSearchRequest,
|
||||
IEnhancedEsSearchRequest,
|
||||
IAsyncSearchOptions,
|
||||
doPartialSearch,
|
||||
throwOnEsError,
|
||||
pollSearch,
|
||||
} from './search';
|
||||
|
|
|
@ -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 = <SearchResponse = any>(
|
||||
searchMethod: () => Promise<SearchResponse>,
|
||||
partialSearchMethod: (id: IKibanaSearchRequest['id']) => Promise<SearchResponse>,
|
||||
isCompleteResponse: (response: SearchResponse) => boolean,
|
||||
getId: (response: SearchResponse) => IKibanaSearchRequest['id'],
|
||||
requestId: IKibanaSearchRequest['id'],
|
||||
{ abortSignal, pollInterval = DEFAULT_POLLING_INTERVAL }: IAsyncSearchOptions
|
||||
) =>
|
||||
doSearch<SearchResponse>(
|
||||
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 = <SearchResponse extends ApiResponse = ApiResponse>() =>
|
||||
map<SearchResponse, SearchResponse>((eqlResponse) => ({
|
||||
...eqlResponse,
|
||||
body: {
|
||||
...eqlResponse.body,
|
||||
...eqlResponse,
|
||||
},
|
||||
}));
|
||||
|
||||
export const throwOnEsError = () =>
|
||||
mergeMap((r: IKibanaSearchResponse) =>
|
||||
isErrorResponse(r) ? merge(of(r), throwError(new AbortError())) : of(r)
|
||||
);
|
|
@ -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';
|
|
@ -5,4 +5,4 @@
|
|||
*/
|
||||
|
||||
export * from './types';
|
||||
export * from './es_search';
|
||||
export * from './poll_search';
|
||||
|
|
31
x-pack/plugins/data_enhanced/common/search/poll_search.ts
Normal file
31
x-pack/plugins/data_enhanced/common/search/poll_search.ts
Normal file
|
@ -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 = <Response extends IKibanaSearchResponse>(
|
||||
search: () => Promise<Response>,
|
||||
{ pollInterval = 1000, ...options }: IAsyncSearchOptions = {}
|
||||
): Observable<Response> => {
|
||||
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<Response>(isPartialResponse, true),
|
||||
takeUntil<Response>(from(aborted.promise)),
|
||||
finalize(aborted.cleanup)
|
||||
);
|
||||
};
|
|
@ -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<Record<string, unknown>>;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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<IEsSearchResponse>(
|
||||
() => 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();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ describe('EQL search strategy', () => {
|
|||
|
||||
expect(requestOptions).toEqual(
|
||||
expect.objectContaining({
|
||||
max_retries: 2,
|
||||
maxRetries: 2,
|
||||
ignore: [300],
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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<ApiResponse<IEsRawSearchResponse>>(
|
||||
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<EqlSearchResponse>({ ...params, id }, request.options)
|
||||
: client.search<EqlSearchResponse>(
|
||||
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)));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
ISearchStrategy,
|
||||
SearchUsage,
|
||||
IEsRawSearchResponse,
|
||||
ISearchOptions,
|
||||
IEsSearchRequest,
|
||||
IEsSearchResponse,
|
||||
ISearchOptions,
|
||||
ISearchStrategy,
|
||||
SearchStrategyDependencies,
|
||||
SearchUsage,
|
||||
} from '../../../../../src/plugins/data/server';
|
||||
|
||||
import type { IEnhancedEsSearchRequest } from '../../common';
|
||||
|
||||
const { utils } = search.esSearch;
|
||||
|
||||
interface IEsRawAsyncSearchResponse<Source = any> extends IEsRawSearchResponse<Source> {
|
||||
response: SearchResponse<Source>;
|
||||
}
|
||||
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<SharedGlobalConfig>,
|
||||
logger: Logger,
|
||||
usage?: SearchUsage
|
||||
): ISearchStrategy<IEnhancedEsSearchRequest> => {
|
||||
): ISearchStrategy<IEsSearchRequest> => {
|
||||
function asyncSearch(
|
||||
request: IEnhancedEsSearchRequest,
|
||||
options: ISearchOptions,
|
||||
{ id, ...request }: IEsSearchRequest,
|
||||
options: IAsyncSearchOptions,
|
||||
{ esClient, uiSettingsClient }: SearchStrategyDependencies
|
||||
) {
|
||||
const asyncOptions = getAsyncOptions();
|
||||
const client = esClient.asCurrentUser.asyncSearch;
|
||||
|
||||
return doPartialSearch<ApiResponse<IEsRawAsyncSearchResponse>>(
|
||||
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<AsyncSearchResponse>({ ...params, id })
|
||||
: client.submit<AsyncSearchResponse>(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<IEsSearchResponse> {
|
||||
|
@ -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<any>;
|
||||
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'
|
||||
|
|
|
@ -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,
|
||||
});
|
64
x-pack/plugins/data_enhanced/server/search/request_utils.ts
Normal file
64
x-pack/plugins/data_enhanced/server/search/request_utils.ts
Normal file
|
@ -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<Pick<Search, 'ignore_throttled'>> {
|
||||
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
|
||||
};
|
||||
}
|
38
x-pack/plugins/data_enhanced/server/search/response_utils.ts
Normal file
38
x-pack/plugins/data_enhanced/server/search/response_utils.ts
Normal file
|
@ -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<EqlSearchResponse>
|
||||
): EqlSearchStrategyResponse {
|
||||
return {
|
||||
id: response.body.id,
|
||||
rawResponse: response,
|
||||
isPartial: response.body.is_partial,
|
||||
isRunning: response.body.is_running,
|
||||
};
|
||||
}
|
20
x-pack/plugins/data_enhanced/server/search/types.ts
Normal file
20
x-pack/plugins/data_enhanced/server/search/types.ts
Normal file
|
@ -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<T = unknown> {
|
||||
id?: string;
|
||||
response: SearchResponse<T>;
|
||||
is_partial: boolean;
|
||||
is_running: boolean;
|
||||
}
|
||||
|
||||
export interface EqlSearchResponse<T = unknown> extends SearchResponse<T> {
|
||||
id?: string;
|
||||
is_partial: boolean;
|
||||
is_running: boolean;
|
||||
}
|
|
@ -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 = <T extends FactoryQueryTyp
|
|||
const queryFactory: SecuritySolutionFactory<T> =
|
||||
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) {
|
||||
|
|
|
@ -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 = <T extends Timelin
|
|||
securitySolutionTimelineFactory[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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue