[Search service] Refactor the way server-side search strategies are registered (#68452) (#69331)

* [search] Refactor the way search strategies are registered/retrieved on the server

* Fix types and tests and update docs

* Fix failing test

* Fix build of example plugin

* Fix functional test

* Make server strategies sync

Co-authored-by: Liza K <liza.katz@elastic.co>

Co-authored-by: Lukas Olson <olson.lukas@gmail.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Liza Katz 2020-06-17 15:09:06 +03:00 committed by GitHub
parent fea77cd5ff
commit 48278a7c28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 550 additions and 739 deletions

View file

@ -4,6 +4,8 @@
## IRequestTypesMap interface
The map of search strategy IDs to the corresponding request type definitions.
<b>Signature:</b>
```typescript

View file

@ -4,6 +4,8 @@
## IResponseTypesMap interface
The map of search strategy IDs to the corresponding response type definitions.
<b>Signature:</b>
```typescript

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
export declare type ISearch<T extends TStrategyTypes> = (request: IRequestTypesMap[T], options?: ISearchOptions) => Promise<IResponseTypesMap[T]>;
export declare type ISearch<T extends TStrategyTypes> = (context: RequestHandlerContext, request: IRequestTypesMap[T], options?: ISearchOptions) => Promise<IResponseTypesMap[T]>;
```

View file

@ -7,5 +7,5 @@
<b>Signature:</b>
```typescript
export declare type ISearchCancel<T extends TStrategyTypes> = (id: string) => Promise<void>;
export declare type ISearchCancel<T extends TStrategyTypes> = (context: RequestHandlerContext, id: string) => Promise<void>;
```

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-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) &gt; [config$](./kibana-plugin-plugins-data-server.isearchcontext.config_.md)
## ISearchContext.config$ property
<b>Signature:</b>
```typescript
config$: Observable<SharedGlobalConfig>;
```

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-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) &gt; [core](./kibana-plugin-plugins-data-server.isearchcontext.core.md)
## ISearchContext.core property
<b>Signature:</b>
```typescript
core: CoreSetup;
```

View file

@ -1,19 +0,0 @@
<!-- 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; [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md)
## ISearchContext interface
<b>Signature:</b>
```typescript
export interface ISearchContext
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [config$](./kibana-plugin-plugins-data-server.isearchcontext.config_.md) | <code>Observable&lt;SharedGlobalConfig&gt;</code> | |
| [core](./kibana-plugin-plugins-data-server.isearchcontext.core.md) | <code>CoreSetup</code> | |

View file

@ -14,5 +14,5 @@ export interface ISearchOptions
| Property | Type | Description |
| --- | --- | --- |
| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | <code>AbortSignal</code> | |
| [signal](./kibana-plugin-plugins-data-server.isearchoptions.signal.md) | <code>AbortSignal</code> | An <code>AbortSignal</code> that allows the caller of <code>search</code> to abort a search request. |

View file

@ -4,6 +4,8 @@
## ISearchOptions.signal property
An `AbortSignal` that allows the caller of `search` to abort a search request.
<b>Signature:</b>
```typescript

View file

@ -0,0 +1,18 @@
<!-- 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; [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md)
## ISearchSetup interface
<b>Signature:</b>
```typescript
export interface ISearchSetup
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <code>TRegisterSearchStrategy</code> | Extension point exposed for other plugins to register their own search strategies. |

View file

@ -0,0 +1,13 @@
<!-- 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; [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) &gt; [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md)
## ISearchSetup.registerSearchStrategy property
Extension point exposed for other plugins to register their own search strategies.
<b>Signature:</b>
```typescript
registerSearchStrategy: TRegisterSearchStrategy;
```

View file

@ -0,0 +1,13 @@
<!-- 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; [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md)
## 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.
<b>Signature:</b>
```typescript
getSearchStrategy: TGetSearchStrategy;
```

View file

@ -0,0 +1,18 @@
<!-- 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)
## ISearchStart interface
<b>Signature:</b>
```typescript
export interface ISearchStart
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | <code>TGetSearchStrategy</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. |

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; [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) &gt; [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md)
## ISearchStrategy.cancel property
<b>Signature:</b>
```typescript
cancel?: ISearchCancel<T>;
```

View file

@ -0,0 +1,21 @@
<!-- 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; [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md)
## ISearchStrategy interface
Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response.
<b>Signature:</b>
```typescript
export interface ISearchStrategy<T extends TStrategyTypes>
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | <code>ISearchCancel&lt;T&gt;</code> | |
| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | <code>ISearch&lt;T&gt;</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; [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) &gt; [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md)
## ISearchStrategy.search property
<b>Signature:</b>
```typescript
search: ISearch<T>;
```

View file

