diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md deleted file mode 100644 index 55b43efc52305..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.indextype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) > [indexType](./kibana-plugin-plugins-data-public.iessearchrequest.indextype.md) - -## IEsSearchRequest.indexType property - -Signature: - -```typescript -indexType?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md index 45cd088ee1203..d8b10b5e9f76f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchrequest.md @@ -2,17 +2,10 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) -## IEsSearchRequest interface +## IEsSearchRequest type Signature: ```typescript -export interface IEsSearchRequest extends IKibanaSearchRequest +export declare type IEsSearchRequest = IKibanaSearchRequest; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [indexType](./kibana-plugin-plugins-data-public.iessearchrequest.indextype.md) | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 7f5a042e0ab81..46b8e30326fea 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -64,7 +64,6 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [IDataPluginServices](./kibana-plugin-plugins-data-public.idatapluginservices.md) | | -| [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) | | | [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) | | @@ -164,6 +163,7 @@ | [IAggConfig](./kibana-plugin-plugins-data-public.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-public.iaggtype.md) | | | [IEsError](./kibana-plugin-plugins-data-public.ieserror.md) | | +| [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | | [IEsSearchResponse](./kibana-plugin-plugins-data-public.iessearchresponse.md) | | | [IFieldFormat](./kibana-plugin-plugins-data-public.ifieldformat.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-public.ifieldformatsregistry.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md deleted file mode 100644 index aaf4e55ee007b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.indextype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) > [indexType](./kibana-plugin-plugins-data-server.iessearchrequest.indextype.md) - -## IEsSearchRequest.indexType property - -Signature: - -```typescript -indexType?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md index 9141bcdd2e8d7..e326ff5773da3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchrequest.md @@ -2,17 +2,10 @@ [Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) -## IEsSearchRequest interface +## IEsSearchRequest type Signature: ```typescript -export interface IEsSearchRequest extends IKibanaSearchRequest +export declare type IEsSearchRequest = IKibanaSearchRequest; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [indexType](./kibana-plugin-plugins-data-server.iessearchrequest.indextype.md) | string | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index b1745b298e27e..184390787b1ae 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -50,7 +50,6 @@ | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | -| [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Interface for an index pattern saved object | @@ -103,6 +102,7 @@ | [Filter](./kibana-plugin-plugins-data-server.filter.md) | | | [IAggConfig](./kibana-plugin-plugins-data-server.iaggconfig.md) | AggConfig This class represents an aggregation, which is displayed in the left-hand nav of the Visualize app. | | [IAggType](./kibana-plugin-plugins-data-server.iaggtype.md) | | +| [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | | [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md) | | diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index badbb94e9752f..7c454c11e00a3 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -17,3 +17,4 @@ export * from './poll_search'; export * from './strategies/es_search'; export * from './strategies/eql_search'; export * from './strategies/ese_search'; +export * from './strategies/rollup_search'; diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 7c0473077d182..1cb793effb5a0 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -10,7 +10,12 @@ import { of } from 'rxjs'; import { IndexPattern } from '../../index_patterns'; import { GetConfigFn } from '../../types'; import { SearchSource, SearchSourceDependencies, SortDirection } from './'; -import { AggConfigs, AggTypesRegistryStart, ES_SEARCH_STRATEGY } from '../../'; +import { + AggConfigs, + AggTypesRegistryStart, + ES_SEARCH_STRATEGY, + ROLLUP_SEARCH_STRATEGY, +} from '../../'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { RequestResponder } from 'src/plugins/inspector/common'; import { switchMap } from 'rxjs/operators'; @@ -32,6 +37,14 @@ const indexPattern = ({ getSourceFiltering: () => mockSource, } as unknown) as IndexPattern; +const rollupIndexPattern = ({ + title: 'foo', + type: 'rollup', + fields: [{ name: 'foo-bar' }, { name: 'field1' }, { name: 'field2' }], + getComputedFields, + getSourceFiltering: () => mockSource, +} as unknown) as IndexPattern; + const indexPattern2 = ({ title: 'foo', getComputedFields, @@ -917,6 +930,44 @@ describe('SearchSource', () => { const [, callOptions] = mockSearchMethod.mock.calls[0]; expect(callOptions.strategy).toBe('banana'); }); + + test('should use rollup search if rollup index', async () => { + searchSource = new SearchSource({ index: rollupIndexPattern }, searchSourceDependencies); + const options = {}; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe(ROLLUP_SEARCH_STRATEGY); + }); + + test('should not use rollup search if overriden', async () => { + searchSource = new SearchSource({ index: rollupIndexPattern }, searchSourceDependencies); + const options = { strategy: 'banana' }; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe('banana'); + }); + }); + + describe('Rollup search', () => { + test('should use rollup search if rollup index', async () => { + searchSource = new SearchSource({ index: rollupIndexPattern }, searchSourceDependencies); + const options = {}; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe(ROLLUP_SEARCH_STRATEGY); + }); + + test('should not use rollup search if overriden', async () => { + searchSource = new SearchSource({ index: rollupIndexPattern }, searchSourceDependencies); + const options = { strategy: 'banana' }; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe('banana'); + }); }); describe('responses', () => { diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index f35d2d47f1bf4..7600d553403c3 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -96,6 +96,7 @@ import { } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { extractReferences } from './extract_references'; +import { ROLLUP_SEARCH_STRATEGY } from '../strategies/rollup_search'; /** @internal */ export const searchSourceRequiredUiSettings = [ @@ -276,10 +277,11 @@ export class SearchSource { ): Observable>> { const { getConfig } = this.dependencies; const syncSearchByDefault = getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES); + const hasExplicitStrategy = !!options.strategy; // Use the sync search strategy if legacy search is enabled. // This still uses bfetch for batching. - if (!options?.strategy && syncSearchByDefault) { + if (!hasExplicitStrategy && syncSearchByDefault) { options.strategy = ES_SEARCH_STRATEGY; } @@ -289,6 +291,9 @@ export class SearchSource { this.history = [searchRequest]; if (searchRequest.index) { options.indexPattern = searchRequest.index; + if (searchRequest.indexType === 'rollup' && !hasExplicitStrategy) { + options.strategy = ROLLUP_SEARCH_STRATEGY; + } } return this.fetchSearch$(searchRequest, options); @@ -445,7 +450,7 @@ export class SearchSource { getConfig, }); - return search({ params, indexType: searchRequest.indexType }, options).pipe( + return search({ params }, options).pipe( switchMap((response) => { return new Observable>((obs) => { if (isErrorResponse(response)) { diff --git a/src/plugins/data/common/search/strategies/es_search/types.ts b/src/plugins/data/common/search/strategies/es_search/types.ts index 05df661d466c8..eb046e61c94f5 100644 --- a/src/plugins/data/common/search/strategies/es_search/types.ts +++ b/src/plugins/data/common/search/strategies/es_search/types.ts @@ -15,8 +15,6 @@ export type ISearchRequestParams = { trackTotalHits?: boolean; } & estypes.SearchRequest; -export interface IEsSearchRequest extends IKibanaSearchRequest { - indexType?: string; -} +export type IEsSearchRequest = IKibanaSearchRequest; export type IEsSearchResponse = IKibanaSearchResponse>; diff --git a/src/plugins/data/common/search/strategies/rollup_search/index.ts b/src/plugins/data/common/search/strategies/rollup_search/index.ts new file mode 100644 index 0000000000000..12594660136d8 --- /dev/null +++ b/src/plugins/data/common/search/strategies/rollup_search/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; diff --git a/src/plugins/data/common/search/strategies/rollup_search/types.ts b/src/plugins/data/common/search/strategies/rollup_search/types.ts new file mode 100644 index 0000000000000..9e10d6ce097a5 --- /dev/null +++ b/src/plugins/data/common/search/strategies/rollup_search/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { estypes } from '@elastic/elasticsearch'; +import type { Indices } from '@elastic/elasticsearch/api/types'; + +import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../types'; + +export const ROLLUP_SEARCH_STRATEGY = 'rollup'; + +export interface IRollupSearchRequestParams extends estypes.SearchRequest { + index: Indices; // index is required +} + +export type IRollupSearchRequest = IKibanaSearchRequest; + +export type IRollupSearchResponse = IKibanaSearchResponse< + estypes.SearchResponse +>; diff --git a/src/plugins/data/common/search/test_data/rollup_wrong_index_exception.json b/src/plugins/data/common/search/test_data/rollup_wrong_index_exception.json new file mode 100644 index 0000000000000..9c3e82cad0b7d --- /dev/null +++ b/src/plugins/data/common/search/test_data/rollup_wrong_index_exception.json @@ -0,0 +1,13 @@ +{ + "error": { + "root_cause": [ + { + "type": "illegal_argument_exception", + "reason": "Must specify at least one concrete index." + } + ], + "type": "illegal_argument_exception", + "reason": "Must specify at least one concrete index." + }, + "status": 400 +} diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index fde7075d9e760..29bc08fa3e23d 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1139,10 +1139,7 @@ export type IEsError = KibanaServerError; // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchRequest extends IKibanaSearchRequest { - // (undocumented) - indexType?: string; -} +export type IEsSearchRequest = IKibanaSearchRequest; // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index a651d7b3bf105..d7bbc4df95f25 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -66,6 +66,7 @@ import { esRawResponse, ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY, + ROLLUP_SEARCH_STRATEGY, } from '../../common/search'; import { getEsaggs, getEsdsl } from './expressions'; import { @@ -80,6 +81,7 @@ import { registerBsearchRoute } from './routes/bsearch'; import { getKibanaContext } from './expressions/kibana_context'; import { enhancedEsSearchStrategyProvider } from './strategies/ese_search'; import { eqlSearchStrategyProvider } from './strategies/eql_search'; +import { rollupSearchStrategyProvider } from './strategies/rollup_search'; type StrategyMap = Record>; @@ -156,6 +158,14 @@ export class SearchService implements Plugin { ); this.registerSearchStrategy(EQL_SEARCH_STRATEGY, eqlSearchStrategyProvider(this.logger)); + this.registerSearchStrategy( + ROLLUP_SEARCH_STRATEGY, + rollupSearchStrategyProvider( + this.initializerContext.config.legacy.globalConfig$, + this.logger, + usage + ) + ); registerBsearchRoute(bfetch, (request: KibanaRequest) => this.asScoped(request)); diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts index c2280737d2496..e4fd90ee29660 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts @@ -189,21 +189,4 @@ describe('ES search strategy', () => { done(); } }); - - it('throws KbnServerError for unknown index type', async (done) => { - const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; - - try { - await esSearchStrategyProvider(mockConfig$, mockLogger) - .search({ indexType: 'banana', params }, {}, getMockedDeps()) - .toPromise(); - } catch (e) { - expect(mockApiCaller).not.toBeCalled(); - expect(e).toBeInstanceOf(KbnServerError); - expect(e.message).toBe('Unsupported index pattern type banana'); - expect(e.statusCode).toBe(400); - expect(e.errBody).toBe(undefined); - done(); - } - }); }); diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts index c24aa37082bd8..f62ba60b33670 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.ts @@ -14,7 +14,7 @@ import type { SearchUsage } from '../../collectors'; import { getDefaultSearchParams, getShardTimeout, shimAbortSignal } from './request_utils'; import { shimHitsTotal, toKibanaSearchResponse } from './response_utils'; import { searchUsageObserver } from '../../collectors/usage'; -import { getKbnServerError, KbnServerError } from '../../../../../kibana_utils/server'; +import { getKbnServerError } from '../../../../../kibana_utils/server'; export const esSearchStrategyProvider = ( config$: Observable, @@ -29,12 +29,6 @@ export const esSearchStrategyProvider = ( * @returns `Observable>` */ search: (request, { abortSignal, ...options }, { esClient, uiSettingsClient }) => { - // Only default index pattern type is supported here. - // See data_enhanced for other type support. - if (request.indexType) { - throw new KbnServerError(`Unsupported index pattern type ${request.indexType}`, 400); - } - const search = async () => { try { const config = await config$.pipe(first()).toPromise(); diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts index 216318339622f..1c636ebe5a14b 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.test.ts @@ -29,19 +29,7 @@ const mockAsyncResponse = { }, }; -const mockRollupResponse = { - body: { - _shards: { - total: 10, - failed: 1, - skipped: 2, - successful: 7, - }, - }, -}; - describe('ES search strategy', () => { - const mockApiCaller = jest.fn(); const mockGetCaller = jest.fn(); const mockSubmitCaller = jest.fn(); const mockDeleteCaller = jest.fn(); @@ -59,7 +47,6 @@ describe('ES search strategy', () => { submit: mockSubmitCaller, delete: mockDeleteCaller, }, - transport: { request: mockApiCaller }, }, }, searchSessionsClient: createSearchSessionsClientMock(), @@ -75,7 +62,6 @@ describe('ES search strategy', () => { }); beforeEach(() => { - mockApiCaller.mockClear(); mockGetCaller.mockClear(); mockSubmitCaller.mockClear(); mockDeleteCaller.mockClear(); @@ -132,29 +118,6 @@ describe('ES search strategy', () => { 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(mockLegacyConfig$, 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'); - }); }); describe('with sessionId', () => { diff --git a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts index ab6162f756ea8..8e293933b18f6 100644 --- a/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts +++ b/src/plugins/data/server/search/strategies/ese_search/ese_search_strategy.ts @@ -8,32 +8,15 @@ import type { Observable } from 'rxjs'; import type { IScopedClusterClient, Logger, SharedGlobalConfig } from 'kibana/server'; -import { catchError, first, tap } from 'rxjs/operators'; -import { SearchResponse } from 'elasticsearch'; -import { from } from 'rxjs'; +import { catchError, tap } from 'rxjs/operators'; import type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; -import type { - IAsyncSearchOptions, - IEsSearchRequest, - IEsSearchResponse, - ISearchOptions, -} from '../../../../common'; +import type { IAsyncSearchOptions, IEsSearchRequest } from '../../../../common'; import { pollSearch } from '../../../../common'; -import { - getDefaultAsyncGetParams, - getDefaultAsyncSubmitParams, - getIgnoreThrottled, -} from './request_utils'; +import { getDefaultAsyncGetParams, getDefaultAsyncSubmitParams } from './request_utils'; import { toAsyncKibanaSearchResponse } from './response_utils'; -import { getKbnServerError, KbnServerError } from '../../../../../kibana_utils/server'; +import { getKbnServerError } from '../../../../../kibana_utils/server'; import { SearchUsage, searchUsageObserver } from '../../collectors'; -import { - getDefaultSearchParams, - getShardTimeout, - getTotalLoaded, - shimAbortSignal, - shimHitsTotal, -} from '../es_search'; +import { shimAbortSignal, shimHitsTotal } from '../es_search'; export const enhancedEsSearchStrategyProvider = ( legacyConfig$: Observable, @@ -95,42 +78,6 @@ export const enhancedEsSearchStrategyProvider = ( ); } - async function rollupSearch( - request: IEsSearchRequest, - options: ISearchOptions, - { esClient, uiSettingsClient }: SearchStrategyDependencies - ): Promise { - const client = useInternalUser ? esClient.asInternalUser : esClient.asCurrentUser; - const legacyConfig = await legacyConfig$.pipe(first()).toPromise(); - const { body, index, ...params } = request.params!; - const method = 'POST'; - const path = encodeURI(`/${index}/_rollup_search`); - const querystring = { - ...getShardTimeout(legacyConfig), - ...(await getIgnoreThrottled(uiSettingsClient)), - ...(await getDefaultSearchParams(uiSettingsClient)), - ...params, - }; - - try { - const promise = client.transport.request({ - method, - path, - body, - querystring, - }); - - const esResponse = await shimAbortSignal(promise, options?.abortSignal); - const response = esResponse.body as SearchResponse; - return { - rawResponse: shimHitsTotal(response, options), - ...getTotalLoaded(response), - }; - } catch (e) { - throw getKbnServerError(e); - } - } - return { /** * @param request @@ -141,15 +88,8 @@ export const enhancedEsSearchStrategyProvider = ( */ search: (request, options: IAsyncSearchOptions, deps) => { logger.debug(`search ${JSON.stringify(request.params) || request.id}`); - if (request.indexType && request.indexType !== 'rollup') { - throw new KbnServerError('Unknown indexType', 400); - } - if (request.indexType === undefined) { - return asyncSearch(request, options, deps); - } else { - return from(rollupSearch(request, options, deps)); - } + return asyncSearch(request, options, deps); }, /** * @param id async search ID to cancel, as returned from _async_search API diff --git a/src/plugins/data/server/search/strategies/rollup_search/index.ts b/src/plugins/data/server/search/strategies/rollup_search/index.ts new file mode 100644 index 0000000000000..809438fd066e8 --- /dev/null +++ b/src/plugins/data/server/search/strategies/rollup_search/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { rollupSearchStrategyProvider } from './rollup_search_strategy'; diff --git a/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.test.ts b/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.test.ts new file mode 100644 index 0000000000000..b276811f42d33 --- /dev/null +++ b/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BehaviorSubject } from 'rxjs'; +import { SearchStrategyDependencies } from '../../types'; +import { rollupSearchStrategyProvider } from './rollup_search_strategy'; +import { createSearchSessionsClientMock } from '../../mocks'; +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import * as rollupWrongIndexException from '../../../../common/search/test_data/rollup_wrong_index_exception.json'; +import { KbnServerError } from '../../../../../kibana_utils/server'; + +const mockRollupResponse = { + body: { + _shards: { + total: 10, + failed: 1, + skipped: 2, + successful: 7, + }, + }, +}; + +describe('Rollup search strategy', () => { + const mockApiCaller = jest.fn(); + const mockLogger: any = { + debug: () => {}, + }; + const mockDeps = ({ + uiSettingsClient: { + get: jest.fn(), + }, + esClient: { + asCurrentUser: { + transport: { request: mockApiCaller }, + }, + }, + searchSessionsClient: createSearchSessionsClientMock(), + } as unknown) as SearchStrategyDependencies; + const mockLegacyConfig$ = new BehaviorSubject({ + elasticsearch: { + shardTimeout: { + asMilliseconds: () => { + return 100; + }, + }, + }, + }); + + beforeEach(() => { + mockApiCaller.mockClear(); + }); + + it('calls the rollup API', async () => { + mockApiCaller.mockResolvedValueOnce(mockRollupResponse); + + const params = { index: 'foo-程', body: {} }; + const rollupSearch = rollupSearchStrategyProvider(mockLegacyConfig$, mockLogger); + + await rollupSearch + .search( + { + 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'); + }); + + it('throws normalized error on ResponseError', async () => { + const errResponse = new ResponseError({ + body: rollupWrongIndexException, + statusCode: 400, + headers: {}, + warnings: [], + meta: {} as any, + }); + + mockApiCaller.mockRejectedValueOnce(errResponse); + + const params = { index: 'non-rollup-index', body: {} }; + const rollupSearch = await rollupSearchStrategyProvider(mockLegacyConfig$, mockLogger); + + let err: KbnServerError | undefined; + try { + await rollupSearch.search({ params }, {}, mockDeps).toPromise(); + } catch (e) { + err = e; + } + + expect(mockApiCaller).toBeCalled(); + expect(err).toBeInstanceOf(KbnServerError); + expect(err?.statusCode).toBe(400); + expect(err?.message).toBe(errResponse.message); + expect(err?.errBody).toBe(rollupWrongIndexException); + }); +}); diff --git a/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.ts b/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.ts new file mode 100644 index 0000000000000..906472ce65095 --- /dev/null +++ b/src/plugins/data/server/search/strategies/rollup_search/rollup_search_strategy.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Observable } from 'rxjs'; +import { from } from 'rxjs'; +import type { Logger, SharedGlobalConfig } from 'kibana/server'; +import { first } from 'rxjs/operators'; +import { SearchResponse } from 'elasticsearch'; +import type { ISearchStrategy, SearchStrategyDependencies } from '../../types'; +import type { + IAsyncSearchOptions, + IEsSearchRequest, + IEsSearchResponse, + ISearchOptions, +} from '../../../../common'; + +import { getKbnServerError } from '../../../../../kibana_utils/server'; +import { SearchUsage } from '../../collectors'; +import { + getDefaultSearchParams, + getShardTimeout, + getTotalLoaded, + shimAbortSignal, + shimHitsTotal, +} from '../es_search'; +import { IRollupSearchRequest, IRollupSearchResponse } from '../../../../common/'; +import { getIgnoreThrottled } from '../ese_search/request_utils'; + +export const rollupSearchStrategyProvider = ( + legacyConfig$: Observable, + logger: Logger, + usage?: SearchUsage +): ISearchStrategy => { + async function rollupSearch( + request: IEsSearchRequest, + options: ISearchOptions, + { esClient, uiSettingsClient }: SearchStrategyDependencies + ): Promise { + const legacyConfig = await legacyConfig$.pipe(first()).toPromise(); + const { body, index, ...params } = request.params!; + const method = 'POST'; + const path = encodeURI(`/${index}/_rollup_search`); + const querystring = { + ...getShardTimeout(legacyConfig), + ...(await getIgnoreThrottled(uiSettingsClient)), + ...(await getDefaultSearchParams(uiSettingsClient)), + ...params, + }; + + try { + // TODO: use esClient.asCurrentUser.rollup.rollupSearch + const promise = esClient.asCurrentUser.transport.request({ + method, + path, + body, + querystring, + }); + + const esResponse = await shimAbortSignal(promise, options?.abortSignal); + const response = esResponse.body as SearchResponse; + return { + rawResponse: shimHitsTotal(response, options), + ...getTotalLoaded(response), + }; + } catch (e) { + throw getKbnServerError(e); + } + } + + return { + /** + * @param request + * @param options + * @param deps `SearchStrategyDependencies` + * @returns `Observable>` + * @throws `KbnServerError` + */ + search: (request, options: IAsyncSearchOptions, deps) => { + logger.debug(`search ${JSON.stringify(request.params) || request.id}`); + + return from(rollupSearch(request, options, deps)); + }, + }; +}; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 4abf430252164..4745a2dba679f 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -654,10 +654,7 @@ export type IAggType = AggType; // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchRequest extends IKibanaSearchRequest { - // (undocumented) - indexType?: string; -} +export type IEsSearchRequest = IKibanaSearchRequest; // Warning: (ae-forgotten-export) The symbol "IKibanaSearchResponse" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts index 7add5cb4a4553..1baefb8f1aa1f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts @@ -87,7 +87,6 @@ describe('AbstractSearchStrategy', () => { body: 'body', index: 'index', }, - indexType: undefined, }, { sessionId: '1', diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 26c3a6c7c8bf7..f1601baf9e501 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -21,7 +21,7 @@ export abstract class AbstractSearchStrategy { requestContext: VisTypeTimeseriesRequestHandlerContext, req: VisTypeTimeseriesVisDataRequest, bodies: any[], - indexType?: string + strategy?: string ) { const requests: any[] = []; @@ -30,12 +30,14 @@ export abstract class AbstractSearchStrategy { requestContext.search .search( { - indexType, params: { ...body, }, }, - req.body.searchSession + { + ...req.body.searchSession, + ...(strategy ? { strategy } : {}), + } ) .toPromise() ); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts index 0ac00863d0a73..068e88db6dca1 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts @@ -18,6 +18,7 @@ import type { VisTypeTimeseriesVisDataRequest, } from '../../../types'; import { MAX_BUCKETS_SETTING } from '../../../../common/constants'; +import { ROLLUP_SEARCH_STRATEGY } from '../../../../../data/common'; const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData); const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); @@ -28,7 +29,7 @@ export class RollupSearchStrategy extends AbstractSearchStrategy { req: VisTypeTimeseriesVisDataRequest, bodies: any[] ) { - return super.search(requestContext, req, bodies, 'rollup'); + return super.search(requestContext, req, bodies, ROLLUP_SEARCH_STRATEGY); } async getRollupData( diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts index f539d0f9e4544..d9aded5f16717 100644 --- a/test/api_integration/apis/search/bsearch.ts +++ b/test/api_integration/apis/search/bsearch.ts @@ -116,34 +116,6 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should return 400 when index type is provided in "es" strategy', async () => { - const resp = await supertest.post(`/internal/bsearch`).send({ - batch: [ - { - request: { - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 400, 'Unsupported index pattern type baad'); - }); - }); - describe('painless', () => { before(async () => { await esArchiver.loadIfNeeded( diff --git a/x-pack/test/api_integration/apis/search/search.ts b/x-pack/test/api_integration/apis/search/search.ts index 05743862c9b6e..edd498f4d2f52 100644 --- a/x-pack/test/api_integration/apis/search/search.ts +++ b/x-pack/test/api_integration/apis/search/search.ts @@ -109,25 +109,6 @@ export default function ({ getService }: FtrProviderContext) { verifyErrorResponse(resp.body, 400, 'Request must contain a kbn-xsrf header.'); }); - it('should return 400 when unknown index type is provided', async () => { - const resp = await supertest - .post(`/internal/search/ese`) - .set('kbn-xsrf', 'foo') - .send({ - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }) - .expect(400); - - verifyErrorResponse(resp.body, 400, 'Unknown indexType'); - }); - it('should return 400 if invalid id is provided', async () => { const resp = await supertest .post(`/internal/search/ese/123`) @@ -146,7 +127,7 @@ export default function ({ getService }: FtrProviderContext) { verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true); }); - it('should return 404 if unkown id is provided', async () => { + it('should return 404 if unknown id is provided', async () => { const resp = await supertest .post( `/internal/search/ese/FkxOb21iV1g2VGR1S2QzaWVtRU9fMVEbc3JWeWc1VHlUdDZ6MENxcXlYVG1Fdzo2NDg4` @@ -193,10 +174,9 @@ export default function ({ getService }: FtrProviderContext) { it('should return 400 if rollup search is called without index', async () => { const resp = await supertest - .post(`/internal/search/ese`) + .post(`/internal/search/rollup`) .set('kbn-xsrf', 'foo') .send({ - indexType: 'rollup', params: { body: { query: { @@ -211,10 +191,9 @@ export default function ({ getService }: FtrProviderContext) { it('should return 400 if rollup search is without non-existent index', async () => { const resp = await supertest - .post(`/internal/search/ese`) + .post(`/internal/search/rollup`) .set('kbn-xsrf', 'foo') .send({ - indexType: 'rollup', params: { index: 'banana', body: { @@ -229,12 +208,31 @@ export default function ({ getService }: FtrProviderContext) { verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true); }); + it('should return 400 when called with non-rollup index', async () => { + const resp = await supertest + .post(`/internal/search/rollup`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + index: 'logstash-*', + size: 0, + body: { + query: { + match_all: {}, + }, + }, + }, + }) + .expect(400); + + verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true); + }); + it('should rollup search', async () => { await supertest - .post(`/internal/search/ese`) + .post(`/internal/search/rollup`) .set('kbn-xsrf', 'foo') .send({ - indexType: 'rollup', params: { index: 'rollup_logstash', size: 0,