[data.search] Add request handler context and asScoped pattern (#80775)

* [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

Co-authored-by: Anton Dosov <dosantappdev@gmail.com>
This commit is contained in:
Lukas Olson 2020-11-04 11:58:15 -07:00 committed by GitHub
parent 4b9570f209
commit 44368b0b66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 386 additions and 417 deletions

View file

@ -18,6 +18,6 @@ export interface IKibanaSearchResponse<RawResponse = any>
| [isPartial](./kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md) | <code>boolean</code> | Indicates whether the results returned are complete or partial |
| [isRunning](./kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md) | <code>boolean</code> | Indicates whether search is still in flight |
| [loaded](./kibana-plugin-plugins-data-public.ikibanasearchresponse.loaded.md) | <code>number</code> | If relevant to the search strategy, return a loaded number that represents how progress is indicated. |
| [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) | <code>RawResponse</code> | |
| [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) | <code>RawResponse</code> | The raw response returned by the internal search method (usually the raw ES response) |
| [total](./kibana-plugin-plugins-data-public.ikibanasearchresponse.total.md) | <code>number</code> | If relevant to the search strategy, return a total number that represents how progress is indicated. |

View file

@ -4,6 +4,8 @@
## IKibanaSearchResponse.rawResponse property
The raw response returned by the internal search method (usually the raw ES response)
<b>Signature:</b>
```typescript

View file

@ -1,11 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [ISearch](./kibana-plugin-plugins-data-public.isearch.md)
## ISearch type
<b>Signature:</b>
```typescript
export declare type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) => Observable<IKibanaSearchResponse>;
```

View file

@ -159,7 +159,6 @@
| [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) | |
| [IndexPatternSelectProps](./kibana-plugin-plugins-data-public.indexpatternselectprops.md) | |
| [InputTimeRange](./kibana-plugin-plugins-data-public.inputtimerange.md) | |
| [ISearch](./kibana-plugin-plugins-data-public.isearch.md) | |
| [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | |
| [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | search source interface |
| [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | |

View file

@ -23,5 +23,5 @@ search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable<IKib
`Observable<IKibanaSearchResponse>`
`Observalbe` emitting the search response or an error.
`Observable` emitting the search response or an error.

View file

@ -42,7 +42,7 @@ export declare class SearchSource
| [getSerializedFields()](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) | | serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)<!-- -->) |
| [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start |
| [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.<!-- -->The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named <code>kibanaSavedObjectMeta.searchSourceJSON.index</code> and <code>kibanaSavedObjectMeta.searchSourceJSON.filter[&lt;number&gt;].meta.index</code>.<!-- -->Using <code>createSearchSource</code>, the instance can be re-created. |
| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | sets value to a single search source feild |
| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | sets value to a single search source field |
| [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | Internal, do not use. Overrides all search source fields with the new field array. |
| [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from |
| [setPreferredSearchStrategyId(searchStrategyId)](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) | | internal, dont use |

View file

@ -15,13 +15,13 @@ Using `createSearchSource`<!-- -->, the instance can be re-created.
```typescript
serialize(): {
searchSourceJSON: string;
references: import("../../../../../core/types").SavedObjectReference[];
references: import("src/core/server").SavedObjectReference[];
};
```
<b>Returns:</b>
`{
searchSourceJSON: string;
references: import("../../../../../core/types").SavedObjectReference[];
references: import("src/core/server").SavedObjectReference[];
}`

View file

@ -4,7 +4,7 @@
## SearchSource.setField() method
sets value to a single search source feild
sets value to a single search source field
<b>Signature:</b>

View file

@ -1,11 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) &gt; [search](./kibana-plugin-plugins-data-server.isearchstart.search.md)
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) &gt; [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md)
## ISearchStart.search property
## ISearchStart.asScoped property
<b>Signature:</b>
```typescript
search: ISearchStrategy['search'];
asScoped: (request: KibanaRequest) => ISearchClient;
```

View file

@ -4,10 +4,10 @@
## ISearchStart.getSearchStrategy property
Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that.
Get other registered search strategies by name (or, by default, the Elasticsearch strategy). For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that.
<b>Signature:</b>
```typescript
getSearchStrategy: (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
getSearchStrategy: (name?: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
```

View file

@ -15,7 +15,7 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
| Property | Type | Description |
| --- | --- | --- |
| [aggs](./kibana-plugin-plugins-data-server.isearchstart.aggs.md) | <code>AggsStart</code> | |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | <code>(name: string) =&gt; ISearchStrategy&lt;SearchStrategyRequest, SearchStrategyResponse&gt;</code> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | <code>ISearchStrategy['search']</code> | |
| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | <code>(request: KibanaRequest) =&gt; ISearchClient</code> | |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | <code>(name?: string) =&gt; ISearchStrategy&lt;SearchStrategyRequest, SearchStrategyResponse&gt;</code> | Get other registered search strategies by name (or, by default, the Elasticsearch strategy). For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. |
| [searchSource](./kibana-plugin-plugins-data-server.isearchstart.searchsource.md) | <code>{</code><br/><code> asScoped: (request: KibanaRequest) =&gt; Promise&lt;ISearchStartSearchSource&gt;;</code><br/><code> }</code> | |

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
cancel?: (context: RequestHandlerContext, id: string) => Promise<void>;
cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void>;
```

View file

@ -16,6 +16,6 @@ export interface ISearchStrategy<SearchStrategyRequest extends IKibanaSearchRequ
| Property | Type | Description |
| --- | --- | --- |
| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | <code>(context: RequestHandlerContext, id: string) =&gt; Promise&lt;void&gt;</code> | |
| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | <code>(request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) =&gt; Observable&lt;SearchStrategyResponse&gt;</code> | |
| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | <code>(id: string, options: ISearchOptions, deps: SearchStrategyDependencies) =&gt; Promise&lt;void&gt;</code> | |
| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | <code>(request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) =&gt; Observable&lt;SearchStrategyResponse&gt;</code> | |

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
search: (request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) => Observable<SearchStrategyResponse>;
search: (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable<SearchStrategyResponse>;
```

View file

@ -58,6 +58,7 @@
| [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | |
| [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | |
| [RefreshInterval](./kibana-plugin-plugins-data-server.refreshinterval.md) | |
| [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) | |
| [SearchUsage](./kibana-plugin-plugins-data-server.searchusage.md) | |
| [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) | \* |
| [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) | \* |

View file

@ -9,10 +9,10 @@
```typescript
start(core: CoreStart): {
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
@ -28,10 +28,10 @@ start(core: CoreStart): {
`{
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
}`

View file

@ -12,7 +12,7 @@ search: {
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("../../../core/server").SearchResponse<unknown>>>(logger: import("@kbn/logging/target/logger").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction<import("rxjs").Observable<KibanaResponse>, import("rxjs").Observable<KibanaResponse>>;
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;

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) &gt; [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md)
## SearchStrategyDependencies.esClient property
<b>Signature:</b>
```typescript
esClient: IScopedClusterClient;
```

View file

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md)
## SearchStrategyDependencies interface
<b>Signature:</b>
```typescript
export interface SearchStrategyDependencies
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md) | <code>IScopedClusterClient</code> | |
| [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md) | <code>SavedObjectsClientContract</code> | |
| [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md) | <code>IUiSettingsClient</code> | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) &gt; [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md)
## SearchStrategyDependencies.savedObjectsClient property
<b>Signature:</b>
```typescript
savedObjectsClient: SavedObjectsClientContract;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) &gt; [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md)
## SearchStrategyDependencies.uiSettingsClient property
<b>Signature:</b>
```typescript
uiSettingsClient: IUiSettingsClient;
```

View file

@ -24,18 +24,18 @@ import { IMyStrategyResponse, IMyStrategyRequest } from '../common';
export const mySearchStrategyProvider = (
data: PluginStart
): ISearchStrategy<IMyStrategyRequest, IMyStrategyResponse> => {
const es = data.search.getSearchStrategy('es');
const es = data.search.getSearchStrategy();
return {
search: (request, options, context) =>
es.search(request, options, context).pipe(
search: (request, options, deps) =>
es.search(request, options, deps).pipe(
map((esSearchRes) => ({
...esSearchRes,
cool: request.get_cool ? 'YES' : 'NOPE',
}))
),
cancel: async (context, id) => {
cancel: async (id, options, deps) => {
if (es.cancel) {
es.cancel(context, id);
await es.cancel(id, options, deps);
}
},
};

View file

@ -39,8 +39,8 @@ export function registerServerSearchRoute(router: IRouter, data: DataPluginStart
// Run a synchronous search server side, by enforcing a high keepalive and waiting for completion.
// If you wish to run the search with polling (in basic+), you'd have to poll on the search API.
// Please reach out to the @app-arch-team if you need this to be implemented.
const res = await data.search
.search(
const res = await context
.search!.search(
{
params: {
index,
@ -57,8 +57,7 @@ export function registerServerSearchRoute(router: IRouter, data: DataPluginStart
keepAlive: '5m',
},
} as IEsSearchRequest,
{},
context
{}
)
.toPromise();

View file

@ -21,13 +21,12 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { Assign, Ensure } from '@kbn/utility-types';
import { ISearchSource } from 'src/plugins/data/public';
import { ISearchOptions, ISearchSource } from 'src/plugins/data/public';
import {
ExpressionAstFunction,
ExpressionAstArgument,
SerializedFieldFormat,
} from 'src/plugins/expressions/common';
import { ISearchOptions } from '../es_search';
import { IAggType } from './agg_type';
import { writeParams } from './agg_params';

View file

@ -22,22 +22,6 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types';
export const ES_SEARCH_STRATEGY = 'es';
export interface ISearchOptions {
/**
* An `AbortSignal` that allows the caller of `search` to abort a search request.
*/
abortSignal?: AbortSignal;
/**
* Use this option to force using a specific server side search strategy. Leave empty to use the default strategy.
*/
strategy?: string;
/**
* A session ID, grouping multiple search requests into a single session.
*/
sessionId?: string;
}
export type ISearchRequestParams<T = Record<string, any>> = {
trackTotalHits?: boolean;
} & Search<T>;

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, of } from 'rxjs';
import { IndexPattern } from '../../index_patterns';
import { GetConfigFn } from '../../types';
import { fetchSoon } from './legacy';
@ -53,7 +53,7 @@ describe('SearchSource', () => {
let searchSourceDependencies: SearchSourceDependencies;
beforeEach(() => {
mockSearchMethod = jest.fn().mockResolvedValue({ rawResponse: '' });
mockSearchMethod = jest.fn().mockReturnValue(of({ rawResponse: '' }));
searchSourceDependencies = {
getConfig: jest.fn(),

View file

@ -71,12 +71,12 @@
import { setWith } from '@elastic/safer-lodash-set';
import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash';
import { map } from 'rxjs/operators';
import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/common';
import { IIndexPattern } from '../../index_patterns';
import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../search';
import type { IKibanaSearchRequest, IKibanaSearchResponse } from '../types';
import { ISearchGeneric, ISearchOptions } from '../..';
import type { ISearchSource, SearchSourceOptions, SearchSourceFields } from './types';
import { FetchHandlers, RequestFailure, getSearchParamsFromRequest, SearchRequest } from './fetch';
@ -102,15 +102,7 @@ export const searchSourceRequiredUiSettings = [
];
export interface SearchSourceDependencies extends FetchHandlers {
// Types are nearly identical to ISearchGeneric, except we are making
// search options required here and returning a promise instead of observable.
search: <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
request: SearchStrategyRequest,
options: ISearchOptions
) => Promise<SearchStrategyResponse>;
search: ISearchGeneric;
}
/** @public **/
@ -144,7 +136,7 @@ export class SearchSource {
}
/**
* sets value to a single search source feild
* sets value to a single search source field
* @param field: field name
* @param value: value for the field
*/
@ -319,9 +311,9 @@ export class SearchSource {
getConfig,
});
return search({ params, indexType: searchRequest.indexType }, options).then(({ rawResponse }) =>
onResponse(searchRequest, rawResponse)
);
return search({ params, indexType: searchRequest.indexType }, options)
.pipe(map(({ rawResponse }) => onResponse(searchRequest, rawResponse)))
.toPromise();
}
/**

View file

@ -18,12 +18,7 @@
*/
import { Observable } from 'rxjs';
import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../common/search';
export type ISearch = (
request: IKibanaSearchRequest,
options?: ISearchOptions
) => Observable<IKibanaSearchResponse>;
import { IEsSearchRequest, IEsSearchResponse } from './es_search';
export type ISearchGeneric = <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
@ -33,6 +28,13 @@ export type ISearchGeneric = <
options?: ISearchOptions
) => Observable<SearchStrategyResponse>;
export type ISearchCancelGeneric = (id: string, options?: ISearchOptions) => Promise<void>;
export interface ISearchClient {
search: ISearchGeneric;
cancel: ISearchCancelGeneric;
}
export interface IKibanaSearchResponse<RawResponse = any> {
/**
* Some responses may contain a unique id to identify the request this response came from.
@ -61,6 +63,9 @@ export interface IKibanaSearchResponse<RawResponse = any> {
*/
isPartial?: boolean;
/**
* The raw response returned by the internal search method (usually the raw ES response)
*/
rawResponse: RawResponse;
}
@ -72,3 +77,19 @@ export interface IKibanaSearchRequest<Params = any> {
params?: Params;
}
export interface ISearchOptions {
/**
* An `AbortSignal` that allows the caller of `search` to abort a search request.
*/
abortSignal?: AbortSignal;
/**
* Use this option to force using a specific server side search strategy. Leave empty to use the default strategy.
*/
strategy?: string;
/**
* A session ID, grouping multiple search requests into a single session.
*/
sessionId?: string;
}

View file

@ -358,7 +358,6 @@ export {
IKibanaSearchRequest,
IKibanaSearchResponse,
injectSearchSourceReferences,
ISearch,
ISearchSetup,
ISearchStart,
ISearchStartSearchSource,

View file

@ -137,7 +137,7 @@ export class AggConfig {
// (undocumented)
makeLabel(percentageMode?: boolean): any;
static nextId(list: IAggConfig[]): number;
onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions): Promise<void> | Promise<any[]>;
onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions_2): Promise<void> | Promise<any[]>;
// (undocumented)
params: any;
// Warning: (ae-incompatible-release-tags) The symbol "parent" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal
@ -1047,7 +1047,6 @@ export interface IKibanaSearchResponse<RawResponse = any> {
isPartial?: boolean;
isRunning?: boolean;
loaded?: number;
// (undocumented)
rawResponse: RawResponse;
total?: number;
}
@ -1384,11 +1383,6 @@ export type InputTimeRange = TimeRange | {
// @public (undocumented)
export const isCompleteResponse: (response?: IKibanaSearchResponse<any> | undefined) => boolean;
// Warning: (ae-missing-release-tag) "ISearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) => Observable<IKibanaSearchResponse>;
// Warning: (ae-missing-release-tag) "ISearchGeneric" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@ -2092,7 +2086,7 @@ export class SearchSource {
onRequestStart(handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise<unknown>): void;
serialize(): {
searchSourceJSON: string;
references: import("../../../../../core/types").SavedObjectReference[];
references: import("src/core/server").SavedObjectReference[];
};
setField<K extends keyof SearchSourceFields>(field: K, value: SearchSourceFields[K]): this;
setFields(newFields: SearchSourceFields): this;
@ -2327,21 +2321,21 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)

View file

@ -31,7 +31,6 @@ export {
IKibanaSearchRequest,
IKibanaSearchResponse,
injectReferences as injectSearchSourceReferences,
ISearch,
ISearchGeneric,
ISearchSource,
parseSearchSourceJSON,

View file

@ -218,7 +218,7 @@ export class SearchInterceptor {
*
* @param request
* @options
* @returns `Observalbe` emitting the search response or an error.
* @returns `Observable` emitting the search response or an error.
*/
public search(
request: IKibanaSearchRequest,

View file

@ -23,12 +23,7 @@ import { ISearchSetup, ISearchStart, SearchEnhancements } from './types';
import { handleResponse } from './fetch';
import {
IEsSearchRequest,
IEsSearchResponse,
IKibanaSearchRequest,
IKibanaSearchResponse,
ISearchGeneric,
ISearchOptions,
SearchSourceService,
SearchSourceDependencies,
ISessionService,
@ -126,15 +121,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
const searchSourceDependencies: SearchSourceDependencies = {
getConfig: uiSettings.get.bind(uiSettings),
search: <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
request: SearchStrategyRequest,
options: ISearchOptions
) => {
return search<SearchStrategyRequest, SearchStrategyResponse>(request, options).toPromise();
},
search,
onResponse: handleResponse,
legacy: {
callMsearch: getCallMsearch({ http }),

View file

@ -221,6 +221,7 @@ export {
ISearchStrategy,
ISearchSetup,
ISearchStart,
SearchStrategyDependencies,
getDefaultSearchParams,
getShardTimeout,
shimHitsTotal,

View file

@ -17,9 +17,9 @@
* under the License.
*/
import { RequestHandlerContext } from '../../../../../core/server';
import { pluginInitializerContextConfigMock } from '../../../../../core/server/mocks';
import { esSearchStrategyProvider } from './es_search_strategy';
import { SearchStrategyDependencies } from '../types';
describe('ES search strategy', () => {
const mockLogger: any = {
@ -36,16 +36,12 @@ describe('ES search strategy', () => {
},
});
const mockContext = ({
core: {
uiSettings: {
client: {
get: () => {},
},
},
elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } },
const mockDeps = ({
uiSettingsClient: {
get: () => {},
},
} as unknown) as RequestHandlerContext;
esClient: { asCurrentUser: { search: mockApiCaller } },
} as unknown) as SearchStrategyDependencies;
const mockConfig$ = pluginInitializerContextConfigMock<any>({}).legacy.globalConfig$;
@ -63,7 +59,7 @@ describe('ES search strategy', () => {
const params = { index: 'logstash-*' };
await esSearchStrategyProvider(mockConfig$, mockLogger)
.search({ params }, {}, mockContext)
.search({ params }, {}, mockDeps)
.subscribe(() => {
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toEqual({
@ -79,7 +75,7 @@ describe('ES search strategy', () => {
const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' };
await esSearchStrategyProvider(mockConfig$, mockLogger)
.search({ params }, {}, mockContext)
.search({ params }, {}, mockDeps)
.subscribe(() => {
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toEqual({
@ -97,7 +93,7 @@ describe('ES search strategy', () => {
params: { index: 'logstash-*' },
},
{},
mockContext
mockDeps
)
.subscribe((data) => {
expect(data.isRunning).toBe(false);

View file

@ -36,7 +36,7 @@ export const esSearchStrategyProvider = (
logger: Logger,
usage?: SearchUsage
): ISearchStrategy => ({
search: (request, { abortSignal }, context) => {
search: (request, { abortSignal }, { esClient, uiSettingsClient }) => {
// Only default index pattern type is supported here.
// See data_enhanced for other type support.
if (request.indexType) {
@ -46,12 +46,12 @@ export const esSearchStrategyProvider = (
return doSearch<ApiResponse<IEsRawSearchResponse>>(async () => {
const config = await config$.pipe(first()).toPromise();
const params = toSnakeCase({
...(await getDefaultSearchParams(context.core.uiSettings.client)),
...(await getDefaultSearchParams(uiSettingsClient)),
...getShardTimeout(config),
...request.params,
});
return context.core.elasticsearch.client.asCurrentUser.search(params);
return esClient.asCurrentUser.search(params);
}, abortSignal).pipe(
toKibanaSearchResponse(),
trackSearchStatus(logger, usage),

View file

@ -17,12 +17,8 @@
* under the License.
*/
export { ISearchStrategy, ISearchSetup, ISearchStart, SearchEnhancements } from './types';
export * from './types';
export * from './es_search';
export { usageProvider, SearchUsage } from './collectors';
export * from './aggs';
export { shimHitsTotal } from './routes';

View file

@ -33,7 +33,10 @@ export function createSearchStartMock(): jest.Mocked<ISearchStart> {
return {
aggs: searchAggsStartMock(),
getSearchStrategy: jest.fn(),
search: jest.fn(),
asScoped: jest.fn().mockReturnValue({
search: jest.fn(),
cancel: jest.fn(),
}),
searchSource: searchSourceMock.createStartContract(),
};
}

View file

@ -16,35 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { MockedKeys } from '@kbn/utility-types/jest';
import { Observable, from } from 'rxjs';
import {
CoreSetup,
RequestHandlerContext,
SharedGlobalConfig,
StartServicesAccessor,
} from 'src/core/server';
import {
coreMock,
httpServerMock,
pluginInitializerContextConfigMock,
} from '../../../../../../src/core/server/mocks';
import type { MockedKeys } from '@kbn/utility-types/jest';
import { from } from 'rxjs';
import { CoreSetup, RequestHandlerContext } from 'src/core/server';
import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { registerSearchRoute } from './search';
import { DataPluginStart } from '../../plugin';
import { dataPluginMock } from '../../mocks';
describe('Search service', () => {
let mockDataStart: MockedKeys<DataPluginStart>;
let mockCoreSetup: MockedKeys<CoreSetup<{}, DataPluginStart>>;
let getStartServices: jest.Mocked<StartServicesAccessor<{}, DataPluginStart>>;
let globalConfig$: Observable<SharedGlobalConfig>;
beforeEach(() => {
mockDataStart = dataPluginMock.createStartContract();
mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart });
getStartServices = mockCoreSetup.getStartServices;
globalConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$;
mockCoreSetup = coreMock.createSetup();
});
it('handler calls context.search.search with the given request and strategy', async () => {
@ -67,8 +51,12 @@ describe('Search service', () => {
},
};
mockDataStart.search.search.mockReturnValue(from(Promise.resolve(response)));
const mockContext = {};
const mockContext = {
search: {
search: jest.fn().mockReturnValue(from(Promise.resolve(response))),
},
};
const mockBody = { id: undefined, params: {} };
const mockParams = { strategy: 'foo' };
const mockRequest = httpServerMock.createKibanaRequest({
@ -77,14 +65,14 @@ describe('Search service', () => {
});
const mockResponse = httpServerMock.createResponseFactory();
registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ });
registerSearchRoute(mockCoreSetup.http.createRouter());
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
expect(mockDataStart.search.search).toBeCalled();
expect(mockDataStart.search.search.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockContext.search.search).toBeCalled();
expect(mockContext.search.search.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({
body: response,
@ -101,9 +89,12 @@ describe('Search service', () => {
})
);
mockDataStart.search.search.mockReturnValue(rejectedValue);
const mockContext = {
search: {
search: jest.fn().mockReturnValue(rejectedValue),
},
};
const mockContext = {};
const mockBody = { id: undefined, params: {} };
const mockParams = { strategy: 'foo' };
const mockRequest = httpServerMock.createKibanaRequest({
@ -112,14 +103,14 @@ describe('Search service', () => {
});
const mockResponse = httpServerMock.createResponseFactory();
registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ });
registerSearchRoute(mockCoreSetup.http.createRouter());
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.post.mock.calls[0][1];
await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse);
expect(mockDataStart.search.search).toBeCalled();
expect(mockDataStart.search.search.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockContext.search.search).toBeCalled();
expect(mockContext.search.search.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockResponse.customError).toBeCalled();
const error: any = mockResponse.customError.mock.calls[0][0];
expect(error.body.message).toBe('oh no');

View file

@ -21,13 +21,9 @@ import { first } from 'rxjs/operators';
import { schema } from '@kbn/config-schema';
import type { IRouter } from 'src/core/server';
import { getRequestAbortedSignal } from '../../lib';
import type { SearchRouteDependencies } from '../search_service';
import { shimHitsTotal } from './shim_hits_total';
export function registerSearchRoute(
router: IRouter,
{ getStartServices }: SearchRouteDependencies
): void {
export function registerSearchRoute(router: IRouter): void {
router.post(
{
path: '/internal/search/{strategy}/{id?}',
@ -47,17 +43,14 @@ export function registerSearchRoute(
const { strategy, id } = request.params;
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
const [, , selfStart] = await getStartServices();
try {
const response = await selfStart.search
.search(
const response = await context
.search!.search(
{ ...searchRequest, id },
{
abortSignal,
strategy,
},
context
}
)
.pipe(first())
.toPromise();
@ -99,12 +92,8 @@ export function registerSearchRoute(
async (context, request, res) => {
const { strategy, id } = request.params;
const [, , selfStart] = await getStartServices();
const searchStrategy = selfStart.search.getSearchStrategy(strategy);
if (!searchStrategy.cancel) return res.ok();
try {
await searchStrategy.cancel(context, id);
await context.search!.cancel(id, { strategy });
return res.ok();
} catch (err) {
return res.customError({

View file

@ -26,12 +26,17 @@ import {
Logger,
Plugin,
PluginInitializerContext,
RequestHandlerContext,
SharedGlobalConfig,
StartServicesAccessor,
} from 'src/core/server';
import { first } from 'rxjs/operators';
import { ISearchSetup, ISearchStart, ISearchStrategy, SearchEnhancements } from './types';
import {
ISearchSetup,
ISearchStart,
ISearchStrategy,
SearchEnhancements,
SearchStrategyDependencies,
} from './types';
import { AggsService, AggsSetupDependencies } from './aggs';
@ -53,6 +58,7 @@ import {
SearchSourceService,
searchSourceRequiredUiSettings,
ISearchOptions,
ISearchClient,
} from '../../common/search';
import {
getShardDelayBucketAgg,
@ -61,6 +67,12 @@ import {
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../../config';
declare module 'src/core/server' {
interface RequestHandlerContext {
search?: ISearchClient;
}
}
type StrategyMap = Record<string, ISearchStrategy<any, any>>;
/** @internal */
@ -103,9 +115,14 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
getStartServices: core.getStartServices,
globalConfig$: this.initializerContext.config.legacy.globalConfig$,
};
registerSearchRoute(router, routeDependencies);
registerSearchRoute(router);
registerMsearchRoute(router, routeDependencies);
core.http.registerRouteHandlerContext('search', async (context, request) => {
const [coreStart] = await core.getStartServices();
return this.asScopedProvider(coreStart)(request);
});
this.registerSearchStrategy(
ES_SEARCH_STRATEGY,
esSearchStrategyProvider(
@ -144,14 +161,17 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
usage,
};
}
public start(
{ elasticsearch, savedObjects, uiSettings }: CoreStart,
core: CoreStart,
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
): ISearchStart {
const { elasticsearch, savedObjects, uiSettings } = core;
const asScoped = this.asScopedProvider(core);
return {
aggs: this.aggsService.start({ fieldFormats, uiSettings, indexPatterns }),
getSearchStrategy: this.getSearchStrategy,
search: this.search.bind(this),
asScoped,
searchSource: {
asScoped: async (request: KibanaRequest) => {
const esClient = elasticsearch.client.asScoped(request);
@ -169,39 +189,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
const searchSourceDependencies: SearchSourceDependencies = {
getConfig: <T = any>(key: string): T => uiSettingsCache[key],
search: <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
searchStrategyRequest: SearchStrategyRequest,
options: ISearchOptions
) => {
/**
* Unless we want all SearchSource users to provide both a KibanaRequest
* (needed for index patterns) AND the RequestHandlerContext (needed for
* low-level search), we need to fake the context as it can be derived
* from the request object anyway. This will pose problems for folks who
* are registering custom search strategies as they are only getting a
* subset of the entire context. Ideally low-level search should be
* refactored to only require the needed dependencies: esClient & uiSettings.
*/
const fakeRequestHandlerContext = {
core: {
elasticsearch: {
client: esClient,
},
uiSettings: {
client: uiSettingsClient,
},
},
} as RequestHandlerContext;
return this.search<SearchStrategyRequest, SearchStrategyResponse>(
searchStrategyRequest,
options,
fakeRequestHandlerContext
).toPromise();
},
search: asScoped(request).search,
// onResponse isn't used on the server, so we just return the original value
onResponse: (req, res) => res,
legacy: {
@ -241,20 +229,26 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
>(
searchRequest: SearchStrategyRequest,
options: ISearchOptions,
context: RequestHandlerContext
deps: SearchStrategyDependencies
) => {
const strategy = this.getSearchStrategy<SearchStrategyRequest, SearchStrategyResponse>(
options.strategy || this.defaultSearchStrategyName
options.strategy
);
return strategy.search(searchRequest, options, context);
return strategy.search(searchRequest, options, deps);
};
private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => {
const strategy = this.getSearchStrategy(options.strategy);
return strategy.cancel ? strategy.cancel(id, options, deps) : Promise.resolve();
};
private getSearchStrategy = <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
>(
name: string
name: string = this.defaultSearchStrategyName
): ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> => {
this.logger.debug(`Get strategy ${name}`);
const strategy = this.searchStrategies[name];
@ -263,4 +257,19 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
}
return strategy;
};
private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => {
return (request: KibanaRequest): ISearchClient => {
const savedObjectsClient = savedObjects.getScopedClient(request);
const deps = {
savedObjectsClient,
esClient: elasticsearch.client.asScoped(request),
uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient),
};
return {
search: (searchRequest, options = {}) => this.search(searchRequest, options, deps),
cancel: (id, options = {}) => this.cancel(id, options, deps),
};
};
};
}

View file

@ -18,12 +18,18 @@
*/
import { Observable } from 'rxjs';
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
import {
IScopedClusterClient,
IUiSettingsClient,
SavedObjectsClientContract,
KibanaRequest,
} from 'src/core/server';
import {
ISearchOptions,
ISearchStartSearchSource,
IKibanaSearchRequest,
IKibanaSearchResponse,
ISearchClient,
} from '../../common/search';
import { AggsSetup, AggsStart } from './aggs';
import { SearchUsage } from './collectors';
@ -33,6 +39,12 @@ export interface SearchEnhancements {
defaultStrategy: string;
}
export interface SearchStrategyDependencies {
savedObjectsClient: SavedObjectsClientContract;
esClient: IScopedClusterClient;
uiSettingsClient: IUiSettingsClient;
}
export interface ISearchSetup {
aggs: AggsSetup;
/**
@ -69,9 +81,9 @@ export interface ISearchStrategy<
search: (
request: SearchStrategyRequest,
options: ISearchOptions,
context: RequestHandlerContext
deps: SearchStrategyDependencies
) => Observable<SearchStrategyResponse>;
cancel?: (context: RequestHandlerContext, id: string) => Promise<void>;
cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void>;
}
export interface ISearchStart<
@ -80,13 +92,14 @@ export interface ISearchStart<
> {
aggs: AggsStart;
/**
* Get other registered search strategies. For example, if a new strategy needs to use the
* already-registered ES search strategy, it can use this function to accomplish that.
* Get other registered search strategies by name (or, by default, the Elasticsearch strategy).
* For example, if a new strategy needs to use the already-registered ES search strategy, it can
* use this function to accomplish that.
*/
getSearchStrategy: (
name: string
name?: string // Name of the search strategy (defaults to the Elasticsearch strategy)
) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
search: ISearchStrategy['search'];
asScoped: (request: KibanaRequest) => ISearchClient;
searchSource: {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
};

View file

@ -22,8 +22,10 @@ import { ErrorToastOptions } from 'src/core/public/notifications';
import { ExpressionAstFunction } from 'src/plugins/expressions/common';
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
import { ISavedObjectsRepository } from 'kibana/server';
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 { KibanaRequest } from 'src/core/server';
import { LegacyAPICaller } from 'kibana/server';
import { Logger } from 'kibana/server';
@ -41,7 +43,6 @@ import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core
import { PublicMethodsOf } from '@kbn/utility-types';
import { RecursiveReadonly } from '@kbn/utility-types';
import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestHandlerContext } from 'src/core/server';
import { RequestStatistics } from 'src/plugins/inspector/common';
import { SavedObject } from 'src/core/server';
import { SavedObjectsClientContract } from 'src/core/server';
@ -364,7 +365,7 @@ export type Filter = {
// 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): Promise<{
export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient_2): Promise<{
maxConcurrentShardRequests: number | undefined;
ignoreUnavailable: boolean;
trackTotalHits: boolean;
@ -723,9 +724,11 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
//
// (undocumented)
aggs: AggsStart;
getSearchStrategy: (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts
//
// (undocumented)
search: ISearchStrategy['search'];
asScoped: (request: KibanaRequest) => ISearchClient;
getSearchStrategy: (name?: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>;
// (undocumented)
searchSource: {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
@ -737,9 +740,9 @@ export interface ISearchStart<SearchStrategyRequest extends IKibanaSearchRequest
// @public
export interface ISearchStrategy<SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse> {
// (undocumented)
cancel?: (context: RequestHandlerContext, id: string) => Promise<void>;
cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void>;
// (undocumented)
search: (request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) => Observable<SearchStrategyResponse>;
search: (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable<SearchStrategyResponse>;
}
// @public (undocumented)
@ -888,10 +891,10 @@ export class Plugin implements Plugin_2<PluginSetup, PluginStart, DataPluginSetu
// (undocumented)
start(core: CoreStart): {
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("src/core/server").SavedObjectsClient, "update" | "find" | "get" | "delete" | "errors" | "create" | "bulkCreate" | "checkConflicts" | "bulkGet" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo">) => Promise<import("../public").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
@ -960,7 +963,7 @@ export const search: {
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("../../../core/server").SearchResponse<unknown>>>(logger: import("@kbn/logging/target/logger").Logger, usage?: import("./search").SearchUsage | undefined) => import("rxjs").UnaryFunction<import("rxjs").Observable<KibanaResponse>, import("rxjs").Observable<KibanaResponse>>;
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;
@ -1007,6 +1010,18 @@ export const search: {
tabifyGetColumns: typeof tabifyGetColumns;
};
// Warning: (ae-missing-release-tag) "SearchStrategyDependencies" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface SearchStrategyDependencies {
// (undocumented)
esClient: IScopedClusterClient;
// (undocumented)
savedObjectsClient: SavedObjectsClientContract;
// (undocumented)
uiSettingsClient: IUiSettingsClient;
}
// Warning: (ae-missing-release-tag) "SearchUsage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@ -1147,24 +1162,24 @@ export function usageProvider(core: CoreSetup_2): SearchUsage;
// src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:234:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:234:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:234:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:234:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:249:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:250:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:275:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:278:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:235:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:235: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:5 - (ae-forgotten-export) The symbol "getTotalLoaded" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:251:5 - (ae-forgotten-export) The symbol "toSnakeCase" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:272:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:276:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:279:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index_patterns/index_patterns_service.ts:50: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:91:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" 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
// (No @packageDocumentation comment for this package)

View file

@ -19,7 +19,6 @@
import _ from 'lodash';
import { IRouter, CoreSetup } from 'kibana/server';
import { TimelionPluginStartDeps } from '../plugin';
export function validateEsRoute(router: IRouter, core: CoreSetup) {
router.get(
@ -29,7 +28,6 @@ export function validateEsRoute(router: IRouter, core: CoreSetup) {
},
async function (context, request, response) {
const uiSettings = await context.core.uiSettings.client.getAll();
const deps = (await core.getStartServices())[1] as TimelionPluginStartDeps;
const timefield = uiSettings['timelion:es.timefield'];
@ -56,7 +54,7 @@ export function validateEsRoute(router: IRouter, core: CoreSetup) {
let resp;
try {
resp = (await deps.data.search.search(body, {}, context).toPromise()).rawResponse;
resp = (await context.search!.search(body, {}).toPromise()).rawResponse;
} catch (errResp) {
resp = errResp;
}

View file

@ -16,8 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { from } from 'rxjs';
import { of } from 'rxjs';
import es from './index';
import tlConfigFn from '../fixtures/tl_config';
import * as aggResponse from './lib/agg_response_to_series_list';
@ -32,21 +32,10 @@ import { UI_SETTINGS } from '../../../../data/server';
describe('es', () => {
let tlConfig;
let dataSearchStub;
let mockResponse;
beforeEach(() => {
dataSearchStub = {
data: {
search: { search: jest.fn(() => from(Promise.resolve(mockResponse))) },
},
};
});
function stubRequestAndServer(response, indexPatternSavedObjects = []) {
mockResponse = response;
return {
getStartServices: sinon.stub().returns(Promise.resolve([{}, dataSearchStub])),
context: { search: { search: jest.fn().mockReturnValue(of(response)) } },
savedObjectsClient: {
find: function () {
return Promise.resolve({
@ -83,7 +72,7 @@ describe('es', () => {
await invoke(es, [5], tlConfig);
expect(dataSearchStub.data.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1);
expect(tlConfig.context.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1);
});
test('returns a seriesList', () => {

View file

@ -128,9 +128,8 @@ export default new Datasource('es', {
const esShardTimeout = tlConfig.esShardTimeout;
const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout);
const deps = (await tlConfig.getStartServices())[1];
const resp = await deps.data.search
const resp = await tlConfig.context.search
.search(
body,
{

View file

@ -18,7 +18,7 @@
*/
import moment from 'moment';
import sinon from 'sinon';
import { of } from 'rxjs';
import timelionDefaults from '../../lib/get_namespaced_settings';
import esResponse from './es_response';
@ -30,14 +30,6 @@ export default function () {
if (!functions[name]) throw new Error('No such function: ' + name);
return functions[name];
},
getStartServices: sinon
.stub()
.returns(
Promise.resolve([
{},
{ data: { search: { search: () => Promise.resolve({ rawResponse: esResponse }) } } },
])
),
esShardTimeout: moment.duration(30000),
allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'],
@ -54,5 +46,9 @@ export default function () {
tlConfig.setTargetSeries();
tlConfig.context = {
search: { search: () => of({ rawResponse: esResponse }) },
};
return tlConfig;
}

View file

@ -60,22 +60,8 @@ describe('AbstractSearchStrategy', () => {
const responses = await abstractSearchStrategy.search(
{
requestContext: {},
framework: {
core: {
getStartServices: jest.fn().mockReturnValue(
Promise.resolve([
{},
{
data: {
search: {
search: searchFn,
},
},
},
])
),
},
requestContext: {
search: { search: searchFn },
},
},
searches
@ -90,7 +76,6 @@ describe('AbstractSearchStrategy', () => {
},
indexType: undefined,
},
{},
{}
);
});

View file

@ -54,12 +54,11 @@ export class AbstractSearchStrategy {
}
async search(req: ReqFacade, bodies: any[], options = {}) {
const [, deps] = await req.framework.core.getStartServices();
const requests: any[] = [];
bodies.forEach((body) => {
requests.push(
deps.data.search
.search(
req.requestContext
.search!.search(
{
params: {
...body,
@ -69,8 +68,7 @@ export class AbstractSearchStrategy {
},
{
...options,
},
req.requestContext
}
)
.toPromise()
);

View file

@ -71,7 +71,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
timeout: this.searchTimeout,
});
const abortedPromise = toPromise(combinedSignal);
const strategy = options?.strategy || ENHANCED_ES_SEARCH_STRATEGY;
const strategy = options?.strategy ?? ENHANCED_ES_SEARCH_STRATEGY;
this.pendingCount$.next(this.pendingCount$.getValue() + 1);

View file

@ -3,10 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import type { RequestHandlerContext, Logger } from 'kibana/server';
import type { Logger } from 'kibana/server';
import { EqlSearchStrategyRequest } from '../../common/search/types';
import { eqlSearchStrategyProvider } from './eql_search_strategy';
import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server';
const getMockEqlResponse = () => ({
body: {
@ -46,32 +46,26 @@ describe('EQL search strategy', () => {
describe('search()', () => {
let mockEqlSearch: jest.Mock;
let mockEqlGet: jest.Mock;
let mockContext: RequestHandlerContext;
let mockDeps: SearchStrategyDependencies;
let params: Required<EqlSearchStrategyRequest>['params'];
let options: Required<EqlSearchStrategyRequest>['options'];
beforeEach(() => {
mockEqlSearch = jest.fn().mockResolvedValueOnce(getMockEqlResponse());
mockEqlGet = jest.fn().mockResolvedValueOnce(getMockEqlResponse());
mockContext = ({
core: {
uiSettings: {
client: {
get: jest.fn(),
},
},
elasticsearch: {
client: {
asCurrentUser: {
eql: {
get: mockEqlGet,
search: mockEqlSearch,
},
},
mockDeps = ({
uiSettingsClient: {
get: jest.fn(),
},
esClient: {
asCurrentUser: {
eql: {
get: mockEqlGet,
search: mockEqlSearch,
},
},
},
} as unknown) as RequestHandlerContext;
} as unknown) as SearchStrategyDependencies;
params = {
index: 'logstash-*',
body: { query: 'process where 1 == 1' },
@ -82,7 +76,7 @@ describe('EQL search strategy', () => {
describe('async functionality', () => {
it('performs an eql client search with params when no ID is provided', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch.search({ options, params }, {}, mockContext).toPromise();
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request, requestOptions]] = mockEqlSearch.mock.calls;
expect(request.index).toEqual('logstash-*');
@ -92,7 +86,7 @@ describe('EQL search strategy', () => {
it('retrieves the current request if an id is provided', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch.search({ id: 'my-search-id' }, {}, mockContext).toPromise();
await eqlSearch.search({ id: 'my-search-id' }, {}, mockDeps).toPromise();
const [[requestParams]] = mockEqlGet.mock.calls;
expect(mockEqlSearch).not.toHaveBeenCalled();
@ -103,7 +97,7 @@ describe('EQL search strategy', () => {
expect.assertions(1);
mockEqlSearch.mockReset().mockRejectedValueOnce(new Error('client error'));
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
eqlSearch.search({ options, params }, {}, mockContext).subscribe(
eqlSearch.search({ options, params }, {}, mockDeps).subscribe(
() => {},
(err) => {
expect(err).toEqual(new Error('client error'));
@ -115,7 +109,7 @@ describe('EQL search strategy', () => {
describe('arguments', () => {
it('sends along async search options', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch.search({ options, params }, {}, mockContext).toPromise();
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request]] = mockEqlSearch.mock.calls;
expect(request).toEqual(
@ -128,7 +122,7 @@ describe('EQL search strategy', () => {
it('sends along default search parameters', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch.search({ options, params }, {}, mockContext).toPromise();
await eqlSearch.search({ options, params }, {}, mockDeps).toPromise();
const [[request]] = mockEqlSearch.mock.calls;
expect(request).toEqual(
@ -152,7 +146,7 @@ describe('EQL search strategy', () => {
},
},
{},
mockContext
mockDeps
)
.toPromise();
const [[request]] = mockEqlSearch.mock.calls;
@ -175,7 +169,7 @@ describe('EQL search strategy', () => {
params,
},
{},
mockContext
mockDeps
)
.toPromise();
const [[, requestOptions]] = mockEqlSearch.mock.calls;
@ -191,7 +185,7 @@ describe('EQL search strategy', () => {
it('passes transport options for an existing request', async () => {
const eqlSearch = await eqlSearchStrategyProvider(mockLogger);
await eqlSearch
.search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockContext)
.search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockDeps)
.toPromise();
const [[, requestOptions]] = mockEqlGet.mock.calls;

View file

@ -21,25 +21,25 @@ export const eqlSearchStrategyProvider = (
logger: Logger
): ISearchStrategy<EqlSearchStrategyRequest, EqlSearchStrategyResponse> => {
return {
cancel: async (context, id) => {
cancel: async (id, options, { esClient }) => {
logger.debug(`_eql/delete ${id}`);
await context.core.elasticsearch.client.asCurrentUser.eql.delete({
await esClient.asCurrentUser.eql.delete({
id,
});
},
search: (request, options, context) => {
search: (request, options, { esClient, uiSettingsClient }) => {
logger.debug(`_eql/search ${JSON.stringify(request.params) || request.id}`);
const { utils } = search.esSearch;
const asyncOptions = getAsyncOptions();
const requestOptions = utils.toSnakeCase({ ...request.options });
const client = context.core.elasticsearch.client.asCurrentUser.eql;
const client = esClient.asCurrentUser.eql;
return doPartialSearch<ApiResponse<IEsRawSearchResponse>>(
async () => {
const { ignoreThrottled, ignoreUnavailable } = await getDefaultSearchParams(
context.core.uiSettings.client
uiSettingsClient
);
return client.search(

View file

@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { RequestHandlerContext } from '../../../../../src/core/server';
import { enhancedEsSearchStrategyProvider } from './es_search_strategy';
import { BehaviorSubject } from 'rxjs';
import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server/search';
const mockAsyncResponse = {
body: {
@ -40,26 +40,20 @@ describe('ES search strategy', () => {
const mockLogger: any = {
debug: () => {},
};
const mockContext = {
core: {
uiSettings: {
client: {
get: jest.fn(),
},
},
elasticsearch: {
client: {
asCurrentUser: {
asyncSearch: {
get: mockGetCaller,
submit: mockSubmitCaller,
},
transport: { request: mockApiCaller },
},
const mockDeps = ({
uiSettingsClient: {
get: jest.fn(),
},
esClient: {
asCurrentUser: {
asyncSearch: {
get: mockGetCaller,
submit: mockSubmitCaller,
},
transport: { request: mockApiCaller },
},
},
};
} as unknown) as SearchStrategyDependencies;
const mockConfig$ = new BehaviorSubject<any>({
elasticsearch: {
shardTimeout: {
@ -86,9 +80,7 @@ describe('ES search strategy', () => {
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
await esSearch
.search({ params }, {}, (mockContext as unknown) as RequestHandlerContext)
.toPromise();
await esSearch.search({ params }, {}, mockDeps).toPromise();
expect(mockSubmitCaller).toBeCalled();
const request = mockSubmitCaller.mock.calls[0][0];
@ -102,9 +94,7 @@ describe('ES search strategy', () => {
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
await esSearch
.search({ id: 'foo', params }, {}, (mockContext as unknown) as RequestHandlerContext)
.toPromise();
await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();
expect(mockGetCaller).toBeCalled();
const request = mockGetCaller.mock.calls[0][0];
@ -126,7 +116,7 @@ describe('ES search strategy', () => {
params,
},
{},
(mockContext as unknown) as RequestHandlerContext
mockDeps
)
.toPromise();
@ -142,9 +132,7 @@ describe('ES search strategy', () => {
const params = { index: 'foo-*', body: {} };
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
await esSearch
.search({ params }, {}, (mockContext as unknown) as RequestHandlerContext)
.toPromise();
await esSearch.search({ params }, {}, mockDeps).toPromise();
expect(mockSubmitCaller).toBeCalled();
const request = mockSubmitCaller.mock.calls[0][0];

View file

@ -11,15 +11,16 @@ import { Observable } from 'rxjs';
import type { SearchResponse } from 'elasticsearch';
import type { ApiResponse } from '@elastic/elasticsearch';
import { getShardTimeout, shimHitsTotal, search } from '../../../../../src/plugins/data/server';
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,
RequestHandlerContext,
Logger,
} from '../../../../../src/core/server';
import type { SharedGlobalConfig, Logger } from '../../../../../src/core/server';
import type {
ISearchStrategy,
@ -41,20 +42,20 @@ export const enhancedEsSearchStrategyProvider = (
config$: Observable<SharedGlobalConfig>,
logger: Logger,
usage?: SearchUsage
): ISearchStrategy => {
): ISearchStrategy<IEnhancedEsSearchRequest> => {
function asyncSearch(
request: IEnhancedEsSearchRequest,
options: ISearchOptions,
context: RequestHandlerContext
{ esClient, uiSettingsClient }: SearchStrategyDependencies
) {
const asyncOptions = getAsyncOptions();
const client = context.core.elasticsearch.client.asCurrentUser.asyncSearch;
const client = esClient.asCurrentUser.asyncSearch;
return doPartialSearch<ApiResponse<IEsRawAsyncSearchResponse>>(
async () =>
client.submit(
utils.toSnakeCase({
...(await getDefaultSearchParams(context.core.uiSettings.client)),
...(await getDefaultSearchParams(uiSettingsClient)),
batchedReduceSize: 64,
...asyncOptions,
...request.params,
@ -80,13 +81,11 @@ export const enhancedEsSearchStrategyProvider = (
);
}
const rollupSearch = async function (
async function rollupSearch(
request: IEnhancedEsSearchRequest,
options: ISearchOptions,
context: RequestHandlerContext
{ esClient, uiSettingsClient }: SearchStrategyDependencies
): Promise<IEsSearchResponse> {
const esClient = context.core.elasticsearch.client.asCurrentUser;
const uiSettingsClient = await context.core.uiSettings.client;
const config = await config$.pipe(first()).toPromise();
const { body, index, ...params } = request.params!;
const method = 'POST';
@ -97,7 +96,7 @@ export const enhancedEsSearchStrategyProvider = (
...params,
});
const promise = esClient.transport.request({
const promise = esClient.asCurrentUser.transport.request({
method,
path,
body,
@ -111,26 +110,19 @@ export const enhancedEsSearchStrategyProvider = (
rawResponse: response,
...utils.getTotalLoaded(response._shards),
};
};
}
return {
search: (
request: IEnhancedEsSearchRequest,
options: ISearchOptions,
context: RequestHandlerContext
) => {
search: (request, options, deps) => {
logger.debug(`search ${JSON.stringify(request.params) || request.id}`);
return request.indexType !== 'rollup'
? asyncSearch(request, options, context)
: from(rollupSearch(request, options, context));
? asyncSearch(request, options, deps)
: from(rollupSearch(request, options, deps));
},
cancel: async (context: RequestHandlerContext, id: string) => {
cancel: async (id, options, { esClient }) => {
logger.debug(`cancel ${id}`);
await context.core.elasticsearch.client.asCurrentUser.asyncSearch.delete({
id,
});
await esClient.asCurrentUser.asyncSearch.delete({ id });
},
};
};

View file

@ -26,11 +26,10 @@ export const securitySolutionIndexFieldsProvider = (): ISearchStrategy<
const beatFields: BeatFields = require('../../utils/beat_schema/fields').fieldsBeat;
return {
search: (request, options, context) =>
search: (request, options, { esClient }) =>
from(
new Promise<IndexFieldsStrategyResponse>(async (resolve) => {
const { elasticsearch } = context.core;
const indexPatternsFetcher = new IndexPatternsFetcher(elasticsearch.client.asCurrentUser);
const indexPatternsFetcher = new IndexPatternsFetcher(esClient.asCurrentUser);
const dedupeIndices = dedupeIndexName(request.indices);
const responsesIndexFields = await Promise.all(

View file

@ -20,7 +20,7 @@ export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTyp
const es = data.search.getSearchStrategy('es');
return {
search: (request, options, context) => {
search: (request, options, deps) => {
if (request.factoryQueryType == null) {
throw new Error('factoryQueryType is required');
}
@ -28,12 +28,12 @@ export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTyp
securitySolutionFactory[request.factoryQueryType];
const dsl = queryFactory.buildDsl(request);
return es
.search({ ...request, params: dsl }, options, context)
.search({ ...request, params: dsl }, options, deps)
.pipe(mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes)));
},
cancel: async (context, id) => {
cancel: async (id, options, deps) => {
if (es.cancel) {
es.cancel(context, id);
return es.cancel(id, options, deps);
}
},
};

View file

@ -20,7 +20,7 @@ export const securitySolutionTimelineSearchStrategyProvider = <T extends Timelin
const es = data.search.getSearchStrategy('es');
return {
search: (request, options, context) => {
search: (request, options, deps) => {
if (request.factoryQueryType == null) {
throw new Error('factoryQueryType is required');
}
@ -29,12 +29,12 @@ export const securitySolutionTimelineSearchStrategyProvider = <T extends Timelin
const dsl = queryFactory.buildDsl(request);
return es
.search({ ...request, params: dsl }, options, context)
.search({ ...request, params: dsl }, options, deps)
.pipe(mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes)));
},
cancel: async (context, id) => {
cancel: async (id, options, deps) => {
if (es.cancel) {
es.cancel(context, id);
return es.cancel(id, options, deps);
}
},
};