From 9434083f356abccf3319b404ffb1fd92eb2937a1 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 22 Apr 2020 17:10:11 -0700 Subject: [PATCH 01/10] Convert server to NP. --- .sass-lint.yml | 1 - x-pack/index.js | 2 - x-pack/legacy/plugins/rollup/common/index.ts | 19 -- x-pack/legacy/plugins/rollup/index.ts | 43 ----- x-pack/legacy/plugins/rollup/kibana.json | 15 -- .../server/lib/__tests__/fixtures/jobs.js | 98 ---------- .../call_with_request_factory.ts | 28 --- .../license_pre_routing_factory.test.js | 62 ------ .../license_pre_routing_factory.ts | 43 ----- x-pack/legacy/plugins/rollup/server/plugin.ts | 95 ---------- .../plugins/rollup/server/routes/api/index.ts | 10 - .../server/routes/api/index_patterns.ts | 131 ------------- .../rollup/server/routes/api/indices.ts | 175 ----------------- .../plugins/rollup/server/routes/api/jobs.ts | 178 ------------------ .../rollup/server/routes/api/search.ts | 50 ----- x-pack/legacy/plugins/rollup/server/types.ts | 21 --- x-pack/legacy/plugins/rollup/tsconfig.json | 3 - x-pack/{legacy => }/plugins/rollup/README.md | 0 x-pack/plugins/rollup/common/index.ts | 9 + x-pack/plugins/rollup/kibana.json | 15 +- .../server/client/elasticsearch_rollup.ts | 0 .../plugins/rollup/server/collectors/index.ts | 0 .../rollup/server/collectors/register.ts | 0 .../rollup/server/config.ts} | 8 +- x-pack/plugins/rollup/server/index.ts | 10 +- .../server/lib/__tests__/fixtures/index.js | 0 .../server/lib/__tests__/fixtures/jobs.js | 98 ++++++++++ .../lib/__tests__/jobs_compatibility.js | 0 .../rollup/server/lib/format_es_error.ts | 78 ++++++++ .../rollup/server/lib}/is_es_error.ts | 0 .../rollup/server/lib/jobs_compatibility.ts | 0 .../rollup/server/lib/map_capabilities.ts | 0 .../lib/merge_capabilities_with_fields.ts | 0 .../server/lib/search_strategies/index.ts | 0 .../lib/interval_helper.test.js | 0 .../search_strategies/lib/interval_helper.ts | 0 .../register_rollup_search_strategy.test.js | 0 .../register_rollup_search_strategy.ts | 18 +- .../rollup_search_capabilities.test.js | 0 .../rollup_search_capabilities.ts | 2 +- .../rollup_search_request.test.js | 0 .../rollup_search_request.ts | 0 .../rollup_search_strategy.test.js | 0 .../rollup_search_strategy.ts | 14 +- x-pack/plugins/rollup/server/plugin.ts | 143 +++++++++++--- .../rollup/server/rollup_data_enricher.ts | 2 +- .../server/routes/api/index_patterns/index.ts | 12 ++ .../register_fields_for_wildcard_route.ts | 141 ++++++++++++++ .../rollup/server/routes/api/indices/index.ts | 14 ++ .../routes/api/indices/register_get_route.ts | 39 ++++ .../register_validate_index_pattern_route.ts | 142 ++++++++++++++ .../rollup/server/routes/api/jobs/index.ts | 20 ++ .../routes/api/jobs/register_create_route.ts | 49 +++++ .../routes/api/jobs/register_delete_route.ts | 51 +++++ .../routes/api/jobs/register_get_route.ts | 32 ++++ .../routes/api/jobs/register_start_route.ts | 48 +++++ .../routes/api/jobs/register_stop_route.ts | 49 +++++ .../rollup/server/routes/api/search}/index.ts | 9 +- .../api/search/register_search_route.ts | 47 +++++ x-pack/plugins/rollup/server/routes/index.ts | 19 ++ .../rollup/server/services/add_base_path.ts} | 4 +- .../rollup/server/services}/index.ts | 3 +- .../plugins/rollup/server/services/license.ts | 93 +++++++++ .../plugins/rollup/server/shared_imports.ts | 2 +- x-pack/plugins/rollup/server/types.ts | 45 +++++ x-pack/plugins/rollup/tsconfig.json | 3 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 68 files changed, 1168 insertions(+), 1027 deletions(-) delete mode 100644 x-pack/legacy/plugins/rollup/common/index.ts delete mode 100644 x-pack/legacy/plugins/rollup/index.ts delete mode 100644 x-pack/legacy/plugins/rollup/kibana.json delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/plugin.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/index.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/indices.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/search.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/types.ts delete mode 100644 x-pack/legacy/plugins/rollup/tsconfig.json rename x-pack/{legacy => }/plugins/rollup/README.md (100%) rename x-pack/{legacy => }/plugins/rollup/server/client/elasticsearch_rollup.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/collectors/index.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/collectors/register.ts (100%) rename x-pack/{legacy/plugins/rollup/server/lib/is_es_error/index.ts => plugins/rollup/server/config.ts} (53%) rename x-pack/{legacy => }/plugins/rollup/server/lib/__tests__/fixtures/index.js (100%) create mode 100644 x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js rename x-pack/{legacy => }/plugins/rollup/server/lib/__tests__/jobs_compatibility.js (100%) create mode 100644 x-pack/plugins/rollup/server/lib/format_es_error.ts rename x-pack/{legacy/plugins/rollup/server/lib/is_es_error => plugins/rollup/server/lib}/is_es_error.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/jobs_compatibility.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/map_capabilities.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/merge_capabilities_with_fields.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/index.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts (76%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts (98%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js (100%) rename x-pack/{legacy => }/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts (84%) rename x-pack/{legacy => }/plugins/rollup/server/rollup_data_enricher.ts (92%) create mode 100644 x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/indices/index.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/index.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts rename x-pack/{legacy/plugins/rollup/server => plugins/rollup/server/routes/api/search}/index.ts (51%) create mode 100644 x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts create mode 100644 x-pack/plugins/rollup/server/routes/index.ts rename x-pack/{legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts => plugins/rollup/server/services/add_base_path.ts} (66%) rename x-pack/{legacy/plugins/rollup/server/lib/license_pre_routing_factory => plugins/rollup/server/services}/index.ts (74%) create mode 100644 x-pack/plugins/rollup/server/services/license.ts rename x-pack/{legacy => }/plugins/rollup/server/shared_imports.ts (75%) create mode 100644 x-pack/plugins/rollup/server/types.ts create mode 100644 x-pack/plugins/rollup/tsconfig.json diff --git a/.sass-lint.yml b/.sass-lint.yml index 89735342a2d6f..9b31f3fae6d16 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -4,7 +4,6 @@ files: - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/vis_type_xy/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' diff --git a/x-pack/index.js b/x-pack/index.js index 43ae5c3e5c5dd..1df806cdb6cce 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -18,7 +18,6 @@ import { spaces } from './legacy/plugins/spaces'; import { canvas } from './legacy/plugins/canvas'; import { infra } from './legacy/plugins/infra'; import { taskManager } from './legacy/plugins/task_manager'; -import { rollup } from './legacy/plugins/rollup'; import { siem } from './legacy/plugins/siem'; import { remoteClusters } from './legacy/plugins/remote_clusters'; import { upgradeAssistant } from './legacy/plugins/upgrade_assistant'; @@ -45,7 +44,6 @@ module.exports = function(kibana) { indexManagement(kibana), infra(kibana), taskManager(kibana), - rollup(kibana), siem(kibana), remoteClusters(kibana), upgradeAssistant(kibana), diff --git a/x-pack/legacy/plugins/rollup/common/index.ts b/x-pack/legacy/plugins/rollup/common/index.ts deleted file mode 100644 index 526af055a3ef6..0000000000000 --- a/x-pack/legacy/plugins/rollup/common/index.ts +++ /dev/null @@ -1,19 +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 { LICENSE_TYPE_BASIC, LicenseType } from '../../../common/constants'; - -export const PLUGIN = { - ID: 'rollup', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, - getI18nName: (i18n: any): string => { - return i18n.translate('xpack.rollupJobs.appName', { - defaultMessage: 'Rollup jobs', - }); - }, -}; - -export * from '../../../../plugins/rollup/common'; diff --git a/x-pack/legacy/plugins/rollup/index.ts b/x-pack/legacy/plugins/rollup/index.ts deleted file mode 100644 index f33ae7cfee0a2..0000000000000 --- a/x-pack/legacy/plugins/rollup/index.ts +++ /dev/null @@ -1,43 +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/server'; -import { RollupSetup } from '../../../plugins/rollup/server'; -import { PLUGIN } from './common'; -import { plugin } from './server'; - -export function rollup(kibana: any) { - return new kibana.Plugin({ - id: PLUGIN.ID, - configPrefix: 'xpack.rollup', - require: ['kibana', 'elasticsearch', 'xpack_main'], - init(server: any) { - const { core: coreSetup, plugins } = server.newPlatform.setup; - const { usageCollection, visTypeTimeseries, indexManagement } = plugins; - - const rollupSetup = (plugins.rollup as unknown) as RollupSetup; - - const initContext = ({ - config: rollupSetup.__legacy.config, - logger: rollupSetup.__legacy.logger, - } as unknown) as PluginInitializerContext; - - const rollupPluginInstance = plugin(initContext); - - rollupPluginInstance.setup(coreSetup, { - usageCollection, - visTypeTimeseries, - indexManagement, - __LEGACY: { - plugins: { - xpack_main: server.plugins.xpack_main, - rollup: server.plugins[PLUGIN.ID], - }, - }, - }); - }, - }); -} diff --git a/x-pack/legacy/plugins/rollup/kibana.json b/x-pack/legacy/plugins/rollup/kibana.json deleted file mode 100644 index 78458c9218be3..0000000000000 --- a/x-pack/legacy/plugins/rollup/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "rollup", - "version": "kibana", - "requiredPlugins": [ - "home", - "index_management", - "visTypeTimeseries", - "indexPatternManagement" - ], - "optionalPlugins": [ - "usageCollection" - ], - "server": true, - "ui": false -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js b/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js deleted file mode 100644 index eb16b211da3fd..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js +++ /dev/null @@ -1,98 +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. - */ - -export const jobs = [ - { - "job_id" : "foo1", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "node" : [ - { - "agg" : "terms" - } - ], - "temperature" : [ - { - "agg" : "min" - }, - { - "agg" : "max" - }, - { - "agg" : "sum" - } - ], - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "UTC", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 5 - }, - { - "agg" : "sum" - } - ] - } - }, - { - "job_id" : "foo2", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "host" : [ - { - "agg" : "terms" - } - ], - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "UTC", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 20 - } - ] - } - }, - { - "job_id" : "foo3", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "PST", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 5 - }, - { - "agg" : "sum" - } - ] - } - } -]; diff --git a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts b/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts deleted file mode 100644 index 883b3552a7c02..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts +++ /dev/null @@ -1,28 +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 { ElasticsearchServiceSetup } from 'kibana/server'; -import { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../../client/elasticsearch_rollup'; - -const callWithRequest = once((elasticsearchService: ElasticsearchServiceSetup) => { - const config = { plugins: [elasticsearchJsPlugin] }; - return elasticsearchService.createClient('rollup', config); -}); - -export const callWithRequestFactory = ( - elasticsearchService: ElasticsearchServiceSetup, - request: any -) => { - return (...args: any[]) => { - return ( - callWithRequest(elasticsearchService) - .asScoped(request) - // @ts-ignore - .callAsCurrentUser(...args) - ); - }; -}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js deleted file mode 100644 index b6cea09e0ea3c..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js +++ /dev/null @@ -1,62 +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 expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '.'; -import { - LICENSE_STATUS_VALID, - LICENSE_STATUS_INVALID, -} from '../../../../../common/constants/license_status'; -import { kibanaResponseFactory } from '../../../../../../../src/core/server'; - -describe('licensePreRoutingFactory()', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - describe('status is invalid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_INVALID, - }; - }); - - it('replies with 403', () => { - const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => {}); - const stubRequest = {}; - const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory); - expect(response.status).to.be(403); - }); - }); - - describe('status is valid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_VALID, - }; - }); - - it('replies with nothing', () => { - const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => null); - const stubRequest = {}; - const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory); - expect(response).to.be(null); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts deleted file mode 100644 index 353510d96a00d..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts +++ /dev/null @@ -1,43 +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 { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'src/core/server'; -import { PLUGIN } from '../../../common'; -import { LICENSE_STATUS_VALID } from '../../../../../common/constants/license_status'; -import { ServerShim } from '../../types'; - -export const licensePreRoutingFactory = ( - server: ServerShim, - handler: RequestHandler -): RequestHandler => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - return function licensePreRouting( - ctx: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - const { status } = licenseCheckResults; - - if (status !== LICENSE_STATUS_VALID) { - return response.customError({ - body: { - message: licenseCheckResults.messsage, - }, - statusCode: 403, - }); - } - - return handler(ctx, request, response); - }; -}; diff --git a/x-pack/legacy/plugins/rollup/server/plugin.ts b/x-pack/legacy/plugins/rollup/server/plugin.ts deleted file mode 100644 index 05c22b030fff9..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/plugin.ts +++ /dev/null @@ -1,95 +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 { CoreSetup, Plugin, PluginInitializerContext, Logger } from 'src/core/server'; -import { first } from 'rxjs/operators'; -import { i18n } from '@kbn/i18n'; - -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; -import { IndexManagementPluginSetup } from '../../../../plugins/index_management/server'; -import { registerLicenseChecker } from '../../../server/lib/register_license_checker'; -import { PLUGIN } from '../common'; -import { ServerShim, RouteDependencies } from './types'; - -import { - registerIndicesRoute, - registerFieldsForWildcardRoute, - registerSearchRoute, - registerJobsRoute, -} from './routes/api'; - -import { registerRollupUsageCollector } from './collectors'; - -import { rollupDataEnricher } from './rollup_data_enricher'; -import { registerRollupSearchStrategy } from './lib/search_strategies'; - -export class RollupsServerPlugin implements Plugin { - log: Logger; - - constructor(private readonly initializerContext: PluginInitializerContext) { - this.log = initializerContext.logger.get(); - } - - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { - __LEGACY: serverShim, - usageCollection, - visTypeTimeseries, - indexManagement, - }: { - __LEGACY: ServerShim; - usageCollection?: UsageCollectionSetup; - visTypeTimeseries?: VisTypeTimeseriesSetup; - indexManagement?: IndexManagementPluginSetup; - } - ) { - const elasticsearch = await elasticsearchService.adminClient; - const router = http.createRouter(); - const routeDependencies: RouteDependencies = { - elasticsearch, - elasticsearchService, - router, - }; - - registerLicenseChecker( - serverShim as any, - PLUGIN.ID, - PLUGIN.getI18nName(i18n), - PLUGIN.MINIMUM_LICENSE_REQUIRED - ); - - registerIndicesRoute(routeDependencies, serverShim); - registerFieldsForWildcardRoute(routeDependencies, serverShim); - registerSearchRoute(routeDependencies, serverShim); - registerJobsRoute(routeDependencies, serverShim); - - if (usageCollection) { - this.initializerContext.config.legacy.globalConfig$ - .pipe(first()) - .toPromise() - .then(config => { - registerRollupUsageCollector(usageCollection, config.kibana.index); - }) - .catch(e => { - this.log.warn(`Registering Rollup collector failed: ${e}`); - }); - } - - if (indexManagement && indexManagement.indexDataEnricher) { - indexManagement.indexDataEnricher.add(rollupDataEnricher); - } - - if (visTypeTimeseries) { - const { addSearchStrategy } = visTypeTimeseries; - registerRollupSearchStrategy(routeDependencies, addSearchStrategy); - } - } - - start() {} - - stop() {} -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index.ts b/x-pack/legacy/plugins/rollup/server/routes/api/index.ts deleted file mode 100644 index 146c3e973f9ea..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/index.ts +++ /dev/null @@ -1,10 +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. - */ - -export { registerIndicesRoute } from './indices'; -export { registerFieldsForWildcardRoute } from './index_patterns'; -export { registerSearchRoute } from './search'; -export { registerJobsRoute } from './jobs'; diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts b/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts deleted file mode 100644 index 2516840bd9537..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts +++ /dev/null @@ -1,131 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; - -import { indexBy } from 'lodash'; -import { IndexPatternsFetcher } from '../../shared_imports'; -import { RouteDependencies, ServerShim } from '../../types'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; -import { mergeCapabilitiesWithFields, Field } from '../../lib/merge_capabilities_with_fields'; - -const parseMetaFields = (metaFields: string | string[]) => { - let parsedFields: string[] = []; - if (typeof metaFields === 'string') { - parsedFields = JSON.parse(metaFields); - } else { - parsedFields = metaFields; - } - return parsedFields; -}; - -const getFieldsForWildcardRequest = async (context: any, request: any, response: any) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; - const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); - const { pattern, meta_fields: metaFields } = request.query; - - let parsedFields: string[] = []; - try { - parsedFields = parseMetaFields(metaFields); - } catch (error) { - return response.badRequest({ - body: error, - }); - } - - try { - const fields = await indexPatterns.getFieldsForWildcard({ - pattern, - metaFields: parsedFields, - }); - - return response.ok({ - body: { fields }, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - return response.notFound(); - } -}; - -/** - * Get list of fields for rollup index pattern, in the format of regular index pattern fields - */ -export function registerFieldsForWildcardRoute(deps: RouteDependencies, legacy: ServerShim) { - const handler: RequestHandler = async (ctx, request, response) => { - const { params, meta_fields: metaFields } = request.query; - - try { - // Make call and use field information from response - const { payload } = await getFieldsForWildcardRequest(ctx, request, response); - const fields = payload.fields; - const parsedParams = JSON.parse(params); - const rollupIndex = parsedParams.rollup_index; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const rollupFields: Field[] = []; - const fieldsFromFieldCapsApi: { [key: string]: any } = indexBy(fields, 'name'); - const rollupIndexCapabilities = getCapabilitiesForRollupIndices( - await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: rollupIndex, - }) - )[rollupIndex].aggs; - // Keep meta fields - metaFields.forEach( - (field: string) => - fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) - ); - const mergedRollupFields = mergeCapabilitiesWithFields( - rollupIndexCapabilities, - fieldsFromFieldCapsApi, - rollupFields - ); - return response.ok({ body: { fields: mergedRollupFields } }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.get( - { - path: '/api/index_patterns/rollup/_fields_for_wildcard', - validate: { - query: schema.object({ - pattern: schema.string(), - meta_fields: schema.arrayOf(schema.string(), { - defaultValue: [], - }), - params: schema.string({ - validate(value) { - try { - const params = JSON.parse(value); - const keys = Object.keys(params); - const { rollup_index: rollupIndex } = params; - - if (!rollupIndex) { - return '[request query.params]: "rollup_index" is required'; - } else if (keys.length > 1) { - const invalidParams = keys.filter(key => key !== 'rollup_index'); - return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; - } - } catch (err) { - return '[request query.params]: expected JSON string'; - } - }, - }), - }), - }, - }, - licensePreRoutingFactory(legacy, handler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts b/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts deleted file mode 100644 index e78f09a71876b..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts +++ /dev/null @@ -1,175 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -type NumericField = - | 'long' - | 'integer' - | 'short' - | 'byte' - | 'scaled_float' - | 'double' - | 'float' - | 'half_float'; - -interface FieldCapability { - date?: any; - keyword?: any; - long?: any; - integer?: any; - short?: any; - byte?: any; - double?: any; - float?: any; - half_float?: any; - scaled_float?: any; -} - -interface FieldCapabilities { - fields: FieldCapability[]; -} - -function isNumericField(fieldCapability: FieldCapability) { - const numericTypes = [ - 'long', - 'integer', - 'short', - 'byte', - 'double', - 'float', - 'half_float', - 'scaled_float', - ]; - return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null); -} - -export function registerIndicesRoute(deps: RouteDependencies, legacy: ServerShim) { - const getIndicesHandler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const data = await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: '_all', - }); - return response.ok({ body: getCapabilitiesForRollupIndices(data) }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const validateIndexPatternHandler: RequestHandler = async ( - ctx, - request, - response - ) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const { indexPattern } = request.params; - const [fieldCapabilities, rollupIndexCapabilities]: [ - FieldCapabilities, - { [key: string]: any } - ] = await Promise.all([ - callWithRequest('rollup.fieldCapabilities', { indexPattern }), - callWithRequest('rollup.rollupIndexCapabilities', { indexPattern }), - ]); - - const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0; - const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0; - - const dateFields: string[] = []; - const numericFields: string[] = []; - const keywordFields: string[] = []; - - const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields); - - fieldCapabilitiesEntries.forEach( - ([fieldName, fieldCapability]: [string, FieldCapability]) => { - if (fieldCapability.date) { - dateFields.push(fieldName); - return; - } - - if (isNumericField(fieldCapability)) { - numericFields.push(fieldName); - return; - } - - if (fieldCapability.keyword) { - keywordFields.push(fieldName); - } - } - ); - - const body = { - doesMatchIndices, - doesMatchRollupIndices, - dateFields, - numericFields, - keywordFields, - }; - - return response.ok({ body }); - } catch (err) { - // 404s are still valid results. - if (err.statusCode === 404) { - const notFoundBody = { - doesMatchIndices: false, - doesMatchRollupIndices: false, - dateFields: [], - numericFields: [], - keywordFields: [], - }; - return response.ok({ body: notFoundBody }); - } - - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - - return response.internalError({ body: err }); - } - }; - - /** - * Returns a list of all rollup index names - */ - deps.router.get( - { - path: `${API_BASE_PATH}/indices`, - validate: false, - }, - licensePreRoutingFactory(legacy, getIndicesHandler) - ); - - /** - * Returns information on validity of an index pattern for creating a rollup job: - * - Does the index pattern match any indices? - * - Does the index pattern match rollup indices? - * - Which date fields, numeric fields, and keyword fields are available in the matching indices? - */ - deps.router.get( - { - path: `${API_BASE_PATH}/index_pattern_validity/{indexPattern}`, - validate: { - params: schema.object({ - indexPattern: schema.string(), - }), - }, - }, - licensePreRoutingFactory(legacy, validateIndexPatternHandler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts b/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts deleted file mode 100644 index e45713e2b807c..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts +++ /dev/null @@ -1,178 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -export function registerJobsRoute(deps: RouteDependencies, legacy: ServerShim) { - const getJobsHandler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const data = await callWithRequest('rollup.jobs'); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const createJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { id, ...rest } = request.body.job; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - // Create job. - await callWithRequest('rollup.createJob', { - id, - body: rest, - }); - // Then request the newly created job. - const results = await callWithRequest('rollup.job', { id }); - return response.ok({ body: results.jobs[0] }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const startJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - const data = await Promise.all( - jobIds.map((id: string) => callWithRequest('rollup.startJob', { id })) - ).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const stopJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - // For our API integration tests we need to wait for the jobs to be stopped - // in order to be able to delete them sequencially. - const { waitForCompletion } = request.query; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const stopRollupJob = (id: string) => - callWithRequest('rollup.stopJob', { - id, - waitForCompletion: waitForCompletion === 'true', - }); - const data = await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const deleteJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const data = await Promise.all( - jobIds.map((id: string) => callWithRequest('rollup.deleteJob', { id })) - ).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - // There is an issue opened on ES to handle the following error correctly - // https://github.com/elastic/elasticsearch/issues/42908 - // Until then we'll modify the response here. - if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) { - err.status = 400; - err.statusCode = 400; - err.displayName = 'Bad request'; - err.message = JSON.parse(err.response).task_failures[0].reason.reason; - } - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.get( - { - path: `${API_BASE_PATH}/jobs`, - validate: false, - }, - licensePreRoutingFactory(legacy, getJobsHandler) - ); - - deps.router.put( - { - path: `${API_BASE_PATH}/create`, - validate: { - body: schema.object({ - job: schema.object( - { - id: schema.string(), - }, - { unknowns: 'allow' } - ), - }), - }, - }, - licensePreRoutingFactory(legacy, createJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/start`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - query: schema.maybe( - schema.object({ - waitForCompletion: schema.maybe(schema.string()), - }) - ), - }, - }, - licensePreRoutingFactory(legacy, startJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/stop`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - }, - }, - licensePreRoutingFactory(legacy, stopJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/delete`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - }, - }, - licensePreRoutingFactory(legacy, deleteJobsHandler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/search.ts b/x-pack/legacy/plugins/rollup/server/routes/api/search.ts deleted file mode 100644 index 97999a4b5ce8d..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/search.ts +++ /dev/null @@ -1,50 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -export function registerSearchRoute(deps: RouteDependencies, legacy: ServerShim) { - const handler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - try { - const requests = request.body.map(({ index, query }: { index: string; query: any }) => - callWithRequest('rollup.search', { - index, - rest_total_hits_as_int: true, - body: query, - }) - ); - const data = await Promise.all(requests); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.post( - { - path: `${API_BASE_PATH}/search`, - validate: { - body: schema.arrayOf( - schema.object({ - index: schema.string(), - query: schema.any(), - }) - ), - }, - }, - licensePreRoutingFactory(legacy, handler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/types.ts b/x-pack/legacy/plugins/rollup/server/types.ts deleted file mode 100644 index bcc6770e9b8ee..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/types.ts +++ /dev/null @@ -1,21 +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 { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'src/core/server'; -import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; - -export interface ServerShim { - plugins: { - xpack_main: XPackMainPlugin; - rollup: any; - }; -} - -export interface RouteDependencies { - router: IRouter; - elasticsearchService: ElasticsearchServiceSetup; - elasticsearch: IClusterClient; -} diff --git a/x-pack/legacy/plugins/rollup/tsconfig.json b/x-pack/legacy/plugins/rollup/tsconfig.json deleted file mode 100644 index 618c6c3e97b57..0000000000000 --- a/x-pack/legacy/plugins/rollup/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig.json" -} diff --git a/x-pack/legacy/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md similarity index 100% rename from x-pack/legacy/plugins/rollup/README.md rename to x-pack/plugins/rollup/README.md diff --git a/x-pack/plugins/rollup/common/index.ts b/x-pack/plugins/rollup/common/index.ts index aeffa3dc3959f..e94726a6f3d95 100644 --- a/x-pack/plugins/rollup/common/index.ts +++ b/x-pack/plugins/rollup/common/index.ts @@ -4,6 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +export const PLUGIN = { + ID: 'rollup', + minimumLicenseType: basicLicense, +}; + export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns'; export const API_BASE_PATH = '/api/rollup'; diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json index 8f832f6c6a345..4c7dcb48a4d3f 100644 --- a/x-pack/plugins/rollup/kibana.json +++ b/x-pack/plugins/rollup/kibana.json @@ -4,6 +4,17 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "optionalPlugins": ["home", "indexManagement", "indexPatternManagement", "usageCollection"], - "requiredPlugins": ["management", "data"] + "requiredPlugins": [ + "indexPatternManagement", + "management", + "licensing", + "data" + ], + "optionalPlugins": [ + "home", + "indexManagement", + "usageCollection", + "visTypeTimeseries" + ], + "configPath": ["xpack", "rollup"] } diff --git a/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts rename to x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts diff --git a/x-pack/legacy/plugins/rollup/server/collectors/index.ts b/x-pack/plugins/rollup/server/collectors/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/collectors/index.ts rename to x-pack/plugins/rollup/server/collectors/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/collectors/register.ts b/x-pack/plugins/rollup/server/collectors/register.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/collectors/register.ts rename to x-pack/plugins/rollup/server/collectors/register.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts b/x-pack/plugins/rollup/server/config.ts similarity index 53% rename from x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts rename to x-pack/plugins/rollup/server/config.ts index a9a3c61472d8c..6d02600521c3a 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts +++ b/x-pack/plugins/rollup/server/config.ts @@ -4,4 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsError } from './is_es_error'; +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type RollupConfig = TypeOf; diff --git a/x-pack/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/index.ts index 4056842453776..78859a959a1e0 100644 --- a/x-pack/plugins/rollup/server/index.ts +++ b/x-pack/plugins/rollup/server/index.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; import { RollupPlugin } from './plugin'; +import { configSchema, RollupConfig } from './config'; -export const plugin = (initContext: PluginInitializerContext) => new RollupPlugin(initContext); +export const plugin = (pluginInitializerContext: PluginInitializerContext) => + new RollupPlugin(pluginInitializerContext); -export { RollupSetup } from './plugin'; +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/index.js b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/index.js rename to x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js diff --git a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js new file mode 100644 index 0000000000000..c03b7c33abe0a --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js @@ -0,0 +1,98 @@ +/* + * 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 const jobs = [ + { + job_id: 'foo1', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + node: [ + { + agg: 'terms', + }, + ], + temperature: [ + { + agg: 'min', + }, + { + agg: 'max', + }, + { + agg: 'sum', + }, + ], + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'UTC', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 5, + }, + { + agg: 'sum', + }, + ], + }, + }, + { + job_id: 'foo2', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + host: [ + { + agg: 'terms', + }, + ], + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'UTC', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 20, + }, + ], + }, + }, + { + job_id: 'foo3', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'PST', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 5, + }, + { + agg: 'sum', + }, + ], + }, + }, +]; diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/jobs_compatibility.js b/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/__tests__/jobs_compatibility.js rename to x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js diff --git a/x-pack/plugins/rollup/server/lib/format_es_error.ts b/x-pack/plugins/rollup/server/lib/format_es_error.ts new file mode 100644 index 0000000000000..9dde027cd6949 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/format_es_error.ts @@ -0,0 +1,78 @@ +/* + * 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. + */ + +function extractCausedByChain( + causedBy: Record = {}, + accumulator: string[] = [] +): string[] { + const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/camelcase + + if (reason) { + accumulator.push(reason); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + if (caused_by) { + return extractCausedByChain(caused_by, accumulator); + } + + return accumulator; +} + +/** + * Wraps an error thrown by the ES JS client into a Boom error response and returns it + * + * @param err Object Error thrown by ES JS client + * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages + */ +export function wrapEsError( + err: any, + statusCodeToMessageMap: Record = {} +): { message: string; body?: { cause?: string[] }; statusCode: number } { + const { statusCode, response } = err; + + const { + error: { + root_cause = [], // eslint-disable-line @typescript-eslint/camelcase + caused_by = undefined, // eslint-disable-line @typescript-eslint/camelcase + } = {}, + } = JSON.parse(response); + + // If no custom message if specified for the error's status code, just + // wrap the error as a Boom error response and return it + if (!statusCodeToMessageMap[statusCode]) { + // The caused_by chain has the most information so use that if it's available. If not then + // settle for the root_cause. + const causedByChain = extractCausedByChain(caused_by); + const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; + + return { + message: err.message, + statusCode, + body: { + cause: causedByChain.length ? causedByChain : defaultCause, + }, + }; + } + + // Otherwise, use the custom message to create a Boom error response and + // return it + const message = statusCodeToMessageMap[statusCode]; + return { message, statusCode }; +} + +export function formatEsError(err: any): any { + const { statusCode, message, body } = wrapEsError(err); + return { + statusCode, + body: { + message, + attributes: { + cause: body?.cause, + }, + }, + }; +} diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts b/x-pack/plugins/rollup/server/lib/is_es_error.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts rename to x-pack/plugins/rollup/server/lib/is_es_error.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts b/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts rename to x-pack/plugins/rollup/server/lib/jobs_compatibility.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts b/x-pack/plugins/rollup/server/lib/map_capabilities.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts rename to x-pack/plugins/rollup/server/lib/map_capabilities.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts b/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts rename to x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts b/x-pack/plugins/rollup/server/lib/search_strategies/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts similarity index 76% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts index 93c4c1b52140b..333863979ba95 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts @@ -3,18 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRollupSearchStrategy } from './rollup_search_strategy'; -import { getRollupSearchRequest } from './rollup_search_request'; -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; + import { AbstractSearchRequest, DefaultSearchCapabilities, AbstractSearchStrategy, -} from '../../../../../../../src/plugins/vis_type_timeseries/server'; -import { RouteDependencies } from '../../types'; +} from '../../../../../../src/plugins/vis_type_timeseries/server'; +import { CallWithRequestFactoryShim } from '../../types'; +import { getRollupSearchStrategy } from './rollup_search_strategy'; +import { getRollupSearchRequest } from './rollup_search_request'; +import { getRollupSearchCapabilities } from './rollup_search_capabilities'; export const registerRollupSearchStrategy = ( - { elasticsearchService }: RouteDependencies, + callWithRequestFactory: CallWithRequestFactoryShim, addSearchStrategy: (searchStrategy: any) => void ) => { const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); @@ -22,8 +23,9 @@ export const registerRollupSearchStrategy = ( const RollupSearchStrategy = getRollupSearchStrategy( AbstractSearchStrategy, RollupSearchRequest, - RollupSearchCapabilities + RollupSearchCapabilities, + callWithRequestFactory ); - addSearchStrategy(new RollupSearchStrategy(elasticsearchService)); + addSearchStrategy(new RollupSearchStrategy()); }; diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts similarity index 98% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts index 5a57129aa6039..151afe660847f 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { get, has } from 'lodash'; -import { KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'src/core/server'; import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts similarity index 84% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts index 9d5aad2c2d3bc..815fe163411b3 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { indexBy, isString } from 'lodash'; -import { ElasticsearchServiceSetup, KibanaRequest } from 'kibana/server'; -import { callWithRequestFactory } from '../call_with_request_factory'; +import { KibanaRequest } from 'src/core/server'; + +import { CallWithRequestFactoryShim } from '../../types'; import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; import { getCapabilitiesForRollupIndices } from '../map_capabilities'; @@ -20,13 +21,16 @@ const isIndexPatternValid = (indexPattern: string) => export const getRollupSearchStrategy = ( AbstractSearchStrategy: any, RollupSearchRequest: any, - RollupSearchCapabilities: any + RollupSearchCapabilities: any, + callWithRequestFactory: CallWithRequestFactoryShim ) => class RollupSearchStrategy extends AbstractSearchStrategy { name = 'rollup'; - constructor(elasticsearchService: ElasticsearchServiceSetup) { - super(elasticsearchService, callWithRequestFactory, RollupSearchRequest); + constructor() { + // TODO: When vis_type_timeseries and AbstractSearchStrategy are migrated to the NP, it + // shouldn't require elasticsearchService to be injected, and we can remove this null argument. + super(null, callWithRequestFactory, RollupSearchRequest); } getRollupData(req: KibanaRequest, indexPattern: string) { diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index ea6d197e22029..db1049462dc3e 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -4,20 +4,98 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server'; +declare module 'src/core/server' { + interface RequestHandlerContext { + rollup?: RollupContext; + } +} + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { + CoreSetup, + Plugin, + Logger, + KibanaRequest, + PluginInitializerContext, + IScopedClusterClient, + APICaller, + SharedGlobalConfig, +} from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { CONFIG_ROLLUPS } from '../common'; -export class RollupPlugin implements Plugin { - private readonly initContext: PluginInitializerContext; +import { PLUGIN, CONFIG_ROLLUPS } from '../common'; +import { Dependencies, CallWithRequestFactoryShim } from './types'; +import { registerApiRoutes } from './routes'; +import { License } from './services'; +import { registerRollupUsageCollector } from './collectors'; +import { rollupDataEnricher } from './rollup_data_enricher'; +import { IndexPatternsFetcher } from './shared_imports'; +import { registerRollupSearchStrategy } from './lib/search_strategies'; +import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; +import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; +import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; + +interface RollupContext { + client: IScopedClusterClient; +} + +export class RollupPlugin implements Plugin { + private readonly logger: Logger; + private readonly globalConfig$: Observable; + private readonly license: License; - constructor(initContext: PluginInitializerContext) { - this.initContext = initContext; + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + this.globalConfig$ = initializerContext.config.legacy.globalConfig$; + this.license = new License(); } - public setup(core: CoreSetup) { - core.uiSettings.register({ + public setup( + { http, uiSettings, elasticsearch }: CoreSetup, + { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies + ) { + this.license.setup( + { + pluginId: PLUGIN.ID, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.rollupJobs.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }, + { + licensing, + logger: this.logger, + } + ); + + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + const rollupEsClient = elasticsearch.createClient('rollup', esClientConfig); + http.registerRouteHandlerContext('rollup', (context, request) => { + return { + client: rollupEsClient.asScoped(request), + }; + }); + + registerApiRoutes({ + router: http.createRouter(), + license: this.license, + lib: { + isEsError, + formatEsError, + getCapabilitiesForRollupIndices, + mergeCapabilitiesWithFields, + }, + sharedImports: { + IndexPatternsFetcher, + }, + }); + + uiSettings.register({ [CONFIG_ROLLUPS]: { name: i18n.translate('xpack.rollupJobs.rollupIndexPatternsTitle', { defaultMessage: 'Enable rollup index patterns', @@ -33,22 +111,39 @@ export class RollupPlugin implements Plugin { }, }); - return { - __legacy: { - config: this.initContext.config, - logger: this.initContext.logger, - }, - }; - } + if (visTypeTimeseries) { + // TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. + const callWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest + ): APICaller => { + return rollupEsClient.asScoped(request).callAsCurrentUser; + // return (...args: any[]): APICaller => { + // return context.rollup!.client;//.callAsCurrentUser(...args); + // } + }; - public start() {} - public stop() {} -} + const { addSearchStrategy } = visTypeTimeseries; + registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); + } + + if (usageCollection) { + this.globalConfig$ + .pipe(first()) + .toPromise() + .then(globalConfig => { + registerRollupUsageCollector(usageCollection, globalConfig.kibana.index); + }) + .catch((e: any) => { + this.logger.warn(`Registering Rollup collector failed: ${e}`); + }); + } + + if (indexManagement && indexManagement.indexDataEnricher) { + indexManagement.indexDataEnricher.add(rollupDataEnricher); + } + } -export interface RollupSetup { - /** @deprecated */ - __legacy: { - config: PluginInitializerContext['config']; - logger: PluginInitializerContext['logger']; - }; + start() {} + stop() {} } diff --git a/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts b/x-pack/plugins/rollup/server/rollup_data_enricher.ts similarity index 92% rename from x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts rename to x-pack/plugins/rollup/server/rollup_data_enricher.ts index ad621f2d9ba80..b06cf971a6460 100644 --- a/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts +++ b/x-pack/plugins/rollup/server/rollup_data_enricher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Index } from '../../../../plugins/index_management/server'; +import { Index } from '../../../plugins/index_management/server'; export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: any) => { if (!indicesList || !indicesList.length) { diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts new file mode 100644 index 0000000000000..7bf525ca4aa98 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerFieldsForWildcardRoute } from './register_fields_for_wildcard_route'; + +export function registerIndexPatternsRoutes(dependencies: RouteDependencies) { + registerFieldsForWildcardRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts new file mode 100644 index 0000000000000..32f23314c5259 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts @@ -0,0 +1,141 @@ +/* + * 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 { indexBy } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { Field } from '../../../lib/merge_capabilities_with_fields'; +import { RouteDependencies } from '../../../types'; + +const parseMetaFields = (metaFields: string | string[]) => { + let parsedFields: string[] = []; + if (typeof metaFields === 'string') { + parsedFields = JSON.parse(metaFields); + } else { + parsedFields = metaFields; + } + return parsedFields; +}; + +const getFieldsForWildcardRequest = async ( + context: any, + request: any, + response: any, + IndexPatternsFetcher: any +) => { + const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); + const { pattern, meta_fields: metaFields } = request.query; + + let parsedFields: string[] = []; + try { + parsedFields = parseMetaFields(metaFields); + } catch (error) { + return response.badRequest({ + body: error, + }); + } + + try { + const fields = await indexPatterns.getFieldsForWildcard({ + pattern, + metaFields: parsedFields, + }); + + return response.ok({ + body: { fields }, + headers: { + 'content-type': 'application/json', + }, + }); + } catch (error) { + return response.notFound(); + } +}; + +/** + * Get list of fields for rollup index pattern, in the format of regular index pattern fields + */ +export const registerFieldsForWildcardRoute = ({ + router, + license, + lib: { isEsError, formatEsError, getCapabilitiesForRollupIndices, mergeCapabilitiesWithFields }, + sharedImports: { IndexPatternsFetcher }, +}: RouteDependencies) => { + const querySchema = schema.object({ + pattern: schema.string(), + meta_fields: schema.arrayOf(schema.string(), { + defaultValue: [], + }), + params: schema.string({ + validate(value) { + try { + const params = JSON.parse(value); + const keys = Object.keys(params); + const { rollup_index: rollupIndex } = params; + + if (!rollupIndex) { + return '[request query.params]: "rollup_index" is required'; + } else if (keys.length > 1) { + const invalidParams = keys.filter(key => key !== 'rollup_index'); + return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; + } + } catch (err) { + return '[request query.params]: expected JSON string'; + } + }, + }), + }); + + router.get( + { + path: '/api/index_patterns/rollup/_fields_for_wildcard', + validate: { + query: querySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { params, meta_fields: metaFields } = request.query; + + try { + // Make call and use field information from response + const { payload } = await getFieldsForWildcardRequest( + context, + request, + response, + IndexPatternsFetcher + ); + const fields = payload.fields; + const parsedParams = JSON.parse(params); + const rollupIndex = parsedParams.rollup_index; + const rollupFields: Field[] = []; + const fieldsFromFieldCapsApi: { [key: string]: any } = indexBy(fields, 'name'); + const rollupIndexCapabilities = getCapabilitiesForRollupIndices( + await context.rollup!.client.callAsCurrentUser('rollup.rollupIndexCapabilities', { + indexPattern: rollupIndex, + }) + )[rollupIndex].aggs; + + // Keep meta fields + metaFields.forEach( + (field: string) => + fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) + ); + + const mergedRollupFields = mergeCapabilitiesWithFields( + rollupIndexCapabilities, + fieldsFromFieldCapsApi, + rollupFields + ); + return response.ok({ body: { fields: mergedRollupFields } }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/index.ts b/x-pack/plugins/rollup/server/routes/api/indices/index.ts new file mode 100644 index 0000000000000..0aa5772b56991 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/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 { RouteDependencies } from '../../../types'; +import { registerGetRoute } from './register_get_route'; +import { registerValidateIndexPatternRoute } from './register_validate_index_pattern_route'; + +export function registerIndicesRoutes(dependencies: RouteDependencies) { + registerGetRoute(dependencies); + registerValidateIndexPatternRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts new file mode 100644 index 0000000000000..3521650c1dc3e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts @@ -0,0 +1,39 @@ +/* + * 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 { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns a list of all rollup index names + */ +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError, getCapabilitiesForRollupIndices }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/indices'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const data = await context.rollup!.client.callAsCurrentUser( + 'rollup.rollupIndexCapabilities', + { + indexPattern: '_all', + } + ); + return response.ok({ body: getCapabilitiesForRollupIndices(data) }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts new file mode 100644 index 0000000000000..9e22060b9beb7 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts @@ -0,0 +1,142 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +type NumericField = + | 'long' + | 'integer' + | 'short' + | 'byte' + | 'scaled_float' + | 'double' + | 'float' + | 'half_float'; + +interface FieldCapability { + date?: any; + keyword?: any; + long?: any; + integer?: any; + short?: any; + byte?: any; + double?: any; + float?: any; + half_float?: any; + scaled_float?: any; +} + +interface FieldCapabilities { + fields: FieldCapability[]; +} + +function isNumericField(fieldCapability: FieldCapability) { + const numericTypes = [ + 'long', + 'integer', + 'short', + 'byte', + 'double', + 'float', + 'half_float', + 'scaled_float', + ]; + return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null); +} + +/** + * Returns information on validity of an index pattern for creating a rollup job: + * - Does the index pattern match any indices? + * - Does the index pattern match rollup indices? + * - Which date fields, numeric fields, and keyword fields are available in the matching indices? + */ +export const registerValidateIndexPatternRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/index_pattern_validity/{indexPattern}'), + validate: { + params: schema.object({ + indexPattern: schema.string(), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { indexPattern } = request.params; + const [fieldCapabilities, rollupIndexCapabilities]: [ + FieldCapabilities, + { [key: string]: any } + ] = await Promise.all([ + context.rollup!.client.callAsCurrentUser('rollup.fieldCapabilities', { indexPattern }), + context.rollup!.client.callAsCurrentUser('rollup.rollupIndexCapabilities', { + indexPattern, + }), + ]); + + const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0; + const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0; + + const dateFields: string[] = []; + const numericFields: string[] = []; + const keywordFields: string[] = []; + + const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields); + + fieldCapabilitiesEntries.forEach( + ([fieldName, fieldCapability]: [string, FieldCapability]) => { + if (fieldCapability.date) { + dateFields.push(fieldName); + return; + } + + if (isNumericField(fieldCapability)) { + numericFields.push(fieldName); + return; + } + + if (fieldCapability.keyword) { + keywordFields.push(fieldName); + } + } + ); + + const body = { + doesMatchIndices, + doesMatchRollupIndices, + dateFields, + numericFields, + keywordFields, + }; + + return response.ok({ body }); + } catch (err) { + // 404s are still valid results. + if (err.statusCode === 404) { + const notFoundBody = { + doesMatchIndices: false, + doesMatchRollupIndices: false, + dateFields: [], + numericFields: [], + keywordFields: [], + }; + return response.ok({ body: notFoundBody }); + } + + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/index.ts b/x-pack/plugins/rollup/server/routes/api/jobs/index.ts new file mode 100644 index 0000000000000..fe1d1c6109a88 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerCreateRoute } from './register_create_route'; +import { registerDeleteRoute } from './register_delete_route'; +import { registerGetRoute } from './register_get_route'; +import { registerStartRoute } from './register_start_route'; +import { registerStopRoute } from './register_stop_route'; + +export function registerJobsRoutes(dependencies: RouteDependencies) { + registerCreateRoute(dependencies); + registerDeleteRoute(dependencies); + registerGetRoute(dependencies); + registerStartRoute(dependencies); + registerStopRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts new file mode 100644 index 0000000000000..adf8c1da0af0e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts @@ -0,0 +1,49 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerCreateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.put( + { + path: addBasePath('/create'), + validate: { + body: schema.object({ + job: schema.object( + { + id: schema.string(), + }, + { unknowns: 'allow' } + ), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { id, ...rest } = request.body.job; + // Create job. + await context.rollup!.client.callAsCurrentUser('rollup.createJob', { + id, + body: rest, + }); + // Then request the newly created job. + const results = await context.rollup!.client.callAsCurrentUser('rollup.job', { id }); + return response.ok({ body: results.jobs[0] }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts new file mode 100644 index 0000000000000..32f7b3f35e163 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts @@ -0,0 +1,51 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerDeleteRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/delete'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + const data = await Promise.all( + jobIds.map((id: string) => + context.rollup!.client.callAsCurrentUser('rollup.deleteJob', { id }) + ) + ).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + // There is an issue opened on ES to handle the following error correctly + // https://github.com/elastic/elasticsearch/issues/42908 + // Until then we'll modify the response here. + if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) { + err.status = 400; + err.statusCode = 400; + err.displayName = 'Bad request'; + err.message = JSON.parse(err.response).task_failures[0].reason.reason; + } + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts new file mode 100644 index 0000000000000..a8d51f4639fc6 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.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 { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/jobs'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const data = await context.rollup!.client.callAsCurrentUser('rollup.jobs'); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts new file mode 100644 index 0000000000000..fb6f2b12ba52e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerStartRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/start'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + query: schema.maybe( + schema.object({ + waitForCompletion: schema.maybe(schema.string()), + }) + ), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + + const data = await Promise.all( + jobIds.map((id: string) => + context.rollup!.client.callAsCurrentUser('rollup.startJob', { id }) + ) + ).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts new file mode 100644 index 0000000000000..118d98e36e03c --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts @@ -0,0 +1,49 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerStopRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/stop'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + query: schema.object({ + waitForCompletion: schema.maybe(schema.string()), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + // For our API integration tests we need to wait for the jobs to be stopped + // in order to be able to delete them sequentially. + const { waitForCompletion } = request.query; + const stopRollupJob = (id: string) => + context.rollup!.client.callAsCurrentUser('rollup.stopJob', { + id, + waitForCompletion: waitForCompletion === 'true', + }); + const data = await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/legacy/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/routes/api/search/index.ts similarity index 51% rename from x-pack/legacy/plugins/rollup/server/index.ts rename to x-pack/plugins/rollup/server/routes/api/search/index.ts index 6bbd00ac6576e..2a2d823e79bc6 100644 --- a/x-pack/legacy/plugins/rollup/server/index.ts +++ b/x-pack/plugins/rollup/server/routes/api/search/index.ts @@ -3,7 +3,10 @@ * 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/server'; -import { RollupsServerPlugin } from './plugin'; -export const plugin = (ctx: PluginInitializerContext) => new RollupsServerPlugin(ctx); +import { RouteDependencies } from '../../../types'; +import { registerSearchRoute } from './register_search_route'; + +export function registerSearchRoutes(dependencies: RouteDependencies) { + registerSearchRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts new file mode 100644 index 0000000000000..c5c56336def1a --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -0,0 +1,47 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerSearchRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/search'), + validate: { + body: schema.arrayOf( + schema.object({ + index: schema.string(), + query: schema.any(), + }) + ), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const requests = request.body.map(({ index, query }: { index: string; query?: any }) => + context.rollup!.client.callAsCurrentUser('rollup.search', { + index, + rest_total_hits_as_int: true, + body: query, + }) + ); + const data = await Promise.all(requests); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/index.ts b/x-pack/plugins/rollup/server/routes/index.ts new file mode 100644 index 0000000000000..b25480855b4a2 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { RouteDependencies } from '../types'; + +import { registerIndexPatternsRoutes } from './api/index_patterns'; +import { registerIndicesRoutes } from './api/indices'; +import { registerJobsRoutes } from './api/jobs'; +import { registerSearchRoutes } from './api/search'; + +export function registerApiRoutes(dependencies: RouteDependencies) { + registerIndexPatternsRoutes(dependencies); + registerIndicesRoutes(dependencies); + registerJobsRoutes(dependencies); + registerSearchRoutes(dependencies); +} diff --git a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts b/x-pack/plugins/rollup/server/services/add_base_path.ts similarity index 66% rename from x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts rename to x-pack/plugins/rollup/server/services/add_base_path.ts index 787814d87dff9..7d7cce3aab334 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts +++ b/x-pack/plugins/rollup/server/services/add_base_path.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +import { API_BASE_PATH } from '../../common'; + +export const addBasePath = (uri: string): string => `${API_BASE_PATH}${uri}`; diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts b/x-pack/plugins/rollup/server/services/index.ts similarity index 74% rename from x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts rename to x-pack/plugins/rollup/server/services/index.ts index 0743e443955f4..7f79c4f446546 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts +++ b/x-pack/plugins/rollup/server/services/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licensePreRoutingFactory } from './license_pre_routing_factory'; +export { addBasePath } from './add_base_path'; +export { License } from './license'; diff --git a/x-pack/plugins/rollup/server/services/license.ts b/x-pack/plugins/rollup/server/services/license.ts new file mode 100644 index 0000000000000..bfd357867c3e2 --- /dev/null +++ b/x-pack/plugins/rollup/server/services/license.ts @@ -0,0 +1,93 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; + +import { LicensingPluginSetup } from '../../../licensing/server'; +import { LicenseType } from '../../../licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + private _isEsSecurityEnabled: boolean = false; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === 'valid'; + + // Retrieving security checks the results of GET /_xpack as well as license state, + // so we're also checking whether the security is disabled in elasticsearch.yml. + this._isEsSecurityEnabled = license.getFeature('security').isEnabled; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + get isEsSecurityEnabled() { + return this._isEsSecurityEnabled; + } +} diff --git a/x-pack/legacy/plugins/rollup/server/shared_imports.ts b/x-pack/plugins/rollup/server/shared_imports.ts similarity index 75% rename from x-pack/legacy/plugins/rollup/server/shared_imports.ts rename to x-pack/plugins/rollup/server/shared_imports.ts index 941610b97707f..09842f529abed 100644 --- a/x-pack/legacy/plugins/rollup/server/shared_imports.ts +++ b/x-pack/plugins/rollup/server/shared_imports.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { IndexPatternsFetcher } from '../../../../../src/plugins/data/server'; +export { IndexPatternsFetcher } from '../../../../src/plugins/data/server'; diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts new file mode 100644 index 0000000000000..c21d76400164e --- /dev/null +++ b/x-pack/plugins/rollup/server/types.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 { IRouter, APICaller, KibanaRequest } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; + +import { IndexManagementPluginSetup } from '../../index_management/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { License } from './services'; +import { IndexPatternsFetcher } from './shared_imports'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; +import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; +import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; + +export interface Dependencies { + indexManagement?: IndexManagementPluginSetup; + visTypeTimeseries?: VisTypeTimeseriesSetup; + usageCollection?: UsageCollectionSetup; + licensing: LicensingPluginSetup; +} + +export interface RouteDependencies { + router: IRouter; + license: License; + lib: { + isEsError: typeof isEsError; + formatEsError: typeof formatEsError; + getCapabilitiesForRollupIndices: typeof getCapabilitiesForRollupIndices; + mergeCapabilitiesWithFields: typeof mergeCapabilitiesWithFields; + }; + sharedImports: { + IndexPatternsFetcher: typeof IndexPatternsFetcher; + }; +} + +// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. +export type CallWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest +) => APICaller; diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json new file mode 100644 index 0000000000000..4082f16a5d91c --- /dev/null +++ b/x-pack/plugins/rollup/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1b77dfb168e88..c8bc11958472d 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12394,7 +12394,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", - "xpack.rollupJobs.appName": "ロールアップジョブ", "xpack.rollupJobs.appTitle": "ロールアップジョブ", "xpack.rollupJobs.breadcrumbsTitle": "ロールアップジョブ", "xpack.rollupJobs.create.backButton.label": "戻る", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ce2469f29b883..639ce5a6697b6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12398,7 +12398,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告", - "xpack.rollupJobs.appName": "汇总/打包作业", "xpack.rollupJobs.appTitle": "汇总/打包作业", "xpack.rollupJobs.breadcrumbsTitle": "汇总/打包作业", "xpack.rollupJobs.create.backButton.label": "上一步", From b41de04ffa972826cfc0337ec13972245ebb411f Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 22 Apr 2020 17:55:38 -0700 Subject: [PATCH 02/10] Update paths in README and code comments. - Note incorrect metric type for tracking requests. --- x-pack/plugins/rollup/README.md | 16 ++++++++-------- .../sections/job_create/steps_config/index.js | 2 +- .../public/crud_app/services/track_ui_metric.ts | 3 +++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md index 3647be38b6a09..b43f4d5981409 100644 --- a/x-pack/plugins/rollup/README.md +++ b/x-pack/plugins/rollup/README.md @@ -14,7 +14,7 @@ The rest of this doc dives into the implementation details of each of the above ## Create and manage rollup jobs -The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives [here](../../../plugins/rollup/public/crud_app) and uses endpoints registered [here](server/routes/api/jobs.js). +The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives in [public/crud_app](public/crud_app) and uses endpoints registered in [(server/routes/api/jobs](server/routes/api/jobs). Refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-getting-started.html) to understand rollup indices and how to create rollup jobs. @@ -22,22 +22,22 @@ Refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elast Kibana uses index patterns to consume and visualize rollup indices. Typically, Kibana can inspect the indices captured by an index pattern, identify its aggregations and fields, and determine how to consume the data. Rollup indices don't contain this type of information, so we predefine how to consume a rollup index pattern with the type and typeMeta fields on the index pattern saved object. All rollup index patterns have `type` defined as "rollup" and `typeMeta` defined as an object of the index pattern's capabilities. -In the Index Pattern app, the "Create index pattern" button includes a context menu when a rollup index is detected. This menu offers items for creating a standard index pattern and a rollup index pattern. A [rollup config is registered to index pattern creation extension point](../../../plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js). The context menu behavior in particular uses the `getIndexPatternCreationOption()` method. When the user chooses to create a rollup index pattern, this config changes the behavior of the index pattern creation wizard: +In the Index Pattern app, the "Create index pattern" button includes a context menu when a rollup index is detected. This menu offers items for creating a standard index pattern and a rollup index pattern. A [rollup config is registered to index pattern creation extension point](public/index_pattern_creation/rollup_index_pattern_creation_config.js). The context menu behavior in particular uses the `getIndexPatternCreationOption()` method. When the user chooses to create a rollup index pattern, this config changes the behavior of the index pattern creation wizard: 1. Adds a `Rollup` badge to rollup indices using `getIndexTags()`. 2. Enforces index pattern rules using `checkIndicesForErrors()`. Rollup index patterns must match **one** rollup index, and optionally, any number of regular indices. A rollup index pattern configured with one or more regular indices is known as a "hybrid" index pattern. This allows the user to visualize historical (rollup) data and live (regular) data in the same visualization. -3. Routes to this plugin's [rollup `_fields_for_wildcard` endpoint](server/routes/api/index_patterns.js), instead of the standard one, using `getFetchForWildcardOptions()`, so that the internal rollup data field names are mapped to the original field names. +3. Routes to this plugin's [rollup `_fields_for_wildcard` endpoint](server/routes/api/index_patterns/register_fields_for_wildcard_route.ts), instead of the standard one, using `getFetchForWildcardOptions()`, so that the internal rollup data field names are mapped to the original field names. 4. Writes additional information about aggregations, fields, histogram interval, and date histogram interval and timezone to the rollup index pattern saved object using `getIndexPatternMappings()`. This collection of information is referred to as its "capabilities". -Once a rollup index pattern is created, it is tagged with `Rollup` in the list of index patterns, and its details page displays capabilities information. This is done by registering [yet another config for the index pattern list](../../../plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js) extension points. +Once a rollup index pattern is created, it is tagged with `Rollup` in the list of index patterns, and its details page displays capabilities information. This is done by registering [yet another config for the index pattern list](public/index_pattern_list/rollup_index_pattern_list_config.js) extension points. ## Create visualizations from rollup index patterns This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. When Visualize sends search requests, this plugin routes the requests to the [Elasticsearch rollup search endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html), which searches the special document structure within rollup indices. The visualization options available to users are based on the capabilities of the rollup index pattern they're visualizing. -Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](../../../plugins/rollup/public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). +Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). -Limiting visualization editor options is done by [registering configs](../../../plugins/rollup/public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: +Limiting visualization editor options is done by [registering configs](public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: * Available aggregation types * Available fields for a particular aggregation * Default and base interval for histogram aggregation @@ -47,6 +47,6 @@ Limiting visualization editor options is done by [registering configs](../../../ In Index Management, similar to system indices, rollup indices are hidden by default. A toggle is provided to show rollup indices and add a badge to the table rows. This is done by using Index Management's extension points. -The toggle and badge are registered on client-side [here](../../../plugins/rollup/public/extend_index_management/index.js). +The toggle and badge are registered on the client-side in [public/extend_index_management](public/extend_index_management). -Additional data needed to filter rollup indices in Index Management is provided with a [data enricher](rollup_data_enricher.js). +Additional data needed to filter rollup indices in Index Management is provided with a [data enricher](rollup_data_enricher.ts). \ No newline at end of file diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js index 4a55c4679c3d8..eca624e16cb86 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js @@ -42,7 +42,7 @@ export const stepIds = [ * 1. getDefaultFields: (overrides) => object * 2. fieldValidations * - * See x-pack/plugins/rollup/public/crud_app/services/jobs.js for more information on override's shape + * See rollup/public/crud_app/services/jobs.js for more information on override's shape */ export const stepIdToStepConfigMap = { [STEP_LOGISTICS]: { diff --git a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts index aa1cc2dfea323..5d9340a140500 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts +++ b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts @@ -16,6 +16,9 @@ export { METRIC_TYPE }; export function trackUserRequest(request: Promise, actionType: string) { // Only track successful actions. return request.then(response => { + // NOTE: METRIC_TYPE.LOADED is probably the wrong metric type here. The correct metric type + // is more likely METRIC_TYPE.APPLICATION_USAGE. This change was introduced in + // https://github.com/elastic/kibana/pull/41113/files#diff-58ac12bdd1a3a05a24e69ff20633c482R20 trackUiMetric(METRIC_TYPE.LOADED, actionType); // We return the response immediately without waiting for the tracking request to resolve, // to avoid adding additional latency. From 649b17208114178f1b5b90396b645e5c74cc58d5 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 07:05:47 -0700 Subject: [PATCH 03/10] Remove rollup search strategy and rollup search endpoint. The data_enhanced plugin now provides this functionality. --- x-pack/plugins/rollup/README.md | 6 +- x-pack/plugins/rollup/kibana.json | 3 +- .../server/client/elasticsearch_rollup.ts | 15 -- .../server/lib/search_strategies/index.ts | 7 - .../lib/interval_helper.test.js | 78 -------- .../search_strategies/lib/interval_helper.ts | 17 -- .../register_rollup_search_strategy.test.js | 27 --- .../register_rollup_search_strategy.ts | 31 ---- .../rollup_search_capabilities.test.js | 156 ---------------- .../rollup_search_capabilities.ts | 115 ------------ .../rollup_search_request.test.js | 53 ------ .../rollup_search_request.ts | 28 --- .../rollup_search_strategy.test.js | 168 ------------------ .../rollup_search_strategy.ts | 82 --------- x-pack/plugins/rollup/server/plugin.ts | 23 +-- .../rollup/server/routes/api/search/index.ts | 12 -- .../api/search/register_search_route.ts | 47 ----- x-pack/plugins/rollup/server/routes/index.ts | 2 - x-pack/plugins/rollup/server/types.ts | 8 +- 19 files changed, 5 insertions(+), 873 deletions(-) delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/index.ts delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js delete mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts delete mode 100644 x-pack/plugins/rollup/server/routes/api/search/index.ts delete mode 100644 x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts diff --git a/x-pack/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md index b43f4d5981409..1e070132f0e30 100644 --- a/x-pack/plugins/rollup/README.md +++ b/x-pack/plugins/rollup/README.md @@ -33,11 +33,7 @@ Once a rollup index pattern is created, it is tagged with `Rollup` in the list o ## Create visualizations from rollup index patterns -This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. When Visualize sends search requests, this plugin routes the requests to the [Elasticsearch rollup search endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html), which searches the special document structure within rollup indices. The visualization options available to users are based on the capabilities of the rollup index pattern they're visualizing. - -Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). - -Limiting visualization editor options is done by [registering configs](public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: +This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. Limiting visualization editor options is done by [registering configs](public/visualize) to various vis extension points. These configs use information stored on the rollup index pattern to limit: * Available aggregation types * Available fields for a particular aggregation * Default and base interval for histogram aggregation diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json index 4c7dcb48a4d3f..3414b9d7e226a 100644 --- a/x-pack/plugins/rollup/kibana.json +++ b/x-pack/plugins/rollup/kibana.json @@ -13,8 +13,7 @@ "optionalPlugins": [ "home", "indexManagement", - "usageCollection", - "visTypeTimeseries" + "usageCollection" ], "configPath": ["xpack", "rollup"] } diff --git a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts index 840f66a056d2d..4403e4a95a13c 100644 --- a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts +++ b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts @@ -24,21 +24,6 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) method: 'GET', }); - rollup.search = ca({ - urls: [ - { - fmt: '/<%=index%>/_rollup_search', - req: { - index: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - rollup.fieldCapabilities = ca({ urls: [ { diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/index.ts b/x-pack/plugins/rollup/server/lib/search_strategies/index.ts deleted file mode 100644 index 7db0b38ea29dd..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/index.ts +++ /dev/null @@ -1,7 +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. - */ - -export { registerRollupSearchStrategy } from './register_rollup_search_strategy'; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js deleted file mode 100644 index 31baeadce6527..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js +++ /dev/null @@ -1,78 +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 { isCalendarInterval, leastCommonInterval } from './interval_helper'; - -describe('interval_helper', () => { - describe('isCalendarInterval', () => { - describe('calendar intervals', () => { - test('should return true for "w" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'w' })).toBeTruthy()); - - test('should return true for "M" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'M' })).toBeTruthy()); - - test('should return true for "y" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'y' })).toBeTruthy()); - - test('should return false for "w" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 2, unit: 'w' })).toBeFalsy()); - - test('should return false for "M" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 3, unit: 'M' })).toBeFalsy()); - - test('should return false for "y" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 4, unit: 'y' })).toBeFalsy()); - }); - - describe('fixed intervals', () => { - test('should return false for "ms" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 2, unit: 'ms' })).toBeFalsy()); - - test('should return false for "s" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 3, unit: 's' })).toBeFalsy()); - - test('should return false for "s" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 's' })).toBeFalsy()); - }); - - describe('mixed intervals', () => { - test('should return true for "m" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'm' })).toBeTruthy()); - - test('should return true for "h" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'h' })).toBeTruthy()); - - test('should return true for "d" intervals and value === 1', () => - expect(isCalendarInterval({ value: 1, unit: 'd' })).toBeTruthy()); - - test('should return false for "m" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 2, unit: 'm' })).toBeFalsy()); - - test('should return false for "h" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 3, unit: 'h' })).toBeFalsy()); - - test('should return false for "d" intervals and value !== 1', () => - expect(isCalendarInterval({ value: 4, unit: 'd' })).toBeFalsy()); - }); - }); -}); - -describe('leastCommonInterval', () => { - test('should return 1 as a least common interval for 0,1', () => - expect(leastCommonInterval(0, 1)).toBe(1)); - - test('should return 3 as a least common interval for 1,3', () => - expect(leastCommonInterval(1, 3)).toBe(3)); - - test('should return 15 as a least common interval for 12,5', () => - expect(leastCommonInterval(12, 5)).toBe(15)); - - test('should return 7 as a least common interval for 4,7', () => - expect(leastCommonInterval(4, 7)).toBe(7)); - - test('should not return least common interval (negative tests)', () => - expect(leastCommonInterval(0, 0)).toBeNaN()); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts deleted file mode 100644 index 91d73cecdf401..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.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 dateMath from '@elastic/datemath'; - -export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; - -export const leastCommonInterval = (num = 0, base = 0) => - Math.max(Math.ceil(num / base) * base, base); - -export const isCalendarInterval = ({ unit, value }: { unit: Unit; value: number }) => { - const { unitsMap } = dateMath; - return value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type); -}; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js deleted file mode 100644 index d466ebd69737e..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js +++ /dev/null @@ -1,27 +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 { registerRollupSearchStrategy } from './register_rollup_search_strategy'; - -describe('Register Rollup Search Strategy', () => { - let routeDependencies; - let addSearchStrategy; - - beforeEach(() => { - routeDependencies = { - router: jest.fn().mockName('router'), - elasticsearchService: jest.fn().mockName('elasticsearchService'), - elasticsearch: jest.fn().mockName('elasticsearch'), - }; - - addSearchStrategy = jest.fn().mockName('addSearchStrategy'); - }); - - test('should run initialization', () => { - registerRollupSearchStrategy(routeDependencies, addSearchStrategy); - - expect(addSearchStrategy).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts deleted file mode 100644 index 333863979ba95..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts +++ /dev/null @@ -1,31 +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 { - AbstractSearchRequest, - DefaultSearchCapabilities, - AbstractSearchStrategy, -} from '../../../../../../src/plugins/vis_type_timeseries/server'; -import { CallWithRequestFactoryShim } from '../../types'; -import { getRollupSearchStrategy } from './rollup_search_strategy'; -import { getRollupSearchRequest } from './rollup_search_request'; -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; - -export const registerRollupSearchStrategy = ( - callWithRequestFactory: CallWithRequestFactoryShim, - addSearchStrategy: (searchStrategy: any) => void -) => { - const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); - const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); - const RollupSearchStrategy = getRollupSearchStrategy( - AbstractSearchStrategy, - RollupSearchRequest, - RollupSearchCapabilities, - callWithRequestFactory - ); - - addSearchStrategy(new RollupSearchStrategy()); -}; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js deleted file mode 100644 index 800f7d2761907..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js +++ /dev/null @@ -1,156 +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 { getRollupSearchCapabilities } from './rollup_search_capabilities'; - -class DefaultSearchCapabilities { - constructor(request, fieldsCapabilities = {}) { - this.fieldsCapabilities = fieldsCapabilities; - this.parseInterval = jest.fn(interval => interval); - } -} - -describe('Rollup Search Capabilities', () => { - const testTimeZone = 'time_zone'; - const testInterval = '10s'; - const rollupIndex = 'rollupIndex'; - const request = {}; - - let RollupSearchCapabilities; - let fieldsCapabilities; - let rollupSearchCaps; - - beforeEach(() => { - RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); - fieldsCapabilities = { - [rollupIndex]: { - aggs: { - date_histogram: { - histogram_field: { - time_zone: testTimeZone, - interval: testInterval, - }, - }, - }, - }, - }; - - rollupSearchCaps = new RollupSearchCapabilities(request, fieldsCapabilities, rollupIndex); - }); - - test('should create instance of RollupSearchRequest', () => { - expect(rollupSearchCaps).toBeInstanceOf(DefaultSearchCapabilities); - expect(rollupSearchCaps.fieldsCapabilities).toBe(fieldsCapabilities); - expect(rollupSearchCaps.rollupIndex).toBe(rollupIndex); - }); - - test('should return the "timezone" for the rollup request', () => { - expect(rollupSearchCaps.searchTimezone).toBe(testTimeZone); - }); - - test('should return the default "interval" for the rollup request', () => { - expect(rollupSearchCaps.defaultTimeInterval).toBe(testInterval); - }); - - describe('getValidTimeInterval', () => { - let rollupJobInterval; - let userInterval; - let getSuitableUnit; - - beforeEach(() => { - rollupSearchCaps.parseInterval = jest - .fn() - .mockImplementationOnce(() => rollupJobInterval) - .mockImplementationOnce(() => userInterval); - - rollupSearchCaps.convertIntervalToUnit = jest.fn(() => userInterval); - rollupSearchCaps.getSuitableUnit = jest.fn(() => getSuitableUnit); - }); - - test('should return 1d as common interval for 5d(user interval) and 1d(rollup interval) - calendar intervals', () => { - rollupJobInterval = { - value: 1, - unit: 'd', - }; - userInterval = { - value: 5, - unit: 'd', - }; - - getSuitableUnit = 'd'; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1d'); - }); - - test('should return 1w as common interval for 7d(user interval) and 1d(rollup interval) - calendar intervals', () => { - rollupJobInterval = { - value: 1, - unit: 'd', - }; - userInterval = { - value: 7, - unit: 'd', - }; - - getSuitableUnit = 'w'; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); - }); - - test('should return 1w as common interval for 1d(user interval) and 1w(rollup interval) - calendar intervals', () => { - rollupJobInterval = { - value: 1, - unit: 'w', - }; - userInterval = { - value: 1, - unit: 'd', - }; - - getSuitableUnit = 'w'; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); - }); - - test('should return 2y as common interval for 0.1y(user interval) and 2y(rollup interval) - fixed intervals', () => { - rollupJobInterval = { - value: 2, - unit: 'y', - }; - userInterval = { - value: 0.1, - unit: 'y', - }; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('2y'); - }); - - test('should return 3h as common interval for 2h(user interval) and 3h(rollup interval) - fixed intervals', () => { - rollupJobInterval = { - value: 3, - unit: 'h', - }; - userInterval = { - value: 2, - unit: 'h', - }; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('3h'); - }); - - test('should return 6m as common interval for 4m(user interval) and 3m(rollup interval) - fixed intervals', () => { - rollupJobInterval = { - value: 3, - unit: 'm', - }; - userInterval = { - value: 4, - unit: 'm', - }; - - expect(rollupSearchCaps.getValidTimeInterval()).toBe('6m'); - }); - }); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts deleted file mode 100644 index 151afe660847f..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts +++ /dev/null @@ -1,115 +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 { get, has } from 'lodash'; -import { KibanaRequest } from 'src/core/server'; -import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; - -export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => - class RollupSearchCapabilities extends DefaultSearchCapabilities { - constructor( - req: KibanaRequest, - fieldsCapabilities: { [key: string]: any }, - rollupIndex: string - ) { - super(req, fieldsCapabilities); - - this.rollupIndex = rollupIndex; - this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {}); - } - - public get dateHistogram() { - const [dateHistogram] = Object.values(this.availableMetrics.date_histogram); - - return dateHistogram; - } - - public get defaultTimeInterval() { - return ( - this.dateHistogram.fixed_interval || - this.dateHistogram.calendar_interval || - /* - Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval] in the future. - We can remove the following line only for versions > 8.x - */ - this.dateHistogram.interval || - null - ); - } - - public get searchTimezone() { - return get(this.dateHistogram, 'time_zone', null); - } - - public get whiteListedMetrics() { - const baseRestrictions = this.createUiRestriction({ - count: this.createUiRestriction(), - }); - - const getFields = (fields: { [key: string]: any }) => - Object.keys(fields).reduce( - (acc, item) => ({ - ...acc, - [item]: true, - }), - this.createUiRestriction({}) - ); - - return Object.keys(this.availableMetrics).reduce( - (acc, item) => ({ - ...acc, - [item]: getFields(this.availableMetrics[item]), - }), - baseRestrictions - ); - } - - public get whiteListedGroupByFields() { - return this.createUiRestriction({ - everything: true, - terms: has(this.availableMetrics, 'terms'), - }); - } - - public get whiteListedTimerangeModes() { - return this.createUiRestriction({ - last_value: true, - }); - } - - getValidTimeInterval(userIntervalString: string) { - const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval); - const inRollupJobUnit = this.convertIntervalToUnit( - userIntervalString, - parsedRollupJobInterval.unit - ); - - const getValidCalendarInterval = () => { - let unit = parsedRollupJobInterval.unit; - - if (inRollupJobUnit.value > parsedRollupJobInterval.value) { - const inSeconds = this.convertIntervalToUnit(userIntervalString, 's'); - - unit = this.getSuitableUnit(inSeconds.value); - } - - return { - value: 1, - unit, - }; - }; - - const getValidFixedInterval = () => ({ - value: leastCommonInterval(inRollupJobUnit.value, parsedRollupJobInterval.value), - unit: parsedRollupJobInterval.unit, - }); - - const { value, unit } = (isCalendarInterval(parsedRollupJobInterval) - ? getValidCalendarInterval - : getValidFixedInterval)(); - - return `${value}${unit}`; - } - }; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js deleted file mode 100644 index 2ea0612140946..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js +++ /dev/null @@ -1,53 +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 { getRollupSearchRequest } from './rollup_search_request'; - -class AbstractSearchRequest { - indexPattern = 'indexPattern'; - callWithRequest = jest.fn(({ body }) => Promise.resolve(body)); -} - -describe('Rollup search request', () => { - let RollupSearchRequest; - - beforeEach(() => { - RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); - }); - - test('should create instance of RollupSearchRequest', () => { - const rollupSearchRequest = new RollupSearchRequest(); - - expect(rollupSearchRequest).toBeInstanceOf(AbstractSearchRequest); - expect(rollupSearchRequest.search).toBeDefined(); - expect(rollupSearchRequest.callWithRequest).toBeDefined(); - }); - - test('should send one request for single search', async () => { - const rollupSearchRequest = new RollupSearchRequest(); - const searches = [{ body: 'body', index: 'index' }]; - - await rollupSearchRequest.search(searches); - - expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(1); - expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledWith('rollup.search', { - body: 'body', - index: 'index', - rest_total_hits_as_int: true, - }); - }); - - test('should send multiple request for multi search', async () => { - const rollupSearchRequest = new RollupSearchRequest(); - const searches = [ - { body: 'body', index: 'index' }, - { body: 'body1', index: 'index' }, - ]; - - await rollupSearchRequest.search(searches); - - expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(2); - }); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts deleted file mode 100644 index 7e12d5286f34c..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts +++ /dev/null @@ -1,28 +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. - */ -const SEARCH_METHOD = 'rollup.search'; - -interface Search { - index: string; - body: { - [key: string]: any; - }; -} - -export const getRollupSearchRequest = (AbstractSearchRequest: any) => - class RollupSearchRequest extends AbstractSearchRequest { - async search(searches: Search[]) { - const requests = searches.map(({ body, index }) => - this.callWithRequest(SEARCH_METHOD, { - body, - index, - rest_total_hits_as_int: true, - }) - ); - - return await Promise.all(requests); - } - }; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js deleted file mode 100644 index 63f4628e36bfe..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js +++ /dev/null @@ -1,168 +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 { getRollupSearchStrategy } from './rollup_search_strategy'; - -describe('Rollup Search Strategy', () => { - let RollupSearchStrategy; - let RollupSearchRequest; - let RollupSearchCapabilities; - let callWithRequest; - let rollupResolvedData; - - const server = 'server'; - const request = 'request'; - const indexPattern = 'indexPattern'; - - beforeEach(() => { - class AbstractSearchStrategy { - getCallWithRequestInstance = jest.fn(() => callWithRequest); - - getFieldsForWildcard() { - return [ - { - name: 'day_of_week.terms.value', - type: 'object', - esTypes: ['object'], - searchable: false, - aggregatable: false, - }, - ]; - } - } - - RollupSearchRequest = jest.fn(); - RollupSearchCapabilities = jest.fn(() => 'capabilities'); - callWithRequest = jest.fn().mockImplementation(() => rollupResolvedData); - - RollupSearchStrategy = getRollupSearchStrategy( - AbstractSearchStrategy, - RollupSearchRequest, - RollupSearchCapabilities - ); - }); - - test('should create instance of RollupSearchRequest', () => { - const rollupSearchStrategy = new RollupSearchStrategy(server); - - expect(rollupSearchStrategy.name).toBe('rollup'); - }); - - describe('checkForViability', () => { - let rollupSearchStrategy; - const rollupIndex = 'rollupIndex'; - - beforeEach(() => { - rollupSearchStrategy = new RollupSearchStrategy(server); - rollupSearchStrategy.getRollupData = jest.fn(() => ({ - [rollupIndex]: { - rollup_jobs: [ - { - job_id: 'test', - rollup_index: rollupIndex, - index_pattern: 'kibana*', - fields: { - order_date: [ - { - agg: 'date_histogram', - delay: '1m', - interval: '1m', - time_zone: 'UTC', - }, - ], - day_of_week: [ - { - agg: 'terms', - }, - ], - }, - }, - ], - }, - })); - }); - - test('isViable should be false for invalid index', async () => { - const result = await rollupSearchStrategy.checkForViability(request, null); - - expect(result).toEqual({ - isViable: false, - capabilities: null, - }); - }); - - test('should get RollupSearchCapabilities for valid rollup index ', async () => { - await rollupSearchStrategy.checkForViability(request, rollupIndex); - - expect(RollupSearchCapabilities).toHaveBeenCalled(); - }); - }); - - describe('getRollupData', () => { - let rollupSearchStrategy; - - beforeEach(() => { - rollupSearchStrategy = new RollupSearchStrategy(server); - }); - - test('should return rollup data', async () => { - rollupResolvedData = Promise.resolve('data'); - - const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern); - - expect(callWithRequest).toHaveBeenCalledWith('rollup.rollupIndexCapabilities', { - indexPattern, - }); - expect(rollupSearchStrategy.getCallWithRequestInstance).toHaveBeenCalledWith(request); - expect(rollupData).toBe('data'); - }); - - test('should return empty object in case of exception', async () => { - rollupResolvedData = Promise.reject('data'); - - const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern); - - expect(rollupData).toEqual({}); - }); - }); - - describe('getFieldsForWildcard', () => { - let rollupSearchStrategy; - let fieldsCapabilities; - - const rollupIndex = 'rollupIndex'; - - beforeEach(() => { - rollupSearchStrategy = new RollupSearchStrategy(server); - fieldsCapabilities = { - [rollupIndex]: { - aggs: { - terms: { - day_of_week: { agg: 'terms' }, - }, - }, - }, - }; - }); - - test('should return fields for wildcard', async () => { - const fields = await rollupSearchStrategy.getFieldsForWildcard(request, indexPattern, { - fieldsCapabilities, - rollupIndex, - }); - - expect(fields).toEqual([ - { - aggregatable: true, - name: 'day_of_week', - readFromDocValues: true, - searchable: true, - type: 'object', - esTypes: ['object'], - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts deleted file mode 100644 index 815fe163411b3..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts +++ /dev/null @@ -1,82 +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 { indexBy, isString } from 'lodash'; -import { KibanaRequest } from 'src/core/server'; - -import { CallWithRequestFactoryShim } from '../../types'; -import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; -import { getCapabilitiesForRollupIndices } from '../map_capabilities'; - -const ROLLUP_INDEX_CAPABILITIES_METHOD = 'rollup.rollupIndexCapabilities'; - -const getRollupIndices = (rollupData: { [key: string]: any[] }) => Object.keys(rollupData); - -const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); -const isIndexPatternValid = (indexPattern: string) => - indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern); - -export const getRollupSearchStrategy = ( - AbstractSearchStrategy: any, - RollupSearchRequest: any, - RollupSearchCapabilities: any, - callWithRequestFactory: CallWithRequestFactoryShim -) => - class RollupSearchStrategy extends AbstractSearchStrategy { - name = 'rollup'; - - constructor() { - // TODO: When vis_type_timeseries and AbstractSearchStrategy are migrated to the NP, it - // shouldn't require elasticsearchService to be injected, and we can remove this null argument. - super(null, callWithRequestFactory, RollupSearchRequest); - } - - getRollupData(req: KibanaRequest, indexPattern: string) { - const callWithRequest = this.getCallWithRequestInstance(req); - - return callWithRequest(ROLLUP_INDEX_CAPABILITIES_METHOD, { - indexPattern, - }).catch(() => Promise.resolve({})); - } - - async checkForViability(req: KibanaRequest, indexPattern: string) { - let isViable = false; - let capabilities = null; - - if (isIndexPatternValid(indexPattern)) { - const rollupData = await this.getRollupData(req, indexPattern); - const rollupIndices = getRollupIndices(rollupData); - - isViable = rollupIndices.length === 1; - - if (isViable) { - const [rollupIndex] = rollupIndices; - const fieldsCapabilities = getCapabilitiesForRollupIndices(rollupData); - - capabilities = new RollupSearchCapabilities(req, fieldsCapabilities, rollupIndex); - } - } - - return { - isViable, - capabilities, - }; - } - - async getFieldsForWildcard( - req: KibanaRequest, - indexPattern: string, - { - fieldsCapabilities, - rollupIndex, - }: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string } - ) { - const fields = await super.getFieldsForWildcard(req, indexPattern); - const fieldsFromFieldCapsApi = indexBy(fields, 'name'); - const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs; - - return mergeCapabilitiesWithFields(rollupIndexCapabilities, fieldsFromFieldCapsApi); - } - }; diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index db1049462dc3e..620c77e3c0a56 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -16,23 +16,20 @@ import { CoreSetup, Plugin, Logger, - KibanaRequest, PluginInitializerContext, IScopedClusterClient, - APICaller, SharedGlobalConfig, } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { PLUGIN, CONFIG_ROLLUPS } from '../common'; -import { Dependencies, CallWithRequestFactoryShim } from './types'; +import { Dependencies } from './types'; import { registerApiRoutes } from './routes'; import { License } from './services'; import { registerRollupUsageCollector } from './collectors'; import { rollupDataEnricher } from './rollup_data_enricher'; import { IndexPatternsFetcher } from './shared_imports'; -import { registerRollupSearchStrategy } from './lib/search_strategies'; import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; import { isEsError } from './lib/is_es_error'; import { formatEsError } from './lib/format_es_error'; @@ -56,7 +53,7 @@ export class RollupPlugin implements Plugin { public setup( { http, uiSettings, elasticsearch }: CoreSetup, - { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies + { licensing, indexManagement, usageCollection }: Dependencies ) { this.license.setup( { @@ -111,22 +108,6 @@ export class RollupPlugin implements Plugin { }, }); - if (visTypeTimeseries) { - // TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. - const callWithRequestFactoryShim = ( - elasticsearchServiceShim: CallWithRequestFactoryShim, - request: KibanaRequest - ): APICaller => { - return rollupEsClient.asScoped(request).callAsCurrentUser; - // return (...args: any[]): APICaller => { - // return context.rollup!.client;//.callAsCurrentUser(...args); - // } - }; - - const { addSearchStrategy } = visTypeTimeseries; - registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); - } - if (usageCollection) { this.globalConfig$ .pipe(first()) diff --git a/x-pack/plugins/rollup/server/routes/api/search/index.ts b/x-pack/plugins/rollup/server/routes/api/search/index.ts deleted file mode 100644 index 2a2d823e79bc6..0000000000000 --- a/x-pack/plugins/rollup/server/routes/api/search/index.ts +++ /dev/null @@ -1,12 +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 { RouteDependencies } from '../../../types'; -import { registerSearchRoute } from './register_search_route'; - -export function registerSearchRoutes(dependencies: RouteDependencies) { - registerSearchRoute(dependencies); -} diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts deleted file mode 100644 index c5c56336def1a..0000000000000 --- a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts +++ /dev/null @@ -1,47 +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 { schema } from '@kbn/config-schema'; -import { addBasePath } from '../../../services'; -import { RouteDependencies } from '../../../types'; - -export const registerSearchRoute = ({ - router, - license, - lib: { isEsError, formatEsError }, -}: RouteDependencies) => { - router.post( - { - path: addBasePath('/search'), - validate: { - body: schema.arrayOf( - schema.object({ - index: schema.string(), - query: schema.any(), - }) - ), - }, - }, - license.guardApiRoute(async (context, request, response) => { - try { - const requests = request.body.map(({ index, query }: { index: string; query?: any }) => - context.rollup!.client.callAsCurrentUser('rollup.search', { - index, - rest_total_hits_as_int: true, - body: query, - }) - ); - const data = await Promise.all(requests); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }) - ); -}; diff --git a/x-pack/plugins/rollup/server/routes/index.ts b/x-pack/plugins/rollup/server/routes/index.ts index b25480855b4a2..fa8bec6eaecae 100644 --- a/x-pack/plugins/rollup/server/routes/index.ts +++ b/x-pack/plugins/rollup/server/routes/index.ts @@ -9,11 +9,9 @@ import { RouteDependencies } from '../types'; import { registerIndexPatternsRoutes } from './api/index_patterns'; import { registerIndicesRoutes } from './api/indices'; import { registerJobsRoutes } from './api/jobs'; -import { registerSearchRoutes } from './api/search'; export function registerApiRoutes(dependencies: RouteDependencies) { registerIndexPatternsRoutes(dependencies); registerIndicesRoutes(dependencies); registerJobsRoutes(dependencies); - registerSearchRoutes(dependencies); } diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index c21d76400164e..b00265badf848 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, APICaller, KibanaRequest } from 'src/core/server'; +import { IRouter } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; @@ -37,9 +37,3 @@ export interface RouteDependencies { IndexPatternsFetcher: typeof IndexPatternsFetcher; }; } - -// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. -export type CallWithRequestFactoryShim = ( - elasticsearchServiceShim: CallWithRequestFactoryShim, - request: KibanaRequest -) => APICaller; From bcb526ff303ebbf13bfe49e38444408b9f0d9660 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 08:36:18 -0700 Subject: [PATCH 04/10] Internationalize rollup prompt callout. --- .../components/rollup_prompt/rollup_prompt.js | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js index 42c950f0b0d74..9d81abf70a55d 100644 --- a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js +++ b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js @@ -5,21 +5,34 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiCallOut } from '@elastic/eui'; export const RollupPrompt = () => (

- Kibana's support for rollup index patterns is in beta. You might encounter issues using - these patterns in saved searches, visualizations, and dashboards. They are not supported in - some advanced features, such as Timelion, and Machine Learning. + {i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text', + { + defaultMessage: + "Kibana's support for rollup index patterns is in beta. You might encounter issues using " + + 'these patterns in saved searches, visualizations, and dashboards. They are not supported in ' + + 'some advanced features, such as Timelion, and Machine Learning.', + } + )}

- You can match a rollup index pattern against one rollup index and zero or more regular - indices. A rollup index pattern has limited metrics, fields, intervals, and aggregations. A - rollup index is limited to indices that have one job configuration, or multiple jobs with - compatible configurations. + {i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text', + { + defaultMessage: + 'You can match a rollup index pattern against one rollup index and zero or more regular ' + + 'indices. A rollup index pattern has limited metrics, fields, intervals, and aggregations. A ' + + 'rollup index is limited to indices that have one job configuration, or multiple jobs with ' + + 'compatible configurations.', + } + )}

); From 9e190e6b70f9cb3bf91247a2acc751f5a60b1c26 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 16:36:49 -0700 Subject: [PATCH 05/10] Fix API integration tests by removing tests for deleted rollup search endpoint. --- .../apis/management/rollup/index.js | 1 - .../apis/management/rollup/rollup_search.js | 53 ------------------- 2 files changed, 54 deletions(-) delete mode 100644 x-pack/test/api_integration/apis/management/rollup/rollup_search.js diff --git a/x-pack/test/api_integration/apis/management/rollup/index.js b/x-pack/test/api_integration/apis/management/rollup/index.js index b55622381bc3c..f04abf0050ec1 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index.js +++ b/x-pack/test/api_integration/apis/management/rollup/index.js @@ -8,6 +8,5 @@ export default function({ loadTestFile }) { describe('rollup', () => { loadTestFile(require.resolve('./rollup')); loadTestFile(require.resolve('./index_patterns_extensions')); - loadTestFile(require.resolve('./rollup_search')); }); } diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js deleted file mode 100644 index 4fd7b304dd78b..0000000000000 --- a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js +++ /dev/null @@ -1,53 +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 expect from '@kbn/expect'; - -import { registerHelpers } from './rollup.test_helpers'; -import { API_BASE_PATH } from './constants'; -import { getRandomString } from './lib'; - -export default function({ getService }) { - const supertest = getService('supertest'); - const es = getService('legacyEs'); - - const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers({ - supertest, - es, - }); - - describe('search', () => { - const URI = `${API_BASE_PATH}/search`; - - it('return a 404 if the rollup index does not exist', async () => { - const { body } = await supertest - .post(URI) - .set('kbn-xsrf', 'xxx') - .send([{ index: 'unknown', query: {} }]) - .expect(404); - - expect(body.message).to.contain('no such index [unknown]'); - }); - - it('should return a 200 when searching on existing rollup index', async () => { - // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS - const indexName = await createIndexWithMappings(); - const rollupIndex = getRandomString(); - await createJob(getJobPayload(indexName, undefined, rollupIndex)); - - const { body } = await supertest - .post(URI) - .set('kbn-xsrf', 'xxx') - .send([{ index: rollupIndex, query: { size: 0 } }]) - .expect(200); - - // make sure total hits is an integer and not an object - expect(body[0].hits.total).to.equal(0); - - await cleanUp(); - }); - }); -} From 5f9937d39fb2050226540240d29770a027d257d8 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 17:10:59 -0700 Subject: [PATCH 06/10] Fix TSVB rollup functional tests. --- test/functional/page_objects/visual_builder_page.ts | 7 +++++++ x-pack/test/functional/apps/rollup_job/tsvb.js | 1 + 2 files changed, 8 insertions(+) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 12962b3a5cdef..3a9474ef519c4 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -418,6 +418,13 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await PageObjects.header.waitUntilLoadingHasFinished(); } + public async setTimeField(index: number) { + await testSubjects.click('metricsIndexPatternFieldsSelect'); + const options = await find.allByCssSelector('.euiFilterSelectItem'); + await options[0].click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + public async setIntervalValue(value: string) { const el = await testSubjects.find('metricsIndexPatternInterval'); await el.clearValue(); diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index acb73b170fbf2..2a71bd7adca1b 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -84,6 +84,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName); await PageObjects.visualBuilder.setIntervalValue('1d'); await PageObjects.visualBuilder.setDropLastBucket(false); + await PageObjects.visualBuilder.setTimeField(0); await PageObjects.common.sleep(3000); const newValue = await PageObjects.visualBuilder.getMetricValue(); expect(newValue).to.eql('3'); From 226a04a23424c180e2e6309077311f7d697c1390 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 17:25:25 -0700 Subject: [PATCH 07/10] Revert "Remove rollup search strategy and rollup search endpoint. The data_enhanced plugin now provides this functionality." This reverts commit 649b17208114178f1b5b90396b645e5c74cc58d5. --- x-pack/plugins/rollup/README.md | 6 +- x-pack/plugins/rollup/kibana.json | 3 +- .../server/client/elasticsearch_rollup.ts | 15 ++ .../server/lib/search_strategies/index.ts | 7 + .../lib/interval_helper.test.js | 78 ++++++++ .../search_strategies/lib/interval_helper.ts | 17 ++ .../register_rollup_search_strategy.test.js | 27 +++ .../register_rollup_search_strategy.ts | 31 ++++ .../rollup_search_capabilities.test.js | 156 ++++++++++++++++ .../rollup_search_capabilities.ts | 115 ++++++++++++ .../rollup_search_request.test.js | 53 ++++++ .../rollup_search_request.ts | 28 +++ .../rollup_search_strategy.test.js | 168 ++++++++++++++++++ .../rollup_search_strategy.ts | 82 +++++++++ x-pack/plugins/rollup/server/plugin.ts | 23 ++- .../rollup/server/routes/api/search/index.ts | 12 ++ .../api/search/register_search_route.ts | 47 +++++ x-pack/plugins/rollup/server/routes/index.ts | 2 + x-pack/plugins/rollup/server/types.ts | 8 +- 19 files changed, 873 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/index.ts create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js create mode 100644 x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/search/index.ts create mode 100644 x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts diff --git a/x-pack/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md index 1e070132f0e30..b43f4d5981409 100644 --- a/x-pack/plugins/rollup/README.md +++ b/x-pack/plugins/rollup/README.md @@ -33,7 +33,11 @@ Once a rollup index pattern is created, it is tagged with `Rollup` in the list o ## Create visualizations from rollup index patterns -This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. Limiting visualization editor options is done by [registering configs](public/visualize) to various vis extension points. These configs use information stored on the rollup index pattern to limit: +This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. When Visualize sends search requests, this plugin routes the requests to the [Elasticsearch rollup search endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html), which searches the special document structure within rollup indices. The visualization options available to users are based on the capabilities of the rollup index pattern they're visualizing. + +Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). + +Limiting visualization editor options is done by [registering configs](public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: * Available aggregation types * Available fields for a particular aggregation * Default and base interval for histogram aggregation diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json index 3414b9d7e226a..4c7dcb48a4d3f 100644 --- a/x-pack/plugins/rollup/kibana.json +++ b/x-pack/plugins/rollup/kibana.json @@ -13,7 +13,8 @@ "optionalPlugins": [ "home", "indexManagement", - "usageCollection" + "usageCollection", + "visTypeTimeseries" ], "configPath": ["xpack", "rollup"] } diff --git a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts index 4403e4a95a13c..840f66a056d2d 100644 --- a/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts +++ b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts @@ -24,6 +24,21 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) method: 'GET', }); + rollup.search = ca({ + urls: [ + { + fmt: '/<%=index%>/_rollup_search', + req: { + index: { + type: 'string', + }, + }, + }, + ], + needBody: true, + method: 'POST', + }); + rollup.fieldCapabilities = ca({ urls: [ { diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/index.ts b/x-pack/plugins/rollup/server/lib/search_strategies/index.ts new file mode 100644 index 0000000000000..7db0b38ea29dd --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/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 { registerRollupSearchStrategy } from './register_rollup_search_strategy'; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js new file mode 100644 index 0000000000000..31baeadce6527 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js @@ -0,0 +1,78 @@ +/* + * 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 { isCalendarInterval, leastCommonInterval } from './interval_helper'; + +describe('interval_helper', () => { + describe('isCalendarInterval', () => { + describe('calendar intervals', () => { + test('should return true for "w" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'w' })).toBeTruthy()); + + test('should return true for "M" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'M' })).toBeTruthy()); + + test('should return true for "y" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'y' })).toBeTruthy()); + + test('should return false for "w" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 2, unit: 'w' })).toBeFalsy()); + + test('should return false for "M" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 3, unit: 'M' })).toBeFalsy()); + + test('should return false for "y" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 4, unit: 'y' })).toBeFalsy()); + }); + + describe('fixed intervals', () => { + test('should return false for "ms" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 2, unit: 'ms' })).toBeFalsy()); + + test('should return false for "s" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 3, unit: 's' })).toBeFalsy()); + + test('should return false for "s" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 's' })).toBeFalsy()); + }); + + describe('mixed intervals', () => { + test('should return true for "m" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'm' })).toBeTruthy()); + + test('should return true for "h" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'h' })).toBeTruthy()); + + test('should return true for "d" intervals and value === 1', () => + expect(isCalendarInterval({ value: 1, unit: 'd' })).toBeTruthy()); + + test('should return false for "m" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 2, unit: 'm' })).toBeFalsy()); + + test('should return false for "h" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 3, unit: 'h' })).toBeFalsy()); + + test('should return false for "d" intervals and value !== 1', () => + expect(isCalendarInterval({ value: 4, unit: 'd' })).toBeFalsy()); + }); + }); +}); + +describe('leastCommonInterval', () => { + test('should return 1 as a least common interval for 0,1', () => + expect(leastCommonInterval(0, 1)).toBe(1)); + + test('should return 3 as a least common interval for 1,3', () => + expect(leastCommonInterval(1, 3)).toBe(3)); + + test('should return 15 as a least common interval for 12,5', () => + expect(leastCommonInterval(12, 5)).toBe(15)); + + test('should return 7 as a least common interval for 4,7', () => + expect(leastCommonInterval(4, 7)).toBe(7)); + + test('should not return least common interval (negative tests)', () => + expect(leastCommonInterval(0, 0)).toBeNaN()); +}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts new file mode 100644 index 0000000000000..91d73cecdf401 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts @@ -0,0 +1,17 @@ +/* + * 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 dateMath from '@elastic/datemath'; + +export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; + +export const leastCommonInterval = (num = 0, base = 0) => + Math.max(Math.ceil(num / base) * base, base); + +export const isCalendarInterval = ({ unit, value }: { unit: Unit; value: number }) => { + const { unitsMap } = dateMath; + return value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type); +}; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js new file mode 100644 index 0000000000000..d466ebd69737e --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js @@ -0,0 +1,27 @@ +/* + * 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 { registerRollupSearchStrategy } from './register_rollup_search_strategy'; + +describe('Register Rollup Search Strategy', () => { + let routeDependencies; + let addSearchStrategy; + + beforeEach(() => { + routeDependencies = { + router: jest.fn().mockName('router'), + elasticsearchService: jest.fn().mockName('elasticsearchService'), + elasticsearch: jest.fn().mockName('elasticsearch'), + }; + + addSearchStrategy = jest.fn().mockName('addSearchStrategy'); + }); + + test('should run initialization', () => { + registerRollupSearchStrategy(routeDependencies, addSearchStrategy); + + expect(addSearchStrategy).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts new file mode 100644 index 0000000000000..333863979ba95 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts @@ -0,0 +1,31 @@ +/* + * 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 { + AbstractSearchRequest, + DefaultSearchCapabilities, + AbstractSearchStrategy, +} from '../../../../../../src/plugins/vis_type_timeseries/server'; +import { CallWithRequestFactoryShim } from '../../types'; +import { getRollupSearchStrategy } from './rollup_search_strategy'; +import { getRollupSearchRequest } from './rollup_search_request'; +import { getRollupSearchCapabilities } from './rollup_search_capabilities'; + +export const registerRollupSearchStrategy = ( + callWithRequestFactory: CallWithRequestFactoryShim, + addSearchStrategy: (searchStrategy: any) => void +) => { + const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); + const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); + const RollupSearchStrategy = getRollupSearchStrategy( + AbstractSearchStrategy, + RollupSearchRequest, + RollupSearchCapabilities, + callWithRequestFactory + ); + + addSearchStrategy(new RollupSearchStrategy()); +}; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js new file mode 100644 index 0000000000000..800f7d2761907 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js @@ -0,0 +1,156 @@ +/* + * 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 { getRollupSearchCapabilities } from './rollup_search_capabilities'; + +class DefaultSearchCapabilities { + constructor(request, fieldsCapabilities = {}) { + this.fieldsCapabilities = fieldsCapabilities; + this.parseInterval = jest.fn(interval => interval); + } +} + +describe('Rollup Search Capabilities', () => { + const testTimeZone = 'time_zone'; + const testInterval = '10s'; + const rollupIndex = 'rollupIndex'; + const request = {}; + + let RollupSearchCapabilities; + let fieldsCapabilities; + let rollupSearchCaps; + + beforeEach(() => { + RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); + fieldsCapabilities = { + [rollupIndex]: { + aggs: { + date_histogram: { + histogram_field: { + time_zone: testTimeZone, + interval: testInterval, + }, + }, + }, + }, + }; + + rollupSearchCaps = new RollupSearchCapabilities(request, fieldsCapabilities, rollupIndex); + }); + + test('should create instance of RollupSearchRequest', () => { + expect(rollupSearchCaps).toBeInstanceOf(DefaultSearchCapabilities); + expect(rollupSearchCaps.fieldsCapabilities).toBe(fieldsCapabilities); + expect(rollupSearchCaps.rollupIndex).toBe(rollupIndex); + }); + + test('should return the "timezone" for the rollup request', () => { + expect(rollupSearchCaps.searchTimezone).toBe(testTimeZone); + }); + + test('should return the default "interval" for the rollup request', () => { + expect(rollupSearchCaps.defaultTimeInterval).toBe(testInterval); + }); + + describe('getValidTimeInterval', () => { + let rollupJobInterval; + let userInterval; + let getSuitableUnit; + + beforeEach(() => { + rollupSearchCaps.parseInterval = jest + .fn() + .mockImplementationOnce(() => rollupJobInterval) + .mockImplementationOnce(() => userInterval); + + rollupSearchCaps.convertIntervalToUnit = jest.fn(() => userInterval); + rollupSearchCaps.getSuitableUnit = jest.fn(() => getSuitableUnit); + }); + + test('should return 1d as common interval for 5d(user interval) and 1d(rollup interval) - calendar intervals', () => { + rollupJobInterval = { + value: 1, + unit: 'd', + }; + userInterval = { + value: 5, + unit: 'd', + }; + + getSuitableUnit = 'd'; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('1d'); + }); + + test('should return 1w as common interval for 7d(user interval) and 1d(rollup interval) - calendar intervals', () => { + rollupJobInterval = { + value: 1, + unit: 'd', + }; + userInterval = { + value: 7, + unit: 'd', + }; + + getSuitableUnit = 'w'; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); + }); + + test('should return 1w as common interval for 1d(user interval) and 1w(rollup interval) - calendar intervals', () => { + rollupJobInterval = { + value: 1, + unit: 'w', + }; + userInterval = { + value: 1, + unit: 'd', + }; + + getSuitableUnit = 'w'; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); + }); + + test('should return 2y as common interval for 0.1y(user interval) and 2y(rollup interval) - fixed intervals', () => { + rollupJobInterval = { + value: 2, + unit: 'y', + }; + userInterval = { + value: 0.1, + unit: 'y', + }; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('2y'); + }); + + test('should return 3h as common interval for 2h(user interval) and 3h(rollup interval) - fixed intervals', () => { + rollupJobInterval = { + value: 3, + unit: 'h', + }; + userInterval = { + value: 2, + unit: 'h', + }; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('3h'); + }); + + test('should return 6m as common interval for 4m(user interval) and 3m(rollup interval) - fixed intervals', () => { + rollupJobInterval = { + value: 3, + unit: 'm', + }; + userInterval = { + value: 4, + unit: 'm', + }; + + expect(rollupSearchCaps.getValidTimeInterval()).toBe('6m'); + }); + }); +}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts new file mode 100644 index 0000000000000..151afe660847f --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts @@ -0,0 +1,115 @@ +/* + * 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 { get, has } from 'lodash'; +import { KibanaRequest } from 'src/core/server'; +import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; + +export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => + class RollupSearchCapabilities extends DefaultSearchCapabilities { + constructor( + req: KibanaRequest, + fieldsCapabilities: { [key: string]: any }, + rollupIndex: string + ) { + super(req, fieldsCapabilities); + + this.rollupIndex = rollupIndex; + this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {}); + } + + public get dateHistogram() { + const [dateHistogram] = Object.values(this.availableMetrics.date_histogram); + + return dateHistogram; + } + + public get defaultTimeInterval() { + return ( + this.dateHistogram.fixed_interval || + this.dateHistogram.calendar_interval || + /* + Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval] in the future. + We can remove the following line only for versions > 8.x + */ + this.dateHistogram.interval || + null + ); + } + + public get searchTimezone() { + return get(this.dateHistogram, 'time_zone', null); + } + + public get whiteListedMetrics() { + const baseRestrictions = this.createUiRestriction({ + count: this.createUiRestriction(), + }); + + const getFields = (fields: { [key: string]: any }) => + Object.keys(fields).reduce( + (acc, item) => ({ + ...acc, + [item]: true, + }), + this.createUiRestriction({}) + ); + + return Object.keys(this.availableMetrics).reduce( + (acc, item) => ({ + ...acc, + [item]: getFields(this.availableMetrics[item]), + }), + baseRestrictions + ); + } + + public get whiteListedGroupByFields() { + return this.createUiRestriction({ + everything: true, + terms: has(this.availableMetrics, 'terms'), + }); + } + + public get whiteListedTimerangeModes() { + return this.createUiRestriction({ + last_value: true, + }); + } + + getValidTimeInterval(userIntervalString: string) { + const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval); + const inRollupJobUnit = this.convertIntervalToUnit( + userIntervalString, + parsedRollupJobInterval.unit + ); + + const getValidCalendarInterval = () => { + let unit = parsedRollupJobInterval.unit; + + if (inRollupJobUnit.value > parsedRollupJobInterval.value) { + const inSeconds = this.convertIntervalToUnit(userIntervalString, 's'); + + unit = this.getSuitableUnit(inSeconds.value); + } + + return { + value: 1, + unit, + }; + }; + + const getValidFixedInterval = () => ({ + value: leastCommonInterval(inRollupJobUnit.value, parsedRollupJobInterval.value), + unit: parsedRollupJobInterval.unit, + }); + + const { value, unit } = (isCalendarInterval(parsedRollupJobInterval) + ? getValidCalendarInterval + : getValidFixedInterval)(); + + return `${value}${unit}`; + } + }; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js new file mode 100644 index 0000000000000..2ea0612140946 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js @@ -0,0 +1,53 @@ +/* + * 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 { getRollupSearchRequest } from './rollup_search_request'; + +class AbstractSearchRequest { + indexPattern = 'indexPattern'; + callWithRequest = jest.fn(({ body }) => Promise.resolve(body)); +} + +describe('Rollup search request', () => { + let RollupSearchRequest; + + beforeEach(() => { + RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); + }); + + test('should create instance of RollupSearchRequest', () => { + const rollupSearchRequest = new RollupSearchRequest(); + + expect(rollupSearchRequest).toBeInstanceOf(AbstractSearchRequest); + expect(rollupSearchRequest.search).toBeDefined(); + expect(rollupSearchRequest.callWithRequest).toBeDefined(); + }); + + test('should send one request for single search', async () => { + const rollupSearchRequest = new RollupSearchRequest(); + const searches = [{ body: 'body', index: 'index' }]; + + await rollupSearchRequest.search(searches); + + expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(1); + expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledWith('rollup.search', { + body: 'body', + index: 'index', + rest_total_hits_as_int: true, + }); + }); + + test('should send multiple request for multi search', async () => { + const rollupSearchRequest = new RollupSearchRequest(); + const searches = [ + { body: 'body', index: 'index' }, + { body: 'body1', index: 'index' }, + ]; + + await rollupSearchRequest.search(searches); + + expect(rollupSearchRequest.callWithRequest).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts new file mode 100644 index 0000000000000..7e12d5286f34c --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ +const SEARCH_METHOD = 'rollup.search'; + +interface Search { + index: string; + body: { + [key: string]: any; + }; +} + +export const getRollupSearchRequest = (AbstractSearchRequest: any) => + class RollupSearchRequest extends AbstractSearchRequest { + async search(searches: Search[]) { + const requests = searches.map(({ body, index }) => + this.callWithRequest(SEARCH_METHOD, { + body, + index, + rest_total_hits_as_int: true, + }) + ); + + return await Promise.all(requests); + } + }; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js new file mode 100644 index 0000000000000..63f4628e36bfe --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js @@ -0,0 +1,168 @@ +/* + * 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 { getRollupSearchStrategy } from './rollup_search_strategy'; + +describe('Rollup Search Strategy', () => { + let RollupSearchStrategy; + let RollupSearchRequest; + let RollupSearchCapabilities; + let callWithRequest; + let rollupResolvedData; + + const server = 'server'; + const request = 'request'; + const indexPattern = 'indexPattern'; + + beforeEach(() => { + class AbstractSearchStrategy { + getCallWithRequestInstance = jest.fn(() => callWithRequest); + + getFieldsForWildcard() { + return [ + { + name: 'day_of_week.terms.value', + type: 'object', + esTypes: ['object'], + searchable: false, + aggregatable: false, + }, + ]; + } + } + + RollupSearchRequest = jest.fn(); + RollupSearchCapabilities = jest.fn(() => 'capabilities'); + callWithRequest = jest.fn().mockImplementation(() => rollupResolvedData); + + RollupSearchStrategy = getRollupSearchStrategy( + AbstractSearchStrategy, + RollupSearchRequest, + RollupSearchCapabilities + ); + }); + + test('should create instance of RollupSearchRequest', () => { + const rollupSearchStrategy = new RollupSearchStrategy(server); + + expect(rollupSearchStrategy.name).toBe('rollup'); + }); + + describe('checkForViability', () => { + let rollupSearchStrategy; + const rollupIndex = 'rollupIndex'; + + beforeEach(() => { + rollupSearchStrategy = new RollupSearchStrategy(server); + rollupSearchStrategy.getRollupData = jest.fn(() => ({ + [rollupIndex]: { + rollup_jobs: [ + { + job_id: 'test', + rollup_index: rollupIndex, + index_pattern: 'kibana*', + fields: { + order_date: [ + { + agg: 'date_histogram', + delay: '1m', + interval: '1m', + time_zone: 'UTC', + }, + ], + day_of_week: [ + { + agg: 'terms', + }, + ], + }, + }, + ], + }, + })); + }); + + test('isViable should be false for invalid index', async () => { + const result = await rollupSearchStrategy.checkForViability(request, null); + + expect(result).toEqual({ + isViable: false, + capabilities: null, + }); + }); + + test('should get RollupSearchCapabilities for valid rollup index ', async () => { + await rollupSearchStrategy.checkForViability(request, rollupIndex); + + expect(RollupSearchCapabilities).toHaveBeenCalled(); + }); + }); + + describe('getRollupData', () => { + let rollupSearchStrategy; + + beforeEach(() => { + rollupSearchStrategy = new RollupSearchStrategy(server); + }); + + test('should return rollup data', async () => { + rollupResolvedData = Promise.resolve('data'); + + const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern); + + expect(callWithRequest).toHaveBeenCalledWith('rollup.rollupIndexCapabilities', { + indexPattern, + }); + expect(rollupSearchStrategy.getCallWithRequestInstance).toHaveBeenCalledWith(request); + expect(rollupData).toBe('data'); + }); + + test('should return empty object in case of exception', async () => { + rollupResolvedData = Promise.reject('data'); + + const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern); + + expect(rollupData).toEqual({}); + }); + }); + + describe('getFieldsForWildcard', () => { + let rollupSearchStrategy; + let fieldsCapabilities; + + const rollupIndex = 'rollupIndex'; + + beforeEach(() => { + rollupSearchStrategy = new RollupSearchStrategy(server); + fieldsCapabilities = { + [rollupIndex]: { + aggs: { + terms: { + day_of_week: { agg: 'terms' }, + }, + }, + }, + }; + }); + + test('should return fields for wildcard', async () => { + const fields = await rollupSearchStrategy.getFieldsForWildcard(request, indexPattern, { + fieldsCapabilities, + rollupIndex, + }); + + expect(fields).toEqual([ + { + aggregatable: true, + name: 'day_of_week', + readFromDocValues: true, + searchable: true, + type: 'object', + esTypes: ['object'], + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts new file mode 100644 index 0000000000000..815fe163411b3 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts @@ -0,0 +1,82 @@ +/* + * 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 { indexBy, isString } from 'lodash'; +import { KibanaRequest } from 'src/core/server'; + +import { CallWithRequestFactoryShim } from '../../types'; +import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; +import { getCapabilitiesForRollupIndices } from '../map_capabilities'; + +const ROLLUP_INDEX_CAPABILITIES_METHOD = 'rollup.rollupIndexCapabilities'; + +const getRollupIndices = (rollupData: { [key: string]: any[] }) => Object.keys(rollupData); + +const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); +const isIndexPatternValid = (indexPattern: string) => + indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern); + +export const getRollupSearchStrategy = ( + AbstractSearchStrategy: any, + RollupSearchRequest: any, + RollupSearchCapabilities: any, + callWithRequestFactory: CallWithRequestFactoryShim +) => + class RollupSearchStrategy extends AbstractSearchStrategy { + name = 'rollup'; + + constructor() { + // TODO: When vis_type_timeseries and AbstractSearchStrategy are migrated to the NP, it + // shouldn't require elasticsearchService to be injected, and we can remove this null argument. + super(null, callWithRequestFactory, RollupSearchRequest); + } + + getRollupData(req: KibanaRequest, indexPattern: string) { + const callWithRequest = this.getCallWithRequestInstance(req); + + return callWithRequest(ROLLUP_INDEX_CAPABILITIES_METHOD, { + indexPattern, + }).catch(() => Promise.resolve({})); + } + + async checkForViability(req: KibanaRequest, indexPattern: string) { + let isViable = false; + let capabilities = null; + + if (isIndexPatternValid(indexPattern)) { + const rollupData = await this.getRollupData(req, indexPattern); + const rollupIndices = getRollupIndices(rollupData); + + isViable = rollupIndices.length === 1; + + if (isViable) { + const [rollupIndex] = rollupIndices; + const fieldsCapabilities = getCapabilitiesForRollupIndices(rollupData); + + capabilities = new RollupSearchCapabilities(req, fieldsCapabilities, rollupIndex); + } + } + + return { + isViable, + capabilities, + }; + } + + async getFieldsForWildcard( + req: KibanaRequest, + indexPattern: string, + { + fieldsCapabilities, + rollupIndex, + }: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string } + ) { + const fields = await super.getFieldsForWildcard(req, indexPattern); + const fieldsFromFieldCapsApi = indexBy(fields, 'name'); + const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs; + + return mergeCapabilitiesWithFields(rollupIndexCapabilities, fieldsFromFieldCapsApi); + } + }; diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 620c77e3c0a56..db1049462dc3e 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -16,20 +16,23 @@ import { CoreSetup, Plugin, Logger, + KibanaRequest, PluginInitializerContext, IScopedClusterClient, + APICaller, SharedGlobalConfig, } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { PLUGIN, CONFIG_ROLLUPS } from '../common'; -import { Dependencies } from './types'; +import { Dependencies, CallWithRequestFactoryShim } from './types'; import { registerApiRoutes } from './routes'; import { License } from './services'; import { registerRollupUsageCollector } from './collectors'; import { rollupDataEnricher } from './rollup_data_enricher'; import { IndexPatternsFetcher } from './shared_imports'; +import { registerRollupSearchStrategy } from './lib/search_strategies'; import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; import { isEsError } from './lib/is_es_error'; import { formatEsError } from './lib/format_es_error'; @@ -53,7 +56,7 @@ export class RollupPlugin implements Plugin { public setup( { http, uiSettings, elasticsearch }: CoreSetup, - { licensing, indexManagement, usageCollection }: Dependencies + { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies ) { this.license.setup( { @@ -108,6 +111,22 @@ export class RollupPlugin implements Plugin { }, }); + if (visTypeTimeseries) { + // TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. + const callWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest + ): APICaller => { + return rollupEsClient.asScoped(request).callAsCurrentUser; + // return (...args: any[]): APICaller => { + // return context.rollup!.client;//.callAsCurrentUser(...args); + // } + }; + + const { addSearchStrategy } = visTypeTimeseries; + registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); + } + if (usageCollection) { this.globalConfig$ .pipe(first()) diff --git a/x-pack/plugins/rollup/server/routes/api/search/index.ts b/x-pack/plugins/rollup/server/routes/api/search/index.ts new file mode 100644 index 0000000000000..2a2d823e79bc6 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/search/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerSearchRoute } from './register_search_route'; + +export function registerSearchRoutes(dependencies: RouteDependencies) { + registerSearchRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts new file mode 100644 index 0000000000000..c5c56336def1a --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -0,0 +1,47 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerSearchRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/search'), + validate: { + body: schema.arrayOf( + schema.object({ + index: schema.string(), + query: schema.any(), + }) + ), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const requests = request.body.map(({ index, query }: { index: string; query?: any }) => + context.rollup!.client.callAsCurrentUser('rollup.search', { + index, + rest_total_hits_as_int: true, + body: query, + }) + ); + const data = await Promise.all(requests); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/index.ts b/x-pack/plugins/rollup/server/routes/index.ts index fa8bec6eaecae..b25480855b4a2 100644 --- a/x-pack/plugins/rollup/server/routes/index.ts +++ b/x-pack/plugins/rollup/server/routes/index.ts @@ -9,9 +9,11 @@ import { RouteDependencies } from '../types'; import { registerIndexPatternsRoutes } from './api/index_patterns'; import { registerIndicesRoutes } from './api/indices'; import { registerJobsRoutes } from './api/jobs'; +import { registerSearchRoutes } from './api/search'; export function registerApiRoutes(dependencies: RouteDependencies) { registerIndexPatternsRoutes(dependencies); registerIndicesRoutes(dependencies); registerJobsRoutes(dependencies); + registerSearchRoutes(dependencies); } diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index b00265badf848..c21d76400164e 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; +import { IRouter, APICaller, KibanaRequest } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; @@ -37,3 +37,9 @@ export interface RouteDependencies { IndexPatternsFetcher: typeof IndexPatternsFetcher; }; } + +// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. +export type CallWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest +) => APICaller; From 93d013f90ebf1f6c7ef3ae3f9ee1471c0d2faf75 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 17:28:03 -0700 Subject: [PATCH 08/10] Revert "Fix TSVB rollup functional tests." This reverts commit 5f9937d39fb2050226540240d29770a027d257d8. --- test/functional/page_objects/visual_builder_page.ts | 7 ------- x-pack/test/functional/apps/rollup_job/tsvb.js | 1 - 2 files changed, 8 deletions(-) diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 3a9474ef519c4..12962b3a5cdef 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -418,13 +418,6 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await PageObjects.header.waitUntilLoadingHasFinished(); } - public async setTimeField(index: number) { - await testSubjects.click('metricsIndexPatternFieldsSelect'); - const options = await find.allByCssSelector('.euiFilterSelectItem'); - await options[0].click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - public async setIntervalValue(value: string) { const el = await testSubjects.find('metricsIndexPatternInterval'); await el.clearValue(); diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index 2a71bd7adca1b..acb73b170fbf2 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -84,7 +84,6 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName); await PageObjects.visualBuilder.setIntervalValue('1d'); await PageObjects.visualBuilder.setDropLastBucket(false); - await PageObjects.visualBuilder.setTimeField(0); await PageObjects.common.sleep(3000); const newValue = await PageObjects.visualBuilder.getMetricValue(); expect(newValue).to.eql('3'); From 4b3295c5e5b70d83a469281881bbdc7e52211730 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 23 Apr 2020 19:32:08 -0700 Subject: [PATCH 09/10] Revert "Fix API integration tests by removing tests for deleted rollup search endpoint." This reverts commit 9e190e6b70f9cb3bf91247a2acc751f5a60b1c26. --- .../apis/management/rollup/index.js | 1 + .../apis/management/rollup/rollup_search.js | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 x-pack/test/api_integration/apis/management/rollup/rollup_search.js diff --git a/x-pack/test/api_integration/apis/management/rollup/index.js b/x-pack/test/api_integration/apis/management/rollup/index.js index f04abf0050ec1..b55622381bc3c 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index.js +++ b/x-pack/test/api_integration/apis/management/rollup/index.js @@ -8,5 +8,6 @@ export default function({ loadTestFile }) { describe('rollup', () => { loadTestFile(require.resolve('./rollup')); loadTestFile(require.resolve('./index_patterns_extensions')); + loadTestFile(require.resolve('./rollup_search')); }); } diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js new file mode 100644 index 0000000000000..4fd7b304dd78b --- /dev/null +++ b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js @@ -0,0 +1,53 @@ +/* + * 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 expect from '@kbn/expect'; + +import { registerHelpers } from './rollup.test_helpers'; +import { API_BASE_PATH } from './constants'; +import { getRandomString } from './lib'; + +export default function({ getService }) { + const supertest = getService('supertest'); + const es = getService('legacyEs'); + + const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers({ + supertest, + es, + }); + + describe('search', () => { + const URI = `${API_BASE_PATH}/search`; + + it('return a 404 if the rollup index does not exist', async () => { + const { body } = await supertest + .post(URI) + .set('kbn-xsrf', 'xxx') + .send([{ index: 'unknown', query: {} }]) + .expect(404); + + expect(body.message).to.contain('no such index [unknown]'); + }); + + it('should return a 200 when searching on existing rollup index', async () => { + // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS + const indexName = await createIndexWithMappings(); + const rollupIndex = getRandomString(); + await createJob(getJobPayload(indexName, undefined, rollupIndex)); + + const { body } = await supertest + .post(URI) + .set('kbn-xsrf', 'xxx') + .send([{ index: rollupIndex, query: { size: 0 } }]) + .expect(200); + + // make sure total hits is an integer and not an object + expect(body[0].hits.total).to.equal(0); + + await cleanUp(); + }); + }); +} From 187e5ac8a95dd3db5096002afaed28f61a78e604 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 24 Apr 2020 11:38:07 -0700 Subject: [PATCH 10/10] Remove commented-out code and unnecessary tsconfig file. --- x-pack/plugins/rollup/server/plugin.ts | 7 +------ x-pack/plugins/rollup/tsconfig.json | 3 --- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 x-pack/plugins/rollup/tsconfig.json diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index db1049462dc3e..ee9a1844c7468 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -116,12 +116,7 @@ export class RollupPlugin implements Plugin { const callWithRequestFactoryShim = ( elasticsearchServiceShim: CallWithRequestFactoryShim, request: KibanaRequest - ): APICaller => { - return rollupEsClient.asScoped(request).callAsCurrentUser; - // return (...args: any[]): APICaller => { - // return context.rollup!.client;//.callAsCurrentUser(...args); - // } - }; + ): APICaller => rollupEsClient.asScoped(request).callAsCurrentUser; const { addSearchStrategy } = visTypeTimeseries; registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json deleted file mode 100644 index 4082f16a5d91c..0000000000000 --- a/x-pack/plugins/rollup/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -}