From a69870b95048135bd2d9ab1c106b64cb115c2511 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 8 Aug 2023 11:25:13 -0500 Subject: [PATCH] [data / data views] Disable rollup functionality when rollup plugin is disabled (#162674) ## Summary Rollup functionality will be disabled on serverless instances. The `rollup` plugin will be disabled. While it won't be possible to create rollups on serverless, it will be possible to import data view saved objects which may be configured for use with rollups. We will make a 'best effort' to improve functionality as to help users transition away from rollups. - In classic environments, the `rollup` plugin will enable `dataViews` and `data` plugin rollup functionality. - The data plugin will run an async search instead of a rollup search. - The data views plugin will fetch normal fields, omitting a query for rollup fields - which would fail anyway. Closes https://github.com/elastic/kibana/issues/152708 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/data/kibana.jsonc | 1 - src/plugins/data/server/plugin.ts | 22 +-- src/plugins/data/server/search/mocks.ts | 1 + .../data/server/search/search_service.ts | 17 +-- .../ese_search/ese_search_strategy.test.ts | 26 ++++ .../ese_search/ese_search_strategy.ts | 2 +- src/plugins/data/server/search/types.ts | 3 +- src/plugins/data/tsconfig.json | 1 - .../server/data_views_service_factory.ts | 9 +- .../fetcher/index_patterns_fetcher.test.ts | 45 +++++- .../server/fetcher/index_patterns_fetcher.ts | 10 +- src/plugins/data_views/server/index.ts | 1 + .../server/index_patterns_api_client.ts | 9 +- src/plugins/data_views/server/plugin.ts | 13 +- .../rest_api_routes/internal/fields_for.ts | 133 +++++++++--------- src/plugins/data_views/server/routes.ts | 3 +- src/plugins/data_views/server/types.ts | 5 +- .../log_entries_search_strategy.test.ts | 1 + x-pack/plugins/rollup/kibana.jsonc | 7 +- x-pack/plugins/rollup/server/plugin.ts | 6 +- x-pack/plugins/rollup/server/types.ts | 4 + x-pack/plugins/rollup/tsconfig.json | 1 + .../test_suites/common/index.ts | 1 + .../test_suites/common/rollups.ts | 44 ++++++ x-pack/test_serverless/tsconfig.json | 2 + 25 files changed, 252 insertions(+), 115 deletions(-) create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/rollups.ts diff --git a/src/plugins/data/kibana.jsonc b/src/plugins/data/kibana.jsonc index 90658599a87b..2881da532d63 100644 --- a/src/plugins/data/kibana.jsonc +++ b/src/plugins/data/kibana.jsonc @@ -28,7 +28,6 @@ ], "optionalPlugins": [ "usageCollection", - "taskManager", "security" ], "requiredBundles": [ diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 70fa72b1634c..1b2b0c3c78ca 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -12,10 +12,6 @@ import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/server'; -import type { - TaskManagerSetupContract, - TaskManagerStartContract, -} from '@kbn/task-manager-plugin/server'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { ConfigSchema } from '../config'; import type { ISearchSetup, ISearchStart } from './search'; @@ -55,7 +51,6 @@ export interface DataPluginSetupDependencies { expressions: ExpressionsServerSetup; usageCollection?: UsageCollectionSetup; fieldFormats: FieldFormatsSetup; - taskManager?: TaskManagerSetupContract; security?: SecurityPluginSetup; } @@ -63,7 +58,6 @@ export interface DataPluginStartDependencies { fieldFormats: FieldFormatsStart; logger: Logger; dataViews: DataViewsServerPluginStart; - taskManager?: TaskManagerStartContract; } export class DataServerPlugin @@ -90,14 +84,7 @@ export class DataServerPlugin public setup( core: CoreSetup, - { - bfetch, - expressions, - usageCollection, - fieldFormats, - taskManager, - security, - }: DataPluginSetupDependencies + { bfetch, expressions, usageCollection, fieldFormats, security }: DataPluginSetupDependencies ) { this.scriptsService.setup(core); const querySetup = this.queryService.setup(core); @@ -110,7 +97,6 @@ export class DataServerPlugin expressions, usageCollection, security, - taskManager, }); return { @@ -120,14 +106,10 @@ export class DataServerPlugin }; } - public start( - core: CoreStart, - { fieldFormats, dataViews, taskManager }: DataPluginStartDependencies - ) { + public start(core: CoreStart, { fieldFormats, dataViews }: DataPluginStartDependencies) { const search = this.searchService.start(core, { fieldFormats, indexPatterns: dataViews, - taskManager, }); const datatableUtilities = new DatatableUtilitiesService( search.aggs, diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 8630f2be1585..118d43ed14d2 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,6 +17,7 @@ export function createSearchSetupMock(): jest.Mocked { aggs: searchAggsSetupMock(), registerSearchStrategy: jest.fn(), searchSource: searchSourceMock.createSetupContract(), + enableRollups: jest.fn(), }; } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index f9d1c79390fb..0387cb820f16 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -25,10 +25,6 @@ import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { KbnServerError } from '@kbn/kibana-utils-plugin/server'; -import type { - TaskManagerSetupContract, - TaskManagerStartContract, -} from '@kbn/task-manager-plugin/server'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import type { @@ -107,7 +103,6 @@ export interface SearchServiceSetupDependencies { bfetch: BfetchServerSetup; expressions: ExpressionsServerSetup; usageCollection?: UsageCollectionSetup; - taskManager?: TaskManagerSetupContract; security?: SecurityPluginSetup; } @@ -115,7 +110,6 @@ export interface SearchServiceSetupDependencies { export interface SearchServiceStartDependencies { fieldFormats: FieldFormatsStart; indexPatterns: DataViewsServerPluginStart; - taskManager?: TaskManagerStartContract; } /** @internal */ @@ -131,6 +125,7 @@ export class SearchService implements Plugin { private sessionService: SearchSessionService; private asScoped!: ISearchStart['asScoped']; private searchAsInternalUser!: ISearchStrategy; + private rollupsEnabled: boolean = false; constructor( private initializerContext: PluginInitializerContext, @@ -145,7 +140,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup, - { bfetch, expressions, usageCollection, taskManager, security }: SearchServiceSetupDependencies + { bfetch, expressions, usageCollection, security }: SearchServiceSetupDependencies ): ISearchSetup { core.savedObjects.registerType(searchSessionSavedObjectType); const usage = usageCollection ? usageProvider(core) : undefined; @@ -261,12 +256,13 @@ export class SearchService implements Plugin { registerSearchStrategy: this.registerSearchStrategy, usage, searchSource: this.searchSourceService.setup(), + enableRollups: () => (this.rollupsEnabled = true), }; } public start( core: CoreStart, - { fieldFormats, indexPatterns, taskManager }: SearchServiceStartDependencies + { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { const { elasticsearch, savedObjects, uiSettings } = core; @@ -278,7 +274,7 @@ export class SearchService implements Plugin { indexPatterns, }); - this.asScoped = this.asScopedProvider(core); + this.asScoped = this.asScopedProvider(core, this.rollupsEnabled); return { aggs, searchAsInternalUser: this.searchAsInternalUser, @@ -516,7 +512,7 @@ export class SearchService implements Plugin { return deps.searchSessionsClient.extend(sessionId, expires); }; - private asScopedProvider = (core: CoreStart) => { + private asScopedProvider = (core: CoreStart, rollupsEnabled: boolean = false) => { const { elasticsearch, savedObjects, uiSettings } = core; const getSessionAsScoped = this.sessionService.asScopedProvider(core); return (request: KibanaRequest): IScopedSearchClient => { @@ -530,6 +526,7 @@ export class SearchService implements Plugin { uiSettings.asScopedToClient(savedObjectsClient) ), request, + rollupsEnabled, }; return { search: < 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 070d07c07c95..627bb5fe2929 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 @@ -64,6 +64,7 @@ describe('ES search strategy', () => { }, }, searchSessionsClient: createSearchSessionsClientMock(), + rollupsEnabled: true, } as unknown as SearchStrategyDependencies; const mockLegacyConfig$ = new BehaviorSubject({ elasticsearch: { @@ -233,6 +234,31 @@ describe('ES search strategy', () => { expect(method).toBe('POST'); expect(path).toBe('/foo-%E7%A8%8B/_rollup_search'); }); + + it("doesn't call the rollup API if the index is a rollup type BUT rollups are disabled", async () => { + mockApiCaller.mockResolvedValueOnce(mockRollupResponse); + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'foo-程', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockLegacyConfig$, + mockSearchConfig, + mockLogger + ); + + await esSearch + .search( + { + indexType: 'rollup', + params, + }, + {}, + { ...mockDeps, rollupsEnabled: false } + ) + .toPromise(); + + expect(mockApiCaller).toBeCalledTimes(0); + }); }); 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 e5567b45f1e0..298933907b8b 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 @@ -154,7 +154,7 @@ export const enhancedEsSearchStrategyProvider = ( throw new KbnServerError('Unknown indexType', 400); } - if (request.indexType === undefined) { + if (request.indexType === undefined || !deps.rollupsEnabled) { return asyncSearch(request, options, deps); } else { return from(rollupSearch(request, options, deps)); diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 50fc29334d22..8b94085c3f80 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -35,6 +35,7 @@ export interface SearchStrategyDependencies { uiSettingsClient: Pick; searchSessionsClient: IScopedSearchSessionsClient; request: KibanaRequest; + rollupsEnabled?: boolean; } export interface ISearchSetup { @@ -55,7 +56,7 @@ export interface ISearchSetup { * Used internally for telemetry */ usage?: SearchUsage; - + enableRollups: () => void; searchSource: ReturnType; } diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 450c85826591..73eb71508a89 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -25,7 +25,6 @@ "@kbn/field-formats-plugin", "@kbn/data-views-plugin", "@kbn/screenshot-mode-plugin", - "@kbn/task-manager-plugin", "@kbn/security-plugin", "@kbn/expressions-plugin", "@kbn/field-types", diff --git a/src/plugins/data_views/server/data_views_service_factory.ts b/src/plugins/data_views/server/data_views_service_factory.ts index fb5ae2c5afe3..ac27ad0bc809 100644 --- a/src/plugins/data_views/server/data_views_service_factory.ts +++ b/src/plugins/data_views/server/data_views_service_factory.ts @@ -25,6 +25,7 @@ interface DataViewsServiceFactoryDeps { uiSettings: UiSettingsServiceStart; fieldFormats: FieldFormatsStart; capabilities: CoreStart['capabilities']; + rollupsEnabled: boolean; } /** @@ -38,14 +39,18 @@ export const dataViewsServiceFactory = (deps: DataViewsServiceFactoryDeps) => request?: KibanaRequest, byPassCapabilities?: boolean ) { - const { logger, uiSettings, fieldFormats, capabilities } = deps; + const { logger, uiSettings, fieldFormats, capabilities, rollupsEnabled } = deps; const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); return new DataViewsService({ uiSettings: new UiSettingsServerToCommon(uiSettingsClient), savedObjectsClient: new SavedObjectsClientWrapper(savedObjectsClient), - apiClient: new IndexPatternsApiServer(elasticsearchClient, savedObjectsClient), + apiClient: new IndexPatternsApiServer( + elasticsearchClient, + savedObjectsClient, + rollupsEnabled + ), fieldFormats: formats, onError: (error) => { logger.error(error); diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts index f6f2d378fef7..6253c68d84fb 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.test.ts @@ -10,6 +10,19 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { IndexPatternsFetcher } from '.'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +const rollupResponse = { + foo: { + rollup_jobs: [ + { + index_pattern: 'foo', + job_id: '123', + rollup_index: 'foo', + fields: [], + }, + ], + }, +}; + describe('Index Pattern Fetcher - server', () => { let indexPatterns: IndexPatternsFetcher; let esClient: ReturnType; @@ -21,12 +34,40 @@ describe('Index Pattern Fetcher - server', () => { beforeEach(() => { jest.clearAllMocks(); esClient = elasticsearchServiceMock.createElasticsearchClient(); - indexPatterns = new IndexPatternsFetcher(esClient); + indexPatterns = new IndexPatternsFetcher(esClient, false, true); }); it('calls fieldcaps once', async () => { esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse); - indexPatterns = new IndexPatternsFetcher(esClient, true); + indexPatterns = new IndexPatternsFetcher(esClient, true, true); await indexPatterns.getFieldsForWildcard({ pattern: patternList }); expect(esClient.fieldCaps).toHaveBeenCalledTimes(1); }); + + it('calls rollup api when given rollup data view', async () => { + esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse); + esClient.rollup.getRollupIndexCaps.mockResponse( + rollupResponse as unknown as estypes.RollupGetRollupIndexCapsResponse + ); + indexPatterns = new IndexPatternsFetcher(esClient, true, true); + await indexPatterns.getFieldsForWildcard({ + pattern: patternList, + type: 'rollup', + rollupIndex: 'foo', + }); + expect(esClient.rollup.getRollupIndexCaps).toHaveBeenCalledTimes(1); + }); + + it("doesn't call rollup api when given rollup data view and rollups are disabled", async () => { + esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse); + esClient.rollup.getRollupIndexCaps.mockResponse( + rollupResponse as unknown as estypes.RollupGetRollupIndexCapsResponse + ); + indexPatterns = new IndexPatternsFetcher(esClient, true, false); + await indexPatterns.getFieldsForWildcard({ + pattern: patternList, + type: 'rollup', + rollupIndex: 'foo', + }); + expect(esClient.rollup.getRollupIndexCaps).toHaveBeenCalledTimes(0); + }); }); diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts index 3511cefd34d8..b1d22dc5523c 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -40,10 +40,16 @@ interface FieldSubType { export class IndexPatternsFetcher { private elasticsearchClient: ElasticsearchClient; private allowNoIndices: boolean; + private rollupsEnabled: boolean; - constructor(elasticsearchClient: ElasticsearchClient, allowNoIndices: boolean = false) { + constructor( + elasticsearchClient: ElasticsearchClient, + allowNoIndices: boolean = false, + rollupsEnabled: boolean = false + ) { this.elasticsearchClient = elasticsearchClient; this.allowNoIndices = allowNoIndices; + this.rollupsEnabled = rollupsEnabled; } /** @@ -81,7 +87,7 @@ export class IndexPatternsFetcher { fields: options.fields || ['*'], }); - if (type === 'rollup' && rollupIndex) { + if (this.rollupsEnabled && type === 'rollup' && rollupIndex) { const rollupFields: FieldDescriptor[] = []; const capabilityCheck = getCapabilitiesForRollupIndices( await this.elasticsearchClient.rollup.getRollupIndexCaps({ diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts index 553f1a48d1d6..33e93df3be89 100644 --- a/src/plugins/data_views/server/index.ts +++ b/src/plugins/data_views/server/index.ts @@ -10,6 +10,7 @@ export { getFieldByName, findIndexPatternById } from './utils'; export type { FieldDescriptor, RollupIndexCapability } from './fetcher'; export { IndexPatternsFetcher, getCapabilitiesForRollupIndices } from './fetcher'; export type { + DataViewsServerPluginSetup, DataViewsServerPluginStart, DataViewsServerPluginSetupDependencies, DataViewsServerPluginStartDependencies, diff --git a/src/plugins/data_views/server/index_patterns_api_client.ts b/src/plugins/data_views/server/index_patterns_api_client.ts index f470e7f8ed7d..0beb1efacf9b 100644 --- a/src/plugins/data_views/server/index_patterns_api_client.ts +++ b/src/plugins/data_views/server/index_patterns_api_client.ts @@ -16,7 +16,8 @@ export class IndexPatternsApiServer implements IDataViewsApiClient { esClient: ElasticsearchClient; constructor( elasticsearchClient: ElasticsearchClient, - private readonly savedObjectsClient: SavedObjectsClientContract + private readonly savedObjectsClient: SavedObjectsClientContract, + private readonly rollupsEnabled: boolean ) { this.esClient = elasticsearchClient; } @@ -29,7 +30,11 @@ export class IndexPatternsApiServer implements IDataViewsApiClient { indexFilter, fields, }: GetFieldsOptions) { - const indexPatterns = new IndexPatternsFetcher(this.esClient, allowNoIndex); + const indexPatterns = new IndexPatternsFetcher( + this.esClient, + allowNoIndex, + this.rollupsEnabled + ); return await indexPatterns .getFieldsForWildcard({ pattern, diff --git a/src/plugins/data_views/server/plugin.ts b/src/plugins/data_views/server/plugin.ts index fab72338bdb3..c96de2e4294f 100644 --- a/src/plugins/data_views/server/plugin.ts +++ b/src/plugins/data_views/server/plugin.ts @@ -33,6 +33,7 @@ export class DataViewsServerPlugin > { private readonly logger: Logger; + private rollupsEnabled: boolean = false; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('dataView'); @@ -46,7 +47,12 @@ export class DataViewsServerPlugin core.capabilities.registerProvider(capabilitiesProvider); const dataViewRestCounter = usageCollection?.createUsageCounter('dataViewsRestApi'); - registerRoutes(core.http, core.getStartServices, dataViewRestCounter); + registerRoutes( + core.http, + core.getStartServices, + () => this.rollupsEnabled, + dataViewRestCounter + ); expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); registerIndexPatternsUsageCollector(core.getStartServices, usageCollection); @@ -60,7 +66,9 @@ export class DataViewsServerPlugin }, }); - return {}; + return { + enableRollups: () => (this.rollupsEnabled = true), + }; } public start( @@ -72,6 +80,7 @@ export class DataViewsServerPlugin uiSettings, fieldFormats, capabilities, + rollupsEnabled: this.rollupsEnabled, }); return { diff --git a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts index 9951bedb8229..15d761935c0a 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/fields_for.ts @@ -111,86 +111,93 @@ const validate: FullValidationConfig = { }, }; -const handler: RequestHandler<{}, IQuery, IBody> = async (context, request, response) => { - const { asCurrentUser } = (await context.core).elasticsearch.client; - const indexPatterns = new IndexPatternsFetcher(asCurrentUser); - const { - pattern, - meta_fields: metaFields, - type, - rollup_index: rollupIndex, - allow_no_index: allowNoIndex, - include_unmapped: includeUnmapped, - } = request.query; - - // not available to get request - const indexFilter = request.body?.index_filter; - - let parsedFields: string[] = []; - let parsedMetaFields: string[] = []; - try { - parsedMetaFields = parseFields(metaFields); - parsedFields = parseFields(request.query.fields ?? []); - } catch (error) { - return response.badRequest(); - } - - try { - const { fields, indices } = await indexPatterns.getFieldsForWildcard({ +const handler: (isRollupsEnabled: () => boolean) => RequestHandler<{}, IQuery, IBody> = + (isRollupsEnabled) => async (context, request, response) => { + const { asCurrentUser } = (await context.core).elasticsearch.client; + const indexPatterns = new IndexPatternsFetcher(asCurrentUser, undefined, isRollupsEnabled()); + const { pattern, - metaFields: parsedMetaFields, + meta_fields: metaFields, type, - rollupIndex, - fieldCapsOptions: { - allow_no_indices: allowNoIndex || false, - includeUnmapped, - }, - indexFilter, - ...(parsedFields.length > 0 ? { fields: parsedFields } : {}), - }); + rollup_index: rollupIndex, + allow_no_index: allowNoIndex, + include_unmapped: includeUnmapped, + } = request.query; - const body: { fields: FieldDescriptorRestResponse[]; indices: string[] } = { fields, indices }; + // not available to get request + const indexFilter = request.body?.index_filter; - return response.ok({ - body, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - if ( - typeof error === 'object' && - !!error?.isBoom && - !!error?.output?.payload && - typeof error?.output?.payload === 'object' - ) { - const payload = error?.output?.payload; - return response.notFound({ - body: { - message: payload.message, - attributes: payload, + let parsedFields: string[] = []; + let parsedMetaFields: string[] = []; + try { + parsedMetaFields = parseFields(metaFields); + parsedFields = parseFields(request.query.fields ?? []); + } catch (error) { + return response.badRequest(); + } + + try { + const { fields, indices } = await indexPatterns.getFieldsForWildcard({ + pattern, + metaFields: parsedMetaFields, + type, + rollupIndex, + fieldCapsOptions: { + allow_no_indices: allowNoIndex || false, + includeUnmapped, + }, + indexFilter, + ...(parsedFields.length > 0 ? { fields: parsedFields } : {}), + }); + + const body: { fields: FieldDescriptorRestResponse[]; indices: string[] } = { + fields, + indices, + }; + + return response.ok({ + body, + headers: { + 'content-type': 'application/json', }, }); - } else { - return response.notFound(); + } catch (error) { + if ( + typeof error === 'object' && + !!error?.isBoom && + !!error?.output?.payload && + typeof error?.output?.payload === 'object' + ) { + const payload = error?.output?.payload; + return response.notFound({ + body: { + message: payload.message, + attributes: payload, + }, + }); + } else { + return response.notFound(); + } } - } -}; + }; -export const registerFieldForWildcard = ( +export const registerFieldForWildcard = async ( router: IRouter, getStartServices: StartServicesAccessor< DataViewsServerPluginStartDependencies, DataViewsServerPluginStart - > + >, + isRollupsEnabled: () => boolean ) => { + const configuredHandler = handler(isRollupsEnabled); + // handler - router.versioned.put({ path, access }).addVersion({ version, validate }, handler); - router.versioned.post({ path, access }).addVersion({ version, validate }, handler); + router.versioned.put({ path, access }).addVersion({ version, validate }, configuredHandler); + router.versioned.post({ path, access }).addVersion({ version, validate }, configuredHandler); router.versioned .get({ path, access }) .addVersion( { version, validate: { request: { query: querySchema }, response: validate.response } }, - handler + configuredHandler ); }; diff --git a/src/plugins/data_views/server/routes.ts b/src/plugins/data_views/server/routes.ts index ef0f342ca4fc..9c1da30bc99e 100644 --- a/src/plugins/data_views/server/routes.ts +++ b/src/plugins/data_views/server/routes.ts @@ -20,12 +20,13 @@ export function registerRoutes( DataViewsServerPluginStartDependencies, DataViewsServerPluginStart >, + isRollupsEnabled: () => boolean, dataViewRestCounter?: UsageCounter ) { const router = http.createRouter(); routes.forEach((route) => route(router, getStartServices, dataViewRestCounter)); - registerFieldForWildcard(router, getStartServices); + registerFieldForWildcard(router, getStartServices, isRollupsEnabled); registerHasDataViewsRoute(router); } diff --git a/src/plugins/data_views/server/types.ts b/src/plugins/data_views/server/types.ts index 5b8757332237..d595f6e04b27 100644 --- a/src/plugins/data_views/server/types.ts +++ b/src/plugins/data_views/server/types.ts @@ -53,8 +53,9 @@ export interface DataViewsServerPluginStart { /** * DataViews server plugin setup api */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DataViewsServerPluginSetup {} +export interface DataViewsServerPluginSetup { + enableRollups: () => void; +} /** * Data Views server setup dependencies diff --git a/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts index bb21053cfe9d..305f6292deb2 100644 --- a/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts +++ b/x-pack/plugins/logs_shared/server/services/log_entries/log_entries_search_strategy.test.ts @@ -298,6 +298,7 @@ const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ( savedObjectsClient: savedObjectsClientMock.create(), searchSessionsClient: createSearchSessionsClientMock(), request: httpServerMock.createKibanaRequest(), + rollupsEnabled: true, }); // using the official data mock from within x-pack doesn't type-check successfully, diff --git a/x-pack/plugins/rollup/kibana.jsonc b/x-pack/plugins/rollup/kibana.jsonc index 7bb5740ff8b2..73f0e76b16d7 100644 --- a/x-pack/plugins/rollup/kibana.jsonc +++ b/x-pack/plugins/rollup/kibana.jsonc @@ -13,13 +13,14 @@ "requiredPlugins": [ "management", "licensing", - "features" + "features", + "dataViews", + "data" ], "optionalPlugins": [ "home", "indexManagement", - "usageCollection", - "visTypeTimeseries" + "usageCollection" ], "requiredBundles": [ "kibanaUtils", diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 06416685ab50..409c730385db 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -30,8 +30,8 @@ export class RollupPlugin implements Plugin { } public setup( - { http, uiSettings, savedObjects, getStartServices }: CoreSetup, - { features, licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies + { http, uiSettings, getStartServices }: CoreSetup, + { features, licensing, indexManagement, usageCollection, dataViews, data }: Dependencies ) { this.license.setup( { @@ -103,6 +103,8 @@ export class RollupPlugin implements Plugin { if (indexManagement && indexManagement.indexDataEnricher) { indexManagement.indexDataEnricher.add(rollupDataEnricher); } + dataViews.enableRollups(); + data.search.enableRollups(); } start() {} diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index 177efe0915fc..29d2fe2e9977 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -12,6 +12,8 @@ import { VisTypeTimeseriesSetup } from '@kbn/vis-type-timeseries-plugin/server'; import { getCapabilitiesForRollupIndices } from '@kbn/data-plugin/server'; import { IndexManagementPluginSetup } from '@kbn/index-management-plugin/server'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { DataViewsServerPluginSetup } from '@kbn/data-views-plugin/server'; +import { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { License } from './services'; import { IndexPatternsFetcher } from './shared_imports'; @@ -24,6 +26,8 @@ export interface Dependencies { usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetup; + dataViews: DataViewsServerPluginSetup; + data: DataPluginSetup; } export interface RouteDependencies { diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json index 151c5151a0c1..366b44b2c33b 100644 --- a/x-pack/plugins/rollup/tsconfig.json +++ b/x-pack/plugins/rollup/tsconfig.json @@ -32,6 +32,7 @@ "@kbn/i18n-react", "@kbn/config-schema", "@kbn/shared-ux-router", + "@kbn/data-views-plugin", ], "exclude": [ diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/index.ts index 7f5c93b0dbaf..de30854beccf 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./security_users')); loadTestFile(require.resolve('./spaces')); loadTestFile(require.resolve('./security_response_headers')); + loadTestFile(require.resolve('./rollups')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts b/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts new file mode 100644 index 000000000000..47dd58f24275 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/rollups.ts @@ -0,0 +1,44 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { INITIAL_REST_VERSION_INTERNAL } from '@kbn/data-views-plugin/server/constants'; +import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common/src/constants'; +import { FIELDS_FOR_WILDCARD_PATH as BASE_URI } from '@kbn/data-views-plugin/common/constants'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('rollup data views - fields for wildcard', function () { + before(async () => { + await esArchiver.load('test/api_integration/fixtures/es_archiver/index_patterns/basic_index'); + }); + + after(async () => { + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/basic_index' + ); + }); + it('returns 200 and best effort response despite lack of rollup support', async () => { + const response = await supertest + .get(BASE_URI) + .query({ + pattern: 'basic_index', + type: 'rollup', + rollup_index: 'bar', + }) + .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION_INTERNAL) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'true'); + + expect(response.status).toBe(200); + expect(response.body.fields.length).toEqual(5); + }); + }); +} diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index 0b9c8cb7792a..8c431b253798 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -44,5 +44,7 @@ "@kbn/fleet-plugin", "@kbn/cases-plugin", "@kbn/test-subj-selector", + "@kbn/core-http-common", + "@kbn/data-views-plugin", ] }