diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.extend.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.extend.md
new file mode 100644
index 0000000000000..65e3c2868f29f
--- /dev/null
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.extend.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) > [extend](./kibana-plugin-plugins-data-server.isearchstrategy.extend.md)
+
+## ISearchStrategy.extend property
+
+Signature:
+
+```typescript
+extend?: (id: string, keepAlive: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
index c9f4c886735a7..c46a580d5ceb8 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md
@@ -17,5 +17,6 @@ export interface ISearchStrategy(id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void> | |
+| [extend](./kibana-plugin-plugins-data-server.isearchstrategy.extend.md) | (id: string, keepAlive: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void>
| |
| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable<SearchStrategyResponse>
| |
diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts
index 695ee34d3b468..34e411aa85c80 100644
--- a/src/plugins/data/common/search/types.ts
+++ b/src/plugins/data/common/search/types.ts
@@ -29,10 +29,22 @@ export type ISearchGeneric = <
) => Observable;
export type ISearchCancelGeneric = (id: string, options?: ISearchOptions) => Promise;
+export type ISearchExtendGeneric = (
+ id: string,
+ keepAlive: string,
+ options?: ISearchOptions
+) => Promise;
export interface ISearchClient {
search: ISearchGeneric;
+ /**
+ * Used to cancel an in-progress search request.
+ */
cancel: ISearchCancelGeneric;
+ /**
+ * Used to extend the TTL of an in-progress search request.
+ */
+ extend: ISearchExtendGeneric;
}
export interface IKibanaSearchResponse {
diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts
index 5de019cd1b83e..d26c099b23f39 100644
--- a/src/plugins/data/server/search/search_service.ts
+++ b/src/plugins/data/server/search/search_service.ts
@@ -315,6 +315,19 @@ export class SearchService implements Plugin {
return strategy.cancel(id, options, deps);
};
+ private extend = (
+ id: string,
+ keepAlive: string,
+ options: ISearchOptions,
+ deps: SearchStrategyDependencies
+ ) => {
+ const strategy = this.getSearchStrategy(options.strategy);
+ if (!strategy.extend) {
+ throw new KbnServerError(`Search strategy ${options.strategy} does not support extend`, 400);
+ }
+ return strategy.extend(id, keepAlive, options, deps);
+ };
+
private getSearchStrategy = <
SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest,
SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse
@@ -344,6 +357,7 @@ export class SearchService implements Plugin {
search: (searchRequest, options = {}) =>
this.search(scopedSession, searchRequest, options, deps),
cancel: (id, options = {}) => this.cancel(id, options, deps),
+ extend: (id, keepAlive, options = {}) => this.extend(id, keepAlive, options, deps),
};
};
};
diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts
index db8b8ac72d0e5..fb00f86464e4e 100644
--- a/src/plugins/data/server/search/types.ts
+++ b/src/plugins/data/server/search/types.ts
@@ -86,6 +86,12 @@ export interface ISearchStrategy<
deps: SearchStrategyDependencies
) => Observable;
cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise;
+ extend?: (
+ id: string,
+ keepAlive: string,
+ options: ISearchOptions,
+ deps: SearchStrategyDependencies
+ ) => Promise;
}
export interface ISearchStart<
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 9968c9717aa96..bfe4637ea5729 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -956,6 +956,8 @@ export interface ISearchStrategy Promise;
// (undocumented)
+ extend?: (id: string, keepAlive: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise;
+ // (undocumented)
search: (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable;
}
@@ -1434,7 +1436,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage;
// src/plugins/data/server/index.ts:279:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index_patterns/index_patterns_service.ts:70:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/plugin.ts:90:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/search/types.ts:106:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/search/types.ts:112:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts
index 26325afc378f7..e615d9d2a660a 100644
--- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts
+++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts
@@ -56,5 +56,10 @@ export const eqlSearchStrategyProvider = (
return pollSearch(search, options).pipe(tap((response) => (id = response.id)));
},
+
+ extend: async (id, keepAlive, options, { esClient }) => {
+ logger.debug(`_eql/extend ${id} by ${keepAlive}`);
+ await esClient.asCurrentUser.eql.get({ id, keep_alive: keepAlive });
+ },
};
};
diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
index b9b6e25067f2f..3230895da7705 100644
--- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
+++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts
@@ -37,6 +37,7 @@ describe('ES search strategy', () => {
const mockApiCaller = jest.fn();
const mockGetCaller = jest.fn();
const mockSubmitCaller = jest.fn();
+ const mockDeleteCaller = jest.fn();
const mockLogger: any = {
debug: () => {},
};
@@ -49,6 +50,7 @@ describe('ES search strategy', () => {
asyncSearch: {
get: mockGetCaller,
submit: mockSubmitCaller,
+ delete: mockDeleteCaller,
},
transport: { request: mockApiCaller },
},
@@ -66,77 +68,113 @@ describe('ES search strategy', () => {
beforeEach(() => {
mockApiCaller.mockClear();
+ mockGetCaller.mockClear();
+ mockSubmitCaller.mockClear();
+ mockDeleteCaller.mockClear();
});
- it('returns a strategy with `search`', async () => {
+ it('returns a strategy with `search and `cancel`', async () => {
const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
expect(typeof esSearch.search).toBe('function');
});
- it('makes a POST request to async search with params when no ID is provided', async () => {
- mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
+ describe('search', () => {
+ it('makes a POST request to async search with params when no ID is provided', async () => {
+ mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
- const params = { index: 'logstash-*', body: { query: {} } };
- const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+ const params = { index: 'logstash-*', body: { query: {} } };
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
- await esSearch.search({ params }, {}, mockDeps).toPromise();
+ await esSearch.search({ params }, {}, mockDeps).toPromise();
- expect(mockSubmitCaller).toBeCalled();
- const request = mockSubmitCaller.mock.calls[0][0];
- expect(request.index).toEqual(params.index);
- expect(request.body).toEqual(params.body);
- });
+ expect(mockSubmitCaller).toBeCalled();
+ const request = mockSubmitCaller.mock.calls[0][0];
+ expect(request.index).toEqual(params.index);
+ expect(request.body).toEqual(params.body);
+ });
- it('makes a GET request to async search with ID when ID is provided', async () => {
- mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
+ it('makes a GET request to async search with ID when ID is provided', async () => {
+ mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
- const params = { index: 'logstash-*', body: { query: {} } };
- const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+ const params = { index: 'logstash-*', body: { query: {} } };
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+
+ await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();
+
+ expect(mockGetCaller).toBeCalled();
+ const request = mockGetCaller.mock.calls[0][0];
+ expect(request.id).toEqual('foo');
+ expect(request).toHaveProperty('wait_for_completion_timeout');
+ expect(request).toHaveProperty('keep_alive');
+ });
+
+ it('calls the rollup API if the index is a rollup type', async () => {
+ mockApiCaller.mockResolvedValueOnce(mockRollupResponse);
+
+ const params = { index: 'foo-程', body: {} };
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+
+ await esSearch
+ .search(
+ {
+ indexType: 'rollup',
+ params,
+ },
+ {},
+ mockDeps
+ )
+ .toPromise();
- await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise();
+ expect(mockApiCaller).toBeCalled();
+ const { method, path } = mockApiCaller.mock.calls[0][0];
+ expect(method).toBe('POST');
+ expect(path).toBe('/foo-%E7%A8%8B/_rollup_search');
+ });
- expect(mockGetCaller).toBeCalled();
- const request = mockGetCaller.mock.calls[0][0];
- expect(request.id).toEqual('foo');
- expect(request).toHaveProperty('wait_for_completion_timeout');
- expect(request).toHaveProperty('keep_alive');
+ it('sets wait_for_completion_timeout and keep_alive in the request', async () => {
+ mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
+
+ const params = { index: 'foo-*', body: {} };
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+
+ await esSearch.search({ params }, {}, mockDeps).toPromise();
+
+ expect(mockSubmitCaller).toBeCalled();
+ const request = mockSubmitCaller.mock.calls[0][0];
+ expect(request).toHaveProperty('wait_for_completion_timeout');
+ expect(request).toHaveProperty('keep_alive');
+ });
});
- it('calls the rollup API if the index is a rollup type', async () => {
- mockApiCaller.mockResolvedValueOnce(mockRollupResponse);
+ describe('cancel', () => {
+ it('makes a DELETE request to async search with the provided ID', async () => {
+ mockDeleteCaller.mockResolvedValueOnce(200);
- const params = { index: 'foo-程', body: {} };
- const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+ const id = 'some_id';
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
- await esSearch
- .search(
- {
- indexType: 'rollup',
- params,
- },
- {},
- mockDeps
- )
- .toPromise();
-
- expect(mockApiCaller).toBeCalled();
- const { method, path } = mockApiCaller.mock.calls[0][0];
- expect(method).toBe('POST');
- expect(path).toBe('/foo-%E7%A8%8B/_rollup_search');
+ await esSearch.cancel!(id, {}, mockDeps);
+
+ expect(mockDeleteCaller).toBeCalled();
+ const request = mockDeleteCaller.mock.calls[0][0];
+ expect(request).toEqual({ id });
+ });
});
- it('sets wait_for_completion_timeout and keep_alive in the request', async () => {
- mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse);
+ describe('extend', () => {
+ it('makes a GET request to async search with the provided ID and keepAlive', async () => {
+ mockGetCaller.mockResolvedValueOnce(mockAsyncResponse);
- const params = { index: 'foo-*', body: {} };
- const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
+ const id = 'some_other_id';
+ const keepAlive = '1d';
+ const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger);
- await esSearch.search({ params }, {}, mockDeps).toPromise();
+ await esSearch.extend!(id, keepAlive, {}, mockDeps);
- expect(mockSubmitCaller).toBeCalled();
- const request = mockSubmitCaller.mock.calls[0][0];
- expect(request).toHaveProperty('wait_for_completion_timeout');
- expect(request).toHaveProperty('keep_alive');
+ expect(mockGetCaller).toBeCalled();
+ const request = mockGetCaller.mock.calls[0][0];
+ expect(request).toEqual({ id, keep_alive: keepAlive });
+ });
});
});
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 fc5787f9e5cab..c1520d931c272 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
@@ -111,5 +111,9 @@ export const enhancedEsSearchStrategyProvider = (
logger.debug(`cancel ${id}`);
await esClient.asCurrentUser.asyncSearch.delete({ id });
},
+ extend: async (id, keepAlive, options, { esClient }) => {
+ logger.debug(`extend ${id} by ${keepAlive}`);
+ await esClient.asCurrentUser.asyncSearch.get({ id, keep_alive: keepAlive });
+ },
};
};