Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Search] Server side search API #70446

Merged
merged 28 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b5f724d
[search] Refactor the way search strategies are registered/retrieved …
lukasolson Jun 5, 2020
e01bc6a
Fix types and tests and update docs
lukasolson Jun 9, 2020
9f471cb
Fix failing test
lukasolson Jun 9, 2020
ad2fe47
Move strategy name into options
Jun 16, 2020
a032ed5
Remove FE strategies
Jun 18, 2020
92b35b3
TypeScript of hell
Jun 28, 2020
718b029
Fix search interceptor OSS tests
Jun 29, 2020
5b3c73f
test cleanup
Jun 30, 2020
3883edd
fix
Jun 30, 2020
284da6e
return search wrapper
Jun 30, 2020
4dde888
initial api
Jul 1, 2020
ab6feb5
Shiny happy cleanup
Jul 1, 2020
d0121fa
docs
Jul 1, 2020
1b02297
fix jest test
Jul 2, 2020
696215b
simplify strategy registration
Jul 5, 2020
873eb48
fix rebase
Jul 7, 2020
e4a8335
fix rebase
Jul 7, 2020
061f9ee
fix backport
Jul 8, 2020
c514457
Merge branch 'master' of github.com:elastic/kibana into search/server…
Jul 8, 2020
6973d22
Merge branch 'master' into search/server-side-search
elasticmachine Jul 9, 2020
4434eb7
Merge branch 'master' of github.com:elastic/kibana into search/server…
Jul 9, 2020
ac93fa8
Merge branch 'master' into search/server-side-search
elasticmachine Jul 11, 2020
6d86b38
types
Jul 13, 2020
d3bc473
Merge branch 'search/server-side-search' of github.com:lizozom/kibana…
Jul 13, 2020
e358a90
Merge branch 'master' of github.com:elastic/kibana into search/server…
Jul 13, 2020
c3bc240
TS for strategy
Jul 13, 2020
7dbe5b4
docs
Jul 13, 2020
9fc3d9d
Merge branch 'master' of github.com:elastic/kibana into search/server…
Jul 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ export interface ISearchSetup

| 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. |
| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <code>(name: string, strategy: ISearchStrategy) =&gt; void</code> | Extension point exposed for other plugins to register their own search strategies. |

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Extension point exposed for other plugins to register their own search strategie
<b>Signature:</b>

```typescript
registerSearchStrategy: TRegisterSearchStrategy;
registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Get other registered search strategies. For example, if a new strategy needs to
<b>Signature:</b>

```typescript
getSearchStrategy: TGetSearchStrategy;
getSearchStrategy: (name: string) => ISearchStrategy;
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ export interface ISearchStart

| 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. |
| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | <code>(name: string) =&gt; ISearchStrategy</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>(context: RequestHandlerContext, request: IKibanaSearchRequest, options: any) =&gt; Promise&lt;IKibanaSearchResponse&gt;</code> | |

Original file line number Diff line number Diff line change
@@ -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; [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) &gt; [search](./kibana-plugin-plugins-data-server.isearchstart.search.md)

## ISearchStart.search property

<b>Signature:</b>

```typescript
search: (context: RequestHandlerContext, request: IKibanaSearchRequest, options: any) => Promise<IKibanaSearchResponse>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<b>Signature:</b>

```typescript
cancel?: ISearchCancel<T>;
cancel?: (context: RequestHandlerContext, id: string) => Promise<void>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ Search strategy interface contains a search method that takes in a request and r
<b>Signature:</b>

```typescript
export interface ISearchStrategy<T extends TStrategyTypes>
export interface ISearchStrategy
```