@ -39,10 +39,12 @@
| [IIndexPattern](./kibana-plugin-plugins-data-server.iindexpattern.md) | |
| [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Use data plugin interface instead |
| [IndexPatternFieldDescriptor](./kibana-plugin-plugins-data-server.indexpatternfielddescriptor.md) | |
| [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) | |
| [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) | |
| [ISearchContext](./kibana-plugin-plugins-data-server.isearchcontext.md) | |
| [IRequestTypesMap](./kibana-plugin-plugins-data-server.irequesttypesmap.md) | The map of search strategy IDs to the corresponding request type definitions. |
| [IResponseTypesMap](./kibana-plugin-plugins-data-server.iresponsetypesmap.md) | The map of search strategy IDs to the corresponding response type definitions. |
| [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | |
| [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | |
| [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | |
| [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. |
| [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | |
| [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | |
| [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | |
@ -73,5 +75,5 @@
| [ISearch](./kibana-plugin-plugins-data-server.isearch.md) | |
| [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md) | |
| [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | |
| [TSearchStrategyProvider](./kibana-plugin-plugins-data-server.tsearchstrategyprovider.md) | Search strategy provider creates an instance of a search strategy with the request handler context bound to it. This way every search strategy can use whatever information they require from the request context. |
| [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md) | Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via:<!-- -->const MY\_STRATEGY = 'MY\_STRATEGY';<!-- -->declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; }<!-- -->export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } } |

View file

@ -7,11 +7,11 @@
<b>Signature:</b>
```typescript
setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
setup(core: CoreSetup<object, DataPluginStart>, { usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
search: ISearchSetup;
};
```
@ -19,15 +19,15 @@ setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
| Parameter | Type | Description |
| --- | --- | --- |
| core | <code>CoreSetup</code> | |
| core | <code>CoreSetup&lt;object, DataPluginStart&gt;</code> | |
| { usageCollection } | <code>DataPluginSetupDependencies</code> | |
<b>Returns:</b>
`{
search: ISearchSetup;
fieldFormats: {
register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
search: ISearchSetup;
}`

View file

@ -8,8 +8,9 @@
```typescript
start(core: CoreStart): {
search: ISearchStart;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
};
```
@ -23,8 +24,9 @@ start(core: CoreStart): {
<b>Returns:</b>
`{
search: ISearchStart;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
}`

View file

@ -15,4 +15,5 @@ export interface DataPluginStart
| Property | Type | Description |
| --- | --- | --- |
| [fieldFormats](./kibana-plugin-plugins-data-server.pluginstart.fieldformats.md) | <code>FieldFormatsStart</code> | |
| [search](./kibana-plugin-plugins-data-server.pluginstart.search.md) | <code>ISearchStart</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; [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) &gt; [search](./kibana-plugin-plugins-data-server.pluginstart.search.md)
## PluginStart.search property
<b>Signature:</b>
```typescript
search: ISearchStart;
```

View file

@ -1,13 +0,0 @@
<!-- 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; [TSearchStrategyProvider](./kibana-plugin-plugins-data-server.tsearchstrategyprovider.md)
## TSearchStrategyProvider type
Search strategy provider creates an instance of a search strategy with the request handler context bound to it. This way every search strategy can use whatever information they require from the request context.
<b>Signature:</b>
```typescript
export declare type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearchContext, caller: APICaller, search: ISearchGeneric) => ISearchStrategy<T>;
```

View file

@ -0,0 +1,19 @@
<!-- 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; [TStrategyTypes](./kibana-plugin-plugins-data-server.tstrategytypes.md)
## TStrategyTypes type
Contains all known strategy type identifiers that will be used to map to request and response shapes. Plugins that wish to add their own custom search strategies should extend this type via:
const MY\_STRATEGY = 'MY\_STRATEGY';
declare module 'src/plugins/search/server' { export interface IRequestTypesMap { \[MY\_STRATEGY\]: IMySearchRequest; }
export interface IResponseTypesMap { \[MY\_STRATEGY\]: IMySearchResponse } }
<b>Signature:</b>
```typescript
export declare type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string;
```

View file

@ -17,30 +17,32 @@
* under the License.
*/
import { TSearchStrategyProvider } from '../../../src/plugins/data/server';
import { ASYNC_DEMO_SEARCH_STRATEGY } from '../common';
import { ISearchStrategy } from '../../../src/plugins/data/server';
import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoRequest } from '../common';
function getFibonacciSequence(n = 0) {
const beginning = [0, 1].slice(0, n);
return Array(Math.max(0, n))
.fill(null)
.reduce((sequence, value, i) => {
if (i < 2) return sequence;
return [...sequence, sequence[i - 1] + sequence[i - 2]];
}, beginning);
}
export const asyncDemoSearchStrategyProvider = (): ISearchStrategy<
typeof ASYNC_DEMO_SEARCH_STRATEGY
> => {
function getFibonacciSequence(n = 0) {
const beginning = [0, 1].slice(0, n);
return Array(Math.max(0, n))
.fill(null)
.reduce((sequence, value, i) => {
if (i < 2) return sequence;
return [...sequence, sequence[i - 1] + sequence[i - 2]];
}, beginning);
}
const generateId = (() => {
let id = 0;
return () => `${id++}`;
})();
const generateId = (() => {
let id = 0;
return () => `${id++}`;
})();
const loadedMap = new Map<string, number>();
const totalMap = new Map<string, number>();
const loadedMap = new Map<string, number>();
const totalMap = new Map<string, number>();
export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider<typeof ASYNC_DEMO_SEARCH_STRATEGY> = () => {
return {
search: async (request) => {
search: async (context, request: IAsyncDemoRequest) => {
const id = request.id ?? generateId();
const loaded = (loadedMap.get(id) ?? 0) + 1;
@ -52,7 +54,7 @@ export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider<typeof ASY
const fibonacciSequence = getFibonacciSequence(loaded);
return { id, total, loaded, fibonacciSequence };
},
cancel: async (id) => {
cancel: async (context, id) => {
loadedMap.delete(id);
totalMap.delete(id);
},

View file

@ -17,12 +17,12 @@
* under the License.
*/
import { TSearchStrategyProvider } from '../../../src/plugins/data/server';
import { DEMO_SEARCH_STRATEGY } from '../common';
import { ISearchStrategy } from '../../../src/plugins/data/server';
import { DEMO_SEARCH_STRATEGY, IDemoRequest } from '../common';
export const demoSearchStrategyProvider: TSearchStrategyProvider<typeof DEMO_SEARCH_STRATEGY> = () => {
export const demoSearchStrategyProvider = (): ISearchStrategy<typeof DEMO_SEARCH_STRATEGY> => {
return {
search: (request) => {
search: (context, request: IDemoRequest) => {
return Promise.resolve({
greeting:
request.mood === 'happy'

View file

@ -17,9 +17,6 @@
* under the License.
*/
import { PluginInitializerContext, PluginInitializer } from 'kibana/server';
import { DemoDataPlugin } from './plugin';
export const plugin: PluginInitializer<void, void> = (
initializerContext: PluginInitializerContext
) => new DemoDataPlugin(initializerContext);
export const plugin = () => new DemoDataPlugin();

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server';
import { Plugin, CoreSetup } from 'kibana/server';
import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server';
import { demoSearchStrategyProvider } from './demo_search_strategy';
import {
@ -56,18 +56,13 @@ declare module '../../../src/plugins/data/server' {
}
export class DemoDataPlugin implements Plugin<void, void, IDemoSearchExplorerDeps> {
constructor(private initializerContext: PluginInitializerContext) {}
constructor() {}
public setup(core: CoreSetup, deps: IDemoSearchExplorerDeps) {
deps.data.search.registerSearchStrategyProvider(
this.initializerContext.opaqueId,
DEMO_SEARCH_STRATEGY,
demoSearchStrategyProvider
);
deps.data.search.registerSearchStrategyProvider(
this.initializerContext.opaqueId,
deps.data.search.registerSearchStrategy(DEMO_SEARCH_STRATEGY, demoSearchStrategyProvider());
deps.data.search.registerSearchStrategy(
ASYNC_DEMO_SEARCH_STRATEGY,
asyncDemoSearchStrategyProvider
asyncDemoSearchStrategyProvider()
);
}

View file

@ -23,11 +23,6 @@ import { GuideSection } from './guide_section';
import publicSearch from '!!raw-loader!./../../../src/plugins/data/public/search/i_search';
// @ts-ignore
import publicPlugin from '!!raw-loader!./../../../src/plugins/data/public/search/search_service';
// @ts-ignore
import serverSetupContract from '!!raw-loader!./../../../src/plugins/data/server/search/i_search_setup';
// @ts-ignore
import serverSearch from '!!raw-loader!./../../../src/plugins/data/server/search/i_search';
// @ts-ignore
import serverPlugin from '!!raw-loader!./../../../src/plugins/data/server/search/search_service';
@ -54,14 +49,6 @@ export const SearchApiPage = () => (
description: 'search_service.ts',
snippet: serverPlugin,
},
{
description: `i_search_setup.ts`,
snippet: serverSetupContract,
},
{
description: 'i_search',
snippet: serverSearch,
},
],
},
]}

View file

@ -17,9 +17,14 @@
* under the License.
*/
import { ISearchGeneric, ISearchCancelGeneric } from './i_search';
export interface IRouteHandlerSearchContext {
search: ISearchGeneric;
cancel: ISearchCancelGeneric;
export function createFieldFormatsSetupMock() {
return {
register: jest.fn(),
};
}
export function createFieldFormatsStartMock() {
return {
fieldFormatServiceFactory: jest.fn(),
};
}

View file

@ -172,8 +172,10 @@ export {
ISearchOptions,
IRequestTypesMap,
IResponseTypesMap,
ISearchContext,
TSearchStrategyProvider,
ISearchSetup,
ISearchStart,
TStrategyTypes,
ISearchStrategy,
getDefaultSearchParams,
getTotalLoaded,
} from './search';

View file

@ -17,10 +17,24 @@
* under the License.
*/
import { Observable } from 'rxjs';
import { CoreSetup, SharedGlobalConfig } from '../../../../core/server';
import { createSearchSetupMock, createSearchStartMock } from './search/mocks';
import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks';
export interface ISearchContext {
core: CoreSetup;
config$: Observable<SharedGlobalConfig>;
function createSetupContract() {
return {
search: createSearchSetupMock(),
fieldFormats: createFieldFormatsSetupMock(),
};
}
function createStartContract() {
return {
search: createSearchStartMock(),
fieldFormats: createFieldFormatsStartMock(),
};
}
export const dataPluginMock = {
createSetupContract,
createStartContract,
};

View file

@ -20,7 +20,7 @@
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server';
import { ConfigSchema } from '../config';
import { IndexPatternsService } from './index_patterns';
import { ISearchSetup } from './search';
import { ISearchSetup, ISearchStart } from './search';
import { SearchService } from './search/search_service';
import { QueryService } from './query/query_service';
import { ScriptsService } from './scripts';
@ -36,6 +36,7 @@ export interface DataPluginSetup {
}
export interface DataPluginStart {
search: ISearchStart;
fieldFormats: FieldFormatsStart;
}
@ -59,7 +60,10 @@ export class DataServerPlugin implements Plugin<DataPluginSetup, DataPluginStart
this.autocompleteService = new AutocompleteService(initializerContext);
}
public setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies) {
public setup(
core: CoreSetup<object, DataPluginStart>,
{ usageCollection }: DataPluginSetupDependencies
) {
this.indexPatterns.setup(core);
this.scriptsService.setup(core);
this.queryService.setup(core);
@ -69,13 +73,14 @@ export class DataServerPlugin implements Plugin<DataPluginSetup, DataPluginStart
core.uiSettings.register(getUiSettings());
return {
fieldFormats: this.fieldFormats.setup(),
search: this.searchService.setup(core),
fieldFormats: this.fieldFormats.setup(),
};
}
public start(core: CoreStart) {
return {
search: this.searchService.start(),
fieldFormats: this.fieldFormats.start(),
};
}

View file

@ -1,71 +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 { createApi } from './create_api';
import { TSearchStrategiesMap } from './i_search_strategy';
import { IRouteHandlerSearchContext } from './i_route_handler_search_context';
import { DEFAULT_SEARCH_STRATEGY } from '../../common/search';
const mockDefaultSearch = jest.fn(() => Promise.resolve({ total: 100, loaded: 0 }));
const mockDefaultSearchStrategyProvider = jest.fn(() =>
Promise.resolve({
search: mockDefaultSearch,
})
);
const mockStrategies: TSearchStrategiesMap = {
[DEFAULT_SEARCH_STRATEGY]: mockDefaultSearchStrategyProvider,
};
describe('createApi', () => {
let api: IRouteHandlerSearchContext;
beforeEach(() => {
api = createApi({
caller: jest.fn(),
searchStrategies: mockStrategies,
});
mockDefaultSearchStrategyProvider.mockClear();
});
it('should default to DEFAULT_SEARCH_STRATEGY if none is provided', async () => {
await api.search({
params: {},
});
expect(mockDefaultSearchStrategyProvider).toBeCalled();
expect(mockDefaultSearch).toBeCalled();
});
it('should throw if no provider is found for the given name', () => {
expect(api.search({}, {}, 'noneByThisName')).rejects.toThrowErrorMatchingInlineSnapshot(
`"No strategy found for noneByThisName"`
);
});
it('logs the response if `debug` is set to `true`', async () => {
const spy = jest.spyOn(console, 'log');
await api.search({ params: {} });
expect(spy).not.toBeCalled();
await api.search({ debug: true, params: {} });
expect(spy).toBeCalled();
});
});

View file

@ -1,58 +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 { APICaller } from 'kibana/server';
import { IRouteHandlerSearchContext } from './i_route_handler_search_context';
import { DEFAULT_SEARCH_STRATEGY } from '../../common/search';
import { TSearchStrategiesMap } from './i_search_strategy';
export function createApi({
caller,
searchStrategies,
}: {
searchStrategies: TSearchStrategiesMap;
caller: APICaller;
}) {
const api: IRouteHandlerSearchContext = {
search: async (request, options, strategyName) => {
if (request.debug) {
// eslint-disable-next-line
console.log(JSON.stringify(request, null, 2));
}
const name = strategyName ?? DEFAULT_SEARCH_STRATEGY;
const strategyProvider = searchStrategies[name];
if (!strategyProvider) {
throw new Error(`No strategy found for ${strategyName}`);
}
// Give providers access to other search strategies by injecting this function
const strategy = await strategyProvider(caller, api.search);
return strategy.search(request, options);
},
cancel: async (id, strategyName) => {
const name = strategyName ?? DEFAULT_SEARCH_STRATEGY;
const strategyProvider = searchStrategies[name];
if (!strategyProvider) {
throw new Error(`No strategy found for ${strategyName}`);
}
const strategy = await strategyProvider(caller, api.search);
return strategy.cancel && strategy.cancel(id);
},
};
return api;
}

View file

@ -17,11 +17,11 @@
* under the License.
*/
import { coreMock, pluginInitializerContextConfigMock } from '../../../../../core/server/mocks';
import { RequestHandlerContext } from '../../../../../core/server';
import { pluginInitializerContextConfigMock } from '../../../../../core/server/mocks';
import { esSearchStrategyProvider } from './es_search_strategy';
describe('ES search strategy', () => {
const mockCoreSetup = coreMock.createSetup();
const mockApiCaller = jest.fn().mockResolvedValue({
_shards: {
total: 10,
@ -30,39 +30,26 @@ describe('ES search strategy', () => {
successful: 7,
},
});
const mockSearch = jest.fn();
const mockContext = {
core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } },
};
const mockConfig$ = pluginInitializerContextConfigMock<any>({}).legacy.globalConfig$;
beforeEach(() => {
mockApiCaller.mockClear();
mockSearch.mockClear();
});
it('returns a strategy with `search`', () => {
const esSearch = esSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
it('returns a strategy with `search`', async () => {
const esSearch = await esSearchStrategyProvider(mockConfig$);
expect(typeof esSearch.search).toBe('function');
});
it('calls the API caller with the params with defaults', async () => {
const params = { index: 'logstash-*' };
const esSearch = esSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await esSearchStrategyProvider(mockConfig$);
await esSearch.search({ params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('search');
@ -76,16 +63,9 @@ describe('ES search strategy', () => {
it('calls the API caller with overridden defaults', async () => {
const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' };
const esSearch = esSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await esSearchStrategyProvider(mockConfig$);
await esSearch.search({ params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('search');
@ -97,16 +77,11 @@ describe('ES search strategy', () => {
it('returns total, loaded, and raw response', async () => {
const params = { index: 'logstash-*' };
const esSearch = esSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await esSearchStrategyProvider(mockConfig$);
const response = await esSearch.search({ params });
const response = await esSearch.search((mockContext as unknown) as RequestHandlerContext, {
params,
});
expect(response).toHaveProperty('total');
expect(response).toHaveProperty('loaded');

View file

@ -17,19 +17,18 @@
* under the License.
*/
import { first } from 'rxjs/operators';
import { APICaller } from 'kibana/server';
import { RequestHandlerContext, SharedGlobalConfig } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
import { ES_SEARCH_STRATEGY } from '../../../common/search';
import { ISearchStrategy, TSearchStrategyProvider } from '../i_search_strategy';
import { getDefaultSearchParams, getTotalLoaded, ISearchContext } from '..';
import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..';
export const esSearchStrategyProvider: TSearchStrategyProvider<typeof ES_SEARCH_STRATEGY> = (
context: ISearchContext,
caller: APICaller
export const esSearchStrategyProvider = (
config$: Observable<SharedGlobalConfig>
): ISearchStrategy<typeof ES_SEARCH_STRATEGY> => {
return {
search: async (request, options) => {
const config = await context.config$.pipe(first()).toPromise();
search: async (context: RequestHandlerContext, request, options) => {
const config = await config$.pipe(first()).toPromise();
const defaultParams = getDefaultSearchParams(config);
// Only default index pattern type is supported here.
@ -42,7 +41,12 @@ export const esSearchStrategyProvider: TSearchStrategyProvider<typeof ES_SEARCH_
...defaultParams,
...request.params,
};
const rawResponse = (await caller('search', params, options)) as SearchResponse<any>;
const rawResponse = (await context.core.elasticsearch.legacy.client.callAsCurrentUser(
'search',
params,
options
)) as SearchResponse<any>;
// The above query will either complete or timeout and throw an error.
// There is no progress indication on this api.

View file

@ -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 { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search';
import { TStrategyTypes } from './strategy_types';
import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../common/search/es_search';
import { IEsSearchRequest } from './es_search';
export interface ISearchOptions {
signal?: AbortSignal;
}
export interface IRequestTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchRequest;
[key: string]: IKibanaSearchRequest;
}
export interface IResponseTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchResponse;
[key: string]: IKibanaSearchResponse;
}
export type ISearchGeneric = <T extends TStrategyTypes = typeof ES_SEARCH_STRATEGY>(
request: IRequestTypesMap[T],
options?: ISearchOptions,
strategy?: T
) => Promise<IResponseTypesMap[T]>;
export type ISearchCancelGeneric = <T extends TStrategyTypes = typeof ES_SEARCH_STRATEGY>(
id: string,
strategy?: T
) => Promise<void>;
export type ISearch<T extends TStrategyTypes> = (
request: IRequestTypesMap[T],
options?: ISearchOptions
) => Promise<IResponseTypesMap[T]>;
export type ISearchCancel<T extends TStrategyTypes> = (id: string) => Promise<void>;

View file

@ -1,40 +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 { IContextProvider } from 'kibana/server';
import { ISearchContext } from './i_search_context';
import { TRegisterSearchStrategyProvider, TSearchStrategyProvider } from './i_search_strategy';
/**
* The setup contract exposed by the Search plugin exposes the search strategy extension
* point.
*/
export interface ISearchSetup {
registerSearchStrategyContext: <TContextName extends keyof ISearchContext>(
pluginId: symbol,
strategyName: TContextName,
provider: IContextProvider<TSearchStrategyProvider<any>, TContextName>
) => void;
/**
* Extension point exposed for other plugins to register their own search
* strategies.
*/
registerSearchStrategyProvider: TRegisterSearchStrategyProvider;
}

View file

@ -1,67 +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 { APICaller } from 'kibana/server';
import { ISearch, ISearchCancel, ISearchGeneric } from './i_search';
import { TStrategyTypes } from './strategy_types';
import { ISearchContext } from './i_search_context';
/**
* Search strategy interface contains a search method that takes in
* a request and returns a promise that resolves to a response.
*/
export interface ISearchStrategy<T extends TStrategyTypes> {
search: ISearch<T>;
cancel?: ISearchCancel<T>;
}
/**
* Search strategy provider creates an instance of a search strategy with the request
* handler context bound to it. This way every search strategy can use
* whatever information they require from the request context.
*/
export type TSearchStrategyProviderEnhanced<T extends TStrategyTypes> = (
caller: APICaller,
search: ISearchGeneric
) => Promise<ISearchStrategy<T>>;
/**
* Search strategy provider creates an instance of a search strategy with the request
* handler context bound to it. This way every search strategy can use
* whatever information they require from the request context.
*/
export type TSearchStrategyProvider<T extends TStrategyTypes> = (
context: ISearchContext,
caller: APICaller,
search: ISearchGeneric
) => ISearchStrategy<T>;
/**
* Extension point exposed for other plugins to register their own search
* strategies.
*/
export type TRegisterSearchStrategyProvider = <T extends TStrategyTypes>(
opaqueId: symbol,
name: T,
searchStrategyProvider: TSearchStrategyProvider<T>
) => void;
export type TSearchStrategiesMap = {
[K in TStrategyTypes]?: TSearchStrategyProviderEnhanced<K>;
};

View file

@ -17,20 +17,16 @@
* under the License.
*/
export { ISearchSetup } from './i_search_setup';
export { ISearchContext } from './i_search_context';
export {
ISearch,
ISearchCancel,
ISearchOptions,
IRequestTypesMap,
IResponseTypesMap,
} from './i_search';
export { TStrategyTypes } from './strategy_types';
export { TSearchStrategyProvider } from './i_search_strategy';
ISearchSetup,
ISearchStart,
TStrategyTypes,
ISearchStrategy,
} from './types';
export { getDefaultSearchParams, getTotalLoaded } from './es_search';

View file

@ -17,10 +17,14 @@
* under the License.
*/
export const searchSetupMock = {
registerSearchStrategyContext: jest.fn(),
registerSearchStrategyProvider: jest.fn(),
__LEGACY: {
search: jest.fn(),
},
};
export function createSearchSetupMock() {
return {
registerSearchStrategy: jest.fn(),
};
}
export function createSearchStartMock() {
return {
getSearchStrategy: jest.fn(),
};
}

View file

@ -17,36 +17,26 @@
* under the License.
*/
import { httpServiceMock, httpServerMock } from '../../../../../src/core/server/mocks';
import { CoreSetup, RequestHandlerContext } from '../../../../../src/core/server';
import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks';
import { registerSearchRoute } from './routes';
import { IRouter, ScopedClusterClient, RequestHandlerContext } from 'kibana/server';
import { DataPluginStart } from '../plugin';
import { dataPluginMock } from '../mocks';
describe('Search service', () => {
let routerMock: jest.Mocked<IRouter>;
let mockDataStart: MockedKeys<DataPluginStart>;
let mockCoreSetup: MockedKeys<CoreSetup<object, DataPluginStart>>;
beforeEach(() => {
routerMock = httpServiceMock.createRouter();
});
it('registers a post route', async () => {
registerSearchRoute(routerMock);
expect(routerMock.post).toBeCalled();
mockDataStart = dataPluginMock.createStartContract();
mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart });
});
it('handler calls context.search.search with the given request and strategy', async () => {
const mockSearch = jest.fn().mockResolvedValue('yay');
const mockContext = {
core: {
elasticsearch: {
legacy: {
client: {} as ScopedClusterClient,
},
},
},
search: {
search: mockSearch,
},
};
mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });
const mockContext = {};
const mockBody = { params: {} };
const mockParams = { strategy: 'foo' };
const mockRequest = httpServerMock.createKibanaRequest({
@ -55,13 +45,15 @@ describe('Search service', () => {
});
const mockResponse = httpServerMock.createResponseFactory();
registerSearchRoute(routerMock);
const handler = routerMock.post.mock.calls[0][1];
registerSearchRoute(mockCoreSetup);
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.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy);
expect(mockSearch).toBeCalled();
expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy);
expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody);
expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' });
});
@ -73,18 +65,9 @@ describe('Search service', () => {
error: 'oops',
},
});
const mockContext = {
core: {
elasticsearch: {
legacy: {
client: {} as ScopedClusterClient,
},
},
},
search: {
search: mockSearch,
},
};
mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });
const mockContext = {};
const mockBody = { params: {} };
const mockParams = { strategy: 'foo' };
const mockRequest = httpServerMock.createKibanaRequest({
@ -93,13 +76,15 @@ describe('Search service', () => {
});
const mockResponse = httpServerMock.createResponseFactory();
registerSearchRoute(routerMock);
const handler = routerMock.post.mock.calls[0][1];
registerSearchRoute(mockCoreSetup);
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.getSearchStrategy.mock.calls[0][0]).toBe(mockParams.strategy);
expect(mockSearch).toBeCalled();
expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody);
expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy);
expect(mockSearch.mock.calls[0][1]).toStrictEqual(mockBody);
expect(mockResponse.customError).toBeCalled();
const error: any = mockResponse.customError.mock.calls[0][0];
expect(error.body.message).toBe('oh no');

View file

@ -18,10 +18,13 @@
*/
import { schema } from '@kbn/config-schema';
import { IRouter } from '../../../../core/server';
import { CoreSetup } from '../../../../core/server';
import { getRequestAbortedSignal } from '../lib';
import { DataPluginStart } from '../plugin';
export function registerSearchRoute(core: CoreSetup<object, DataPluginStart>): void {
const router = core.http.createRouter();
export function registerSearchRoute(router: IRouter): void {
router.post(
{
path: '/internal/search/{strategy}',
@ -38,8 +41,11 @@ export function registerSearchRoute(router: IRouter): void {
const { strategy } = request.params;
const signal = getRequestAbortedSignal(request.events.aborted$);
const [, , selfStart] = await core.getStartServices();
const searchStrategy = selfStart.search.getSearchStrategy(strategy);
try {
const response = await context.search!.search(searchRequest, { signal }, strategy);
const response = await searchStrategy.search(context, searchRequest, { signal });
return res.ok({ body: response });
} catch (err) {
return res.customError({
@ -69,8 +75,13 @@ export function registerSearchRoute(router: IRouter): void {
},
async (context, request, res) => {
const { strategy, id } = request.params;
const [, , selfStart] = await core.getStartServices();
const searchStrategy = selfStart.search.getSearchStrategy(strategy);
if (!searchStrategy.cancel) return res.ok();
try {
await context.search!.cancel(id, strategy);
await searchStrategy.cancel(context, id);
return res.ok();
} catch (err) {
return res.customError({

View file

@ -21,27 +21,28 @@ import { coreMock } from '../../../../core/server/mocks';
import { SearchService } from './search_service';
import { CoreSetup } from '../../../../core/server';
const mockSearchApi = { search: jest.fn() };
jest.mock('./create_api', () => ({
createApi: () => mockSearchApi,
}));
import { DataPluginStart } from '../plugin';
describe('Search service', () => {
let plugin: SearchService;
let mockCoreSetup: MockedKeys<CoreSetup>;
let mockCoreSetup: MockedKeys<CoreSetup<object, DataPluginStart>>;
beforeEach(() => {
plugin = new SearchService(coreMock.createPluginInitializerContext({}));
mockCoreSetup = coreMock.createSetup();
mockSearchApi.search.mockClear();
});
describe('setup()', () => {
it('exposes proper contract', async () => {
const setup = plugin.setup(mockCoreSetup);
expect(setup).toHaveProperty('registerSearchStrategyContext');
expect(setup).toHaveProperty('registerSearchStrategyProvider');
expect(setup).toHaveProperty('registerSearchStrategy');
});
});
describe('start()', () => {
it('exposes proper contract', async () => {
const setup = plugin.start();
expect(setup).toHaveProperty('getSearchStrategy');
});
});
});

View file

@ -17,82 +17,52 @@
* under the License.
*/
import { Plugin, PluginInitializerContext, CoreSetup } from '../../../../core/server';
import {
PluginInitializerContext,
Plugin,
CoreSetup,
IContextContainer,
} from '../../../../core/server';
import { registerSearchRoute } from './routes';
import { ISearchSetup } from './i_search_setup';
import { createApi } from './create_api';
import {
ISearchSetup,
ISearchStart,
TSearchStrategiesMap,
TSearchStrategyProvider,
TRegisterSearchStrategyProvider,
} from './i_search_strategy';
import { IRouteHandlerSearchContext } from './i_route_handler_search_context';
TRegisterSearchStrategy,
TGetSearchStrategy,
} from './types';
import { registerSearchRoute } from './routes';
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search';
import { searchSavedObjectType } from '../saved_objects';
import { DataPluginStart } from '../plugin';
declare module 'kibana/server' {
interface RequestHandlerContext {
search?: IRouteHandlerSearchContext;
}
}
export class SearchService implements Plugin<ISearchSetup, void> {
export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private searchStrategies: TSearchStrategiesMap = {};
private contextContainer?: IContextContainer<TSearchStrategyProvider<any>>;
constructor(private initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup): ISearchSetup {
const router = core.http.createRouter();
registerSearchRoute(router);
this.contextContainer = core.context.createContextContainer();
public setup(core: CoreSetup<object, DataPluginStart>): ISearchSetup {
core.savedObjects.registerType(searchSavedObjectType);
core.http.registerRouteHandlerContext<'search'>('search', (context) => {
return createApi({
caller: context.core.elasticsearch.legacy.client.callAsCurrentUser,
searchStrategies: this.searchStrategies,
});
});
const registerSearchStrategyProvider: TRegisterSearchStrategyProvider = (
plugin,
name,
strategyProvider
) => {
this.searchStrategies[name] = this.contextContainer!.createHandler(plugin, strategyProvider);
};
const api: ISearchSetup = {
registerSearchStrategyContext: this.contextContainer!.registerContext,
registerSearchStrategyProvider,
};
api.registerSearchStrategyContext(this.initializerContext.opaqueId, 'core', () => core);
api.registerSearchStrategyContext(
this.initializerContext.opaqueId,
'config$',
() => this.initializerContext.config.legacy.globalConfig$
);
api.registerSearchStrategyProvider(
this.initializerContext.opaqueId,
this.registerSearchStrategy(
ES_SEARCH_STRATEGY,
esSearchStrategyProvider
esSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$)
);
return api;
registerSearchRoute(core);
return { registerSearchStrategy: this.registerSearchStrategy };
}
public start(): ISearchStart {
return { getSearchStrategy: this.getSearchStrategy };
}
public start() {}
public stop() {}
private registerSearchStrategy: TRegisterSearchStrategy = (name, strategy) => {
this.searchStrategies[name] = strategy;
};
private getSearchStrategy: TGetSearchStrategy = (name) => {
const strategy = this.searchStrategies[name];
if (!strategy) {
throw new Error(`Search strategy ${name} not found`);
}
return strategy;
};
}

View file

@ -1,39 +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 { ES_SEARCH_STRATEGY } from '../../common/search/es_search';
/**
* Contains all known strategy type identifiers that will be used to map to
* request and response shapes. Plugins that wish to add their own custom search
* strategies should extend this type via:
*
* const MY_STRATEGY = 'MY_STRATEGY';
*
* declare module 'src/plugins/search/server' {
* export interface IRequestTypesMap {
* [MY_STRATEGY]: IMySearchRequest;
* }
*
* export interface IResponseTypesMap {
* [MY_STRATEGY]: IMySearchResponse
* }
* }
*/
export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string;

View file

@ -0,0 +1,111 @@
/*
* 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 { RequestHandlerContext } from '../../../../core/server';
import { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search';
import { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from './es_search';
export interface ISearchSetup {
/**
* Extension point exposed for other plugins to register their own search
* strategies.
*/
registerSearchStrategy: TRegisterSearchStrategy;
}
export interface ISearchStart {
/**
* 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.
*/
getSearchStrategy: TGetSearchStrategy;
}
export interface ISearchOptions {
/**
* An `AbortSignal` that allows the caller of `search` to abort a search request.
*/
signal?: AbortSignal;
}
/**
* Contains all known strategy type identifiers that will be used to map to
* request and response shapes. Plugins that wish to add their own custom search
* strategies should extend this type via:
*
* const MY_STRATEGY = 'MY_STRATEGY';
*
* declare module 'src/plugins/search/server' {
* export interface IRequestTypesMap {
* [MY_STRATEGY]: IMySearchRequest;
* }
*
* export interface IResponseTypesMap {
* [MY_STRATEGY]: IMySearchResponse
* }
* }
*/
export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string;
/**
* The map of search strategy IDs to the corresponding request type definitions.
*/
export interface IRequestTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchRequest;
[key: string]: IKibanaSearchRequest;
}
/**
* The map of search strategy IDs to the corresponding response type definitions.
*/
export interface IResponseTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchResponse;
[key: string]: IKibanaSearchResponse;
}
export type ISearch<T extends TStrategyTypes> = (
context: RequestHandlerContext,
request: IRequestTypesMap[T],
options?: ISearchOptions
) => Promise<IResponseTypesMap[T]>;
export type ISearchCancel<T extends TStrategyTypes> = (
context: RequestHandlerContext,
id: string
) => Promise<void>;
/**
* Search strategy interface contains a search method that takes in a request and returns a promise
* that resolves to a response.
*/
export interface ISearchStrategy<T extends TStrategyTypes> {
search: ISearch<T>;
cancel?: ISearchCancel<T>;
}
export type TRegisterSearchStrategy = <T extends TStrategyTypes>(
name: T,
searchStrategy: ISearchStrategy<T>
) => void;
export type TGetSearchStrategy = <T extends TStrategyTypes>(name: T) => ISearchStrategy<T>;
export type TSearchStrategiesMap = {
[K in TStrategyTypes]?: ISearchStrategy<any>;
};

View file

@ -48,7 +48,6 @@ import { GetResponse } from 'elasticsearch';
import { GetScriptParams } from 'elasticsearch';
import { GetSourceParams } from 'elasticsearch';
import { GetTemplateParams } from 'elasticsearch';
import { IContextProvider as IContextProvider_2 } from 'kibana/server';
import { IncomingHttpHeaders } from 'http';
import { IndexDocumentParams } from 'elasticsearch';
import { IndicesAnalyzeParams } from 'elasticsearch';
@ -492,7 +491,7 @@ export class IndexPatternsFetcher {
// Warning: (ae-missing-release-tag) "IRequestTypesMap" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export interface IRequestTypesMap {
// Warning: (ae-forgotten-export) The symbol "IKibanaSearchRequest" needs to be exported by the entry point index.d.ts
//
@ -507,7 +506,7 @@ export interface IRequestTypesMap {
// Warning: (ae-missing-release-tag) "IResponseTypesMap" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export interface IResponseTypesMap {
// Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts
//
@ -519,37 +518,50 @@ export interface IResponseTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchResponse;
}
// Warning: (ae-forgotten-export) The symbol "TStrategyTypes" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts
// 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<T extends TStrategyTypes> = (request: IRequestTypesMap[T], options?: ISearchOptions) => Promise<IResponseTypesMap[T]>;
export type ISearch<T extends TStrategyTypes> = (context: RequestHandlerContext, request: IRequestTypesMap[T], options?: ISearchOptions) => Promise<IResponseTypesMap[T]>;
// Warning: (ae-missing-release-tag) "ISearchCancel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type ISearchCancel<T extends TStrategyTypes> = (id: string) => Promise<void>;
// Warning: (ae-missing-release-tag) "ISearchContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface ISearchContext {
// (undocumented)
config$: Observable<SharedGlobalConfig>;
// Warning: (ae-forgotten-export) The symbol "CoreSetup" needs to be exported by the entry point index.d.ts
//
// (undocumented)
core: CoreSetup;
}
export type ISearchCancel<T extends TStrategyTypes> = (context: RequestHandlerContext, id: string) => Promise<void>;
// Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface ISearchOptions {
// (undocumented)
signal?: AbortSignal;
}
// Warning: (ae-missing-release-tag) "ISearchSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface ISearchSetup {
// Warning: (ae-forgotten-export) The symbol "TRegisterSearchStrategy" needs to be exported by the entry point index.d.ts
registerSearchStrategy: TRegisterSearchStrategy;
}
// Warning: (ae-missing-release-tag) "ISearchStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface ISearchStart {
// Warning: (ae-forgotten-export) The symbol "TGetSearchStrategy" needs to be exported by the entry point index.d.ts
getSearchStrategy: TGetSearchStrategy;
}
// Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export interface ISearchStrategy<T extends TStrategyTypes> {
// (undocumented)
cancel?: ISearchCancel<T>;
// (undocumented)
search: ISearch<T>;
}
// @public (undocumented)
export enum KBN_FIELD_TYPES {
// (undocumented)
@ -614,21 +626,23 @@ export function parseInterval(interval: string): moment.Duration | null;
export class Plugin implements Plugin_2<PluginSetup, PluginStart> {
// Warning: (ae-forgotten-export) The symbol "PluginInitializerContext" needs to be exported by the entry point index.d.ts
constructor(initializerContext: PluginInitializerContext<ConfigSchema>);
// Warning: (ae-forgotten-export) The symbol "CoreSetup" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "DataPluginSetupDependencies" needs to be exported by the entry point index.d.ts
//
// (undocumented)
setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
setup(core: CoreSetup<object, PluginStart>, { usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
};
search: ISearchSetup;
};
// Warning: (ae-forgotten-export) The symbol "CoreStart" needs to be exported by the entry point index.d.ts
//
// (undocumented)
start(core: CoreStart): {
search: ISearchStart;
fieldFormats: {
fieldFormatServiceFactory: (uiSettings: import("kibana/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
};
// (undocumented)
@ -658,6 +672,8 @@ export interface PluginStart {
//
// (undocumented)
fieldFormats: FieldFormatsStart;
// (undocumented)
search: ISearchStart;
}
// Warning: (ae-missing-release-tag) "Query" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@ -715,12 +731,10 @@ export interface TimeRange {
to: string;
}
// Warning: (ae-forgotten-export) The symbol "ISearchGeneric" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "ISearchStrategy" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "TSearchStrategyProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
// Warning: (ae-missing-release-tag) "TStrategyTypes" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearchContext, caller: APICaller_2, search: ISearchGeneric) => ISearchStrategy<T>;
export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string;
// Warning: (ae-missing-release-tag) "UI_SETTINGS" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@ -780,13 +794,12 @@ export const UI_SETTINGS: {
// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/plugin.ts:66:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)

View file

@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide
await supertest
.get('/api/np-context-in-legacy')
.expect(200)
.expect(JSON.stringify({ contexts: ['core', 'search', 'pluginA'] }));
.expect(JSON.stringify({ contexts: ['core', 'pluginA'] }));
});
});

View file

@ -22,10 +22,9 @@ export class EnhancedDataServerPlugin implements Plugin<void, void, SetupDepende
constructor(private initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup, deps: SetupDependencies) {
deps.data.search.registerSearchStrategyProvider(
this.initializerContext.opaqueId,
deps.data.search.registerSearchStrategy(
ES_SEARCH_STRATEGY,
enhancedEsSearchStrategyProvider
enhancedEsSearchStrategyProvider(this.initializerContext.config.legacy.globalConfig$)
);
}

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { coreMock, pluginInitializerContextConfigMock } from '../../../../../src/core/server/mocks';
import { RequestHandlerContext } from '../../../../../src/core/server';
import { pluginInitializerContextConfigMock } from '../../../../../src/core/server/mocks';
import { enhancedEsSearchStrategyProvider } from './es_search_strategy';
const mockAsyncResponse = {
@ -29,25 +30,18 @@ const mockRollupResponse = {
};
describe('ES search strategy', () => {
const mockCoreSetup = coreMock.createSetup();
const mockApiCaller = jest.fn();
const mockSearch = jest.fn();
const mockContext = {
core: { elasticsearch: { legacy: { client: { callAsCurrentUser: mockApiCaller } } } },
};
const mockConfig$ = pluginInitializerContextConfigMock<any>({}).legacy.globalConfig$;
beforeEach(() => {
mockApiCaller.mockClear();
mockSearch.mockClear();
});
it('returns a strategy with `search`', () => {
const esSearch = enhancedEsSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
it('returns a strategy with `search`', async () => {
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$);
expect(typeof esSearch.search).toBe('function');
});
@ -56,16 +50,9 @@ describe('ES search strategy', () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = enhancedEsSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$);
await esSearch.search({ params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
@ -79,16 +66,9 @@ describe('ES search strategy', () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'logstash-*', body: { query: {} } };
const esSearch = enhancedEsSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$);
await esSearch.search({ id: 'foo', params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { id: 'foo', params });
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
@ -102,16 +82,9 @@ describe('ES search strategy', () => {
mockApiCaller.mockResolvedValueOnce(mockAsyncResponse);
const params = { index: 'foo-程', body: {} };
const esSearch = enhancedEsSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$);
await esSearch.search({ params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, { params });
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');
@ -124,16 +97,12 @@ describe('ES search strategy', () => {
mockApiCaller.mockResolvedValueOnce(mockRollupResponse);
const params = { index: 'foo-程', body: {} };
const esSearch = enhancedEsSearchStrategyProvider(
{
core: mockCoreSetup,
config$: mockConfig$,
},
mockApiCaller,
mockSearch
);
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$);
await esSearch.search({ indexType: 'rollup', params });
await esSearch.search((mockContext as unknown) as RequestHandlerContext, {
indexType: 'rollup',
params,
});
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request');

View file

@ -7,16 +7,16 @@
import { first } from 'rxjs/operators';
import { mapKeys, snakeCase } from 'lodash';
import { SearchResponse } from 'elasticsearch';
import { APICaller } from '../../../../../src/core/server';
import { Observable } from 'rxjs';
import { APICaller, SharedGlobalConfig } from '../../../../../src/core/server';
import { ES_SEARCH_STRATEGY } from '../../../../../src/plugins/data/common';
import {
ISearchContext,
TSearchStrategyProvider,
ISearch,
ISearchOptions,
ISearchCancel,
getDefaultSearchParams,
getTotalLoaded,
ISearchStrategy,
} from '../../../../../src/plugins/data/server';
import { IEnhancedEsSearchRequest } from '../../common';
import { shimHitsTotal } from './shim_hits_total';
@ -28,15 +28,16 @@ export interface AsyncSearchResponse<T> {
response: SearchResponse<T>;
}
export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider<typeof ES_SEARCH_STRATEGY> = (
context: ISearchContext,
caller: APICaller
) => {
export const enhancedEsSearchStrategyProvider = (
config$: Observable<SharedGlobalConfig>
): ISearchStrategy<typeof ES_SEARCH_STRATEGY> => {
const search: ISearch<typeof ES_SEARCH_STRATEGY> = async (
context,
request: IEnhancedEsSearchRequest,
options
) => {
const config = await context.config$.pipe(first()).toPromise();
const config = await config$.pipe(first()).toPromise();
const caller = context.core.elasticsearch.legacy.client.callAsCurrentUser;
const defaultParams = getDefaultSearchParams(config);
const params = { ...defaultParams, ...request.params };
@ -45,10 +46,13 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider<typeof ES
: asyncSearch(caller, { ...request, params }, options);
};
const cancel: ISearchCancel<typeof ES_SEARCH_STRATEGY> = async (id) => {
const cancel: ISearchCancel<typeof ES_SEARCH_STRATEGY> = async (context, id) => {
const method = 'DELETE';
const path = encodeURI(`/_async_search/${id}`);
await caller('transport.request', { method, path });
await context.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', {
method,
path,
});
};
return { search, cancel };