From 02c57f3493c5ef490f32690dba84b299f6500d44 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 9 Dec 2019 11:16:05 -0700 Subject: [PATCH 01/59] Add async search strategy --- src/plugins/data/public/search/index.ts | 2 +- .../plugins/demo_search/server/demo_search_strategy.ts | 9 +++++++++ .../plugins/search_explorer/public/demo_strategy.tsx | 5 +++-- .../plugins/search_explorer/public/do_search.tsx | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index d36202debd9b9..8d18ccb5741fb 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -35,7 +35,7 @@ export { TSearchStrategyProvider, ISearchStrategy } from './i_search_strategy'; export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search'; -export { SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; +export { ISyncSearchRequest, SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; diff --git a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts b/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts index d3f2360add6c0..d46c10b53c28e 100644 --- a/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts +++ b/test/plugin_functional/plugins/demo_search/server/demo_search_strategy.ts @@ -20,14 +20,23 @@ import { TSearchStrategyProvider } from 'src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; +let idCounter = 0; +const loadedMap = new Map(); + export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { return { search: request => { + const id = request.id ?? `${idCounter++}`; + const loaded = (loadedMap.get(id) ?? 0) + 1; + loadedMap.set(id, loaded); return Promise.resolve({ greeting: request.mood === 'happy' ? `Lovely to meet you, ${request.name}` : `Hope you feel better, ${request.name}`, + total: 10, + loaded, + id, }); }, }; diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx index 8a0dd31e3595f..33bcb082e7275 100644 --- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx @@ -73,6 +73,7 @@ export class DemoStrategy extends React.Component { const request: IDemoRequest = { name: this.state.name, mood: this.state.mood, + serverStrategy: 'DEMO_SEARCH_STRATEGY', }; return ( @@ -97,9 +98,9 @@ export class DemoStrategy extends React.Component { - this.props.search(request, { signal }, DEMO_SEARCH_STRATEGY) + this.props.search(request, { signal }, 'ASYNC_SEARCH_STRATEGY') } /> diff --git a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx b/test/plugin_functional/plugins/search_explorer/public/do_search.tsx index e039e4ff3f63f..47b8672f6e3ea 100644 --- a/test/plugin_functional/plugins/search_explorer/public/do_search.tsx +++ b/test/plugin_functional/plugins/search_explorer/public/do_search.tsx @@ -121,8 +121,8 @@ ${requestStr} Response: Date: Mon, 9 Dec 2019 11:29:27 -0700 Subject: [PATCH 02/59] Add async search --- x-pack/plugins/enhanced_data/kibana.json | 7 ++ x-pack/plugins/enhanced_data/public/index.ts | 14 +++ x-pack/plugins/enhanced_data/public/plugin.ts | 32 +++++++ .../search/async_search_strategy.test.ts | 94 +++++++++++++++++++ .../public/search/async_search_strategy.ts | 64 +++++++++++++ x-pack/plugins/enhanced_data/public/types.ts | 5 + 6 files changed, 216 insertions(+) create mode 100644 x-pack/plugins/enhanced_data/kibana.json create mode 100644 x-pack/plugins/enhanced_data/public/index.ts create mode 100644 x-pack/plugins/enhanced_data/public/plugin.ts create mode 100644 x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts create mode 100644 x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts create mode 100644 x-pack/plugins/enhanced_data/public/types.ts diff --git a/x-pack/plugins/enhanced_data/kibana.json b/x-pack/plugins/enhanced_data/kibana.json new file mode 100644 index 0000000000000..c30c528b2ee84 --- /dev/null +++ b/x-pack/plugins/enhanced_data/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "enhancedData", + "version": "kibana", + "requiredPlugins": ["data"], + "server": false, + "ui": true +} diff --git a/x-pack/plugins/enhanced_data/public/index.ts b/x-pack/plugins/enhanced_data/public/index.ts new file mode 100644 index 0000000000000..40d7fd203a94d --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from '../../../../src/core/public'; +import { EnhancedDataPublicPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new EnhancedDataPublicPlugin(initializerContext); +} + +export { EnhancedDataPublicPlugin as Plugin }; diff --git a/x-pack/plugins/enhanced_data/public/plugin.ts b/x-pack/plugins/enhanced_data/public/plugin.ts new file mode 100644 index 0000000000000..9f76dcec9776d --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/plugin.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext, CoreSetup, Plugin } from '../../../../src/core/public'; +import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; +import { + ASYNC_SEARCH_STRATEGY, + asyncSearchStrategyProvider, +} from './search/async_search_strategy'; + +interface SetupDependencies { + data: DataPublicPluginSetup; +} + +export class EnhancedDataPublicPlugin implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { data }: SetupDependencies) { + data.search.registerSearchStrategyProvider( + this.initializerContext.opaqueId, + ASYNC_SEARCH_STRATEGY, + asyncSearchStrategyProvider + ); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts new file mode 100644 index 0000000000000..f36bdf1c38522 --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { of } from 'rxjs'; +import { CoreSetup } from '../../../../../src/core/public'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { asyncSearchStrategyProvider } from './async_search_strategy'; + +jest.useFakeTimers(); + +describe('Async search strategy', () => { + let mockCoreSetup: MockedKeys; + const mockSearch = jest.fn(); + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockSearch.mockClear(); + }); + + it('only sends one request if the first response is complete', async () => { + const request = { params: {}, serverStrategy: 'foo' }; + const options = {}; + + mockSearch.mockReturnValueOnce( + of({ + id: 1, + total: 1, + loaded: 1, + }) + ); + + const asyncSearch = asyncSearchStrategyProvider( + { + core: mockCoreSetup, + }, + mockSearch + ); + + await asyncSearch.search(request, options).toPromise(); + jest.runAllTimers(); + + expect(mockSearch.mock.calls[0][0]).toEqual(request); + expect(mockSearch.mock.calls[0][1]).toEqual(options); + expect(mockSearch).toBeCalledTimes(1); + }); + + // it('polls using the given interval', done => { + // const request = { params: {}, serverStrategy: 'foo' }; + // const pollInterval = 100; + // const options = { pollInterval }; + // + // mockSearch + // .mockReturnValueOnce( + // of({ + // id: 1, + // total: 2, + // loaded: 1, + // }) + // ) + // .mockReturnValueOnce( + // of({ + // id: 1, + // total: 2, + // loaded: 2, + // }) + // ); + // + // const asyncSearch = asyncSearchStrategyProvider( + // { + // core: mockCoreSetup, + // }, + // mockSearch + // ); + // + // expect(mockSearch).toBeCalledTimes(0); + // + // asyncSearch.search(request, options).subscribe( + // () => { + // jest.advanceTimersByTime(pollInterval); + // }, + // () => {}, + // () => { + // done(); + // } + // ); + // }); + + // it('continues polling until it reaches 100% complete'); + + // it('only sends the ID and server strategy after the first request'); +}); diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts new file mode 100644 index 0000000000000..c4adbfa2b9cc3 --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { interval, Observable, merge } from 'rxjs'; +import { flatMap, map, takeWhile, tap } from 'rxjs/operators'; +import { + IKibanaSearchResponse, + ISearchContext, + ISearchGeneric, + ISearchOptions, + ISearchStrategy, + ISyncSearchRequest, + SYNC_SEARCH_STRATEGY, + TSearchStrategyProvider, +} from '../../../../../src/plugins/data/public'; + +export const ASYNC_SEARCH_STRATEGY = 'ASYNC_SEARCH_STRATEGY'; + +export interface IAsyncSearchOptions extends ISearchOptions { + pollInterval?: number; +} + +export interface IAsyncSearchRequest extends ISyncSearchRequest { + id?: string; +} + +declare module '../../../../../src/plugins/data/public' { + export interface IRequestTypesMap { + [ASYNC_SEARCH_STRATEGY]: IAsyncSearchRequest; + } +} + +export const asyncSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext, + search: ISearchGeneric +): ISearchStrategy => { + return { + search: ( + request: IAsyncSearchRequest, + { pollInterval = 1000, ...options }: IAsyncSearchOptions = {} + ): Observable => { + return merge( + search(request, options, SYNC_SEARCH_STRATEGY).pipe( + tap(response => { + // After the initial request, we only send the ID and server strategy in subsequent requests + request = { id: response.id, serverStrategy: request.serverStrategy }; + }) + ), + interval(pollInterval).pipe( + flatMap(() => { + return search(request, options, SYNC_SEARCH_STRATEGY); + }) + ) + ).pipe( + takeWhile(response => { + return (response.loaded ?? 0) < (response.total ?? 1); + }) + ); + }, + }; +}; diff --git a/x-pack/plugins/enhanced_data/public/types.ts b/x-pack/plugins/enhanced_data/public/types.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/types.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ From cf5ceb99c8097e138d1234e8e69fb3ee94e7bab4 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 13 Dec 2019 14:58:29 -0700 Subject: [PATCH 03/59] Fix async strategy and add tests --- src/plugins/data/common/search/types.ts | 6 - .../create_app_mount_context_search.test.ts | 6 +- .../data/server/search/create_api.test.ts | 2 +- .../search/async_search_strategy.test.ts | 113 +++++++----------- .../public/search/async_search_strategy.ts | 36 +++--- 5 files changed, 64 insertions(+), 99 deletions(-) diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index e1fe7d414756a..7600bd9db6094 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -23,12 +23,6 @@ export interface IKibanaSearchResponse { */ id?: string; - /** - * If relevant to the search strategy, return a percentage - * that represents how progress is indicated. - */ - percentComplete?: number; - /** * If relevant to the search strategy, return a total number * that represents how progress is indicated. diff --git a/src/plugins/data/public/search/create_app_mount_context_search.test.ts b/src/plugins/data/public/search/create_app_mount_context_search.test.ts index fa7cdbcda3082..117bb0e20a016 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.test.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.test.ts @@ -37,16 +37,16 @@ describe('Create app mount search context', () => { const context = createAppMountSearchContext({ mysearch: search => Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 98 })), + search: () => from(Promise.resolve({ total: 100, loaded: 98 })), }), anothersearch: search => Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 0 })), + search: () => from(Promise.resolve({ total: 100, loaded: 0 })), }), }); context.search({}, {}, 'mysearch').subscribe(response => { - expect(response).toEqual({ percentComplete: 98 }); + expect(response).toEqual({ total: 100, loaded: 98 }); done(); }); }); diff --git a/src/plugins/data/server/search/create_api.test.ts b/src/plugins/data/server/search/create_api.test.ts index cc13269e1aa21..99e48056ef857 100644 --- a/src/plugins/data/server/search/create_api.test.ts +++ b/src/plugins/data/server/search/create_api.test.ts @@ -25,7 +25,7 @@ import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; // let mockCoreSetup: MockedKeys; -const mockDefaultSearch = jest.fn(() => Promise.resolve({ percentComplete: 0 })); +const mockDefaultSearch = jest.fn(() => Promise.resolve({ total: 100, loaded: 0 })); const mockDefaultSearchStrategyProvider = jest.fn(() => Promise.resolve({ search: mockDefaultSearch, diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts index f36bdf1c38522..a12b9b8745671 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts @@ -7,88 +7,59 @@ import { of } from 'rxjs'; import { CoreSetup } from '../../../../../src/core/public'; import { coreMock } from '../../../../../src/core/public/mocks'; -import { asyncSearchStrategyProvider } from './async_search_strategy'; - -jest.useFakeTimers(); +import { asyncSearchStrategyProvider, IAsyncSearchOptions } from './async_search_strategy'; describe('Async search strategy', () => { let mockCoreSetup: MockedKeys; const mockSearch = jest.fn(); + const mockRequest = { params: {}, serverStrategy: 'foo' }; + const mockOptions = { pollInterval: 0 }; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); - mockSearch.mockClear(); + mockSearch.mockReset(); }); it('only sends one request if the first response is complete', async () => { - const request = { params: {}, serverStrategy: 'foo' }; - const options = {}; - - mockSearch.mockReturnValueOnce( - of({ - id: 1, - total: 1, - loaded: 1, - }) - ); - - const asyncSearch = asyncSearchStrategyProvider( - { - core: mockCoreSetup, - }, - mockSearch - ); - - await asyncSearch.search(request, options).toPromise(); - jest.runAllTimers(); - - expect(mockSearch.mock.calls[0][0]).toEqual(request); - expect(mockSearch.mock.calls[0][1]).toEqual(options); + mockSearch.mockReturnValueOnce(of({ id: 1, total: 1, loaded: 1 })); + + const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch.mock.calls[0][0]).toEqual(mockRequest); + expect(mockSearch.mock.calls[0][1]).toEqual({}); expect(mockSearch).toBeCalledTimes(1); }); - // it('polls using the given interval', done => { - // const request = { params: {}, serverStrategy: 'foo' }; - // const pollInterval = 100; - // const options = { pollInterval }; - // - // mockSearch - // .mockReturnValueOnce( - // of({ - // id: 1, - // total: 2, - // loaded: 1, - // }) - // ) - // .mockReturnValueOnce( - // of({ - // id: 1, - // total: 2, - // loaded: 2, - // }) - // ); - // - // const asyncSearch = asyncSearchStrategyProvider( - // { - // core: mockCoreSetup, - // }, - // mockSearch - // ); - // - // expect(mockSearch).toBeCalledTimes(0); - // - // asyncSearch.search(request, options).subscribe( - // () => { - // jest.advanceTimersByTime(pollInterval); - // }, - // () => {}, - // () => { - // done(); - // } - // ); - // }); - - // it('continues polling until it reaches 100% complete'); - - // it('only sends the ID and server strategy after the first request'); + it('stops polling when the response is complete', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + + expect(mockSearch).toBeCalledTimes(0); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch).toBeCalledTimes(2); + }); + + it('only sends the ID and server strategy after the first request', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + + expect(mockSearch).toBeCalledTimes(0); + + await asyncSearch.search(mockRequest, mockOptions).toPromise(); + + expect(mockSearch).toBeCalledTimes(2); + expect(mockSearch.mock.calls[0][0]).toEqual(mockRequest); + expect(mockSearch.mock.calls[1][0]).toEqual({ id: 1, serverStrategy: 'foo' }); + }); }); diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts index c4adbfa2b9cc3..29b915c636d67 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { interval, Observable, merge } from 'rxjs'; -import { flatMap, map, takeWhile, tap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; +import { mergeMap, takeWhile, expand, delay } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, @@ -20,6 +20,9 @@ import { export const ASYNC_SEARCH_STRATEGY = 'ASYNC_SEARCH_STRATEGY'; export interface IAsyncSearchOptions extends ISearchOptions { + /** + * The number of milliseconds to wait between receiving a response and sending another request + */ pollInterval?: number; } @@ -42,22 +45,19 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider => { - return merge( - search(request, options, SYNC_SEARCH_STRATEGY).pipe( - tap(response => { - // After the initial request, we only send the ID and server strategy in subsequent requests - request = { id: response.id, serverStrategy: request.serverStrategy }; - }) - ), - interval(pollInterval).pipe( - flatMap(() => { - return search(request, options, SYNC_SEARCH_STRATEGY); - }) - ) - ).pipe( - takeWhile(response => { - return (response.loaded ?? 0) < (response.total ?? 1); - }) + const { serverStrategy } = request; + + return search(request, options, 'SYNC_SEARCH_STRATEGY').pipe( + expand(({ id }) => { + return of(null).pipe( + // Delay by the given poll interval + delay(pollInterval), + // Send future requests using just the ID from the response + mergeMap(() => search({ id, serverStrategy }, options, 'SYNC_SEARCH_STRATEGY')) + ); + }), + // Continue polling until the response indicates it is complete + takeWhile(({ total = 1, loaded = 1 }) => loaded < total, true) ); }, }; From 20093436bd987798e6f94f3a40371a19551191dc Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 13 Dec 2019 17:08:07 -0700 Subject: [PATCH 04/59] Move types to separate file --- x-pack/plugins/enhanced_data/public/index.ts | 2 ++ .../public/search/async_search_strategy.ts | 18 +++------------- .../enhanced_data/public/search/types.ts | 21 +++++++++++++++++++ x-pack/plugins/enhanced_data/public/types.ts | 5 ----- 4 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/enhanced_data/public/search/types.ts delete mode 100644 x-pack/plugins/enhanced_data/public/types.ts diff --git a/x-pack/plugins/enhanced_data/public/index.ts b/x-pack/plugins/enhanced_data/public/index.ts index 40d7fd203a94d..ce6590ebdc63f 100644 --- a/x-pack/plugins/enhanced_data/public/index.ts +++ b/x-pack/plugins/enhanced_data/public/index.ts @@ -12,3 +12,5 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { EnhancedDataPublicPlugin as Plugin }; + +export { IAsyncSearchRequest, IAsyncSearchOptions } from './search/types'; diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts index 29b915c636d67..28ea2d0c1afd8 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -10,26 +10,14 @@ import { IKibanaSearchResponse, ISearchContext, ISearchGeneric, - ISearchOptions, ISearchStrategy, - ISyncSearchRequest, SYNC_SEARCH_STRATEGY, TSearchStrategyProvider, } from '../../../../../src/plugins/data/public'; +import { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; export const ASYNC_SEARCH_STRATEGY = 'ASYNC_SEARCH_STRATEGY'; -export interface IAsyncSearchOptions extends ISearchOptions { - /** - * The number of milliseconds to wait between receiving a response and sending another request - */ - pollInterval?: number; -} - -export interface IAsyncSearchRequest extends ISyncSearchRequest { - id?: string; -} - declare module '../../../../../src/plugins/data/public' { export interface IRequestTypesMap { [ASYNC_SEARCH_STRATEGY]: IAsyncSearchRequest; @@ -47,13 +35,13 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider => { const { serverStrategy } = request; - return search(request, options, 'SYNC_SEARCH_STRATEGY').pipe( + return search(request, options, SYNC_SEARCH_STRATEGY).pipe( expand(({ id }) => { return of(null).pipe( // Delay by the given poll interval delay(pollInterval), // Send future requests using just the ID from the response - mergeMap(() => search({ id, serverStrategy }, options, 'SYNC_SEARCH_STRATEGY')) + mergeMap(() => search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY)) ); }), // Continue polling until the response indicates it is complete diff --git a/x-pack/plugins/enhanced_data/public/search/types.ts b/x-pack/plugins/enhanced_data/public/search/types.ts new file mode 100644 index 0000000000000..edaaf1b22654d --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/search/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISearchOptions, ISyncSearchRequest } from '../../../../../src/plugins/data/public'; + +export interface IAsyncSearchRequest extends ISyncSearchRequest { + /** + * The ID received from the response from the initial request + */ + id?: string; +} + +export interface IAsyncSearchOptions extends ISearchOptions { + /** + * The number of milliseconds to wait between receiving a response and sending another request + */ + pollInterval?: number; +} diff --git a/x-pack/plugins/enhanced_data/public/types.ts b/x-pack/plugins/enhanced_data/public/types.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/enhanced_data/public/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ From ebebdafc17fb1c4a048328ef997058fdbb165870 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 13 Dec 2019 17:10:39 -0700 Subject: [PATCH 05/59] Revert changes to demo search --- examples/demo_search/server/demo_search_strategy.ts | 9 --------- examples/search_explorer/public/demo_strategy.tsx | 5 ++--- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts index c2b12aa42763a..5b0883be1fc51 100644 --- a/examples/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -20,23 +20,14 @@ import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; -let idCounter = 0; -const loadedMap = new Map(); - export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { return { search: request => { - const id = request.id ?? `${idCounter++}`; - const loaded = (loadedMap.get(id) ?? 0) + 1; - loadedMap.set(id, loaded); return Promise.resolve({ greeting: request.mood === 'happy' ? `Lovely to meet you, ${request.name}` : `Hope you feel better, ${request.name}`, - total: 10, - loaded, - id, }); }, }; diff --git a/examples/search_explorer/public/demo_strategy.tsx b/examples/search_explorer/public/demo_strategy.tsx index 49523f01feff2..7c6c21d2b7aed 100644 --- a/examples/search_explorer/public/demo_strategy.tsx +++ b/examples/search_explorer/public/demo_strategy.tsx @@ -73,7 +73,6 @@ export class DemoStrategy extends React.Component { const request: IDemoRequest = { name: this.state.name, mood: this.state.mood, - serverStrategy: 'DEMO_SEARCH_STRATEGY', }; return ( @@ -98,9 +97,9 @@ export class DemoStrategy extends React.Component { - this.props.search(request, { signal }, 'ASYNC_SEARCH_STRATEGY') + this.props.search(request, { signal }, DEMO_SEARCH_STRATEGY) } /> From 5dd876f949885c5eedaeb4140786f686deb77e45 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 17 Dec 2019 17:03:07 -0700 Subject: [PATCH 06/59] Update demo search strategy to use async --- .../public/demo_search_strategy.ts | 11 ++++------- .../server/demo_search_strategy.ts | 19 ++++++++++++++++--- x-pack/plugins/enhanced_data/public/index.ts | 1 + 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/examples/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts index d2854151e14c8..7ce8ed0cb707c 100644 --- a/examples/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -18,14 +18,11 @@ */ import { Observable } from 'rxjs'; -import { - ISearchContext, - SYNC_SEARCH_STRATEGY, - ISearchGeneric, -} from '../../../src/plugins/data/public'; +import { ISearchContext, ISearchGeneric } from '../../../src/plugins/data/public'; import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; +import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/enhanced_data/public'; /** * This demo search strategy provider simply provides a shortcut for calling the DEMO_SEARCH_STRATEGY @@ -47,7 +44,7 @@ import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; * ``` * context.search(request, options, DEMO_SEARCH_STRATEGY); * ``` - * + * * and are ensured type safety in regard to the request and response objects. * * @param context - context supplied by other plugins. @@ -62,7 +59,7 @@ export const demoClientSearchStrategyProvider: TSearchStrategyProvider, }; }; diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts index 5b0883be1fc51..3e1b3db43659e 100644 --- a/examples/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -20,15 +20,28 @@ import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; +const responseMap = new Map(); +const getId = (() => { + let id = 0; + return () => `${id++}`; +})(); + export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { return { search: request => { - return Promise.resolve({ + const { id = getId() } = request; + const response = responseMap.get(id) ?? {}; + const nextResponse = { + id, + loaded: (response.loaded ?? 0) + 1, + total: response.total ?? 10, greeting: - request.mood === 'happy' + response.greeting ?? request.mood === 'happy' ? `Lovely to meet you, ${request.name}` : `Hope you feel better, ${request.name}`, - }); + }; + responseMap.set(id, nextResponse); + return Promise.resolve(nextResponse); }, }; }; diff --git a/x-pack/plugins/enhanced_data/public/index.ts b/x-pack/plugins/enhanced_data/public/index.ts index ce6590ebdc63f..983d135b5106f 100644 --- a/x-pack/plugins/enhanced_data/public/index.ts +++ b/x-pack/plugins/enhanced_data/public/index.ts @@ -13,4 +13,5 @@ export function plugin(initializerContext: PluginInitializerContext) { export { EnhancedDataPublicPlugin as Plugin }; +export { ASYNC_SEARCH_STRATEGY } from './search/async_search_strategy'; export { IAsyncSearchRequest, IAsyncSearchOptions } from './search/types'; From d4878124f21a43a54794f096752b2a68223fa97f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Dec 2019 15:20:45 -0700 Subject: [PATCH 07/59] Add async es search strategy --- src/plugins/data/common/index.ts | 1 + src/plugins/data/public/search/index.ts | 1 + src/plugins/data/server/index.ts | 2 + src/plugins/data/server/plugin.ts | 1 + x-pack/plugins/enhanced_data/kibana.json | 2 +- x-pack/plugins/enhanced_data/public/plugin.ts | 12 +++-- .../search/es_search/es_search_strategy.ts | 30 +++++++++++++ .../public/search/es_search/index.ts | 7 +++ x-pack/plugins/enhanced_data/server/index.ts | 14 ++++++ x-pack/plugins/enhanced_data/server/plugin.ts | 32 +++++++++++++ .../search/es_search/es_search_strategy.ts | 45 +++++++++++++++++++ .../server/search/es_search/index.ts | 7 +++ 12 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/enhanced_data/public/search/es_search/es_search_strategy.ts create mode 100644 x-pack/plugins/enhanced_data/public/search/es_search/index.ts create mode 100644 x-pack/plugins/enhanced_data/server/index.ts create mode 100644 x-pack/plugins/enhanced_data/server/plugin.ts create mode 100644 x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts create mode 100644 x-pack/plugins/enhanced_data/server/search/es_search/index.ts diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index b334342a57ec6..d41f13bfe1f43 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -24,3 +24,4 @@ export * from './index_patterns'; export * from './es_query'; export * from './utils'; export * from './types'; +export * from './search'; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 8d18ccb5741fb..881e6ef6b4eeb 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -36,6 +36,7 @@ export { TSearchStrategyProvider, ISearchStrategy } from './i_search_strategy'; export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search'; export { ISyncSearchRequest, SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; +export { esSearchStrategyProvider } from './es_search/es_search_strategy'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 022eb0ae50295..c1e9d0d8eec60 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -90,4 +90,6 @@ export { getKbnTypeNames, } from '../common'; +export { DataPluginSetup } from './plugin'; + export { DataServerPlugin as Plugin }; diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index e81250e653ebd..ee03acb195156 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -25,6 +25,7 @@ import { SearchService } from './search/search_service'; export interface DataPluginSetup { search: ISearchSetup; } + export class DataServerPlugin implements Plugin { private readonly searchService: SearchService; private readonly indexPatterns = new IndexPatternsService(); diff --git a/x-pack/plugins/enhanced_data/kibana.json b/x-pack/plugins/enhanced_data/kibana.json index c30c528b2ee84..091bf19cffd8f 100644 --- a/x-pack/plugins/enhanced_data/kibana.json +++ b/x-pack/plugins/enhanced_data/kibana.json @@ -2,6 +2,6 @@ "id": "enhancedData", "version": "kibana", "requiredPlugins": ["data"], - "server": false, + "server": true, "ui": true } diff --git a/x-pack/plugins/enhanced_data/public/plugin.ts b/x-pack/plugins/enhanced_data/public/plugin.ts index 9f76dcec9776d..a96429f1d7434 100644 --- a/x-pack/plugins/enhanced_data/public/plugin.ts +++ b/x-pack/plugins/enhanced_data/public/plugin.ts @@ -6,10 +6,9 @@ import { PluginInitializerContext, CoreSetup, Plugin } from '../../../../src/core/public'; import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; -import { - ASYNC_SEARCH_STRATEGY, - asyncSearchStrategyProvider, -} from './search/async_search_strategy'; +import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; +import { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './search/async_search_strategy'; +import { enhancedEsSearchStrategyProvider } from './search/es_search'; interface SetupDependencies { data: DataPublicPluginSetup; @@ -24,6 +23,11 @@ export class EnhancedDataPublicPlugin implements Plugin = ( + context: ISearchContext, + search: ISearchGeneric +): ISearchStrategy => { + return { + search: (request, options) => { + return search( + { ...request, serverStrategy: ES_SEARCH_STRATEGY }, + options, + ASYNC_SEARCH_STRATEGY + ) as Observable; + }, + }; +}; diff --git a/x-pack/plugins/enhanced_data/public/search/es_search/index.ts b/x-pack/plugins/enhanced_data/public/search/es_search/index.ts new file mode 100644 index 0000000000000..f914326f30d32 --- /dev/null +++ b/x-pack/plugins/enhanced_data/public/search/es_search/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { enhancedEsSearchStrategyProvider } from './es_search_strategy'; diff --git a/x-pack/plugins/enhanced_data/server/index.ts b/x-pack/plugins/enhanced_data/server/index.ts new file mode 100644 index 0000000000000..fbe1ecc10d632 --- /dev/null +++ b/x-pack/plugins/enhanced_data/server/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'kibana/server'; +import { EnhancedDataServerPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new EnhancedDataServerPlugin(initializerContext); +} + +export { EnhancedDataServerPlugin as Plugin }; diff --git a/x-pack/plugins/enhanced_data/server/plugin.ts b/x-pack/plugins/enhanced_data/server/plugin.ts new file mode 100644 index 0000000000000..134731c0c850e --- /dev/null +++ b/x-pack/plugins/enhanced_data/server/plugin.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/server'; +import { DataPluginSetup } from '../../../../src/plugins/data/server'; +import { enhancedEsSearchStrategyProvider } from '../server/search/es_search'; +import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; + +interface SetupDependencies { + data: DataPluginSetup; +} + +export class EnhancedDataServerPlugin implements Plugin { + constructor(private initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, deps: SetupDependencies) { + deps.data.search.registerSearchStrategyProvider( + this.initializerContext.opaqueId, + ES_SEARCH_STRATEGY, + enhancedEsSearchStrategyProvider + ); + } + + public start(core: CoreStart) {} + + public stop() {} +} + +export { EnhancedDataServerPlugin as Plugin }; diff --git a/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts new file mode 100644 index 0000000000000..e88b7fe1c4021 --- /dev/null +++ b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { APICaller } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; +import { ES_SEARCH_STRATEGY } from '../../../../../../src/plugins/data/common'; +import { + ISearchStrategy, + ISearchContext, + TSearchStrategyProvider, +} from '../../../../../../src/plugins/data/public'; + +export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext, + caller: APICaller +): ISearchStrategy => { + return { + search: async (request, options) => { + const { index, ...params } = request.params; + + const rawResponse = (await caller( + 'transport.request', + { + path: `${index}/_async_search`, + method: 'POST', + ...params, + }, + options + )) as SearchResponse; + + const { + id, + response: { + _shards: { total, successful, skipped, failed }, + }, + } = rawResponse; + const loaded = failed + skipped + successful; + + return { id, total, loaded, rawResponse }; + }, + }; +}; diff --git a/x-pack/plugins/enhanced_data/server/search/es_search/index.ts b/x-pack/plugins/enhanced_data/server/search/es_search/index.ts new file mode 100644 index 0000000000000..f914326f30d32 --- /dev/null +++ b/x-pack/plugins/enhanced_data/server/search/es_search/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { enhancedEsSearchStrategyProvider } from './es_search_strategy'; From 7433aa6979c33b787b571086aebbf0c6af02ec02 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Dec 2019 17:09:32 -0700 Subject: [PATCH 08/59] Return response as rawResponse --- .../server/search/es_search/es_search_strategy.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts index e88b7fe1c4021..745e365b46cb8 100644 --- a/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts @@ -21,7 +21,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { const { index, ...params } = request.params; - const rawResponse = (await caller( + const esSearchResponse = (await caller( 'transport.request', { path: `${index}/_async_search`, @@ -31,14 +31,11 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider; + const { id, response: rawResponse } = esSearchResponse; const { - id, - response: { - _shards: { total, successful, skipped, failed }, - }, + _shards: { total, successful, skipped, failed }, } = rawResponse; const loaded = failed + skipped + successful; - return { id, total, loaded, rawResponse }; }, }; From 93ea93e9082036abf9a787a221a07a3041d8a306 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 19 Dec 2019 09:40:33 -0700 Subject: [PATCH 09/59] Poll after initial request --- .../courier/search_source/search_source.ts | 30 ++++++---------- .../search/es_search/es_search_strategy.ts | 36 +++++++++++-------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/legacy/ui/public/courier/search_source/search_source.ts b/src/legacy/ui/public/courier/search_source/search_source.ts index e862bb1118a74..7d849fecf78de 100644 --- a/src/legacy/ui/public/courier/search_source/search_source.ts +++ b/src/legacy/ui/public/courier/search_source/search_source.ts @@ -70,7 +70,8 @@ */ import _ from 'lodash'; -import { npSetup } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; +import { map } from 'rxjs/operators'; import { normalizeSortRequest } from './normalize_sort_request'; import { fetchSoon } from '../fetch'; import { fieldWildcardFilter } from '../../field_wildcard'; @@ -192,28 +193,17 @@ export class SearchSource { * @async */ async fetch(options: FetchOptions = {}) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const es = $injector.get('es') as ApiCaller; - await this.requestIsStarting(options); - const searchRequest = await this.flatten(); this.history = [searchRequest]; - - const response = await fetchSoon( - searchRequest, - { - ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), - ...options, - }, - { es, config, esShardTimeout } - ); - - if (response.error) { - throw new RequestFailure(null, response); - } - - return response; + const params = { + index: searchRequest.index.title || searchRequest.index, + body: searchRequest.body, + }; + return npStart.plugins.data.search + .search({ params }, { signal: options.abortSignal }) + .pipe(map(response => response.rawResponse)) + .toPromise(); } /** diff --git a/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts index 745e365b46cb8..901a279900355 100644 --- a/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/server/search/es_search/es_search_strategy.ts @@ -19,23 +19,31 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider => { return { search: async (request, options) => { - const { index, ...params } = request.params; - - const esSearchResponse = (await caller( + // If we have an ID, then just poll for that ID, otherwise send the entire request body + const args = [ 'transport.request', - { - path: `${index}/_async_search`, - method: 'POST', - ...params, - }, - options - )) as SearchResponse; + request.id + ? { + path: `_async_search/${request.id}`, + method: 'GET', + } + : { + path: `${request.params.index}/_async_search`, + method: 'POST', + ...request.params, + }, + options, + ]; + + const esSearchResponse = (await caller(...args)) as SearchResponse; + // TODO: This can be simplified once the async API is updated const { id, response: rawResponse } = esSearchResponse; - const { - _shards: { total, successful, skipped, failed }, - } = rawResponse; - const loaded = failed + skipped + successful; + const failed = rawResponse._shards?.failed ?? rawResponse.shard_failures; + const total = rawResponse._shards?.total ?? rawResponse.total_shards; + const successful = rawResponse._shards?.successful ?? rawResponse.successful_shards; + const skipped = rawResponse._shards?.skipped ?? 0; + const loaded = failed + successful + skipped; return { id, total, loaded, rawResponse }; }, }; From 72607613f7c50569535105e54f929579f1f896bb Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 23 Jan 2020 17:03:37 -0700 Subject: [PATCH 10/59] Add cancellation to search strategies --- .../search/create_app_mount_context_search.ts | 13 ++++---- .../public/search/sync_search_strategy.ts | 4 +-- src/plugins/data/server/search/create_api.ts | 13 +++++++- .../search/i_route_handler_search_context.ts | 3 +- src/plugins/data/server/search/i_search.ts | 7 ++++ .../data/server/search/i_search_strategy.ts | 3 +- src/plugins/data/server/search/routes.ts | 33 ++++++++++++++++++- .../search/async_search_strategy.test.ts | 2 ++ .../public/search/async_search_strategy.ts | 22 +++++++++++-- 9 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index f480b8f3e042e..44ca6154837b5 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -18,7 +18,7 @@ */ import { mergeMap, tap } from 'rxjs/operators'; -import { from, BehaviorSubject } from 'rxjs'; +import { from, BehaviorSubject, PartialObserver } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -50,12 +50,11 @@ export const createAppMountSearchContext = ( return from(strategyPromise).pipe( mergeMap(strategy => { loadingCount$.next(loadingCount$.getValue() + 1); - return strategy.search(request, options).pipe( - tap( - error => loadingCount$.next(loadingCount$.getValue() - 1), - complete => loadingCount$.next(loadingCount$.getValue() - 1) - ) - ); + const observer: PartialObserver = { + error: () => loadingCount$.next(loadingCount$.getValue() - 1), + complete: () => loadingCount$.next(loadingCount$.getValue() - 1), + }; + return strategy.search(request, options).pipe(tap(observer)); }) ); }; diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index 3885a97a98571..42b08a8dbf737 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -48,9 +48,7 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider = { - search, - }; + const strategy: ISearchStrategy = { search }; return strategy; }; diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index e1613103ac399..744a377b82a89 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -31,7 +31,7 @@ export function createApi({ }) { const api: IRouteHandlerSearchContext = { search: async (request, options, strategyName) => { - const name = strategyName ? strategyName : DEFAULT_SEARCH_STRATEGY; + const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; const strategyProvider = searchStrategies[name]; if (!strategyProvider) { throw new Error(`No strategy found for ${strategyName}`); @@ -40,6 +40,17 @@ export function createApi({ const strategy = await strategyProvider(caller, api.search); return strategy.search(request, options); }, + cancel: async (request, strategyName) => { + 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); + const { cancel = () => {} } = strategy; + return cancel(request); + }, }; return api; } diff --git a/src/plugins/data/server/search/i_route_handler_search_context.ts b/src/plugins/data/server/search/i_route_handler_search_context.ts index 8a44738a1dcfa..89862781b826e 100644 --- a/src/plugins/data/server/search/i_route_handler_search_context.ts +++ b/src/plugins/data/server/search/i_route_handler_search_context.ts @@ -17,8 +17,9 @@ * under the License. */ -import { ISearchGeneric } from './i_search'; +import { ISearchGeneric, ICancelGeneric } from './i_search'; export interface IRouteHandlerSearchContext { search: ISearchGeneric; + cancel: ICancelGeneric; } diff --git a/src/plugins/data/server/search/i_search.ts b/src/plugins/data/server/search/i_search.ts index 0a35734574153..15cdb690cd970 100644 --- a/src/plugins/data/server/search/i_search.ts +++ b/src/plugins/data/server/search/i_search.ts @@ -42,7 +42,14 @@ export type ISearchGeneric = Promise; +export type ICancelGeneric = ( + id: string, + strategy?: T +) => Promise; + export type ISearch = ( request: IRequestTypesMap[T], options?: ISearchOptions ) => Promise; + +export type ICancel = (request: IRequestTypesMap[T]) => Promise; diff --git a/src/plugins/data/server/search/i_search_strategy.ts b/src/plugins/data/server/search/i_search_strategy.ts index d00dd552c9e95..4cfc9608383a9 100644 --- a/src/plugins/data/server/search/i_search_strategy.ts +++ b/src/plugins/data/server/search/i_search_strategy.ts @@ -18,7 +18,7 @@ */ import { APICaller } from 'kibana/server'; -import { ISearch, ISearchGeneric } from './i_search'; +import { ISearch, ICancel, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; import { ISearchContext } from './i_search_context'; @@ -28,6 +28,7 @@ import { ISearchContext } from './i_search_context'; */ export interface ISearchStrategy { search: ISearch; + cancel?: ICancel; } /** diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 6f726771c41b2..9467f5fb5b5cf 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -34,7 +34,7 @@ export function registerSearchRoute(router: IRouter): void { }, async (context, request, res) => { const searchRequest = request.body; - const strategy = request.params.strategy; + const { strategy } = request.params; try { const response = await context.search!.search(searchRequest, {}, strategy); return res.ok({ body: response }); @@ -51,4 +51,35 @@ export function registerSearchRoute(router: IRouter): void { } } ); + + router.delete( + { + path: '/internal/search/{strategy}/{id}', + validate: { + params: schema.object({ + strategy: schema.string(), + id: schema.string(), + }), + + query: schema.object({}, { allowUnknowns: true }), + }, + }, + async (context, request, res) => { + const { strategy, id } = request.params; + try { + await context.search!.cancel(id, strategy); + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode, + body: { + message: err.message, + attributes: { + error: err.body.error, + }, + }, + }); + } + } + ); } diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts index a12b9b8745671..5def435872b10 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts @@ -62,4 +62,6 @@ describe('Async search strategy', () => { expect(mockSearch.mock.calls[0][0]).toEqual(mockRequest); expect(mockSearch.mock.calls[1][0]).toEqual({ id: 1, serverStrategy: 'foo' }); }); + + it('sends a DELETE request and stops polling when the signal is aborted', async () => {}); }); diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts index 28ea2d0c1afd8..4552a2589dee8 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable, of } from 'rxjs'; -import { mergeMap, takeWhile, expand, delay } from 'rxjs/operators'; +import { fromEvent, NEVER, Observable, of } from 'rxjs'; +import { mergeMap, takeWhile, expand, delay, first, tap, takeUntil } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, @@ -34,9 +34,21 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider => { const { serverStrategy } = request; + let id: string | undefined; + + const aborted$ = options.signal + ? fromEvent(options.signal, 'abort').pipe( + first(), + tap(() => { + if (id === undefined) return; + context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); + }) + ) + : NEVER; return search(request, options, SYNC_SEARCH_STRATEGY).pipe( - expand(({ id }) => { + expand(response => { + id = response.id; return of(null).pipe( // Delay by the given poll interval delay(pollInterval), @@ -44,6 +56,10 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY)) ); }), + + // Stop polling if the signal is aborted + takeUntil(aborted$), + // Continue polling until the response indicates it is complete takeWhile(({ total = 1, loaded = 1 }) => loaded < total, true) ); From be300db6414a1ea085f6a3a26eca96633c94cec5 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 27 Jan 2020 15:28:03 -0700 Subject: [PATCH 11/59] Add tests --- .../search/create_app_mount_context_search.ts | 13 ++++---- src/plugins/data/server/search/create_api.ts | 5 ++- src/plugins/data/server/search/i_search.ts | 2 +- .../data/server/search/i_search_strategy.ts | 5 +-- .../search/async_search_strategy.test.ts | 31 ++++++++++++++++--- .../public/search/async_search_strategy.ts | 24 +++++++++----- 6 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index 44ca6154837b5..600af48892c59 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -18,7 +18,7 @@ */ import { mergeMap, tap } from 'rxjs/operators'; -import { from, BehaviorSubject, PartialObserver } from 'rxjs'; +import { from, BehaviorSubject } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -50,11 +50,12 @@ export const createAppMountSearchContext = ( return from(strategyPromise).pipe( mergeMap(strategy => { loadingCount$.next(loadingCount$.getValue() + 1); - const observer: PartialObserver = { - error: () => loadingCount$.next(loadingCount$.getValue() - 1), - complete: () => loadingCount$.next(loadingCount$.getValue() - 1), - }; - return strategy.search(request, options).pipe(tap(observer)); + return strategy.search(request, options).pipe( + tap({ + error: () => loadingCount$.next(loadingCount$.getValue() - 1), + complete: () => loadingCount$.next(loadingCount$.getValue() - 1), + }) + ); }) ); }; diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index 744a377b82a89..b22c13510f84c 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -40,7 +40,7 @@ export function createApi({ const strategy = await strategyProvider(caller, api.search); return strategy.search(request, options); }, - cancel: async (request, strategyName) => { + cancel: async (id, strategyName) => { const name = strategyName ?? DEFAULT_SEARCH_STRATEGY; const strategyProvider = searchStrategies[name]; if (!strategyProvider) { @@ -48,8 +48,7 @@ export function createApi({ } // Give providers access to other search strategies by injecting this function const strategy = await strategyProvider(caller, api.search); - const { cancel = () => {} } = strategy; - return cancel(request); + return strategy.cancel && strategy.cancel(id); }, }; return api; diff --git a/src/plugins/data/server/search/i_search.ts b/src/plugins/data/server/search/i_search.ts index 15cdb690cd970..ea014c5e136d9 100644 --- a/src/plugins/data/server/search/i_search.ts +++ b/src/plugins/data/server/search/i_search.ts @@ -52,4 +52,4 @@ export type ISearch = ( options?: ISearchOptions ) => Promise; -export type ICancel = (request: IRequestTypesMap[T]) => Promise; +export type ICancel = (id: string) => Promise; diff --git a/src/plugins/data/server/search/i_search_strategy.ts b/src/plugins/data/server/search/i_search_strategy.ts index 4cfc9608383a9..1837dc21ec8a0 100644 --- a/src/plugins/data/server/search/i_search_strategy.ts +++ b/src/plugins/data/server/search/i_search_strategy.ts @@ -18,7 +18,7 @@ */ import { APICaller } from 'kibana/server'; -import { ISearch, ICancel, ISearchGeneric } from './i_search'; +import { ISearch, ICancel, ISearchGeneric, ICancelGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; import { ISearchContext } from './i_search_context'; @@ -38,7 +38,8 @@ export interface ISearchStrategy { */ export type TSearchStrategyProviderEnhanced = ( caller: APICaller, - search: ISearchGeneric + search: ISearchGeneric, + cancel?: ICancelGeneric ) => Promise>; /** diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts index 5def435872b10..de1bde847bf7e 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts @@ -5,15 +5,15 @@ */ import { of } from 'rxjs'; -import { CoreSetup } from '../../../../../src/core/public'; import { coreMock } from '../../../../../src/core/public/mocks'; -import { asyncSearchStrategyProvider, IAsyncSearchOptions } from './async_search_strategy'; +import { asyncSearchStrategyProvider } from './async_search_strategy'; +import { IAsyncSearchOptions } from './types'; describe('Async search strategy', () => { - let mockCoreSetup: MockedKeys; + let mockCoreSetup: ReturnType; const mockSearch = jest.fn(); const mockRequest = { params: {}, serverStrategy: 'foo' }; - const mockOptions = { pollInterval: 0 }; + const mockOptions: IAsyncSearchOptions = { pollInterval: 0 }; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); @@ -63,5 +63,26 @@ describe('Async search strategy', () => { expect(mockSearch.mock.calls[1][0]).toEqual({ id: 1, serverStrategy: 'foo' }); }); - it('sends a DELETE request and stops polling when the signal is aborted', async () => {}); + it('sends a DELETE request and stops polling when the signal is aborted', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + + const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + const abortController = new AbortController(); + const options = { ...mockOptions, signal: abortController.signal }; + + const promise = asyncSearch.search(mockRequest, options).toPromise(); + abortController.abort(); + + expect.assertions(2); + + try { + await promise; + } catch (e) { + expect(mockSearch).toBeCalledTimes(1); + expect(mockCoreSetup.http.delete).toBeCalled(); + } + }); }); diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts index 4552a2589dee8..8ddccb610e715 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromEvent, NEVER, Observable, of } from 'rxjs'; -import { mergeMap, takeWhile, expand, delay, first, tap, takeUntil } from 'rxjs/operators'; +import { EMPTY, fromEvent, NEVER, Observable, of, throwError } from 'rxjs'; +import { mergeMap, takeWhile, expand, delay, first, takeUntil } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, @@ -34,14 +34,19 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider => { const { serverStrategy } = request; - let id: string | undefined; + let id: string | undefined = request.id; const aborted$ = options.signal ? fromEvent(options.signal, 'abort').pipe( first(), - tap(() => { - if (id === undefined) return; + mergeMap(() => { + // If we haven't received the response to the initial request, including the ID, then + // we don't need to send a follow-up request to delete this search + if (id === undefined) return EMPTY; // mergeMap expects to return an observable + + // Send the follow-up request to delete this search, then throw an abort error context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); + return throwError(new AbortError()); }) ) : NEVER; @@ -56,13 +61,18 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY)) ); }), - // Stop polling if the signal is aborted takeUntil(aborted$), - // Continue polling until the response indicates it is complete takeWhile(({ total = 1, loaded = 1 }) => loaded < total, true) ); }, }; }; + +export class AbortError extends Error { + constructor(...args: Parameters) { + super(...args); + this.name = 'AbortError'; + } +} From e4eb6517adc80cbf343e603e719749c3c092ab52 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 28 Jan 2020 15:56:41 -0700 Subject: [PATCH 12/59] Simplify async search strategy --- .../public/search/async_search_strategy.ts | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts index 8ddccb610e715..6160f51d56b4f 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EMPTY, fromEvent, NEVER, Observable, of, throwError } from 'rxjs'; -import { mergeMap, takeWhile, expand, delay, first, takeUntil } from 'rxjs/operators'; +import { Observable, timer } from 'rxjs'; +import { mergeMap, takeWhile, expand } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, @@ -36,43 +36,31 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider { - // If we haven't received the response to the initial request, including the ID, then - // we don't need to send a follow-up request to delete this search - if (id === undefined) return EMPTY; // mergeMap expects to return an observable + if (options.signal) { + options.signal.addEventListener('abort', () => { + // If we haven't received the response to the initial request, including the ID, then + // we don't need to send a follow-up request to delete this search + if (id === undefined) return; - // Send the follow-up request to delete this search, then throw an abort error - context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); - return throwError(new AbortError()); - }) - ) - : NEVER; + // Send the follow-up request to delete this search, then throw an abort error + context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); + }); + } return search(request, options, SYNC_SEARCH_STRATEGY).pipe( expand(response => { id = response.id; - return of(null).pipe( - // Delay by the given poll interval - delay(pollInterval), + // Delay by the given poll interval + return timer(pollInterval).pipe( // Send future requests using just the ID from the response - mergeMap(() => search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY)) + mergeMap(() => { + return search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY); + }) ); }), - // Stop polling if the signal is aborted - takeUntil(aborted$), // Continue polling until the response indicates it is complete takeWhile(({ total = 1, loaded = 1 }) => loaded < total, true) ); }, }; }; - -export class AbortError extends Error { - constructor(...args: Parameters) { - super(...args); - this.name = 'AbortError'; - } -} From 6188c742862a8a5db205c06fc35fde4134e2959e Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 29 Jan 2020 12:26:23 -0700 Subject: [PATCH 13/59] Move loadingCount to search strategy --- .../create_app_mount_context_search.test.ts | 50 ++++++++----------- .../search/create_app_mount_context_search.ts | 19 ++----- .../public/search/es_client/get_es_client.ts | 6 ++- .../data/public/search/search_service.ts | 10 +--- .../public/search/sync_search_strategy.ts | 16 +++--- 5 files changed, 41 insertions(+), 60 deletions(-) diff --git a/src/plugins/data/public/search/create_app_mount_context_search.test.ts b/src/plugins/data/public/search/create_app_mount_context_search.test.ts index 15b85ee270bed..fa7cdbcda3082 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.test.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.test.ts @@ -18,35 +18,32 @@ */ import { createAppMountSearchContext } from './create_app_mount_context_search'; -import { from, BehaviorSubject } from 'rxjs'; +import { from } from 'rxjs'; describe('Create app mount search context', () => { it('Returns search fn when there are no strategies', () => { - const context = createAppMountSearchContext({}, new BehaviorSubject(0)); + const context = createAppMountSearchContext({}); expect(context.search).toBeDefined(); }); it(`Search throws an error when the strategy doesn't exist`, () => { - const context = createAppMountSearchContext({}, new BehaviorSubject(0)); + const context = createAppMountSearchContext({}); expect(() => context.search({}, {}, 'noexist').toPromise()).toThrowErrorMatchingInlineSnapshot( `"Strategy with name noexist does not exist"` ); }); it(`Search fn is called on appropriate strategy name`, done => { - const context = createAppMountSearchContext( - { - mysearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 98 })), - }), - anothersearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 0 })), - }), - }, - new BehaviorSubject(0) - ); + const context = createAppMountSearchContext({ + mysearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 98 })), + }), + anothersearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 0 })), + }), + }); context.search({}, {}, 'mysearch').subscribe(response => { expect(response).toEqual({ percentComplete: 98 }); @@ -55,19 +52,16 @@ describe('Create app mount search context', () => { }); it(`Search fn is called with the passed in request object`, done => { - const context = createAppMountSearchContext( - { - mysearch: search => { - return Promise.resolve({ - search: request => { - expect(request).toEqual({ greeting: 'hi' }); - return from(Promise.resolve({})); - }, - }); - }, + const context = createAppMountSearchContext({ + mysearch: search => { + return Promise.resolve({ + search: request => { + expect(request).toEqual({ greeting: 'hi' }); + return from(Promise.resolve({})); + }, + }); }, - new BehaviorSubject(0) - ); + }); context.search({ greeting: 'hi' } as any, {}, 'mysearch').subscribe( response => {}, () => {}, diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index f480b8f3e042e..7a617e0bab837 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -17,8 +17,8 @@ * under the License. */ -import { mergeMap, tap } from 'rxjs/operators'; -import { from, BehaviorSubject } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; +import { from } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -30,8 +30,7 @@ import { TStrategyTypes } from './strategy_types'; import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; export const createAppMountSearchContext = ( - searchStrategies: TSearchStrategiesMap, - loadingCount$: BehaviorSubject + searchStrategies: TSearchStrategiesMap ): ISearchAppMountContext => { const getSearchStrategy = ( strategyName?: K @@ -47,17 +46,7 @@ export const createAppMountSearchContext = ( const search: ISearchGeneric = (request, options, strategyName) => { const strategyPromise = getSearchStrategy(strategyName); - return from(strategyPromise).pipe( - mergeMap(strategy => { - loadingCount$.next(loadingCount$.getValue() + 1); - return strategy.search(request, options).pipe( - tap( - error => loadingCount$.next(loadingCount$.getValue() - 1), - complete => loadingCount$.next(loadingCount$.getValue() - 1) - ) - ); - }) - ); + return from(strategyPromise).pipe(mergeMap(strategy => strategy.search(request, options))); }; return { search }; diff --git a/src/plugins/data/public/search/es_client/get_es_client.ts b/src/plugins/data/public/search/es_client/get_es_client.ts index 6c271643ba012..93d9d24920271 100644 --- a/src/plugins/data/public/search/es_client/get_es_client.ts +++ b/src/plugins/data/public/search/es_client/get_es_client.ts @@ -25,8 +25,7 @@ import { BehaviorSubject } from 'rxjs'; export function getEsClient( injectedMetadata: CoreStart['injectedMetadata'], http: CoreStart['http'], - packageInfo: PackageInfo, - loadingCount$: BehaviorSubject + packageInfo: PackageInfo ) { const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; @@ -39,6 +38,9 @@ export function getEsClient( apiVersion: esApiVersion, }); + const loadingCount$ = new BehaviorSubject(0); + http.addLoadingCountSource(loadingCount$); + return { search: wrapEsClientMethod(client, 'search', loadingCount$), msearch: wrapEsClientMethod(client, 'msearch', loadingCount$), diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index dad2b3d078aa0..dd67c04f1d7bd 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { BehaviorSubject } from 'rxjs'; import { Plugin, CoreSetup, @@ -80,22 +79,17 @@ export class SearchService implements Plugin { private contextContainer?: IContextContainer>; private esClient?: LegacyApiCaller; private search?: ISearchGeneric; - private readonly loadingCount$ = new BehaviorSubject(0); constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, packageInfo: PackageInfo): ISearchSetup { - core.http.addLoadingCountSource(this.loadingCount$); - const search = (this.search = createAppMountSearchContext( - this.searchStrategies, - this.loadingCount$ - ).search); + const search = (this.search = createAppMountSearchContext(this.searchStrategies).search); core.application.registerMountContext<'search'>('search', () => { return { search }; }); this.contextContainer = core.context.createContextContainer(); - this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo, this.loadingCount$); + this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); const registerSearchStrategyProvider: TRegisterSearchStrategyProvider = < T extends TStrategyTypes diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index 65fe10f39aaa0..352e19b5875e8 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -17,7 +17,7 @@ * under the License. */ -import { from } from 'rxjs'; +import { BehaviorSubject, from } from 'rxjs'; import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search'; import { ISearchContext } from './i_search_context'; import { ISearch, ISearchOptions } from './i_search'; @@ -31,11 +31,15 @@ export interface ISyncSearchRequest extends IKibanaSearchRequest { export const syncSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext -) => { +): ISearchStrategy => { const search: ISearch = ( request: ISyncSearchRequest, options: ISearchOptions = {} ) => { + const loadingCount$ = new BehaviorSubject(0); + context.core.http.addLoadingCountSource(loadingCount$); + loadingCount$.next(loadingCount$.getValue() + 1); + const response: Promise = context.core.http.fetch({ path: `/internal/search/${request.serverStrategy}`, method: 'POST', @@ -43,12 +47,10 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider loadingCount$.next(loadingCount$.getValue() - 1)); - const strategy: ISearchStrategy = { - search, + return from(response); }; - return strategy; + return { search }; }; From f15f138c338d5db9423be2bc53bd196d8cc80cce Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 3 Feb 2020 11:13:52 -0700 Subject: [PATCH 14/59] Update abort controller library --- package.json | 2 +- packages/kbn-ui-shared-deps/package.json | 7 +++---- packages/kbn-ui-shared-deps/polyfills.js | 2 +- .../elasticsearch/server/lib/abortable_request_handler.js | 2 +- .../server/lib/abortable_request_handler.test.js | 2 +- yarn.lock | 5 ----- 6 files changed, 7 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index e249546e71581..4a98559b92505 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", - "abortcontroller-polyfill": "^1.3.0", + "abort-controller": "^3.0.0", "angular": "^1.7.9", "angular-aria": "^1.7.8", "angular-elastic": "^2.5.1", diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 47a47449927e4..08c4edd8510aa 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,12 +9,11 @@ "kbn:watch": "node scripts/build --watch" }, "devDependencies": { - "@elastic/eui": "18.2.1", "@elastic/charts": "^16.1.0", + "@elastic/eui": "18.2.1", "@kbn/dev-utils": "1.0.0", "@kbn/i18n": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", - "abortcontroller-polyfill": "^1.3.0", "angular": "^1.7.9", "core-js": "^3.2.1", "css-loader": "^2.1.1", @@ -24,13 +23,13 @@ "mini-css-extract-plugin": "0.8.0", "moment": "^2.24.0", "moment-timezone": "^0.5.27", + "react": "^16.12.0", "react-dom": "^16.12.0", "react-intl": "^2.8.0", - "react": "^16.12.0", "read-pkg": "^5.2.0", "regenerator-runtime": "^0.13.3", "symbol-observable": "^1.2.0", "webpack": "4.41.0", "whatwg-fetch": "^3.0.0" } -} \ No newline at end of file +} diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/polyfills.js index d2305d643e4d2..612fbb9a78b50 100644 --- a/packages/kbn-ui-shared-deps/polyfills.js +++ b/packages/kbn-ui-shared-deps/polyfills.js @@ -21,6 +21,6 @@ require('core-js/stable'); require('regenerator-runtime/runtime'); require('custom-event-polyfill'); require('whatwg-fetch'); -require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); +require('abort-controller/polyfill'); require('./vendor/childnode_remove_polyfill'); require('symbol-observable'); diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js index 0b8786f0c2841..68216b92840fc 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js @@ -17,7 +17,7 @@ * under the License. */ -import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; +import { AbortController } from 'abort-controller'; /* * A simple utility for generating a handler that provides a signal to the handler that signals when diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js index d79dd4ae4e449..1c154370d1674 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { AbortSignal } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; +import { AbortSignal } from 'abort-controller'; import { abortableRequestHandler } from './abortable_request_handler'; describe('abortableRequestHandler', () => { diff --git a/yarn.lock b/yarn.lock index a3acc2ae216c5..40aad5c08c914 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5655,11 +5655,6 @@ abort-controller@^2.0.3: dependencies: event-target-shim "^5.0.0" -abortcontroller-polyfill@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.3.0.tgz#de69af32ae926c210b7efbcc29bf644ee4838b00" - integrity sha512-lbWQgf+eRvku3va8poBlDBO12FigTQr9Zb7NIjXrePrhxWVKdCP2wbDl1tLDaYa18PWTom3UEWwdH13S46I+yA== - accept@3.x.x: version "3.0.2" resolved "https://registry.yarnpkg.com/accept/-/accept-3.0.2.tgz#83e41cec7e1149f3fd474880423873db6c6cc9ac" From 4d36124efb511963b9e0ea6a1ce521c32dc8b35f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 3 Feb 2020 11:54:57 -0700 Subject: [PATCH 15/59] Bootstrap --- yarn.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yarn.lock b/yarn.lock index 40aad5c08c914..752e39abf8c90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5655,6 +5655,13 @@ abort-controller@^2.0.3: dependencies: event-target-shim "^5.0.0" +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accept@3.x.x: version "3.0.2" resolved "https://registry.yarnpkg.com/accept/-/accept-3.0.2.tgz#83e41cec7e1149f3fd474880423873db6c6cc9ac" From 491e8bb9d3dfc1e98cd2fc4aed7ada345625db40 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 4 Feb 2020 10:39:29 -0700 Subject: [PATCH 16/59] Abort when the request is aborted --- src/plugins/data/server/search/routes.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 6f726771c41b2..5e54f0237d7dc 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -18,6 +18,7 @@ */ import { schema } from '@kbn/config-schema'; +import { AbortController } from 'abort-controller'; import { IRouter } from '../../../../core/server'; export function registerSearchRoute(router: IRouter): void { @@ -35,8 +36,15 @@ export function registerSearchRoute(router: IRouter): void { async (context, request, res) => { const searchRequest = request.body; const strategy = request.params.strategy; + + const controller = new AbortController(); + const { signal } = controller; + request.events.aborted$.subscribe(() => { + controller.abort(); + }); + try { - const response = await context.search!.search(searchRequest, {}, strategy); + const response = await context.search!.search(searchRequest, { signal }, strategy); return res.ok({ body: response }); } catch (err) { return res.customError({ From 0db725745b15b83cd06fe19f52e05362a48bc7f3 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 4 Feb 2020 12:48:59 -0700 Subject: [PATCH 17/59] Add utility and update value suggestions route --- .../autocomplete/value_suggestions_route.ts | 4 +- .../lib/get_request_aborted_signal.test.ts | 42 +++++++++++++++++++ .../server/lib/get_request_aborted_signal.ts | 32 ++++++++++++++ src/plugins/data/server/lib/index.ts | 20 +++++++++ src/plugins/data/server/search/routes.ts | 9 +--- 5 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/plugins/data/server/lib/get_request_aborted_signal.test.ts create mode 100644 src/plugins/data/server/lib/get_request_aborted_signal.ts create mode 100644 src/plugins/data/server/lib/index.ts diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index b415e83becf93..fa847368742cf 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -22,6 +22,7 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'kibana/server'; import { IFieldType, indexPatterns, esFilters } from '../index'; +import { getRequestAbortedSignal } from '../lib'; export function registerValueSuggestionsRoute(router: IRouter) { router.post( @@ -49,6 +50,7 @@ export function registerValueSuggestionsRoute(router: IRouter) { const { field: fieldName, query, boolFilter } = request.body; const { index } = request.params; const { dataClient } = context.core.elasticsearch; + const signal = getRequestAbortedSignal(request.events.aborted$); const autocompleteSearchOptions = { timeout: await uiSettings.get('kibana.autocompleteTimeout'), @@ -64,7 +66,7 @@ export function registerValueSuggestionsRoute(router: IRouter) { const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); try { - const result = await dataClient.callAsCurrentUser('search', { index, body }); + const result = await dataClient.callAsCurrentUser('search', { index, body }, { signal }); const buckets: any[] = get(result, 'aggregations.suggestions.buckets') || diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts new file mode 100644 index 0000000000000..2792452279161 --- /dev/null +++ b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { Subject } from 'rxjs'; +import { getRequestAbortedSignal } from './get_request_aborted_signal'; + +describe('abortableRequestHandler', () => { + it('should call abort if disconnected', () => { + const abortedSubject = new Subject(); + const aborted$ = abortedSubject.asObservable(); + const onAborted = jest.fn(); + + const signal = getRequestAbortedSignal(aborted$); + signal.addEventListener('abort', onAborted); + + // Shouldn't be aborted or call onAborted prior to disconnecting + expect(signal.aborted).toBe(false); + expect(onAborted).not.toBeCalled(); + + abortedSubject.next(); + + // Should be aborted and call onAborted after disconnecting + expect(signal.aborted).toBe(true); + expect(onAborted).toBeCalled(); + }); +}); diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.ts b/src/plugins/data/server/lib/get_request_aborted_signal.ts new file mode 100644 index 0000000000000..60f56cf406b00 --- /dev/null +++ b/src/plugins/data/server/lib/get_request_aborted_signal.ts @@ -0,0 +1,32 @@ +/* + * 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 { Observable } from 'rxjs'; +import { AbortController } from 'abort-controller'; + +/** + * A simple utility function that returns an `AbortSignal` corresponding to an `AbortController` + * which aborts when the given request is aborted. + * @param aborted$ The observable of abort events (usually `request.events.aborted$`) + */ +export function getRequestAbortedSignal(aborted$: Observable): AbortSignal { + const controller = new AbortController(); + aborted$.subscribe(() => controller.abort()); + return controller.signal; +} diff --git a/src/plugins/data/server/lib/index.ts b/src/plugins/data/server/lib/index.ts new file mode 100644 index 0000000000000..a2af456846e14 --- /dev/null +++ b/src/plugins/data/server/lib/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { getRequestAbortedSignal } from './get_request_aborted_signal'; diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 5e54f0237d7dc..11879d14931ae 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -18,8 +18,8 @@ */ import { schema } from '@kbn/config-schema'; -import { AbortController } from 'abort-controller'; import { IRouter } from '../../../../core/server'; +import { getRequestAbortedSignal } from '../lib'; export function registerSearchRoute(router: IRouter): void { router.post( @@ -36,12 +36,7 @@ export function registerSearchRoute(router: IRouter): void { async (context, request, res) => { const searchRequest = request.body; const strategy = request.params.strategy; - - const controller = new AbortController(); - const { signal } = controller; - request.events.aborted$.subscribe(() => { - controller.abort(); - }); + const signal = getRequestAbortedSignal(request.events.aborted$); try { const response = await context.search!.search(searchRequest, { signal }, strategy); From f02792fd23e49f4e2c8cec19bd458e163067d216 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 4 Feb 2020 15:04:49 -0700 Subject: [PATCH 18/59] Fix bad merge conflict --- packages/kbn-ui-shared-deps/package.json | 1 - yarn.lock | 27 ------------------------ 2 files changed, 28 deletions(-) diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 58bb6eb0725a7..8c8b8b8a21488 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -12,7 +12,6 @@ "abort-controller": "^3.0.0", "@elastic/eui": "18.3.0", "@elastic/charts": "^16.1.0", - "@elastic/eui": "18.2.1", "@kbn/dev-utils": "1.0.0", "@kbn/i18n": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index 6e9e76cf675d0..fce71f6af9e66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1953,33 +1953,6 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@18.2.1": - version "18.2.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.2.1.tgz#6ce6d0bd1d0541052d21f2918305524d71e91678" - integrity sha512-6C5tnWJTlBB++475i0vRoCsnz4JaYznb4zMNFLc+z5GY3vA3/E3AXTjmmBwybEicCCi3h1SnpJxZsgMakiZwRA== - dependencies: - "@types/chroma-js" "^1.4.3" - "@types/lodash" "^4.14.116" - "@types/numeral" "^0.0.25" - "@types/react-beautiful-dnd" "^10.1.0" - chroma-js "^2.0.4" - classnames "^2.2.5" - highlight.js "^9.12.0" - html "^1.0.0" - keymirror "^0.1.1" - lodash "^4.17.11" - numeral "^2.0.6" - prop-types "^15.6.0" - react-ace "^7.0.5" - react-beautiful-dnd "^10.1.0" - react-focus-lock "^1.17.7" - react-input-autosize "^2.2.2" - react-is "~16.3.0" - react-virtualized "^9.21.2" - resize-observer-polyfill "^1.5.0" - tabbable "^3.0.0" - uuid "^3.1.0" - "@elastic/eui@18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.3.0.tgz#e21c6246624f694e2ae1c7c1f1a11b612faf260a" From 2cb8dd4832620ea4ea03927a0ff764f3ffdc0cda Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 4 Feb 2020 16:00:33 -0700 Subject: [PATCH 19/59] Update tests --- .../public/search/async_search_strategy.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts index de1bde847bf7e..d45550b539de1 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts @@ -8,6 +8,7 @@ import { of } from 'rxjs'; import { coreMock } from '../../../../../src/core/public/mocks'; import { asyncSearchStrategyProvider } from './async_search_strategy'; import { IAsyncSearchOptions } from './types'; +import { AbortController } from 'abort-controller'; describe('Async search strategy', () => { let mockCoreSetup: ReturnType; @@ -76,13 +77,8 @@ describe('Async search strategy', () => { const promise = asyncSearch.search(mockRequest, options).toPromise(); abortController.abort(); - expect.assertions(2); - - try { - await promise; - } catch (e) { - expect(mockSearch).toBeCalledTimes(1); - expect(mockCoreSetup.http.delete).toBeCalled(); - } + await promise; + expect(mockSearch).toBeCalledTimes(2); + expect(mockCoreSetup.http.delete).toBeCalled(); }); }); From 2a538340f5b6b9138369849610d14ffceed5a35b Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 5 Feb 2020 13:13:06 -0700 Subject: [PATCH 20/59] Move to data_enhanced plugin --- .../public/demo_search_strategy.ts | 2 +- x-pack/plugins/data_enhanced/public/index.ts | 6 +++- x-pack/plugins/data_enhanced/public/plugin.ts | 14 ++++++-- .../search/async_search_strategy.test.ts | 2 +- .../public/search/async_search_strategy.ts | 0 .../data_enhanced/public/search/index.ts | 8 +++++ .../public/search/types.ts | 0 x-pack/plugins/enhanced_data/kibana.json | 7 ---- x-pack/plugins/enhanced_data/public/index.ts | 17 ---------- x-pack/plugins/enhanced_data/public/plugin.ts | 32 ------------------- 10 files changed, 26 insertions(+), 62 deletions(-) rename x-pack/plugins/{enhanced_data => data_enhanced}/public/search/async_search_strategy.test.ts (100%) rename x-pack/plugins/{enhanced_data => data_enhanced}/public/search/async_search_strategy.ts (100%) create mode 100644 x-pack/plugins/data_enhanced/public/search/index.ts rename x-pack/plugins/{enhanced_data => data_enhanced}/public/search/types.ts (100%) delete mode 100644 x-pack/plugins/enhanced_data/kibana.json delete mode 100644 x-pack/plugins/enhanced_data/public/index.ts delete mode 100644 x-pack/plugins/enhanced_data/public/plugin.ts diff --git a/examples/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts index 7ce8ed0cb707c..db628a63e2942 100644 --- a/examples/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -22,7 +22,7 @@ import { ISearchContext, ISearchGeneric } from '../../../src/plugins/data/public import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; -import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/enhanced_data/public'; +import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/data_enhanced/public'; /** * This demo search strategy provider simply provides a shortcut for calling the DEMO_SEARCH_STRATEGY diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index 93b6b7a957182..bbc858b484564 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializerContext } from '../../../../src/core/public'; import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plugin'; -export const plugin = () => new DataEnhancedPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new DataEnhancedPlugin(initializerContext); export { DataEnhancedSetup, DataEnhancedStart }; + +export { ASYNC_SEARCH_STRATEGY, IAsyncSearchRequest, IAsyncSearchOptions } from './search'; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 14b5382bc85aa..d85e0e47460d5 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; +import { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './search'; export interface DataEnhancedSetupDependencies { data: DataPublicPluginSetup; @@ -20,11 +21,18 @@ export type DataEnhancedSetup = ReturnType; export type DataEnhancedStart = ReturnType; export class DataEnhancedPlugin implements Plugin { - public setup(core: CoreSetup, plugins: DataEnhancedSetupDependencies) { - plugins.data.autocomplete.addQuerySuggestionProvider( + constructor(private initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { data }: DataEnhancedSetupDependencies) { + data.autocomplete.addQuerySuggestionProvider( KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core) ); + data.search.registerSearchStrategyProvider( + this.initializerContext.opaqueId, + ASYNC_SEARCH_STRATEGY, + asyncSearchStrategyProvider + ); } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts similarity index 100% rename from x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts rename to x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts index d45550b539de1..f30787ee8200e 100644 --- a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts @@ -5,10 +5,10 @@ */ import { of } from 'rxjs'; +import { AbortController } from 'abort-controller'; import { coreMock } from '../../../../../src/core/public/mocks'; import { asyncSearchStrategyProvider } from './async_search_strategy'; import { IAsyncSearchOptions } from './types'; -import { AbortController } from 'abort-controller'; describe('Async search strategy', () => { let mockCoreSetup: ReturnType; diff --git a/x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts similarity index 100% rename from x-pack/plugins/enhanced_data/public/search/async_search_strategy.ts rename to x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts diff --git a/x-pack/plugins/data_enhanced/public/search/index.ts b/x-pack/plugins/data_enhanced/public/search/index.ts new file mode 100644 index 0000000000000..a7729aeea5647 --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './async_search_strategy'; +export { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; diff --git a/x-pack/plugins/enhanced_data/public/search/types.ts b/x-pack/plugins/data_enhanced/public/search/types.ts similarity index 100% rename from x-pack/plugins/enhanced_data/public/search/types.ts rename to x-pack/plugins/data_enhanced/public/search/types.ts diff --git a/x-pack/plugins/enhanced_data/kibana.json b/x-pack/plugins/enhanced_data/kibana.json deleted file mode 100644 index c30c528b2ee84..0000000000000 --- a/x-pack/plugins/enhanced_data/kibana.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "enhancedData", - "version": "kibana", - "requiredPlugins": ["data"], - "server": false, - "ui": true -} diff --git a/x-pack/plugins/enhanced_data/public/index.ts b/x-pack/plugins/enhanced_data/public/index.ts deleted file mode 100644 index 983d135b5106f..0000000000000 --- a/x-pack/plugins/enhanced_data/public/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext } from '../../../../src/core/public'; -import { EnhancedDataPublicPlugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new EnhancedDataPublicPlugin(initializerContext); -} - -export { EnhancedDataPublicPlugin as Plugin }; - -export { ASYNC_SEARCH_STRATEGY } from './search/async_search_strategy'; -export { IAsyncSearchRequest, IAsyncSearchOptions } from './search/types'; diff --git a/x-pack/plugins/enhanced_data/public/plugin.ts b/x-pack/plugins/enhanced_data/public/plugin.ts deleted file mode 100644 index 9f76dcec9776d..0000000000000 --- a/x-pack/plugins/enhanced_data/public/plugin.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext, CoreSetup, Plugin } from '../../../../src/core/public'; -import { DataPublicPluginSetup } from '../../../../src/plugins/data/public'; -import { - ASYNC_SEARCH_STRATEGY, - asyncSearchStrategyProvider, -} from './search/async_search_strategy'; - -interface SetupDependencies { - data: DataPublicPluginSetup; -} - -export class EnhancedDataPublicPlugin implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup, { data }: SetupDependencies) { - data.search.registerSearchStrategyProvider( - this.initializerContext.opaqueId, - ASYNC_SEARCH_STRATEGY, - asyncSearchStrategyProvider - ); - } - - public start() {} - - public stop() {} -} From ad5f18875523c11b63a42778c7ce2e943043e6b3 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 6 Feb 2020 11:05:47 -0700 Subject: [PATCH 21/59] Remove bad merge --- packages/kbn-ui-shared-deps/package.json | 1 - yarn.lock | 27 ------------------------ 2 files changed, 28 deletions(-) diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 58bb6eb0725a7..8c8b8b8a21488 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -12,7 +12,6 @@ "abort-controller": "^3.0.0", "@elastic/eui": "18.3.0", "@elastic/charts": "^16.1.0", - "@elastic/eui": "18.2.1", "@kbn/dev-utils": "1.0.0", "@kbn/i18n": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", diff --git a/yarn.lock b/yarn.lock index b3f9d85b5524f..4a787532b1e1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1953,33 +1953,6 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@18.2.1": - version "18.2.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.2.1.tgz#6ce6d0bd1d0541052d21f2918305524d71e91678" - integrity sha512-6C5tnWJTlBB++475i0vRoCsnz4JaYznb4zMNFLc+z5GY3vA3/E3AXTjmmBwybEicCCi3h1SnpJxZsgMakiZwRA== - dependencies: - "@types/chroma-js" "^1.4.3" - "@types/lodash" "^4.14.116" - "@types/numeral" "^0.0.25" - "@types/react-beautiful-dnd" "^10.1.0" - chroma-js "^2.0.4" - classnames "^2.2.5" - highlight.js "^9.12.0" - html "^1.0.0" - keymirror "^0.1.1" - lodash "^4.17.11" - numeral "^2.0.6" - prop-types "^15.6.0" - react-ace "^7.0.5" - react-beautiful-dnd "^10.1.0" - react-focus-lock "^1.17.7" - react-input-autosize "^2.2.2" - react-is "~16.3.0" - react-virtualized "^9.21.2" - resize-observer-polyfill "^1.5.0" - tabbable "^3.0.0" - uuid "^3.1.0" - "@elastic/eui@18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.3.0.tgz#e21c6246624f694e2ae1c7c1f1a11b612faf260a" From 48b88c0d0c76a8cc781634fa002e1e00c749d097 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 10 Feb 2020 09:32:45 -0700 Subject: [PATCH 22/59] Revert switching abort controller libraries --- package.json | 2 +- packages/kbn-ui-shared-deps/polyfills.js | 2 +- .../elasticsearch/server/lib/abortable_request_handler.js | 2 +- .../server/lib/abortable_request_handler.test.js | 2 +- yarn.lock | 5 +++++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 539d3a067665f..4d7c31f8227b8 100644 --- a/package.json +++ b/package.json @@ -144,7 +144,7 @@ "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", - "abort-controller": "^3.0.0", + "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", "angular-aria": "^1.7.9", "angular-elastic": "^2.5.1", diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/polyfills.js index 612fbb9a78b50..d2305d643e4d2 100644 --- a/packages/kbn-ui-shared-deps/polyfills.js +++ b/packages/kbn-ui-shared-deps/polyfills.js @@ -21,6 +21,6 @@ require('core-js/stable'); require('regenerator-runtime/runtime'); require('custom-event-polyfill'); require('whatwg-fetch'); -require('abort-controller/polyfill'); +require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); require('./vendor/childnode_remove_polyfill'); require('symbol-observable'); diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js index 68216b92840fc..0b8786f0c2841 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.js @@ -17,7 +17,7 @@ * under the License. */ -import { AbortController } from 'abort-controller'; +import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; /* * A simple utility for generating a handler that provides a signal to the handler that signals when diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js index 1c154370d1674..d79dd4ae4e449 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js +++ b/src/legacy/core_plugins/elasticsearch/server/lib/abortable_request_handler.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { AbortSignal } from 'abort-controller'; +import { AbortSignal } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; import { abortableRequestHandler } from './abortable_request_handler'; describe('abortableRequestHandler', () => { diff --git a/yarn.lock b/yarn.lock index 7ebf896964378..a6787f37be737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5664,6 +5664,11 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abortcontroller-polyfill@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" + integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA== + accept@3.x.x: version "3.0.2" resolved "https://registry.yarnpkg.com/accept/-/accept-3.0.2.tgz#83e41cec7e1149f3fd474880423873db6c6cc9ac" From 21dd1c8042224a4c4cf3cd7fa055dfc23c4f68c5 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 10 Feb 2020 09:46:19 -0700 Subject: [PATCH 23/59] Revert package.json in lib --- packages/kbn-ui-shared-deps/package.json | 2 +- yarn.lock | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index fc9d159ea9b95..fa691e1f07775 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@elastic/charts": "^17.0.2", - "abort-controller": "^3.0.0", + "abortcontroller-polyfill": "^1.4.0", "@elastic/eui": "18.3.0", "@kbn/dev-utils": "1.0.0", "@kbn/i18n": "1.0.0", diff --git a/yarn.lock b/yarn.lock index a6787f37be737..db6c4127c3519 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5657,13 +5657,6 @@ abort-controller@^2.0.3: dependencies: event-target-shim "^5.0.0" -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - abortcontroller-polyfill@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" From 0b87a0bd71a77e35b4acff9e507f2da1e838c09e Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 10 Feb 2020 10:29:30 -0700 Subject: [PATCH 24/59] Move to previous abort controller --- src/plugins/data/server/lib/get_request_aborted_signal.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.ts b/src/plugins/data/server/lib/get_request_aborted_signal.ts index 60f56cf406b00..d1541f1df9384 100644 --- a/src/plugins/data/server/lib/get_request_aborted_signal.ts +++ b/src/plugins/data/server/lib/get_request_aborted_signal.ts @@ -18,7 +18,8 @@ */ import { Observable } from 'rxjs'; -import { AbortController } from 'abort-controller'; +// @ts-ignore not typed +import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill'; /** * A simple utility function that returns an `AbortSignal` corresponding to an `AbortController` From e959aaddcfecb5b05f14fef2e70f9f5f8fb99de3 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 14 Feb 2020 08:05:10 -0700 Subject: [PATCH 25/59] Add support for frozen indices --- src/plugins/data/server/search/create_api.ts | 4 ++++ .../data/server/search/es_search/es_search_strategy.ts | 4 ---- .../public/search/es_search/es_search_strategy.ts | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index b22c13510f84c..66dc6b8116891 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -31,6 +31,10 @@ export function createApi({ }) { 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) { diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 20bc964effc02..c9f701153308c 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -36,10 +36,6 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; // The above query will either complete or timeout and throw an error. diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts index 1fe626ed79454..8527d3034ca75 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts @@ -20,8 +20,16 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider => { return { search: (request, options) => { + const params = { + ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), + ...request.params, + }; return search( - { ...request, serverStrategy: ES_SEARCH_STRATEGY }, + { + ...request, + params, + serverStrategy: ES_SEARCH_STRATEGY, + }, options, ASYNC_SEARCH_STRATEGY ) as Observable; From 35b1abb1eaf2f18a7067bdf1533af6bc3322fc11 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 14 Feb 2020 13:25:21 -0700 Subject: [PATCH 26/59] Fix test to use fake timers to run debounced handlers --- src/plugins/data/server/lib/get_request_aborted_signal.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts index 2792452279161..3c1e20dbcb158 100644 --- a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts +++ b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts @@ -21,6 +21,8 @@ import { Subject } from 'rxjs'; import { getRequestAbortedSignal } from './get_request_aborted_signal'; describe('abortableRequestHandler', () => { + jest.useFakeTimers(); + it('should call abort if disconnected', () => { const abortedSubject = new Subject(); const aborted$ = abortedSubject.asObservable(); @@ -34,6 +36,7 @@ describe('abortableRequestHandler', () => { expect(onAborted).not.toBeCalled(); abortedSubject.next(); + jest.runAllTimers(); // Should be aborted and call onAborted after disconnecting expect(signal.aborted).toBe(true); From d71d9001b57940e3f91342476422b2ecdd6c762d Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 18 Feb 2020 12:56:50 -0700 Subject: [PATCH 27/59] Revert changes to example plugin --- .../public/demo_search_strategy.ts | 9 ++++++--- .../server/demo_search_strategy.ts | 19 +++---------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/examples/demo_search/public/demo_search_strategy.ts b/examples/demo_search/public/demo_search_strategy.ts index db628a63e2942..85f1ebd1cd109 100644 --- a/examples/demo_search/public/demo_search_strategy.ts +++ b/examples/demo_search/public/demo_search_strategy.ts @@ -18,11 +18,14 @@ */ import { Observable } from 'rxjs'; -import { ISearchContext, ISearchGeneric } from '../../../src/plugins/data/public'; +import { + ISearchContext, + SYNC_SEARCH_STRATEGY, + ISearchGeneric, +} from '../../../src/plugins/data/public'; import { TSearchStrategyProvider, ISearchStrategy } from '../../../src/plugins/data/public'; import { DEMO_SEARCH_STRATEGY, IDemoResponse } from '../common'; -import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/data_enhanced/public'; /** * This demo search strategy provider simply provides a shortcut for calling the DEMO_SEARCH_STRATEGY @@ -59,7 +62,7 @@ export const demoClientSearchStrategyProvider: TSearchStrategyProvider, }; }; diff --git a/examples/demo_search/server/demo_search_strategy.ts b/examples/demo_search/server/demo_search_strategy.ts index 3e1b3db43659e..5b0883be1fc51 100644 --- a/examples/demo_search/server/demo_search_strategy.ts +++ b/examples/demo_search/server/demo_search_strategy.ts @@ -20,28 +20,15 @@ import { TSearchStrategyProvider } from '../../../src/plugins/data/server'; import { DEMO_SEARCH_STRATEGY } from '../common'; -const responseMap = new Map(); -const getId = (() => { - let id = 0; - return () => `${id++}`; -})(); - export const demoSearchStrategyProvider: TSearchStrategyProvider = () => { return { search: request => { - const { id = getId() } = request; - const response = responseMap.get(id) ?? {}; - const nextResponse = { - id, - loaded: (response.loaded ?? 0) + 1, - total: response.total ?? 10, + return Promise.resolve({ greeting: - response.greeting ?? request.mood === 'happy' + request.mood === 'happy' ? `Lovely to meet you, ${request.name}` : `Hope you feel better, ${request.name}`, - }; - responseMap.set(id, nextResponse); - return Promise.resolve(nextResponse); + }); }, }; }; From 914d3300055a62a93b186da658261407515e92f9 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 18 Feb 2020 15:00:39 -0700 Subject: [PATCH 28/59] Fix loading bar not going away when cancelling --- src/plugins/data/public/search/sync_search_strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index b895a177d8608..dc4bc7462709d 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -48,7 +48,7 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider loadingCount$.next(loadingCount$.getValue() - 1)); + response.finally(() => loadingCount$.next(loadingCount$.getValue() - 1)); return from(response); }; From d8fd75938df55b52951db2d59896ca88e138ce8c Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 18 Feb 2020 17:38:51 -0700 Subject: [PATCH 29/59] Call getSearchStrategy instead of passing directly --- .../data/public/search/es_search/es_search_strategy.ts | 10 +++++----- src/plugins/data/server/search/create_api.ts | 5 ++--- .../public/search/async_search_strategy.ts | 7 ++++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/plugins/data/public/search/es_search/es_search_strategy.ts b/src/plugins/data/public/search/es_search/es_search_strategy.ts index a5eab20a89f53..5382a59123e78 100644 --- a/src/plugins/data/public/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/public/search/es_search/es_search_strategy.ts @@ -26,6 +26,8 @@ import { ISearchContext, TSearchStrategyProvider, ISearchStrategy } from '../typ export const esSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext ): ISearchStrategy => { + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search } = syncStrategyProvider(context); return { search: (request, options) => { if (typeof request.params.preference === 'undefined') { @@ -33,11 +35,9 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; + return search({ ...request, serverStrategy: ES_SEARCH_STRATEGY }, options) as Observable< + IEsSearchResponse + >; }, }; }; diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index b22c13510f84c..1ab9add44093b 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -46,9 +46,8 @@ export function createApi({ 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.cancel && strategy.cancel(id); + const strategy = await strategyProvider(caller); + return strategy.cancel ?? strategy.cancel(id); }, }; return api; diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts index 6160f51d56b4f..c9a6d492dbeac 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts @@ -25,9 +25,10 @@ declare module '../../../../../src/plugins/data/public' { } export const asyncSearchStrategyProvider: TSearchStrategyProvider = ( - context: ISearchContext, - search: ISearchGeneric + context: ISearchContext ): ISearchStrategy => { + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search } = syncStrategyProvider(context); return { search: ( request: IAsyncSearchRequest, @@ -47,7 +48,7 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider { id = response.id; // Delay by the given poll interval From e6f11dbf0df10fca98c50f28840265210bc013dc Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 20 Feb 2020 12:44:07 -0700 Subject: [PATCH 30/59] Add async demo search strategy --- examples/demo_search/common/index.ts | 9 ++ examples/demo_search/kibana.json | 4 +- .../public/async_demo_search_strategy.ts | 69 ++++++++++ examples/demo_search/public/plugin.ts | 17 ++- .../server/async_demo_search_strategy.ts | 60 +++++++++ examples/demo_search/server/plugin.ts | 17 ++- examples/search_explorer/package.json | 2 +- .../search_explorer/public/application.tsx | 6 + .../public/async_demo_strategy.tsx | 123 ++++++++++++++++++ x-pack/plugins/data_enhanced/public/index.ts | 3 +- x-pack/plugins/data_enhanced/public/plugin.ts | 8 +- 11 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 examples/demo_search/public/async_demo_search_strategy.ts create mode 100644 examples/demo_search/server/async_demo_search_strategy.ts create mode 100644 examples/search_explorer/public/async_demo_strategy.tsx diff --git a/examples/demo_search/common/index.ts b/examples/demo_search/common/index.ts index 6587ee96ef61b..8ea8d6186ee82 100644 --- a/examples/demo_search/common/index.ts +++ b/examples/demo_search/common/index.ts @@ -20,6 +20,7 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../src/plugins/data/public'; export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; +export const ASYNC_DEMO_SEARCH_STRATEGY = 'ASYNC_DEMO_SEARCH_STRATEGY'; export interface IDemoRequest extends IKibanaSearchRequest { mood: string | 'sad' | 'happy'; @@ -29,3 +30,11 @@ export interface IDemoRequest extends IKibanaSearchRequest { export interface IDemoResponse extends IKibanaSearchResponse { greeting: string; } + +export interface IAsyncDemoRequest extends IKibanaSearchRequest { + fibonacciNumbers: number; +} + +export interface IAsyncDemoResponse extends IKibanaSearchResponse { + fibonacciSequence: number[]; +} diff --git a/examples/demo_search/kibana.json b/examples/demo_search/kibana.json index 0603706b07d1f..fdf3f0d2dd535 100644 --- a/examples/demo_search/kibana.json +++ b/examples/demo_search/kibana.json @@ -2,9 +2,9 @@ "id": "demoSearch", "version": "0.0.1", "kibanaVersion": "kibana", - "configPath": ["demo_search"], + "configPath": ["demoSearch"], "server": true, "ui": true, - "requiredPlugins": ["data"], + "requiredPlugins": ["data", "data_enhanced"], "optionalPlugins": [] } diff --git a/examples/demo_search/public/async_demo_search_strategy.ts b/examples/demo_search/public/async_demo_search_strategy.ts new file mode 100644 index 0000000000000..7a3f33ce05a75 --- /dev/null +++ b/examples/demo_search/public/async_demo_search_strategy.ts @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { + ISearchContext, + TSearchStrategyProvider, + ISearchStrategy, +} from '../../../src/plugins/data/public'; + +import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoResponse } from '../common'; +import { ASYNC_SEARCH_STRATEGY } from '../../../x-pack/plugins/data_enhanced/public'; + +/** + * This demo search strategy provider simply provides a shortcut for calling the DEMO_ASYNC_SEARCH_STRATEGY + * on the server side, without users having to pass it in explicitly, and it takes advantage of the + * already registered ASYNC_SEARCH_STRATEGY that exists on the client. + * + * so instead of callers having to do: + * + * ``` + * search( + * { ...request, serverStrategy: DEMO_ASYNC_SEARCH_STRATEGY }, + * options, + * ASYNC_SEARCH_STRATEGY + * ) as Observable, + *``` + + * They can instead just do + * + * ``` + * search(request, options, DEMO_ASYNC_SEARCH_STRATEGY); + * ``` + * + * and are ensured type safety in regard to the request and response objects. + * + * @param context - context supplied by other plugins. + * @param search - a search function to access other strategies that have already been registered. + */ +export const asyncDemoClientSearchStrategyProvider: TSearchStrategyProvider = ( + context: ISearchContext +): ISearchStrategy => { + const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); + const { search } = asyncStrategyProvider(context); + return { + search: (request, options) => { + return search( + { ...request, serverStrategy: ASYNC_DEMO_SEARCH_STRATEGY }, + options + ) as Observable; + }, + }; +}; diff --git a/examples/demo_search/public/plugin.ts b/examples/demo_search/public/plugin.ts index 62c912716e627..a2539cc7a21c5 100644 --- a/examples/demo_search/public/plugin.ts +++ b/examples/demo_search/public/plugin.ts @@ -19,9 +19,16 @@ import { DataPublicPluginSetup } from '../../../src/plugins/data/public'; import { Plugin, CoreSetup } from '../../../src/core/public'; -import { DEMO_SEARCH_STRATEGY } from '../common'; +import { + DEMO_SEARCH_STRATEGY, + IDemoRequest, + IDemoResponse, + ASYNC_DEMO_SEARCH_STRATEGY, + IAsyncDemoRequest, + IAsyncDemoResponse, +} from '../common'; import { demoClientSearchStrategyProvider } from './demo_search_strategy'; -import { IDemoRequest, IDemoResponse } from '../common'; +import { asyncDemoClientSearchStrategyProvider } from './async_demo_search_strategy'; interface DemoDataSearchSetupDependencies { data: DataPublicPluginSetup; @@ -39,10 +46,12 @@ interface DemoDataSearchSetupDependencies { declare module '../../../src/plugins/data/public' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoRequest; } export interface IResponseTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoResponse; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoResponse; } } @@ -52,6 +61,10 @@ export class DemoDataPlugin implements Plugin { DEMO_SEARCH_STRATEGY, demoClientSearchStrategyProvider ); + deps.data.search.registerSearchStrategyProvider( + ASYNC_DEMO_SEARCH_STRATEGY, + asyncDemoClientSearchStrategyProvider + ); } public start() {} diff --git a/examples/demo_search/server/async_demo_search_strategy.ts b/examples/demo_search/server/async_demo_search_strategy.ts new file mode 100644 index 0000000000000..5daa8d9d56eae --- /dev/null +++ b/examples/demo_search/server/async_demo_search_strategy.ts @@ -0,0 +1,60 @@ +/* + * 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 { TSearchStrategyProvider } from '../../../src/plugins/data/server'; +import { ASYNC_DEMO_SEARCH_STRATEGY } from '../common'; + +function getFibonacciSequence(n = 0) { + const beginning = [0, 1].slice(0, n); + return Array(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 loadedMap = new Map(); +const totalMap = new Map(); + +export const asyncDemoSearchStrategyProvider: TSearchStrategyProvider = () => { + return { + search: async request => { + const id = request.id ?? generateId(); + + const loaded = (loadedMap.get(id) ?? 0) + 1; + loadedMap.set(id, loaded); + + const total = request.fibonacciNumbers ?? totalMap.get(id); + totalMap.set(id, total); + + const fibonacciSequence = getFibonacciSequence(loaded); + return { id, total, loaded, fibonacciSequence }; + }, + cancel: async id => { + loadedMap.delete(id); + totalMap.delete(id); + }, + }; +}; diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts index 653aa217717fa..49fbae43e3aa2 100644 --- a/examples/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -20,7 +20,15 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; import { demoSearchStrategyProvider } from './demo_search_strategy'; -import { DEMO_SEARCH_STRATEGY, IDemoRequest, IDemoResponse } from '../common'; +import { + DEMO_SEARCH_STRATEGY, + IDemoRequest, + IDemoResponse, + ASYNC_DEMO_SEARCH_STRATEGY, + IAsyncDemoRequest, + IAsyncDemoResponse, +} from '../common'; +import { asyncDemoSearchStrategyProvider } from './async_demo_search_strategy'; interface IDemoSearchExplorerDeps { data: DataPluginSetup; @@ -38,10 +46,12 @@ interface IDemoSearchExplorerDeps { declare module '../../../src/plugins/data/server' { export interface IRequestTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoRequest; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoRequest; } export interface IResponseTypesMap { [DEMO_SEARCH_STRATEGY]: IDemoResponse; + [ASYNC_DEMO_SEARCH_STRATEGY]: IAsyncDemoResponse; } } @@ -54,6 +64,11 @@ export class DemoDataPlugin implements Plugin id: 'demoSearch', component: , }, + { + title: 'Async demo search strategy', + id: 'asyncDemoSearch', + component: , + }, ]; const routes = pages.map((page, i) => ( diff --git a/examples/search_explorer/public/async_demo_strategy.tsx b/examples/search_explorer/public/async_demo_strategy.tsx new file mode 100644 index 0000000000000..8e2169ea09572 --- /dev/null +++ b/examples/search_explorer/public/async_demo_strategy.tsx @@ -0,0 +1,123 @@ +/* + * 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 React from 'react'; +import { + EuiPageContentBody, + EuiFormRow, + EuiFlexItem, + EuiFlexGroup, + EuiFieldNumber, +} from '@elastic/eui'; +import { ISearchGeneric } from '../../../src/plugins/data/public'; +import { DoSearch } from './do_search'; +import { GuideSection } from './guide_section'; + +import { ASYNC_DEMO_SEARCH_STRATEGY, IAsyncDemoRequest } from '../../demo_search/common'; + +// @ts-ignore +import demoStrategyServerProvider from '!!raw-loader!./../../demo_search/server/async_demo_search_strategy'; +// @ts-ignore +import demoStrategyPublicProvider from '!!raw-loader!./../../demo_search/public/async_demo_search_strategy'; +// @ts-ignore +import demoStrategyServerPlugin from '!!raw-loader!./../../demo_search/server/plugin'; +// @ts-ignore +import demoStrategyPublicPlugin from '!!raw-loader!./../../demo_search/public/plugin'; + +interface Props { + search: ISearchGeneric; +} + +interface State { + searching: boolean; + fibonacciNumbers: number; + changes: boolean; + error?: any; +} + +export class AsyncDemoStrategy extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + searching: false, + changes: false, + fibonacciNumbers: 5, + }; + } + + renderDemo = () => { + const request: IAsyncDemoRequest = { + fibonacciNumbers: 5, + }; + return ( + + + + + this.setState({ fibonacciNumbers: parseFloat(e.target.value) })} + /> + + + + + this.props.search(request, { signal }, ASYNC_DEMO_SEARCH_STRATEGY) + } + /> + + ); + }; + + render() { + return ( + + + + ); + } +} diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index bbc858b484564..6aacbabbf41c4 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -7,8 +7,7 @@ import { PluginInitializerContext } from '../../../../src/core/public'; import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plugin'; -export const plugin = (initializerContext: PluginInitializerContext) => - new DataEnhancedPlugin(initializerContext); +export const plugin = () => new DataEnhancedPlugin(); export { DataEnhancedSetup, DataEnhancedStart }; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index d85e0e47460d5..4ea480e85f1c2 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -21,18 +21,14 @@ export type DataEnhancedSetup = ReturnType; export type DataEnhancedStart = ReturnType; export class DataEnhancedPlugin implements Plugin { - constructor(private initializerContext: PluginInitializerContext) {} + constructor() {} public setup(core: CoreSetup, { data }: DataEnhancedSetupDependencies) { data.autocomplete.addQuerySuggestionProvider( KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core) ); - data.search.registerSearchStrategyProvider( - this.initializerContext.opaqueId, - ASYNC_SEARCH_STRATEGY, - asyncSearchStrategyProvider - ); + data.search.registerSearchStrategyProvider(ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider); } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { From 9337dead21f7d0d941ec0816fb2702881939a6a7 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 20 Feb 2020 14:55:55 -0700 Subject: [PATCH 31/59] Fix error with setting state --- examples/search_explorer/public/async_demo_strategy.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/search_explorer/public/async_demo_strategy.tsx b/examples/search_explorer/public/async_demo_strategy.tsx index 8e2169ea09572..40ddcc1f48fe7 100644 --- a/examples/search_explorer/public/async_demo_strategy.tsx +++ b/examples/search_explorer/public/async_demo_strategy.tsx @@ -64,7 +64,7 @@ export class AsyncDemoStrategy extends React.Component { renderDemo = () => { const request: IAsyncDemoRequest = { - fibonacciNumbers: 5, + fibonacciNumbers: this.state.fibonacciNumbers, }; return ( From 4267bf2d7e31be45acedd31c58d78cee0298c350 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 24 Feb 2020 14:37:27 -0700 Subject: [PATCH 32/59] Update how aborting works --- .../server/async_demo_search_strategy.ts | 2 +- src/plugins/data/server/search/create_api.ts | 4 +- .../data/server/search/i_search_strategy.ts | 5 +- .../search/async_search_strategy.test.ts | 66 +++++++++++++++++-- .../public/search/async_search_strategy.ts | 34 ++++++---- 5 files changed, 85 insertions(+), 26 deletions(-) diff --git a/examples/demo_search/server/async_demo_search_strategy.ts b/examples/demo_search/server/async_demo_search_strategy.ts index 5daa8d9d56eae..d2d40891a5d93 100644 --- a/examples/demo_search/server/async_demo_search_strategy.ts +++ b/examples/demo_search/server/async_demo_search_strategy.ts @@ -22,7 +22,7 @@ import { ASYNC_DEMO_SEARCH_STRATEGY } from '../common'; function getFibonacciSequence(n = 0) { const beginning = [0, 1].slice(0, n); - return Array(n) + return Array(Math.max(0, n)) .fill(null) .reduce((sequence, value, i) => { if (i < 2) return sequence; diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index 1ab9add44093b..798a4b82caaef 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -46,8 +46,8 @@ export function createApi({ if (!strategyProvider) { throw new Error(`No strategy found for ${strategyName}`); } - const strategy = await strategyProvider(caller); - return strategy.cancel ?? strategy.cancel(id); + const strategy = await strategyProvider(caller, api.search); + return strategy.cancel && strategy.cancel(id); }, }; return api; diff --git a/src/plugins/data/server/search/i_search_strategy.ts b/src/plugins/data/server/search/i_search_strategy.ts index 1837dc21ec8a0..4cfc9608383a9 100644 --- a/src/plugins/data/server/search/i_search_strategy.ts +++ b/src/plugins/data/server/search/i_search_strategy.ts @@ -18,7 +18,7 @@ */ import { APICaller } from 'kibana/server'; -import { ISearch, ICancel, ISearchGeneric, ICancelGeneric } from './i_search'; +import { ISearch, ICancel, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; import { ISearchContext } from './i_search_context'; @@ -38,8 +38,7 @@ export interface ISearchStrategy { */ export type TSearchStrategyProviderEnhanced = ( caller: APICaller, - search: ISearchGeneric, - cancel?: ICancelGeneric + search: ISearchGeneric ) => Promise>; /** diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts index f30787ee8200e..a7f01ecc6cd4a 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts @@ -24,7 +24,19 @@ describe('Async search strategy', () => { it('only sends one request if the first response is complete', async () => { mockSearch.mockReturnValueOnce(of({ id: 1, total: 1, loaded: 1 })); - const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + const asyncSearch = asyncSearchStrategyProvider( + { + core: mockCoreSetup, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }, + mockSearch + ); await asyncSearch.search(mockRequest, mockOptions).toPromise(); @@ -39,7 +51,19 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + const asyncSearch = asyncSearchStrategyProvider( + { + core: mockCoreSetup, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }, + mockSearch + ); expect(mockSearch).toBeCalledTimes(0); @@ -53,7 +77,19 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + const asyncSearch = asyncSearchStrategyProvider( + { + core: mockCoreSetup, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }, + mockSearch + ); expect(mockSearch).toBeCalledTimes(0); @@ -70,15 +106,31 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreSetup }, mockSearch); + const asyncSearch = asyncSearchStrategyProvider( + { + core: mockCoreSetup, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }, + mockSearch + ); const abortController = new AbortController(); const options = { ...mockOptions, signal: abortController.signal }; const promise = asyncSearch.search(mockRequest, options).toPromise(); abortController.abort(); - await promise; - expect(mockSearch).toBeCalledTimes(2); - expect(mockCoreSetup.http.delete).toBeCalled(); + try { + await promise; + } catch (e) { + expect(e.name).toBe('AbortError'); + expect(mockSearch).toBeCalledTimes(1); + expect(mockCoreSetup.http.delete).toBeCalled(); + } }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts index c9a6d492dbeac..71706636f5c4b 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Observable, timer } from 'rxjs'; -import { mergeMap, takeWhile, expand } from 'rxjs/operators'; +import { EMPTY, fromEvent, NEVER, Observable, throwError, timer } from 'rxjs'; +import { mergeMap, expand, takeUntil } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, @@ -37,19 +37,28 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider { - // If we haven't received the response to the initial request, including the ID, then - // we don't need to send a follow-up request to delete this search - if (id === undefined) return; + const aborted$ = options.signal + ? fromEvent(options.signal, 'abort').pipe( + mergeMap(() => { + // If we haven't received the response to the initial request, including the ID, then + // we don't need to send a follow-up request to delete this search. Otherwise, we + // send the follow-up request to delete this search, then throw an abort error. + if (id !== undefined) { + context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); + } - // Send the follow-up request to delete this search, then throw an abort error - context.core.http.delete(`/internal/search/${request.serverStrategy}/${id}`); - }); - } + const error = new Error('Aborted'); + error.name = 'AbortError'; + return throwError(error); + }) + ) + : NEVER; return search(request, options).pipe( expand(response => { + // If the response indicates it is complete, stop polling and complete the observable + if (response.loaded >= response.total) return EMPTY; + id = response.id; // Delay by the given poll interval return timer(pollInterval).pipe( @@ -59,8 +68,7 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider loaded < total, true) + takeUntil(aborted$) ); }, }; From 418e4a1aeda14594d7c8b7227bf16ce059346a64 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 24 Feb 2020 16:02:19 -0700 Subject: [PATCH 33/59] Fix type checks --- x-pack/plugins/data_enhanced/public/index.ts | 1 - x-pack/plugins/data_enhanced/public/plugin.ts | 2 +- .../search/async_search_strategy.test.ts | 91 ++++++++----------- .../public/search/async_search_strategy.ts | 6 +- 4 files changed, 44 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts index 6aacbabbf41c4..927716aae9780 100644 --- a/x-pack/plugins/data_enhanced/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/public'; import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plugin'; export const plugin = () => new DataEnhancedPlugin(); diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 4ea480e85f1c2..4fe27d400f45f 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts index a7f01ecc6cd4a..95f2c9e477064 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts @@ -9,34 +9,32 @@ import { AbortController } from 'abort-controller'; import { coreMock } from '../../../../../src/core/public/mocks'; import { asyncSearchStrategyProvider } from './async_search_strategy'; import { IAsyncSearchOptions } from './types'; +import { CoreStart } from 'kibana/public'; describe('Async search strategy', () => { - let mockCoreSetup: ReturnType; + let mockCoreStart: MockedKeys; const mockSearch = jest.fn(); const mockRequest = { params: {}, serverStrategy: 'foo' }; const mockOptions: IAsyncSearchOptions = { pollInterval: 0 }; beforeEach(() => { - mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockSearch.mockReset(); }); it('only sends one request if the first response is complete', async () => { mockSearch.mockReturnValueOnce(of({ id: 1, total: 1, loaded: 1 })); - const asyncSearch = asyncSearchStrategyProvider( - { - core: mockCoreSetup, - getSearchStrategy: jest.fn().mockImplementation(() => { - return () => { - return { - search: mockSearch, - }; + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, }; - }), - }, - mockSearch - ); + }; + }), + }); await asyncSearch.search(mockRequest, mockOptions).toPromise(); @@ -51,19 +49,16 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider( - { - core: mockCoreSetup, - getSearchStrategy: jest.fn().mockImplementation(() => { - return () => { - return { - search: mockSearch, - }; + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, }; - }), - }, - mockSearch - ); + }; + }), + }); expect(mockSearch).toBeCalledTimes(0); @@ -77,19 +72,16 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider( - { - core: mockCoreSetup, - getSearchStrategy: jest.fn().mockImplementation(() => { - return () => { - return { - search: mockSearch, - }; + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, }; - }), - }, - mockSearch - ); + }; + }), + }); expect(mockSearch).toBeCalledTimes(0); @@ -106,19 +98,16 @@ describe('Async search strategy', () => { .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); - const asyncSearch = asyncSearchStrategyProvider( - { - core: mockCoreSetup, - getSearchStrategy: jest.fn().mockImplementation(() => { - return () => { - return { - search: mockSearch, - }; + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, }; - }), - }, - mockSearch - ); + }; + }), + }); const abortController = new AbortController(); const options = { ...mockOptions, signal: abortController.signal }; @@ -130,7 +119,7 @@ describe('Async search strategy', () => { } catch (e) { expect(e.name).toBe('AbortError'); expect(mockSearch).toBeCalledTimes(1); - expect(mockCoreSetup.http.delete).toBeCalled(); + expect(mockCoreStart.http.delete).toBeCalled(); } }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts index 71706636f5c4b..fa5d677a53b2a 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts @@ -9,7 +9,6 @@ import { mergeMap, expand, takeUntil } from 'rxjs/operators'; import { IKibanaSearchResponse, ISearchContext, - ISearchGeneric, ISearchStrategy, SYNC_SEARCH_STRATEGY, TSearchStrategyProvider, @@ -57,14 +56,15 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider { // If the response indicates it is complete, stop polling and complete the observable - if (response.loaded >= response.total) return EMPTY; + if ((response.loaded ?? 0) >= (response.total ?? 0)) return EMPTY; id = response.id; + // Delay by the given poll interval return timer(pollInterval).pipe( // Send future requests using just the ID from the response mergeMap(() => { - return search({ id, serverStrategy }, options, SYNC_SEARCH_STRATEGY); + return search({ id, serverStrategy }, options); }) ); }), From 159f5f04b68010d23784175ae34b28eaf4b02196 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 24 Feb 2020 16:43:47 -0700 Subject: [PATCH 34/59] Add test for loading count --- .../search/sync_search_strategy.test.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/plugins/data/public/search/sync_search_strategy.test.ts b/src/plugins/data/public/search/sync_search_strategy.test.ts index 9378e5833f8d7..ca64690e65a33 100644 --- a/src/plugins/data/public/search/sync_search_strategy.test.ts +++ b/src/plugins/data/public/search/sync_search_strategy.test.ts @@ -50,4 +50,58 @@ describe('Sync search strategy', () => { signal: undefined, }); }); + + it('increments and decrements loading count on success', async () => { + const expectedLoadingCountValues = [0, 1, 0]; + expect.assertions(expectedLoadingCountValues.length); + let i = 0; + + mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.resolve()); + + const syncSearch = syncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn(), + }); + + const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; + loadingCount$.subscribe(value => { + expect(value).toBe(expectedLoadingCountValues[i++]); + }); + + await syncSearch.search( + { + serverStrategy: SYNC_SEARCH_STRATEGY, + }, + {} + ); + }); + + it('increments and decrements loading count on failure', async () => { + const expectedLoadingCountValues = [0, 1, 0]; + expect.assertions(expectedLoadingCountValues.length); + let i = 0; + + mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.reject()); + + const syncSearch = syncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn(), + }); + + const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; + loadingCount$.subscribe(value => { + expect(value).toBe(expectedLoadingCountValues[i++]); + }); + + try { + await syncSearch.search( + { + serverStrategy: SYNC_SEARCH_STRATEGY, + }, + {} + ); + } catch (e) { + // Just swallow the error silently (because of the expect.assertions) + } + }); }); From dae93b17ac12ec4fbf3b3699e5d5972487d5ebc5 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 24 Feb 2020 17:00:09 -0700 Subject: [PATCH 35/59] Attempt to fix broken example test --- test/examples/search/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/examples/search/index.ts b/test/examples/search/index.ts index 819230f9e6fd9..9bbef96d9a22e 100644 --- a/test/examples/search/index.ts +++ b/test/examples/search/index.ts @@ -25,7 +25,6 @@ export default function({ getService, getPageObjects, loadTestFile }: FtrProvide const appsMenu = getService('appsMenu'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'header']); describe('search services', function() { before(async () => { @@ -36,7 +35,6 @@ export default function({ getService, getPageObjects, loadTestFile }: FtrProvide defaultIndex: 'logstash-*', }); await browser.setWindowSize(1300, 900); - await PageObjects.common.navigateToApp('settings'); await appsMenu.clickLink('Search Explorer'); }); From 1da1ce08230ecfe370613a30fa239a09983e3ab3 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 24 Feb 2020 19:41:22 -0700 Subject: [PATCH 36/59] Revert changes to test --- test/examples/search/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/examples/search/index.ts b/test/examples/search/index.ts index 9bbef96d9a22e..819230f9e6fd9 100644 --- a/test/examples/search/index.ts +++ b/test/examples/search/index.ts @@ -25,6 +25,7 @@ export default function({ getService, getPageObjects, loadTestFile }: FtrProvide const appsMenu = getService('appsMenu'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'header']); describe('search services', function() { before(async () => { @@ -35,6 +36,7 @@ export default function({ getService, getPageObjects, loadTestFile }: FtrProvide defaultIndex: 'logstash-*', }); await browser.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('settings'); await appsMenu.clickLink('Search Explorer'); }); From efa6cf6c7a2fc5abe1c16f37bcc63eddae2f62ba Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 25 Feb 2020 07:32:31 -0700 Subject: [PATCH 37/59] Fix test --- .../search/sync_search_strategy.test.ts | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/plugins/data/public/search/sync_search_strategy.test.ts b/src/plugins/data/public/search/sync_search_strategy.test.ts index ca64690e65a33..b118f737f56e3 100644 --- a/src/plugins/data/public/search/sync_search_strategy.test.ts +++ b/src/plugins/data/public/search/sync_search_strategy.test.ts @@ -53,8 +53,7 @@ describe('Sync search strategy', () => { it('increments and decrements loading count on success', async () => { const expectedLoadingCountValues = [0, 1, 0]; - expect.assertions(expectedLoadingCountValues.length); - let i = 0; + const receivedLoadingCountValues: number[] = []; mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.resolve()); @@ -64,22 +63,24 @@ describe('Sync search strategy', () => { }); const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; - loadingCount$.subscribe(value => { - expect(value).toBe(expectedLoadingCountValues[i++]); - }); + loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); - await syncSearch.search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ); + await syncSearch + .search( + { + serverStrategy: SYNC_SEARCH_STRATEGY, + }, + {} + ) + .toPromise(); + + expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); }); it('increments and decrements loading count on failure', async () => { + expect.assertions(1); const expectedLoadingCountValues = [0, 1, 0]; - expect.assertions(expectedLoadingCountValues.length); - let i = 0; + const receivedLoadingCountValues: number[] = []; mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.reject()); @@ -89,19 +90,19 @@ describe('Sync search strategy', () => { }); const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; - loadingCount$.subscribe(value => { - expect(value).toBe(expectedLoadingCountValues[i++]); - }); + loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); try { - await syncSearch.search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ); + await syncSearch + .search( + { + serverStrategy: SYNC_SEARCH_STRATEGY, + }, + {} + ) + .toPromise(); } catch (e) { - // Just swallow the error silently (because of the expect.assertions) + expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); } }); }); From cdb5417e3c3443e647d689384ae7543ee849db46 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 25 Feb 2020 08:00:36 -0700 Subject: [PATCH 38/59] Update name to camelCase --- examples/search_explorer/kibana.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/search_explorer/kibana.json b/examples/search_explorer/kibana.json index 39b866ca4c2c6..ca8c6a5b24243 100644 --- a/examples/search_explorer/kibana.json +++ b/examples/search_explorer/kibana.json @@ -1,5 +1,5 @@ { - "id": "search_explorer", + "id": "searchExplorer", "version": "0.0.1", "kibanaVersion": "kibana", "configPath": ["search_explorer"], From 2ec0d0be726a4d87e1457942552ba231a539584c Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 25 Feb 2020 11:15:38 -0700 Subject: [PATCH 39/59] Fix failing test --- .../search/sync_search_strategy.test.ts | 33 +++++-------------- .../public/search/sync_search_strategy.ts | 21 ++++++------ 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/plugins/data/public/search/sync_search_strategy.test.ts b/src/plugins/data/public/search/sync_search_strategy.test.ts index b118f737f56e3..31a1adfa01c75 100644 --- a/src/plugins/data/public/search/sync_search_strategy.test.ts +++ b/src/plugins/data/public/search/sync_search_strategy.test.ts @@ -35,12 +35,9 @@ describe('Sync search strategy', () => { core: mockCoreStart, getSearchStrategy: jest.fn(), }); - syncSearch.search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; + syncSearch.search(request, {}); + expect(mockCoreStart.http.fetch.mock.calls[0][0]).toEqual({ path: `/internal/search/${SYNC_SEARCH_STRATEGY}`, body: JSON.stringify({ @@ -55,24 +52,18 @@ describe('Sync search strategy', () => { const expectedLoadingCountValues = [0, 1, 0]; const receivedLoadingCountValues: number[] = []; - mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.resolve()); + mockCoreStart.http.fetch.mockResolvedValueOnce('response'); const syncSearch = syncSearchStrategyProvider({ core: mockCoreStart, getSearchStrategy: jest.fn(), }); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); - await syncSearch - .search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ) - .toPromise(); + await syncSearch.search(request, {}).toPromise(); expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); }); @@ -82,25 +73,19 @@ describe('Sync search strategy', () => { const expectedLoadingCountValues = [0, 1, 0]; const receivedLoadingCountValues: number[] = []; - mockCoreStart.http.fetch.mockImplementationOnce(() => Promise.reject()); + mockCoreStart.http.fetch.mockRejectedValueOnce('error'); const syncSearch = syncSearchStrategyProvider({ core: mockCoreStart, getSearchStrategy: jest.fn(), }); + const request = { serverStrategy: SYNC_SEARCH_STRATEGY }; const loadingCount$ = mockCoreStart.http.addLoadingCountSource.mock.calls[0][0]; loadingCount$.subscribe(value => receivedLoadingCountValues.push(value)); try { - await syncSearch - .search( - { - serverStrategy: SYNC_SEARCH_STRATEGY, - }, - {} - ) - .toPromise(); + await syncSearch.search(request, {}).toPromise(); } catch (e) { expect(receivedLoadingCountValues).toEqual(expectedLoadingCountValues); } diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index 4b66b0e32894a..860ce593ae217 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -18,7 +18,8 @@ */ import { BehaviorSubject, from } from 'rxjs'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search'; +import { finalize } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '../../common/search'; import { ISearch, ISearchOptions } from './i_search'; import { TSearchStrategyProvider, ISearchStrategy, ISearchContext } from './types'; @@ -40,16 +41,14 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider { loadingCount$.next(loadingCount$.getValue() + 1); - const response: Promise = context.core.http.fetch({ - path: `/internal/search/${request.serverStrategy}`, - method: 'POST', - body: JSON.stringify(request), - signal: options.signal, - }); - - response.finally(() => loadingCount$.next(loadingCount$.getValue() - 1)); - - return from(response); + return from( + context.core.http.fetch({ + path: `/internal/search/${request.serverStrategy}`, + method: 'POST', + body: JSON.stringify(request), + signal: options.signal, + }) + ).pipe(finalize(() => loadingCount$.next(loadingCount$.getValue() - 1))); }; return { search }; From 4325fecfd0c66b54df9ba1976676f56e4f2a5473 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 25 Feb 2020 11:59:46 -0700 Subject: [PATCH 40/59] Don't require data_enhanced in example plugin --- examples/demo_search/kibana.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo_search/kibana.json b/examples/demo_search/kibana.json index fdf3f0d2dd535..cb73274ed23f7 100644 --- a/examples/demo_search/kibana.json +++ b/examples/demo_search/kibana.json @@ -5,6 +5,6 @@ "configPath": ["demoSearch"], "server": true, "ui": true, - "requiredPlugins": ["data", "data_enhanced"], + "requiredPlugins": ["data"], "optionalPlugins": [] } From 1f99f2145c8ca59d631c27378b4b6575aa822c89 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 26 Feb 2020 15:37:06 -0700 Subject: [PATCH 41/59] Actually send DELETE request --- .../server/search/es_search/es_search_strategy.ts | 13 ++++--------- .../public/search/es_search/es_search_strategy.ts | 6 +++--- .../server/search/es_search/es_search_strategy.ts | 14 ++++++++------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index c9f701153308c..658d132810f6b 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -36,18 +36,13 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; + const rawResponse = (await caller('search', params, options)) as SearchResponse; // The above query will either complete or timeout and throw an error. // There is no progress indication on this api. - return { - total: esSearchResponse._shards.total, - loaded: - esSearchResponse._shards.failed + - esSearchResponse._shards.skipped + - esSearchResponse._shards.successful, - rawResponse: esSearchResponse, - }; + const { total, failed, skipped, successful } = rawResponse._shards; + const loaded = failed + skipped + successful; + return { total, loaded, rawResponse }; }, }; }; diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts index 8527d3034ca75..ebe2697a62e6d 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts @@ -10,14 +10,14 @@ import { ASYNC_SEARCH_STRATEGY } from '../async_search_strategy'; import { TSearchStrategyProvider, ISearchStrategy, - ISearchGeneric, ISearchContext, } from '../../../../../../src/plugins/data/public'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( - context: ISearchContext, - search: ISearchGeneric + context: ISearchContext ): ISearchStrategy => { + const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); + const { search } = asyncStrategyProvider(context); return { search: (request, options) => { const params = { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts index 901a279900355..6066eb94a1552 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts @@ -37,14 +37,16 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider; - // TODO: This can be simplified once the async API is updated const { id, response: rawResponse } = esSearchResponse; - const failed = rawResponse._shards?.failed ?? rawResponse.shard_failures; - const total = rawResponse._shards?.total ?? rawResponse.total_shards; - const successful = rawResponse._shards?.successful ?? rawResponse.successful_shards; - const skipped = rawResponse._shards?.skipped ?? 0; - const loaded = failed + successful + skipped; + const { total, failed, skipped, successful } = rawResponse._shards; + const loaded = failed + skipped + successful; return { id, total, loaded, rawResponse }; }, + cancel: id => { + caller('transport.request', { + path: `/_async_search/${id}`, + method: 'DELETE', + }); + }, }; }; From 74b3718937618f902272fee5c85135a4d522c279 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 27 Feb 2020 10:00:26 -0700 Subject: [PATCH 42/59] Use waitForCompletion parameter --- .../data_enhanced/public/search/es_search/es_search_strategy.ts | 1 + .../data_enhanced/server/search/es_search/es_search_strategy.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts index ebe2697a62e6d..b812dcb90a4cd 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts @@ -21,6 +21,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { const params = { + waitForCompletion: '1s', ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), ...request.params, }; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts index 6066eb94a1552..5d6a024bd6a34 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts @@ -44,7 +44,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { caller('transport.request', { - path: `/_async_search/${id}`, + path: `_async_search/${id}`, method: 'DELETE', }); }, From e30e047368306b74a694af2213ed23cc2e60ed65 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 28 Feb 2020 17:14:02 -0700 Subject: [PATCH 43/59] Use default search params --- .../search/es_search/es_search_strategy.ts | 16 ++-- .../search/es_search/get_es_preference.ts | 14 ++-- .../data/public/search/es_search/index.ts | 21 +++++ src/plugins/data/public/search/index.ts | 2 +- src/plugins/data/server/index.ts | 11 ++- .../search/es_search/es_search_strategy.ts | 7 +- .../es_search/get_default_search_params.ts | 28 +++++++ .../data/server/search/es_search/index.ts | 1 + src/plugins/data/server/search/index.ts | 4 +- .../search/es_search/es_search_strategy.ts | 42 +++++----- .../search/es_search/es_search_strategy.ts | 78 +++++++++++-------- 11 files changed, 147 insertions(+), 77 deletions(-) create mode 100644 src/plugins/data/public/search/es_search/index.ts create mode 100644 src/plugins/data/server/search/es_search/get_default_search_params.ts diff --git a/src/plugins/data/public/search/es_search/es_search_strategy.ts b/src/plugins/data/public/search/es_search/es_search_strategy.ts index 5382a59123e78..03d678c99d072 100644 --- a/src/plugins/data/public/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/public/search/es_search/es_search_strategy.ts @@ -30,14 +30,14 @@ export const esSearchStrategyProvider: TSearchStrategyProvider { - if (typeof request.params.preference === 'undefined') { - const setPreference = context.core.uiSettings.get('courier:setRequestPreference'); - const customPreference = context.core.uiSettings.get('courier:customRequestPreference'); - request.params.preference = getEsPreference(setPreference, customPreference); - } - return search({ ...request, serverStrategy: ES_SEARCH_STRATEGY }, options) as Observable< - IEsSearchResponse - >; + const params = { + preference: getEsPreference(context.core.uiSettings), + ...request.params, + }; + return search( + { ...request, params, serverStrategy: ES_SEARCH_STRATEGY }, + options + ) as Observable; }, }; }; diff --git a/src/plugins/data/public/search/es_search/get_es_preference.ts b/src/plugins/data/public/search/es_search/get_es_preference.ts index 200e5bacb7f18..3f1c2b9b3b736 100644 --- a/src/plugins/data/public/search/es_search/get_es_preference.ts +++ b/src/plugins/data/public/search/es_search/get_es_preference.ts @@ -17,13 +17,13 @@ * under the License. */ +import { IUiSettingsClient } from '../../../../../core/public'; + const defaultSessionId = `${Date.now()}`; -export function getEsPreference( - setRequestPreference: string, - customRequestPreference?: string, - sessionId: string = defaultSessionId -) { - if (setRequestPreference === 'sessionId') return `${sessionId}`; - return setRequestPreference === 'custom' ? customRequestPreference : undefined; +export function getEsPreference(uiSettings: IUiSettingsClient, sessionId = defaultSessionId) { + const setPreference = uiSettings.get('courier:setRequestPreference'); + if (setPreference === 'sessionId') return `${sessionId}`; + const customPreference = uiSettings.get('courier:customRequestPreference'); + return setPreference === 'custom' ? customPreference : undefined; } diff --git a/src/plugins/data/public/search/es_search/index.ts b/src/plugins/data/public/search/es_search/index.ts new file mode 100644 index 0000000000000..41c6ec388bfaf --- /dev/null +++ b/src/plugins/data/public/search/es_search/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export { esSearchStrategyProvider } from './es_search_strategy'; +export { getEsPreference } from './get_es_preference'; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 358e132f7e931..2a54cfe2be785 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -36,7 +36,7 @@ export { export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search'; export { ISyncSearchRequest, SYNC_SEARCH_STRATEGY } from './sync_search_strategy'; -export { esSearchStrategyProvider } from './es_search/es_search_strategy'; +export { esSearchStrategyProvider, getEsPreference } from './es_search'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 020c3c4c1192d..5e86e9184cfc0 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -149,8 +149,15 @@ export { * Search */ -export { IRequestTypesMap, IResponseTypesMap } from './search'; -export * from './search'; +export { + ISearch, + ICancel, + IRequestTypesMap, + IResponseTypesMap, + ISearchContext, + TSearchStrategyProvider, + getDefaultSearchParams, +} from './search'; /** * Types to be shared externally diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 658d132810f6b..d750ed2905e7f 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -21,7 +21,7 @@ import { APICaller } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { ES_SEARCH_STRATEGY } from '../../../common/search'; import { ISearchStrategy, TSearchStrategyProvider } from '../i_search_strategy'; -import { ISearchContext } from '..'; +import { getDefaultSearchParams, ISearchContext } from '..'; export const esSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext, @@ -30,10 +30,9 @@ export const esSearchStrategyProvider: TSearchStrategyProvider { const config = await context.config$.pipe(first()).toPromise(); + const defaultParams = getDefaultSearchParams(config); const params = { - timeout: `${config.elasticsearch.shardTimeout.asMilliseconds()}ms`, - ignoreUnavailable: true, // Don't fail if the index/indices don't exist - restTotalHitsAsInt: true, // Get the number of hits as an int rather than a range + ...defaultParams, ...request.params, }; const rawResponse = (await caller('search', params, options)) as SearchResponse; diff --git a/src/plugins/data/server/search/es_search/get_default_search_params.ts b/src/plugins/data/server/search/es_search/get_default_search_params.ts new file mode 100644 index 0000000000000..b2341ccc0f3c8 --- /dev/null +++ b/src/plugins/data/server/search/es_search/get_default_search_params.ts @@ -0,0 +1,28 @@ +/* + * 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 { SharedGlobalConfig } from '../../../../../core/server'; + +export function getDefaultSearchParams(config: SharedGlobalConfig) { + return { + timeout: `${config.elasticsearch.shardTimeout.asMilliseconds()}ms`, + ignoreUnavailable: true, // Don't fail if the index/indices don't exist + restTotalHitsAsInt: true, // Get the number of hits as an int rather than a range + }; +} diff --git a/src/plugins/data/server/search/es_search/index.ts b/src/plugins/data/server/search/es_search/index.ts index a1d4070114ad5..77e798b074484 100644 --- a/src/plugins/data/server/search/es_search/index.ts +++ b/src/plugins/data/server/search/es_search/index.ts @@ -21,6 +21,7 @@ import { PluginInitializerContext } from '../../../../../core/server'; import { EsSearchService } from './es_search_service'; export { ES_SEARCH_STRATEGY, IEsSearchRequest, IEsSearchResponse } from '../../../common/search'; +export { getDefaultSearchParams } from './get_default_search_params'; export function esSearchService(initializerContext: PluginInitializerContext) { return new EsSearchService(initializerContext); diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 298a665fd5b2c..b62095e4a5578 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -21,8 +21,10 @@ export { ISearchSetup } from './i_search_setup'; export { ISearchContext } from './i_search_context'; -export { IRequestTypesMap, IResponseTypesMap } from './i_search'; +export { ISearch, ICancel, IRequestTypesMap, IResponseTypesMap } from './i_search'; export { TStrategyTypes } from './strategy_types'; export { TSearchStrategyProvider } from './i_search_strategy'; + +export { getDefaultSearchParams } from './es_search'; diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts index b812dcb90a4cd..d4f3ab0db16ed 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts @@ -9,31 +9,33 @@ import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../../../../../src/plu import { ASYNC_SEARCH_STRATEGY } from '../async_search_strategy'; import { TSearchStrategyProvider, - ISearchStrategy, ISearchContext, + ISearch, + getEsPreference, } from '../../../../../../src/plugins/data/public'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext -): ISearchStrategy => { +) => { const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); - const { search } = asyncStrategyProvider(context); - return { - search: (request, options) => { - const params = { - waitForCompletion: '1s', - ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), - ...request.params, - }; - return search( - { - ...request, - params, - serverStrategy: ES_SEARCH_STRATEGY, - }, - options, - ASYNC_SEARCH_STRATEGY - ) as Observable; - }, + const { search: asyncSearch } = asyncStrategyProvider(context); + + const search: ISearch = (request, options) => { + const params = { + ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), + preference: getEsPreference(context.core.uiSettings), + ...request.params, + }; + + return asyncSearch( + { + ...request, + params, + serverStrategy: ES_SEARCH_STRATEGY, + }, + options + ) as Observable; }; + + return { search }; }; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts index 5d6a024bd6a34..c1f6df9cd6ff7 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts @@ -6,47 +6,57 @@ import { APICaller } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; +import { first } from 'rxjs/operators'; +import { mapKeys, snakeCase } from 'lodash'; import { ES_SEARCH_STRATEGY } from '../../../../../../src/plugins/data/common'; import { - ISearchStrategy, ISearchContext, TSearchStrategyProvider, -} from '../../../../../../src/plugins/data/public'; + ISearch, + ICancel, + getDefaultSearchParams, +} from '../../../../../../src/plugins/data/server'; + +export interface AsyncSearchResponse { + id: string; + response: SearchResponse; +} export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext, caller: APICaller -): ISearchStrategy => { - return { - search: async (request, options) => { - // If we have an ID, then just poll for that ID, otherwise send the entire request body - const args = [ - 'transport.request', - request.id - ? { - path: `_async_search/${request.id}`, - method: 'GET', - } - : { - path: `${request.params.index}/_async_search`, - method: 'POST', - ...request.params, - }, - options, - ]; - - const esSearchResponse = (await caller(...args)) as SearchResponse; - - const { id, response: rawResponse } = esSearchResponse; - const { total, failed, skipped, successful } = rawResponse._shards; - const loaded = failed + skipped + successful; - return { id, total, loaded, rawResponse }; - }, - cancel: id => { - caller('transport.request', { - path: `_async_search/${id}`, - method: 'DELETE', - }); - }, +) => { + const search: ISearch = async (request, options) => { + // If we have an ID, then just poll for that ID, otherwise send the entire request body + const method = request.id ? 'GET' : 'POST'; + const path = request.id + ? `_async_search/${request.id}` + : `${request.params.index}/_async_search`; + + const config = await context.config$.pipe(first()).toPromise(); + const defaultParams = getDefaultSearchParams(config); + const params = request.id + ? {} + : { waitForCompletion: '1s', ...defaultParams, ...request.params }; + const { body, ...query } = params; + + const esSearchResponse = (await caller( + 'transport.request', + { method, path, body, query: mapKeys(query, (value, key) => snakeCase(key)) }, + options + )) as AsyncSearchResponse; + + const { id, response: rawResponse } = esSearchResponse; + const { total, failed, skipped, successful } = rawResponse._shards; + const loaded = failed + skipped + successful; + return { id, total, loaded, rawResponse }; }; + + const cancel: ICancel = id => { + const method = 'DELETE'; + const path = `_async_search/${id}`; + return void caller('transport.request', { method, path }); + }; + + return { search, cancel }; }; From e45021cc7ff84630ab3e5f3d1b115684b1d827ee Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 2 Mar 2020 16:18:01 -0700 Subject: [PATCH 44/59] Add support for rollups --- src/plugins/data/server/index.ts | 1 + src/plugins/data/server/search/index.ts | 2 +- .../search/es_search => common}/index.ts | 2 +- .../data_enhanced/common/search/index.ts | 7 +++ .../data_enhanced/common/search/types.ts | 14 ++++++ x-pack/plugins/data_enhanced/public/plugin.ts | 7 ++- .../{es_search => }/es_search_strategy.ts | 12 +++-- .../data_enhanced/public/search/index.ts | 1 + x-pack/plugins/data_enhanced/server/plugin.ts | 11 +++-- .../{es_search => }/es_search_strategy.ts | 44 +++++++++++++++---- .../es_search => server/search}/index.ts | 0 11 files changed, 81 insertions(+), 20 deletions(-) rename x-pack/plugins/data_enhanced/{server/search/es_search => common}/index.ts (76%) create mode 100644 x-pack/plugins/data_enhanced/common/search/index.ts create mode 100644 x-pack/plugins/data_enhanced/common/search/types.ts rename x-pack/plugins/data_enhanced/public/search/{es_search => }/es_search_strategy.ts (77%) rename x-pack/plugins/data_enhanced/server/search/{es_search => }/es_search_strategy.ts (60%) rename x-pack/plugins/data_enhanced/{public/search/es_search => server/search}/index.ts (100%) diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 5e86e9184cfc0..c18eb92bf636d 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -152,6 +152,7 @@ export { export { ISearch, ICancel, + ISearchOptions, IRequestTypesMap, IResponseTypesMap, ISearchContext, diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index b62095e4a5578..385e96ee803b6 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -21,7 +21,7 @@ export { ISearchSetup } from './i_search_setup'; export { ISearchContext } from './i_search_context'; -export { ISearch, ICancel, IRequestTypesMap, IResponseTypesMap } from './i_search'; +export { ISearch, ICancel, ISearchOptions, IRequestTypesMap, IResponseTypesMap } from './i_search'; export { TStrategyTypes } from './strategy_types'; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search/index.ts b/x-pack/plugins/data_enhanced/common/index.ts similarity index 76% rename from x-pack/plugins/data_enhanced/server/search/es_search/index.ts rename to x-pack/plugins/data_enhanced/common/index.ts index f914326f30d32..2faaddae00571 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { enhancedEsSearchStrategyProvider } from './es_search_strategy'; +export { IEnhancedEsSearchRequest } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts new file mode 100644 index 0000000000000..18fb95767b06c --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { IEnhancedEsSearchRequest } from './types'; diff --git a/x-pack/plugins/data_enhanced/common/search/types.ts b/x-pack/plugins/data_enhanced/common/search/types.ts new file mode 100644 index 0000000000000..b64bded4f9258 --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchRequest } from '../../../../../src/plugins/data/common/search/es_search'; + +export interface IEnhancedEsSearchRequest extends IEsSearchRequest { + /** + * Used to determine whether to use the _rollups_search or a regular search endpoint. + */ + isRollup?: boolean; +} diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 288e1c99ff16e..6316d87c50519 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -12,8 +12,11 @@ import { } from '../../../../src/plugins/data/public'; import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; -import { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './search'; -import { enhancedEsSearchStrategyProvider } from './search/es_search'; +import { + ASYNC_SEARCH_STRATEGY, + asyncSearchStrategyProvider, + enhancedEsSearchStrategyProvider, +} from './search'; export interface DataEnhancedSetupDependencies { data: DataPublicPluginSetup; diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts similarity index 77% rename from x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts rename to x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index d4f3ab0db16ed..453adc6a5f327 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -5,14 +5,15 @@ */ import { Observable } from 'rxjs'; -import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../../../../../src/plugins/data/common'; -import { ASYNC_SEARCH_STRATEGY } from '../async_search_strategy'; +import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../../../../src/plugins/data/common'; import { TSearchStrategyProvider, ISearchContext, ISearch, getEsPreference, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; +import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; +import { IEnhancedEsSearchRequest } from '../../common'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext @@ -20,7 +21,10 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = (request, options) => { + const search: ISearch = ( + request: IEnhancedEsSearchRequest, + options + ) => { const params = { ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), preference: getEsPreference(context.core.uiSettings), diff --git a/x-pack/plugins/data_enhanced/public/search/index.ts b/x-pack/plugins/data_enhanced/public/search/index.ts index a7729aeea5647..e39c1b6a1dd61 100644 --- a/x-pack/plugins/data_enhanced/public/search/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/index.ts @@ -5,4 +5,5 @@ */ export { ASYNC_SEARCH_STRATEGY, asyncSearchStrategyProvider } from './async_search_strategy'; +export { enhancedEsSearchStrategyProvider } from './es_search_strategy'; export { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 134731c0c850e..231784f3e2894 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/server'; -import { DataPluginSetup } from '../../../../src/plugins/data/server'; -import { enhancedEsSearchStrategyProvider } from '../server/search/es_search'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, +} from '../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; +import { DataPluginSetup } from '../../../../src/plugins/data/server'; +import { enhancedEsSearchStrategyProvider } from './search'; interface SetupDependencies { data: DataPluginSetup; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts similarity index 60% rename from x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts rename to x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index c1f6df9cd6ff7..81800ee7ef2bb 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { APICaller } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { first } from 'rxjs/operators'; import { mapKeys, snakeCase } from 'lodash'; -import { ES_SEARCH_STRATEGY } from '../../../../../../src/plugins/data/common'; +import { APICaller } from '../../../../../src/core/server'; +import { ES_SEARCH_STRATEGY } from '../../../../../src/plugins/data/common'; import { ISearchContext, TSearchStrategyProvider, ISearch, ICancel, + ISearchOptions, getDefaultSearchParams, -} from '../../../../../../src/plugins/data/server'; +} from '../../../../../src/plugins/data/server'; +import { IEnhancedEsSearchRequest } from '../../common'; export interface AsyncSearchResponse { id: string; @@ -27,18 +29,21 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { const search: ISearch = async (request, options) => { + const config = await context.config$.pipe(first()).toPromise(); + const params = { ...getDefaultSearchParams(config), ...request.params }; + + if (request.isRollup) { + return rollupSearch(caller, { ...request, params }, options); + } + // If we have an ID, then just poll for that ID, otherwise send the entire request body const method = request.id ? 'GET' : 'POST'; const path = request.id ? `_async_search/${request.id}` : `${request.params.index}/_async_search`; - const config = await context.config$.pipe(first()).toPromise(); - const defaultParams = getDefaultSearchParams(config); - const params = request.id - ? {} - : { waitForCompletion: '1s', ...defaultParams, ...request.params }; - const { body, ...query } = params; + // Wait up to 1s for the initial response to return + const { body, ...query } = request.id ? {} : { waitForCompletion: '1s', ...params }; const esSearchResponse = (await caller( 'transport.request', @@ -60,3 +65,24 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider snakeCase(key)), + }, + options + )) as AsyncSearchResponse; + const { total, failed, skipped, successful } = rawResponse._shards; + const loaded = failed + skipped + successful; + return { total, loaded, rawResponse }; +} diff --git a/x-pack/plugins/data_enhanced/public/search/es_search/index.ts b/x-pack/plugins/data_enhanced/server/search/index.ts similarity index 100% rename from x-pack/plugins/data_enhanced/public/search/es_search/index.ts rename to x-pack/plugins/data_enhanced/server/search/index.ts From 20ce66a3e9c7f273f26762facddb49b4516ed17c Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 3 Mar 2020 13:38:54 -0700 Subject: [PATCH 45/59] Only make changes needed for frozen indices/rollups --- .../public/search/es_search_strategy.ts | 8 +-- .../server/search/es_search_strategy.ts | 55 +++++-------------- 2 files changed, 18 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index 453adc6a5f327..6cd431655cd7b 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -10,16 +10,16 @@ import { TSearchStrategyProvider, ISearchContext, ISearch, + SYNC_SEARCH_STRATEGY, getEsPreference, } from '../../../../../src/plugins/data/public'; -import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; import { IEnhancedEsSearchRequest } from '../../common'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext ) => { - const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); - const { search: asyncSearch } = asyncStrategyProvider(context); + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search: syncSearch } = syncStrategyProvider(context); const search: ISearch = ( request: IEnhancedEsSearchRequest, @@ -31,7 +31,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { - id: string; - response: SearchResponse; -} - export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext, caller: APICaller ) => { - const search: ISearch = async (request, options) => { + const search: ISearch = async ( + request: IEnhancedEsSearchRequest, + options + ) => { const config = await context.config$.pipe(first()).toPromise(); - const params = { ...getDefaultSearchParams(config), ...request.params }; - - if (request.isRollup) { - return rollupSearch(caller, { ...request, params }, options); - } + const defaultParams = getDefaultSearchParams(config); + const params = { ...defaultParams, ...request.params }; - // If we have an ID, then just poll for that ID, otherwise send the entire request body - const method = request.id ? 'GET' : 'POST'; - const path = request.id - ? `_async_search/${request.id}` - : `${request.params.index}/_async_search`; + const rawResponse = (await (request.isRollup + ? rollupSearch(caller, { ...request, params }, options) + : caller('search', params, options))) as SearchResponse; - // Wait up to 1s for the initial response to return - const { body, ...query } = request.id ? {} : { waitForCompletion: '1s', ...params }; - - const esSearchResponse = (await caller( - 'transport.request', - { method, path, body, query: mapKeys(query, (value, key) => snakeCase(key)) }, - options - )) as AsyncSearchResponse; - - const { id, response: rawResponse } = esSearchResponse; const { total, failed, skipped, successful } = rawResponse._shards; const loaded = failed + skipped + successful; - return { id, total, loaded, rawResponse }; - }; - - const cancel: ICancel = id => { - const method = 'DELETE'; - const path = `_async_search/${id}`; - return void caller('transport.request', { method, path }); + return { total, loaded, rawResponse }; }; - return { search, cancel }; + return { search }; }; -async function rollupSearch( +function rollupSearch( caller: APICaller, request: IEnhancedEsSearchRequest, options: ISearchOptions ) { const { body, ...query } = request.params; - const rawResponse = (await caller( + return caller( 'transport.request', { method: 'POST', @@ -81,8 +57,5 @@ async function rollupSearch( query: mapKeys(query, (value, key) => snakeCase(key)), }, options - )) as AsyncSearchResponse; - const { total, failed, skipped, successful } = rawResponse._shards; - const loaded = failed + skipped + successful; - return { total, loaded, rawResponse }; + ) as SearchResponse; } From 33418810b6c5cf699b2cde4194ff84bb93c2da9e Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 3 Mar 2020 13:38:54 -0700 Subject: [PATCH 46/59] Only make changes needed for frozen indices/rollups --- .../public/search/es_search_strategy.ts | 8 +-- .../server/search/es_search_strategy.ts | 55 +++++-------------- 2 files changed, 18 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index 453adc6a5f327..6cd431655cd7b 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -10,16 +10,16 @@ import { TSearchStrategyProvider, ISearchContext, ISearch, + SYNC_SEARCH_STRATEGY, getEsPreference, } from '../../../../../src/plugins/data/public'; -import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; import { IEnhancedEsSearchRequest } from '../../common'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext ) => { - const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); - const { search: asyncSearch } = asyncStrategyProvider(context); + const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); + const { search: syncSearch } = syncStrategyProvider(context); const search: ISearch = ( request: IEnhancedEsSearchRequest, @@ -31,7 +31,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { - id: string; - response: SearchResponse; -} - export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext, caller: APICaller ) => { - const search: ISearch = async (request, options) => { + const search: ISearch = async ( + request: IEnhancedEsSearchRequest, + options + ) => { const config = await context.config$.pipe(first()).toPromise(); - const params = { ...getDefaultSearchParams(config), ...request.params }; - - if (request.isRollup) { - return rollupSearch(caller, { ...request, params }, options); - } + const defaultParams = getDefaultSearchParams(config); + const params = { ...defaultParams, ...request.params }; - // If we have an ID, then just poll for that ID, otherwise send the entire request body - const method = request.id ? 'GET' : 'POST'; - const path = request.id - ? `_async_search/${request.id}` - : `${request.params.index}/_async_search`; + const rawResponse = (await (request.isRollup + ? rollupSearch(caller, { ...request, params }, options) + : caller('search', params, options))) as SearchResponse; - // Wait up to 1s for the initial response to return - const { body, ...query } = request.id ? {} : { waitForCompletion: '1s', ...params }; - - const esSearchResponse = (await caller( - 'transport.request', - { method, path, body, query: mapKeys(query, (value, key) => snakeCase(key)) }, - options - )) as AsyncSearchResponse; - - const { id, response: rawResponse } = esSearchResponse; const { total, failed, skipped, successful } = rawResponse._shards; const loaded = failed + skipped + successful; - return { id, total, loaded, rawResponse }; - }; - - const cancel: ICancel = id => { - const method = 'DELETE'; - const path = `_async_search/${id}`; - return void caller('transport.request', { method, path }); + return { total, loaded, rawResponse }; }; - return { search, cancel }; + return { search }; }; -async function rollupSearch( +function rollupSearch( caller: APICaller, request: IEnhancedEsSearchRequest, options: ISearchOptions ) { const { body, ...query } = request.params; - const rawResponse = (await caller( + return caller( 'transport.request', { method: 'POST', @@ -81,8 +57,5 @@ async function rollupSearch( query: mapKeys(query, (value, key) => snakeCase(key)), }, options - )) as AsyncSearchResponse; - const { total, failed, skipped, successful } = rawResponse._shards; - const loaded = failed + skipped + successful; - return { total, loaded, rawResponse }; + ) as SearchResponse; } From ce642c748a1379c45d73493fa1ecdb7149806495 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 3 Mar 2020 16:40:34 -0700 Subject: [PATCH 47/59] Add back in async functionality --- .../public/search/es_search_strategy.ts | 8 +-- .../server/search/es_search_strategy.ts | 50 ++++++++++++++++--- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index 6cd431655cd7b..f05d257cdde4c 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -10,16 +10,16 @@ import { TSearchStrategyProvider, ISearchContext, ISearch, - SYNC_SEARCH_STRATEGY, getEsPreference, } from '../../../../../src/plugins/data/public'; import { IEnhancedEsSearchRequest } from '../../common'; +import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext ) => { - const syncStrategyProvider = context.getSearchStrategy(SYNC_SEARCH_STRATEGY); - const { search: syncSearch } = syncStrategyProvider(context); + const asyncStrategyProvider = context.getSearchStrategy(ASYNC_SEARCH_STRATEGY); + const { search: asyncSearch } = asyncStrategyProvider(context); const search: ISearch = ( request: IEnhancedEsSearchRequest, @@ -31,7 +31,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { + id: string; + response: SearchResponse; +} + export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext, caller: APICaller @@ -30,25 +36,54 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider; + : asyncSearch(caller, { ...request, params }, options); + const { id, rawResponse } = await response; const { total, failed, skipped, successful } = rawResponse._shards; const loaded = failed + skipped + successful; - return { total, loaded, rawResponse }; + return { id, total, loaded, rawResponse }; + }; + + const cancel: ICancel = id => { + const method = 'DELETE'; + const path = `_async_search/${id}`; + return void caller('transport.request', { method, path }); }; - return { search }; + return { search, cancel }; }; -function rollupSearch( +async function asyncSearch( + caller: APICaller, + request: IEnhancedEsSearchRequest, + options: ISearchOptions +) { + // If we have an ID, then just poll for that ID, otherwise send the entire request body + const method = request.id ? 'GET' : 'POST'; + const path = request.id ? `_async_search/${request.id}` : `${request.params.index}/_async_search`; + + // Wait up to 1s for the initial response to return + const { body, ...otherParams } = request.id ? {} : { waitForCompletion: '1s', ...request.params }; + const query = mapKeys(otherParams ?? {}, (value, key) => snakeCase(key)); + + const { id, response: rawResponse } = (await caller( + 'transport.request', + { method, path, body, query }, + options + )) as AsyncSearchResponse; + + return { id, rawResponse }; +} + +async function rollupSearch( caller: APICaller, request: IEnhancedEsSearchRequest, options: ISearchOptions ) { const { body, ...query } = request.params; - return caller( + const rawResponse = (await caller( 'transport.request', { method: 'POST', @@ -57,5 +92,6 @@ function rollupSearch( query: mapKeys(query, (value, key) => snakeCase(key)), }, options - ) as SearchResponse; + )) as SearchResponse; + return { rawResponse }; } From e0ce774f35b1a004dad47bb3189b82a12d6ef3ff Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 5 Mar 2020 10:28:37 -0700 Subject: [PATCH 48/59] Fix tests/types --- .../search/es_search/es_search_strategy.ts | 9 ++--- .../es_search/get_es_preference.test.ts | 39 ++++++++++++------- .../data/server/search/create_api.test.ts | 13 ++++++- .../es_search/es_search_strategy.test.ts | 18 --------- x-pack/plugins/data_enhanced/common/index.ts | 2 +- .../data_enhanced/common/search/index.ts | 2 +- .../data_enhanced/common/search/types.ts | 7 +++- .../public/search/es_search_strategy.ts | 16 +++----- x-pack/plugins/data_enhanced/server/plugin.ts | 4 +- .../server/search/es_search_strategy.ts | 24 ++++++------ 10 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/plugins/data/public/search/es_search/es_search_strategy.ts b/src/plugins/data/public/search/es_search/es_search_strategy.ts index 03d678c99d072..a61428c998157 100644 --- a/src/plugins/data/public/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/public/search/es_search/es_search_strategy.ts @@ -30,14 +30,13 @@ export const esSearchStrategyProvider: TSearchStrategyProvider { - const params = { + request.params = { preference: getEsPreference(context.core.uiSettings), ...request.params, }; - return search( - { ...request, params, serverStrategy: ES_SEARCH_STRATEGY }, - options - ) as Observable; + return search({ ...request, serverStrategy: ES_SEARCH_STRATEGY }, options) as Observable< + IEsSearchResponse + >; }, }; }; diff --git a/src/plugins/data/public/search/es_search/get_es_preference.test.ts b/src/plugins/data/public/search/es_search/get_es_preference.test.ts index 27e6f9b48bbdd..8b8156b4519d6 100644 --- a/src/plugins/data/public/search/es_search/get_es_preference.test.ts +++ b/src/plugins/data/public/search/es_search/get_es_preference.test.ts @@ -18,29 +18,40 @@ */ import { getEsPreference } from './get_es_preference'; - -jest.useFakeTimers(); +import { CoreStart } from '../../../../../core/public'; +import { coreMock } from '../../../../../core/public/mocks'; describe('Get ES preference', () => { + let mockCoreStart: MockedKeys; + + beforeEach(() => { + mockCoreStart = coreMock.createStart(); + }); + test('returns the session ID if set to sessionId', () => { - const setPreference = 'sessionId'; - const customPreference = 'foobar'; - const sessionId = 'my_session_id'; - const preference = getEsPreference(setPreference, customPreference, sessionId); - expect(preference).toBe(sessionId); + mockCoreStart.uiSettings.get.mockImplementation((key: string) => { + if (key === 'courier:setRequestPreference') return 'sessionId'; + if (key === 'courier:customRequestPreference') return 'foobar'; + }); + const preference = getEsPreference(mockCoreStart.uiSettings, 'my_session_id'); + expect(preference).toBe('my_session_id'); }); test('returns the custom preference if set to custom', () => { - const setPreference = 'custom'; - const customPreference = 'foobar'; - const preference = getEsPreference(setPreference, customPreference); - expect(preference).toBe(customPreference); + mockCoreStart.uiSettings.get.mockImplementation((key: string) => { + if (key === 'courier:setRequestPreference') return 'custom'; + if (key === 'courier:customRequestPreference') return 'foobar'; + }); + const preference = getEsPreference(mockCoreStart.uiSettings); + expect(preference).toBe('foobar'); }); test('returns undefined if set to none', () => { - const setPreference = 'none'; - const customPreference = 'foobar'; - const preference = getEsPreference(setPreference, customPreference); + mockCoreStart.uiSettings.get.mockImplementation((key: string) => { + if (key === 'courier:setRequestPreference') return 'none'; + if (key === 'courier:customRequestPreference') return 'foobar'; + }); + const preference = getEsPreference(mockCoreStart.uiSettings); expect(preference).toBe(undefined); }); }); diff --git a/src/plugins/data/server/search/create_api.test.ts b/src/plugins/data/server/search/create_api.test.ts index 99e48056ef857..0cf68b7e020ce 100644 --- a/src/plugins/data/server/search/create_api.test.ts +++ b/src/plugins/data/server/search/create_api.test.ts @@ -23,8 +23,6 @@ import { TSearchStrategiesMap } from './i_search_strategy'; import { IRouteHandlerSearchContext } from './i_route_handler_search_context'; import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; -// let mockCoreSetup: MockedKeys; - const mockDefaultSearch = jest.fn(() => Promise.resolve({ total: 100, loaded: 0 })); const mockDefaultSearchStrategyProvider = jest.fn(() => Promise.resolve({ @@ -59,4 +57,15 @@ describe('createApi', () => { `"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(); + }); }); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 99ccb4dcbebab..c4b8119f9e095 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -51,24 +51,6 @@ describe('ES search strategy', () => { expect(typeof esSearch.search).toBe('function'); }); - it('logs the response if `debug` is set to `true`', async () => { - const spy = jest.spyOn(console, 'log'); - const esSearch = esSearchStrategyProvider( - { - core: mockCoreSetup, - config$: mockConfig$, - }, - mockApiCaller, - mockSearch - ); - - expect(spy).not.toBeCalled(); - - await esSearch.search({ params: {}, debug: true }); - - expect(spy).toBeCalled(); - }); - it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; const esSearch = esSearchStrategyProvider( diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 2faaddae00571..0d5e353b0e83b 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { IEnhancedEsSearchRequest } from './search'; +export { EnhancedSearchParams, IEnhancedEsSearchRequest } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index 18fb95767b06c..3fe4fd029b940 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { IEnhancedEsSearchRequest } from './types'; +export { EnhancedSearchParams, IEnhancedEsSearchRequest } from './types'; diff --git a/x-pack/plugins/data_enhanced/common/search/types.ts b/x-pack/plugins/data_enhanced/common/search/types.ts index b64bded4f9258..59ce9f0b36f20 100644 --- a/x-pack/plugins/data_enhanced/common/search/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/types.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IEsSearchRequest } from '../../../../../src/plugins/data/common/search/es_search'; +import { SearchParams } from 'elasticsearch'; +import { IEsSearchRequest } from '../../../../../src/plugins/data/common'; + +export interface EnhancedSearchParams extends SearchParams { + ignoreThrottled: boolean; +} export interface IEnhancedEsSearchRequest extends IEsSearchRequest { /** diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index 6cd431655cd7b..25c6a789cca93 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -13,7 +13,7 @@ import { SYNC_SEARCH_STRATEGY, getEsPreference, } from '../../../../../src/plugins/data/public'; -import { IEnhancedEsSearchRequest } from '../../common'; +import { IEnhancedEsSearchRequest, EnhancedSearchParams } from '../../common'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext @@ -25,20 +25,16 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider { - const params = { + const params: EnhancedSearchParams = { ignoreThrottled: !context.core.uiSettings.get('search:includeFrozen'), preference: getEsPreference(context.core.uiSettings), ...request.params, }; + request.params = params; - return syncSearch( - { - ...request, - params, - serverStrategy: ES_SEARCH_STRATEGY, - }, - options - ) as Observable; + return syncSearch({ ...request, serverStrategy: ES_SEARCH_STRATEGY }, options) as Observable< + IEsSearchResponse + >; }; return { search }; diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 231784f3e2894..a27a73431574b 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -11,14 +11,14 @@ import { Plugin, } from '../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../src/plugins/data/common'; -import { DataPluginSetup } from '../../../../src/plugins/data/server'; +import { PluginSetup as DataPluginSetup } from '../../../../src/plugins/data/server'; import { enhancedEsSearchStrategyProvider } from './search'; interface SetupDependencies { data: DataPluginSetup; } -export class EnhancedDataServerPlugin implements Plugin { +export class EnhancedDataServerPlugin implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, deps: SetupDependencies) { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 40fdd979e2c97..ca7a97ef938ec 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; import { first } from 'rxjs/operators'; import { mapKeys, snakeCase } from 'lodash'; +import { SearchResponse } from 'elasticsearch'; import { APICaller } from '../../../../../src/core/server'; import { ES_SEARCH_STRATEGY } from '../../../../../src/plugins/data/common'; import { @@ -45,17 +45,15 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider snakeCase(key)), - }, - options - ) as SearchResponse; + const method = 'POST'; + const path = `${request.params.index}/_rollup_search`; + const { body, ...params } = request.params; + const query = toSnakeCase(params); + return caller('transport.request', { method, path, body, query }, options); +} + +function toSnakeCase(obj: Record) { + return mapKeys(obj, (value, key) => snakeCase(key)); } From 7d94b6d724645e18a4e877989d81cad6e510283d Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 5 Mar 2020 14:37:58 -0700 Subject: [PATCH 49/59] Fix issue with sending empty body in GET --- .../plugins/data_enhanced/server/search/es_search_strategy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index b9868b83b2279..8d01c716fcf45 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -69,7 +69,9 @@ function asyncSearch( const path = request.id ? `_async_search/${request.id}` : `${request.params.index}/_async_search`; // Wait up to 1s for the initial response to return - const { body = {}, ...params } = request.id ? {} : { waitForCompletion: '1s', ...request.params }; + const { body = undefined, ...params } = request.id + ? {} + : { waitForCompletion: '1s', ...request.params }; const query = toSnakeCase(params ?? {}); return caller('transport.request', { method, path, body, query }, options); From 9842cff062c105f9395e2a327bdf0afd6c21ef03 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 5 Mar 2020 14:41:26 -0700 Subject: [PATCH 50/59] Don't include skipped in loaded/total --- .../data/server/search/es_search/es_search_strategy.ts | 4 ++-- .../plugins/data_enhanced/server/search/es_search_strategy.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index d750ed2905e7f..26055a3ae41f7 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -39,8 +39,8 @@ export const esSearchStrategyProvider: TSearchStrategyProvider; - const { total, failed, skipped, successful } = rawResponse._shards; - const loaded = failed + skipped + successful; + const { total, failed, successful } = rawResponse._shards; + const loaded = failed + successful; return { total, loaded, rawResponse }; }; From 8805775096c87f1fc9b8a5edbf17d6055509da04 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 5 Mar 2020 16:04:09 -0700 Subject: [PATCH 51/59] Don't wait before polling the next time --- .../data_enhanced/public/search/es_search_strategy.ts | 10 +++++++--- .../data_enhanced/server/search/es_search_strategy.ts | 8 +++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts index 9a93f054816a8..c493e8ce86781 100644 --- a/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/es_search_strategy.ts @@ -14,6 +14,7 @@ import { } from '../../../../../src/plugins/data/public'; import { IEnhancedEsSearchRequest, EnhancedSearchParams } from '../../common'; import { ASYNC_SEARCH_STRATEGY } from './async_search_strategy'; +import { IAsyncSearchOptions } from './types'; export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = ( context: ISearchContext @@ -32,9 +33,12 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider; + const asyncOptions: IAsyncSearchOptions = { pollInterval: 0, ...options }; + + return asyncSearch( + { ...request, serverStrategy: ES_SEARCH_STRATEGY }, + asyncOptions + ) as Observable; }; return { search }; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 7553579d7a0ef..9019b67ad7eb2 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -68,11 +68,9 @@ function asyncSearch( const method = request.id ? 'GET' : 'POST'; const path = request.id ? `_async_search/${request.id}` : `${request.params.index}/_async_search`; - // Wait up to 1s for the initial response to return - const { body = undefined, ...params } = request.id - ? {} - : { waitForCompletion: '1s', ...request.params }; - const query = toSnakeCase(params ?? {}); + // Wait up to 1s for the response to return + const { body = undefined, ...params } = request.id ? {} : request.params; + const query = toSnakeCase({ ...params, waitForCompletion: '1s' }); return caller('transport.request', { method, path, body, query }, options); } From 171c7ff18f5dcdc77995bb8e24cac5b06c8e4d0d Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 9 Mar 2020 16:35:49 -0700 Subject: [PATCH 52/59] Add search interceptor for bulk managing searches --- .../dashboard/np_ready/dashboard_app.html | 4 + .../np_ready/dashboard_app_controller.tsx | 8 +- .../discover/np_ready/angular/discover.html | 6 +- .../discover/np_ready/angular/discover.js | 7 ++ src/plugins/data/public/search/index.ts | 2 + .../data/public/search/search_interceptor.ts | 82 +++++++++++++++++++ .../data/public/search/search_service.ts | 13 ++- src/plugins/data/public/search/types.ts | 3 + .../public/angular/kbn_top_nav.js | 1 + 9 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/plugins/data/public/search/search_interceptor.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html index 3cf8932958b6d..b4e7e75016398 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html @@ -7,6 +7,7 @@ ng-show="isVisible" app-name="'dashboard'" config="topNavMenu" + is-loading="isLoading" show-search-bar="isVisible" show-filter-bar="showFilterBar()" @@ -48,6 +49,9 @@ > + + +

{{screenTitle}}

diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index af3347afa9c5f..a249b5ff57d19 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -109,7 +109,7 @@ export class DashboardAppController { share, dashboardCapabilities, embeddableCapabilities: { visualizeCapabilities, mapsCapabilities }, - data: { query: queryService }, + data: { query: queryService, search: searchService }, core: { notifications, overlays, @@ -123,6 +123,12 @@ export class DashboardAppController { history, kbnUrlStateStorage, }: DashboardAppControllerDependencies) { + // TODO: Remove these few lines + searchService.getPendingSearchesCount$().subscribe(count => { + $scope.$evalAsync(() => ($scope.isLoading = count > 0)); + }); + $scope.cancelPending = () => searchService.cancelPendingSearches(); + const filterManager = queryService.filterManager; const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index 18254aeca5094..b4615a664ac25 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -12,7 +12,8 @@

{{screenTitle}}

query="state.query" on-query-submit="updateQuery" - + is-loading="isLoading" + show-save-query="showSaveQuery" saved-query-id="state.savedQuery" on-saved-query-id-change="updateSavedQueryId" @@ -21,6 +22,9 @@

{{screenTitle}}

> + + +