## 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> | |
| [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>(context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) =&gt; Promise&lt;IEsSearchResponse&gt;</code> | |

Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
<b>Signature:</b>

```typescript
search: ISearch<T>;
search: (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise<IEsSearchResponse>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
| [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) | 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) | |
Expand Down Expand Up @@ -73,8 +71,5 @@
| --- | --- |
| [FieldFormatsGetConfigFn](./kibana-plugin-plugins-data-server.fieldformatsgetconfigfn.md) | |
| [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | |
| [ISearch](./kibana-plugin-plugins-data-server.isearch.md) | |
| [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md) | |
| [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | |
| [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 } } |

This file was deleted.

7 changes: 1 addition & 6 deletions src/plugins/data/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,10 @@ import {
export { ParsedInterval } from '../common';

export {
ISearch,
ISearchCancel,
ISearchStrategy,
ISearchOptions,
IRequestTypesMap,
IResponseTypesMap,
ISearchSetup,
ISearchStart,
TStrategyTypes,
ISearchStrategy,
getDefaultSearchParams,
getTotalLoaded,
} from './search';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@
* under the License.
*/
import { first } from 'rxjs/operators';
import { RequestHandlerContext, SharedGlobalConfig } from 'kibana/server';
import { SharedGlobalConfig } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { Observable } from 'rxjs';
import { ES_SEARCH_STRATEGY } from '../../../common/search';
import { ISearchStrategy, getDefaultSearchParams, getTotalLoaded } from '..';

export const esSearchStrategyProvider = (
config$: Observable<SharedGlobalConfig>
): ISearchStrategy<typeof ES_SEARCH_STRATEGY> => {
): ISearchStrategy => {
return {
search: async (context: RequestHandlerContext, request, options) => {
search: async (context, request, options) => {
const config = await config$.pipe(first()).toPromise();
const defaultParams = getDefaultSearchParams(config);

Expand Down
12 changes: 1 addition & 11 deletions src/plugins/data/server/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,6 @@
* under the License.
*/

export {
ISearch,
ISearchCancel,
ISearchOptions,
IRequestTypesMap,
IResponseTypesMap,
ISearchSetup,
ISearchStart,
TStrategyTypes,
ISearchStrategy,
} from './types';
export { ISearchStrategy, ISearchOptions, ISearchSetup, ISearchStart } from './types';

export { getDefaultSearchParams, getTotalLoaded } from './es_search';
1 change: 1 addition & 0 deletions src/plugins/data/server/search/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ export function createSearchSetupMock() {
export function createSearchStartMock() {
return {
getSearchStrategy: jest.fn(),
search: jest.fn(),
};
}
22 changes: 10 additions & 12 deletions src/plugins/data/server/search/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ describe('Search service', () => {
});

it('handler calls context.search.search with the given request and strategy', async () => {
const mockSearch = jest.fn().mockResolvedValue('yay');
mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });

const response = { id: 'yay' };
mockDataStart.search.search.mockResolvedValue(response);
const mockContext = {};
const mockBody = { params: {} };
const mockParams = { strategy: 'foo' };
Expand All @@ -51,21 +50,21 @@ describe('Search service', () => {
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][1]).toStrictEqual(mockBody);
expect(mockDataStart.search.search).toBeCalled();
expect(mockDataStart.search.search.mock.calls[0][1]).toStrictEqual(mockBody);
expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' });
expect(mockResponse.ok.mock.calls[0][0]).toEqual({
body: response,
});
});

it('handler throws an error if the search throws an error', async () => {
const mockSearch = jest.fn().mockRejectedValue({
mockDataStart.search.search.mockRejectedValue({
message: 'oh no',
body: {
error: 'oops',
},
});
mockDataStart.search.getSearchStrategy.mockReturnValueOnce({ search: mockSearch });

const mockContext = {};
const mockBody = { params: {} };
Expand All @@ -82,9 +81,8 @@ describe('Search service', () => {
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][1]).toStrictEqual(mockBody);
expect(mockDataStart.search.search).toBeCalled();
expect(mockDataStart.search.search.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');
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/data/server/search/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export function registerSearchRoute(core: CoreSetup<object, DataPluginStart>): v
const signal = getRequestAbortedSignal(request.events.aborted$);

const [, , selfStart] = await core.getStartServices();
const searchStrategy = selfStart.search.getSearchStrategy(strategy);

try {
const response = await searchStrategy.search(context, searchRequest, { signal });
const response = await selfStart.search.search(context, searchRequest, {
signal,
strategy,
});
return res.ok({ body: response });
} catch (err) {
return res.customError({
Expand Down
37 changes: 26 additions & 11 deletions src/plugins/data/server/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@
* under the License.
*/

import { Plugin, PluginInitializerContext, CoreSetup } from '../../../../core/server';
import {
ISearchSetup,
ISearchStart,
TSearchStrategiesMap,
TRegisterSearchStrategy,
TGetSearchStrategy,
} from './types';
Plugin,
PluginInitializerContext,
CoreSetup,
RequestHandlerContext,
} from '../../../../core/server';
import { ISearchSetup, ISearchStart, ISearchStrategy } from './types';
import { registerSearchRoute } from './routes';
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search';
import { DataPluginStart } from '../plugin';
import { IEsSearchRequest } from '../../common';

interface StrategyMap {
[name: string]: ISearchStrategy;
}

export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private searchStrategies: TSearchStrategiesMap = {};
private searchStrategies: StrategyMap = {};

constructor(private initializerContext: PluginInitializerContext) {}

Expand All @@ -45,17 +49,28 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
return { registerSearchStrategy: this.registerSearchStrategy };
}

private search(context: RequestHandlerContext, searchRequest: IEsSearchRequest, options: any) {
return this.getSearchStrategy(options.strategy || ES_SEARCH_STRATEGY).search(
context,
searchRequest,
{ signal: options.signal }
);
}

public start(): ISearchStart {
return { getSearchStrategy: this.getSearchStrategy };
return {
getSearchStrategy: this.getSearchStrategy,
search: this.search,
};
}

public stop() {}

private registerSearchStrategy: TRegisterSearchStrategy = (name, strategy) => {
private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => {
this.searchStrategies[name] = strategy;
};

private getSearchStrategy: TGetSearchStrategy = (name) => {
private getSearchStrategy = (name: string): ISearchStrategy => {
const strategy = this.searchStrategies[name];
if (!strategy) {
throw new Error(`Search strategy ${name} not found`);
Expand Down
Loading