From 18d1af24d43d89b82d67375f097bd9ae0f6c8d6d Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 24 Apr 2020 14:27:35 -0700 Subject: [PATCH] Move Rollup out of legacy (#62891) --- .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 | 16 +- x-pack/plugins/rollup/common/index.ts | 9 + x-pack/plugins/rollup/kibana.json | 15 +- .../sections/job_create/steps_config/index.js | 2 +- .../crud_app/services/track_ui_metric.ts | 3 + .../components/rollup_prompt/rollup_prompt.js | 27 ++- .../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 | 138 +++++++++++--- .../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 +++++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 70 files changed, 1192 insertions(+), 1043 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 (76%) 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 diff --git a/.sass-lint.yml b/.sass-lint.yml index 89735342a2d6..9b31f3fae6d1 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 82a35f171036..e65f6cf60928 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -17,7 +17,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'; @@ -43,7 +42,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 526af055a3ef..000000000000 --- 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 f33ae7cfee0a..000000000000 --- 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 78458c9218be..000000000000 --- 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 eb16b211da3f..000000000000 --- 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 883b3552a7c0..000000000000 --- 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 b6cea09e0ea3..000000000000 --- 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 353510d96a00..000000000000 --- 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 05c22b030fff..000000000000 --- 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 146c3e973f9e..000000000000 --- 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 2516840bd953..000000000000 --- 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 e78f09a71876..000000000000 --- 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 e45713e2b807..000000000000 --- 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 97999a4b5ce8..000000000000 --- 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 bcc6770e9b8e..000000000000 --- 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 618c6c3e97b5..000000000000 --- 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 76% rename from x-pack/legacy/plugins/rollup/README.md rename to x-pack/plugins/rollup/README.md index 3647be38b6a0..b43f4d598140 100644 --- a/x-pack/legacy/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/common/index.ts b/x-pack/plugins/rollup/common/index.ts index aeffa3dc3959..e94726a6f3d9 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 8f832f6c6a34..4c7dcb48a4d3 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/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 4a55c4679c3d..eca624e16cb8 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 aa1cc2dfea32..5d9340a14050 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. 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 42c950f0b0d7..9d81abf70a55 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.', + } + )}

); 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 a9a3c61472d8..6d02600521c3 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 405684245377..78859a959a1e 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 000000000000..c03b7c33abe0 --- /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 000000000000..9dde027cd694 --- /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 93c4c1b52140..333863979ba9 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 5a57129aa603..151afe660847 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 9d5aad2c2d3b..815fe163411b 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 ea6d197e2202..ee9a1844c746 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'; - constructor(initContext: PluginInitializerContext) { - this.initContext = initContext; +interface RollupContext { + client: IScopedClusterClient; +} + +export class RollupPlugin implements Plugin { + private readonly logger: Logger; + private readonly globalConfig$: Observable; + private readonly license: License; + + 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,34 @@ 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 => rollupEsClient.asScoped(request).callAsCurrentUser; + + 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); + } } - public start() {} - public stop() {} -} - -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 ad621f2d9ba8..b06cf971a646 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 000000000000..7bf525ca4aa9 --- /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 000000000000..32f23314c525 --- /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 000000000000..0aa5772b5699 --- /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 000000000000..3521650c1dc3 --- /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 000000000000..9e22060b9beb --- /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 000000000000..fe1d1c6109a8 --- /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 000000000000..adf8c1da0af0 --- /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 000000000000..32f7b3f35e16 --- /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 000000000000..a8d51f4639fc --- /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 000000000000..fb6f2b12ba52 --- /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 000000000000..118d98e36e03 --- /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 6bbd00ac6576..2a2d823e79bc 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 000000000000..c5c56336def1 --- /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 000000000000..b25480855b4a --- /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 787814d87dff..7d7cce3aab33 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 0743e443955f..7f79c4f44654 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 000000000000..bfd357867c3e --- /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 941610b97707..09842f529abe 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 000000000000..c21d76400164 --- /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/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5b55c7a15f1b..19d43870f467 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12366,7 +12366,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 1758588d01ba..9875b66e425f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12370,7 +12370,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": "上一步",