From 6d03ad440f64f4ef6e998fc45eeff9a2a1124d40 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 7 Feb 2020 07:34:41 -0700 Subject: [PATCH 01/10] [Metrics UI] Limit group by selector to only 2 fields (#56800) * [Metrics UI] Limit group by selector to only 2 fields * Removing unused variable * Removing unused import Co-authored-by: Elastic Machine --- .../components/waffle/custom_field_panel.tsx | 14 ++++++++++--- .../waffle/waffle_group_by_controls.tsx | 20 +++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx index 01bff0b4f96e1e..15d8b8b0e42b8e 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/custom_field_panel.tsx @@ -8,10 +8,12 @@ import { EuiButton, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { IFieldType } from 'src/plugins/data/public'; +import { InfraGroupByOptions } from '../../lib/lib'; interface Props { onSubmit: (field: string) => void; fields: IFieldType[]; + currentOptions: InfraGroupByOptions[]; } interface SelectedOption { @@ -28,10 +30,16 @@ export const CustomFieldPanel = class extends React.PureComponent public static displayName = 'CustomFieldPanel'; public readonly state: State = initialState; public render() { - const { fields } = this.props; + const { fields, currentOptions } = this.props; const options = fields - .filter(f => f.aggregatable && f.type === 'string') + .filter( + f => + f.aggregatable && + f.type === 'string' && + !(currentOptions && currentOptions.some(o => o.field === f.name)) + ) .map(f => ({ label: f.name })); + const isSubmitDisabled = !this.state.selectedOptions.length; return (
@@ -57,7 +65,7 @@ export const CustomFieldPanel = class extends React.PureComponent /> = 2; + const maxGroupByTooltip = i18n.translate('xpack.infra.waffle.maxGroupByTooltip', { + defaultMessage: 'Only two groupings can be selected at a time', + }); const panels: EuiContextMenuPanelDescriptor[] = [ { id: 'firstPanel', @@ -72,6 +76,8 @@ export const WaffleGroupByControls = class extends React.PureComponent, + content: ( + + ), }, ]; const buttonBody = @@ -167,8 +183,8 @@ export const WaffleGroupByControls = class extends React.PureComponent Date: Fri, 7 Feb 2020 10:00:58 -0500 Subject: [PATCH 02/10] [Rollups] Server NP migration (#55606) --- x-pack/legacy/plugins/rollup/common/index.ts | 10 + .../plugins/rollup/{index.js => index.ts} | 55 +++--- x-pack/legacy/plugins/rollup/kibana.json | 14 ++ ...arch_rollup.js => elasticsearch_rollup.ts} | 2 +- .../index.js => collectors/index.ts} | 2 +- .../collector.js => collectors/register.ts} | 43 +++-- .../{lib/error_wrappers/index.js => index.ts} | 6 +- .../call_with_request_factory.js | 21 --- .../call_with_request_factory.ts | 28 +++ .../{index.js => index.ts} | 0 .../check_license/__tests__/check_license.js | 145 -------------- .../server/lib/check_license/check_license.js | 66 ------- .../__tests__/wrap_custom_error.js | 21 --- .../error_wrappers/__tests__/wrap_es_error.js | 39 ---- .../__tests__/wrap_unknown_error.js | 19 -- .../lib/error_wrappers/wrap_custom_error.js | 18 -- .../lib/error_wrappers/wrap_es_error.js | 59 ------ .../lib/error_wrappers/wrap_unknown_error.js | 17 -- .../index.js => is_es_error/index.ts} | 2 +- .../server/lib/is_es_error/is_es_error.ts | 13 ++ .../__tests__/is_es_error_factory.js | 44 ----- .../is_es_error_factory.js | 18 -- ...compatibility.js => jobs_compatibility.ts} | 6 +- .../__tests__/license_pre_routing_factory.js | 66 ------- .../{index.js => index.ts} | 0 .../license_pre_routing_factory.js | 28 --- .../license_pre_routing_factory.test.js | 62 ++++++ .../license_pre_routing_factory.ts | 43 +++++ ...ap_capabilities.js => map_capabilities.ts} | 4 +- ...s.js => merge_capabilities_with_fields.ts} | 13 +- .../register_license_checker.js | 24 --- .../search_strategies/{index.js => index.ts} | 0 ...{interval_helper.js => interval_helper.ts} | 11 +- .../register_rollup_search_strategy.js | 32 ---- .../register_rollup_search_strategy.test.js | 43 +---- .../register_rollup_search_strategy.ts | 29 +++ ...ities.js => rollup_search_capabilities.ts} | 25 ++- ...ch_request.js => rollup_search_request.ts} | 11 +- ..._strategy.js => rollup_search_strategy.ts} | 30 +-- x-pack/legacy/plugins/rollup/server/plugin.ts | 95 ++++++++++ .../rollup_data_enricher.ts} | 11 +- .../server/routes/api/{index.js => index.ts} | 0 .../server/routes/api/index_patterns.js | 93 --------- .../server/routes/api/index_patterns.ts | 131 +++++++++++++ .../rollup/server/routes/api/indices.js | 128 ------------- .../rollup/server/routes/api/indices.ts | 175 +++++++++++++++++ .../plugins/rollup/server/routes/api/jobs.js | 153 --------------- .../plugins/rollup/server/routes/api/jobs.ts | 178 ++++++++++++++++++ .../rollup/server/routes/api/search.js | 44 ----- .../rollup/server/routes/api/search.ts | 50 +++++ .../index.js => shared_imports.ts} | 2 +- x-pack/legacy/plugins/rollup/server/types.ts | 22 +++ .../plugins/rollup/server/usage/index.js | 7 - x-pack/plugins/rollup/kibana.json | 6 + x-pack/plugins/rollup/server/index.ts | 12 ++ x-pack/plugins/rollup/server/plugin.ts | 35 ++++ .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - .../rollup/index_patterns_extensions.js | 36 +++- 59 files changed, 1075 insertions(+), 1178 deletions(-) rename x-pack/legacy/plugins/rollup/{index.js => index.ts} (57%) create mode 100644 x-pack/legacy/plugins/rollup/kibana.json rename x-pack/legacy/plugins/rollup/server/client/{elasticsearch_rollup.js => elasticsearch_rollup.ts} (96%) rename x-pack/legacy/plugins/rollup/server/{lib/is_es_error_factory/index.js => collectors/index.ts} (80%) rename x-pack/legacy/plugins/rollup/server/{usage/collector.js => collectors/register.ts} (83%) rename x-pack/legacy/plugins/rollup/server/{lib/error_wrappers/index.js => index.ts} (55%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.js create mode 100644 x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts rename x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/{index.js => index.ts} (100%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/check_license/__tests__/check_license.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/check_license/check_license.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_custom_error.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_es_error.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_unknown_error.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_custom_error.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_es_error.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_unknown_error.js rename x-pack/legacy/plugins/rollup/server/lib/{check_license/index.js => is_es_error/index.ts} (83%) create mode 100644 x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/is_es_error_factory.js rename x-pack/legacy/plugins/rollup/server/lib/{jobs_compatibility.js => jobs_compatibility.ts} (94%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js rename x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/{index.js => index.ts} (100%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.js create mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js create mode 100644 x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts rename x-pack/legacy/plugins/rollup/server/lib/{map_capabilities.js => map_capabilities.ts} (81%) rename x-pack/legacy/plugins/rollup/server/lib/{merge_capabilities_with_fields.js => merge_capabilities_with_fields.ts} (90%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/register_license_checker/register_license_checker.js rename x-pack/legacy/plugins/rollup/server/lib/search_strategies/{index.js => index.ts} (100%) rename x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/{interval_helper.js => interval_helper.ts} (52%) delete mode 100644 x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.js create mode 100644 x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts rename x-pack/legacy/plugins/rollup/server/lib/search_strategies/{rollup_search_capabilities.js => rollup_search_capabilities.ts} (82%) rename x-pack/legacy/plugins/rollup/server/lib/search_strategies/{rollup_search_request.js => rollup_search_request.ts} (75%) rename x-pack/legacy/plugins/rollup/server/lib/search_strategies/{rollup_search_strategy.js => rollup_search_strategy.ts} (68%) create mode 100644 x-pack/legacy/plugins/rollup/server/plugin.ts rename x-pack/legacy/plugins/rollup/{rollup_data_enricher.js => server/rollup_data_enricher.ts} (77%) rename x-pack/legacy/plugins/rollup/server/routes/api/{index.js => index.ts} (100%) delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.js create 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.js create mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/indices.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/jobs.js create mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/search.js create mode 100644 x-pack/legacy/plugins/rollup/server/routes/api/search.ts rename x-pack/legacy/plugins/rollup/server/{lib/register_license_checker/index.js => shared_imports.ts} (75%) create mode 100644 x-pack/legacy/plugins/rollup/server/types.ts delete mode 100644 x-pack/legacy/plugins/rollup/server/usage/index.js create mode 100644 x-pack/plugins/rollup/kibana.json create mode 100644 x-pack/plugins/rollup/server/index.ts create mode 100644 x-pack/plugins/rollup/server/plugin.ts diff --git a/x-pack/legacy/plugins/rollup/common/index.ts b/x-pack/legacy/plugins/rollup/common/index.ts index 800da79552a57a..42298034622034 100644 --- a/x-pack/legacy/plugins/rollup/common/index.ts +++ b/x-pack/legacy/plugins/rollup/common/index.ts @@ -4,12 +4,22 @@ * 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 const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns'; +export const API_BASE_PATH = '/api/rollup'; + export { UIM_APP_NAME, UIM_APP_LOAD, diff --git a/x-pack/legacy/plugins/rollup/index.js b/x-pack/legacy/plugins/rollup/index.ts similarity index 57% rename from x-pack/legacy/plugins/rollup/index.js rename to x-pack/legacy/plugins/rollup/index.ts index cace3bba1592b9..7548af23b3aae9 100644 --- a/x-pack/legacy/plugins/rollup/index.js +++ b/x-pack/legacy/plugins/rollup/index.ts @@ -5,20 +5,13 @@ */ import { resolve } from 'path'; -import { PLUGIN, CONFIG_ROLLUPS } from './common'; -import { registerLicenseChecker } from './server/lib/register_license_checker'; -import { rollupDataEnricher } from './rollup_data_enricher'; -import { registerRollupSearchStrategy } from './server/lib/search_strategies'; -import { - registerIndicesRoute, - registerFieldsForWildcardRoute, - registerSearchRoute, - registerJobsRoute, -} from './server/routes/api'; -import { registerRollupUsageCollector } from './server/usage'; import { i18n } from '@kbn/i18n'; +import { PluginInitializerContext } from 'src/core/server'; +import { RollupSetup } from '../../../plugins/rollup/server'; +import { PLUGIN, CONFIG_ROLLUPS } from './common'; +import { plugin } from './server'; -export function rollup(kibana) { +export function rollup(kibana: any) { return new kibana.Plugin({ id: PLUGIN.ID, configPrefix: 'xpack.rollup', @@ -45,22 +38,30 @@ export function rollup(kibana) { visualize: ['plugins/rollup/legacy'], search: ['plugins/rollup/legacy'], }, - init: function(server) { - const { usageCollection } = server.newPlatform.setup.plugins; - registerLicenseChecker(server); - registerIndicesRoute(server); - registerFieldsForWildcardRoute(server); - registerSearchRoute(server); - registerJobsRoute(server); - registerRollupUsageCollector(usageCollection, server); - if ( - server.plugins.index_management && - server.plugins.index_management.addIndexManagementDataEnricher - ) { - server.plugins.index_management.addIndexManagementDataEnricher(rollupDataEnricher); - } + init(server: any) { + const { core: coreSetup, plugins } = server.newPlatform.setup; + const { usageCollection, metrics } = plugins; + + const rollupSetup = (plugins.rollup as unknown) as RollupSetup; - registerRollupSearchStrategy(this.kbnServer); + const initContext = ({ + config: rollupSetup.__legacy.config, + logger: rollupSetup.__legacy.logger, + } as unknown) as PluginInitializerContext; + + const rollupPluginInstance = plugin(initContext); + + rollupPluginInstance.setup(coreSetup, { + usageCollection, + metrics, + __LEGACY: { + plugins: { + xpack_main: server.plugins.xpack_main, + rollup: server.plugins[PLUGIN.ID], + index_management: server.plugins.index_management, + }, + }, + }); }, }); } diff --git a/x-pack/legacy/plugins/rollup/kibana.json b/x-pack/legacy/plugins/rollup/kibana.json new file mode 100644 index 00000000000000..3781d59d8c0f36 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/kibana.json @@ -0,0 +1,14 @@ +{ + "id": "rollup", + "version": "kibana", + "requiredPlugins": [ + "home", + "index_management", + "metrics" + ], + "optionalPlugins": [ + "usageCollection" + ], + "server": true, + "ui": false +} diff --git a/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.js b/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts similarity index 96% rename from x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.js rename to x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts index 3b073cd2139c18..840f66a056d2d3 100644 --- a/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.js +++ b/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const elasticsearchJsPlugin = (Client, config, components) => { +export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { const ca = components.clientAction.factory; Client.prototype.rollup = components.clientAction.namespaceFactory(); diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/index.js b/x-pack/legacy/plugins/rollup/server/collectors/index.ts similarity index 80% rename from x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/index.js rename to x-pack/legacy/plugins/rollup/server/collectors/index.ts index 441648a8701e08..47c1bcb6c72489 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/index.js +++ b/x-pack/legacy/plugins/rollup/server/collectors/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsErrorFactory } from './is_es_error_factory'; +export { registerRollupUsageCollector } from './register'; diff --git a/x-pack/legacy/plugins/rollup/server/usage/collector.js b/x-pack/legacy/plugins/rollup/server/collectors/register.ts similarity index 83% rename from x-pack/legacy/plugins/rollup/server/usage/collector.js rename to x-pack/legacy/plugins/rollup/server/collectors/register.ts index 21c4de62c8fdcb..02ad5dc92fd136 100644 --- a/x-pack/legacy/plugins/rollup/server/usage/collector.js +++ b/x-pack/legacy/plugins/rollup/server/collectors/register.ts @@ -5,25 +5,31 @@ */ import { get } from 'lodash'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; + +interface IdToFlagMap { + [key: string]: boolean; +} const ROLLUP_USAGE_TYPE = 'rollups'; // elasticsearch index.max_result_window default value const ES_MAX_RESULT_WINDOW_DEFAULT_VALUE = 1000; -function getIdFromSavedObjectId(savedObjectId) { +function getIdFromSavedObjectId(savedObjectId: string) { // The saved object ID is formatted `{TYPE}:{ID}`. return savedObjectId.split(':')[1]; } -function createIdToFlagMap(ids) { +function createIdToFlagMap(ids: string[]) { return ids.reduce((map, id) => { map[id] = true; return map; - }, {}); + }, {} as any); } -async function fetchRollupIndexPatterns(kibanaIndex, callCluster) { +async function fetchRollupIndexPatterns(kibanaIndex: string, callCluster: CallCluster) { const searchParams = { size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE, index: kibanaIndex, @@ -50,7 +56,11 @@ async function fetchRollupIndexPatterns(kibanaIndex, callCluster) { }); } -async function fetchRollupSavedSearches(kibanaIndex, callCluster, rollupIndexPatternToFlagMap) { +async function fetchRollupSavedSearches( + kibanaIndex: string, + callCluster: CallCluster, + rollupIndexPatternToFlagMap: IdToFlagMap +) { const searchParams = { size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE, index: kibanaIndex, @@ -86,19 +96,19 @@ async function fetchRollupSavedSearches(kibanaIndex, callCluster, rollupIndexPat const searchSource = JSON.parse(searchSourceJSON); if (rollupIndexPatternToFlagMap[searchSource.index]) { - const id = getIdFromSavedObjectId(savedObjectId); + const id = getIdFromSavedObjectId(savedObjectId) as string; rollupSavedSearches.push(id); } return rollupSavedSearches; - }, []); + }, [] as string[]); } async function fetchRollupVisualizations( - kibanaIndex, - callCluster, - rollupIndexPatternToFlagMap, - rollupSavedSearchesToFlagMap + kibanaIndex: string, + callCluster: CallCluster, + rollupIndexPatternToFlagMap: IdToFlagMap, + rollupSavedSearchesToFlagMap: IdToFlagMap ) { const searchParams = { size: ES_MAX_RESULT_WINDOW_DEFAULT_VALUE, @@ -135,7 +145,7 @@ async function fetchRollupVisualizations( savedSearchRefName, kibanaSavedObjectMeta: { searchSourceJSON }, }, - references = [], + references = [] as any[], }, } = visualization; @@ -164,13 +174,14 @@ async function fetchRollupVisualizations( }; } -export function registerRollupUsageCollector(usageCollection, server) { - const kibanaIndex = server.config().get('kibana.index'); - +export function registerRollupUsageCollector( + usageCollection: UsageCollectionSetup, + kibanaIndex: string +): void { const collector = usageCollection.makeUsageCollector({ type: ROLLUP_USAGE_TYPE, isReady: () => true, - fetch: async callCluster => { + fetch: async (callCluster: CallCluster) => { const rollupIndexPatterns = await fetchRollupIndexPatterns(kibanaIndex, callCluster); const rollupIndexPatternToFlagMap = createIdToFlagMap(rollupIndexPatterns); diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/index.js b/x-pack/legacy/plugins/rollup/server/index.ts similarity index 55% rename from x-pack/legacy/plugins/rollup/server/lib/error_wrappers/index.js rename to x-pack/legacy/plugins/rollup/server/index.ts index f275f156370912..6bbd00ac6576e0 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/index.js +++ b/x-pack/legacy/plugins/rollup/server/index.ts @@ -3,7 +3,7 @@ * 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 { wrapCustomError } from './wrap_custom_error'; -export { wrapEsError } from './wrap_es_error'; -export { wrapUnknownError } from './wrap_unknown_error'; +export const plugin = (ctx: PluginInitializerContext) => new RollupsServerPlugin(ctx); diff --git a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index 284151d404a470..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.js +++ /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 { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../../client/elasticsearch_rollup'; - -const callWithRequest = once(server => { - const client = server.newPlatform.setup.core.elasticsearch.createClient('rollup', { - plugins: [elasticsearchJsPlugin], - }); - return (request, ...args) => client.asScoped(request).callAsCurrentUser(...args); -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; 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 new file mode 100644 index 00000000000000..883b3552a7c020 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +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/call_with_request_factory/index.js b/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.js rename to x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/check_license/__tests__/check_license.js b/x-pack/legacy/plugins/rollup/server/lib/check_license/__tests__/check_license.js deleted file mode 100644 index 933fda01c055db..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/check_license/__tests__/check_license.js +++ /dev/null @@ -1,145 +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 { set } from 'lodash'; -import { checkLicense } from '../check_license'; - -describe('check_license', function() { - let mockLicenseInfo; - beforeEach(() => (mockLicenseInfo = {})); - - describe('license information is undefined', () => { - beforeEach(() => (mockLicenseInfo = undefined)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => (mockLicenseInfo.isAvailable = () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is > basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it('should not set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true)); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it('should not set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set showLinks to true', () => { - expect(checkLicense(mockLicenseInfo).showLinks).to.be(true); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/check_license/check_license.js b/x-pack/legacy/plugins/rollup/server/lib/check_license/check_license.js deleted file mode 100644 index 3885a20a1f358a..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/check_license/check_license.js +++ /dev/null @@ -1,66 +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 { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo) { - const pluginName = 'Rollups'; - - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - isAvailable: false, - showLinks: true, - enableLinks: false, - message: i18n.translate('xpack.rollupJobs.checkLicense.errorUnavailableMessage', { - defaultMessage: - 'You cannot use {pluginName} because license information is not available at this time.', - values: { pluginName }, - }), - }; - } - - const VALID_LICENSE_MODES = ['trial', 'basic', 'standard', 'gold', 'platinum', 'enterprise']; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - - // License is not valid - if (!isLicenseModeValid) { - return { - isAvailable: false, - showLinks: false, - message: i18n.translate('xpack.rollupJobs.checkLicense.errorUnsupportedMessage', { - defaultMessage: - 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid but not active - if (!isLicenseActive) { - return { - isAvailable: false, - showLinks: true, - enableLinks: false, - message: i18n.translate('xpack.rollupJobs.checkLicense.errorExpiredMessage', { - defaultMessage: - 'You cannot use {pluginName} because your {licenseType} license has expired', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid and active - return { - isAvailable: true, - showLinks: true, - enableLinks: true, - }; -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index f9c102be7a1ff3..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /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 expect from '@kbn/expect'; -import { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index 8241dc43291371..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,39 +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 { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - originalError.response = '{}'; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return the correct Boom object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 85e0b2b3033ad4..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /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 expect from '@kbn/expect'; -import { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 3295113d38ee5a..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +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 Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.boomify(err, { statusCode }); -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_es_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_es_error.js deleted file mode 100644 index 5f4884a3f2d266..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_es_error.js +++ /dev/null @@ -1,59 +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 Boom from 'boom'; - -function extractCausedByChain(causedBy = {}, accumulator = []) { - const { reason, caused_by } = causedBy; // eslint-disable-line camelcase - - if (reason) { - accumulator.push(reason); - } - - // eslint-disable-next-line 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 - * @return Object Boom error response - */ -export function wrapEsError(err, statusCodeToMessageMap = {}) { - const { statusCode, response } = err; - - const { - error: { - root_cause = [], // eslint-disable-line camelcase - caused_by, // eslint-disable-line 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]) { - const boomError = Boom.boomify(err, { 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; - - boomError.output.payload.cause = causedByChain.length ? causedByChain : defaultCause; - return boomError; - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return new Boom(message, { statusCode }); -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index ffd915c5133626..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.boomify(err); -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/check_license/index.js b/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts similarity index 83% rename from x-pack/legacy/plugins/rollup/server/lib/check_license/index.js rename to x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts index f2c070fd44b6e6..a9a3c61472d8c7 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/check_license/index.js +++ b/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { checkLicense } from './check_license'; +export { isEsError } from './is_es_error'; diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts b/x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts new file mode 100644 index 00000000000000..4137293cf39c06 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts @@ -0,0 +1,13 @@ +/* + * 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 * as legacyElasticsearch from 'elasticsearch'; + +const esErrorsParent = legacyElasticsearch.errors._Abstract; + +export function isEsError(err: Error) { + return err instanceof esErrorsParent; +} diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index 5f2141cce93954..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,44 +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 { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from 'lodash'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError, - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/is_es_error_factory.js b/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/is_es_error_factory.js deleted file mode 100644 index 6c17554385ef85..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/is_es_error_factory/is_es_error_factory.js +++ /dev/null @@ -1,18 +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 { memoize } from 'lodash'; - -const esErrorsFactory = memoize(server => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server) { - const esErrors = esErrorsFactory(server); - return function isEsError(err) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.js b/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts similarity index 94% rename from x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.js rename to x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts index 9423e7befb557d..f93641e5962b7b 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.js +++ b/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts @@ -37,10 +37,10 @@ export function mergeJobConfigurations(jobs = []) { throw new Error('No capabilities available'); } - const allAggs = {}; + const allAggs: { [key: string]: any } = {}; // For each job, look through all of its fields - jobs.forEach(job => { + jobs.forEach((job: { fields: { [key: string]: any } }) => { const fields = job.fields; const fieldNames = Object.keys(fields); @@ -49,7 +49,7 @@ export function mergeJobConfigurations(jobs = []) { const fieldAggs = fields[fieldName]; // Look through each field's capabilities (aggregations) - fieldAggs.forEach(agg => { + fieldAggs.forEach((agg: { agg: string; interval: string }) => { const aggName = agg.agg; const aggDoesntExist = !allAggs[aggName]; const fieldDoesntExist = allAggs[aggName] && !allAggs[aggName][fieldName]; diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index a73aa96209c262..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,66 +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 '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#reportingFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false, - }; - }); - - it('replies with 403', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const response = licensePreRouting(); - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true, - }; - }); - - it('replies with nothing', () => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const response = licensePreRouting(); - expect(response).to.be(null); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.js b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.js rename to x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 1c2c9f2b2276b8..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /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 { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common'; - -export const licensePreRoutingFactory = once(server => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting() { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - const wrappedError = wrapCustomError(error, statusCode); - return wrappedError; - } else { - return null; - } - } - - return licensePreRouting; -}); 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 new file mode 100644 index 00000000000000..b6cea09e0ea3c1 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js @@ -0,0 +1,62 @@ +/* + * 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 new file mode 100644 index 00000000000000..353510d96a00d4 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts @@ -0,0 +1,43 @@ +/* + * 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/lib/map_capabilities.js b/x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts similarity index 81% rename from x-pack/legacy/plugins/rollup/server/lib/map_capabilities.js rename to x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts index a365ca4c756166..e0f8af865beb44 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/map_capabilities.js +++ b/x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts @@ -6,9 +6,9 @@ import { mergeJobConfigurations } from './jobs_compatibility'; -export function getCapabilitiesForRollupIndices(indices) { +export function getCapabilitiesForRollupIndices(indices: { [key: string]: any }) { const indexNames = Object.keys(indices); - const capabilities = {}; + const capabilities = {} as { [key: string]: any }; indexNames.forEach(index => { try { diff --git a/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.js b/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts similarity index 90% rename from x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.js rename to x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts index 76592bf12b2e38..24abe9045aae83 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.js +++ b/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts @@ -6,13 +6,18 @@ // Merge rollup capabilities information with field information +export interface Field { + name?: string; + [key: string]: any; +} + export const mergeCapabilitiesWithFields = ( - rollupIndexCapabilities, - fieldsFromFieldCapsApi, - previousFields = [] + rollupIndexCapabilities: { [key: string]: any }, + fieldsFromFieldCapsApi: { [key: string]: any }, + previousFields: Field[] = [] ) => { const rollupFields = [...previousFields]; - const rollupFieldNames = []; + const rollupFieldNames: string[] = []; Object.keys(rollupIndexCapabilities).forEach(agg => { // Field names of the aggregation diff --git a/x-pack/legacy/plugins/rollup/server/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/rollup/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index 5f1772800a0122..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,24 +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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; -import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common'; - -export function registerLicenseChecker(server) { - const xpackMainPlugin = server.plugins.xpack_main; - const rollupPlugin = server.plugins[PLUGIN.ID]; - - mirrorPluginStatus(xpackMainPlugin, rollupPlugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.js rename to x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts similarity index 52% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.js rename to x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts index 8fc17252f9943a..91d73cecdf4011 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.js +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts @@ -4,9 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { unitsMap } from '@elastic/datemath'; +import dateMath from '@elastic/datemath'; + +export type Unit = 'ms' | 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y'; export const leastCommonInterval = (num = 0, base = 0) => Math.max(Math.ceil(num / base) * base, base); -export const isCalendarInterval = ({ unit, value }) => - value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type); + +export const isCalendarInterval = ({ unit, value }: { unit: Unit; value: number }) => { + const { unitsMap } = dateMath; + return value === 1 && ['calendar', 'mixed'].includes(unitsMap[unit].type); +}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.js deleted file mode 100644 index fe65a7f1f30e90..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { getRollupSearchStrategy } from './rollup_search_strategy'; -import { getRollupSearchRequest } from './rollup_search_request'; -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; -import { - AbstractSearchRequest, - DefaultSearchCapabilities, - AbstractSearchStrategy, -} from '../../../../../../../src/plugins/vis_type_timeseries/server'; - -export const registerRollupSearchStrategy = kbnServer => - kbnServer.afterPluginsInit(() => { - if (!kbnServer.newPlatform.setup.plugins.metrics) { - return; - } - - const { addSearchStrategy } = kbnServer.newPlatform.setup.plugins.metrics; - - const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); - const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); - const RollupSearchStrategy = getRollupSearchStrategy( - AbstractSearchStrategy, - RollupSearchRequest, - RollupSearchCapabilities - ); - - addSearchStrategy(new RollupSearchStrategy(kbnServer)); - }); diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js index acd016d75f97ed..d466ebd69737ee 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js @@ -6,45 +6,22 @@ import { registerRollupSearchStrategy } from './register_rollup_search_strategy'; describe('Register Rollup Search Strategy', () => { - let kbnServer; - let metrics; + let routeDependencies; + let addSearchStrategy; beforeEach(() => { - const afterPluginsInit = jest.fn(callback => callback()); - - kbnServer = { - afterPluginsInit, - newPlatform: { - setup: { plugins: {} }, - }, - }; - - metrics = { - addSearchStrategy: jest.fn().mockName('addSearchStrategy'), - AbstractSearchRequest: jest.fn().mockName('AbstractSearchRequest'), - AbstractSearchStrategy: jest.fn().mockName('AbstractSearchStrategy'), - DefaultSearchCapabilities: jest.fn().mockName('DefaultSearchCapabilities'), + routeDependencies = { + router: jest.fn().mockName('router'), + elasticsearchService: jest.fn().mockName('elasticsearchService'), + elasticsearch: jest.fn().mockName('elasticsearch'), }; - }); - - test('should run initialization on "afterPluginsInit" hook', () => { - registerRollupSearchStrategy(kbnServer); - - expect(kbnServer.afterPluginsInit).toHaveBeenCalled(); - }); - - test('should run initialization if metrics plugin available', () => { - registerRollupSearchStrategy({ - ...kbnServer, - newPlatform: { setup: { plugins: { metrics } } }, - }); - expect(metrics.addSearchStrategy).toHaveBeenCalled(); + addSearchStrategy = jest.fn().mockName('addSearchStrategy'); }); - test('should not run initialization if metrics plugin unavailable', () => { - registerRollupSearchStrategy(kbnServer); + test('should run initialization', () => { + registerRollupSearchStrategy(routeDependencies, addSearchStrategy); - expect(metrics.addSearchStrategy).not.toHaveBeenCalled(); + expect(addSearchStrategy).toHaveBeenCalled(); }); }); diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts new file mode 100644 index 00000000000000..93c4c1b52140b2 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getRollupSearchStrategy } from './rollup_search_strategy'; +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'; + +export const registerRollupSearchStrategy = ( + { elasticsearchService }: RouteDependencies, + addSearchStrategy: (searchStrategy: any) => void +) => { + const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); + const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); + const RollupSearchStrategy = getRollupSearchStrategy( + AbstractSearchStrategy, + RollupSearchRequest, + RollupSearchCapabilities + ); + + addSearchStrategy(new RollupSearchStrategy(elasticsearchService)); +}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts similarity index 82% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js rename to x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts index b84664c765dc64..5a57129aa60395 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.js +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts @@ -4,24 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ import { get, has } from 'lodash'; +import { KibanaRequest } from 'kibana/server'; import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; -export const getRollupSearchCapabilities = DefaultSearchCapabilities => +export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => class RollupSearchCapabilities extends DefaultSearchCapabilities { - constructor(req, fieldsCapabilities, rollupIndex) { + constructor( + req: KibanaRequest, + fieldsCapabilities: { [key: string]: any }, + rollupIndex: string + ) { super(req, fieldsCapabilities); this.rollupIndex = rollupIndex; this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {}); } - get dateHistogram() { + public get dateHistogram() { const [dateHistogram] = Object.values(this.availableMetrics.date_histogram); return dateHistogram; } - get defaultTimeInterval() { + public get defaultTimeInterval() { return ( this.dateHistogram.fixed_interval || this.dateHistogram.calendar_interval || @@ -34,16 +39,16 @@ export const getRollupSearchCapabilities = DefaultSearchCapabilities => ); } - get searchTimezone() { + public get searchTimezone() { return get(this.dateHistogram, 'time_zone', null); } - get whiteListedMetrics() { + public get whiteListedMetrics() { const baseRestrictions = this.createUiRestriction({ count: this.createUiRestriction(), }); - const getFields = fields => + const getFields = (fields: { [key: string]: any }) => Object.keys(fields).reduce( (acc, item) => ({ ...acc, @@ -61,20 +66,20 @@ export const getRollupSearchCapabilities = DefaultSearchCapabilities => ); } - get whiteListedGroupByFields() { + public get whiteListedGroupByFields() { return this.createUiRestriction({ everything: true, terms: has(this.availableMetrics, 'terms'), }); } - get whiteListedTimerangeModes() { + public get whiteListedTimerangeModes() { return this.createUiRestriction({ last_value: true, }); } - getValidTimeInterval(userIntervalString) { + getValidTimeInterval(userIntervalString: string) { const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval); const inRollupJobUnit = this.convertIntervalToUnit( userIntervalString, diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts similarity index 75% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.js rename to x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts index ee8e5553c89631..7e12d5286f34c5 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.js +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts @@ -5,9 +5,16 @@ */ const SEARCH_METHOD = 'rollup.search'; -export const getRollupSearchRequest = AbstractSearchRequest => +interface Search { + index: string; + body: { + [key: string]: any; + }; +} + +export const getRollupSearchRequest = (AbstractSearchRequest: any) => class RollupSearchRequest extends AbstractSearchRequest { - async search(searches) { + async search(searches: Search[]) { const requests = searches.map(({ body, index }) => this.callWithRequest(SEARCH_METHOD, { body, diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.js b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts similarity index 68% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.js rename to x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts index 5cf7a3c8fd9417..9d5aad2c2d3bca 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.js +++ b/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts @@ -4,31 +4,32 @@ * 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 { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; import { getCapabilitiesForRollupIndices } from '../map_capabilities'; const ROLLUP_INDEX_CAPABILITIES_METHOD = 'rollup.rollupIndexCapabilities'; -const getRollupIndices = rollupData => Object.keys(rollupData); +const getRollupIndices = (rollupData: { [key: string]: any[] }) => Object.keys(rollupData); -const isIndexPatternContainsWildcard = indexPattern => indexPattern.includes('*'); -const isIndexPatternValid = indexPattern => +const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); +const isIndexPatternValid = (indexPattern: string) => indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern); export const getRollupSearchStrategy = ( - AbstractSearchStrategy, - RollupSearchRequest, - RollupSearchCapabilities + AbstractSearchStrategy: any, + RollupSearchRequest: any, + RollupSearchCapabilities: any ) => class RollupSearchStrategy extends AbstractSearchStrategy { name = 'rollup'; - constructor(server) { - super(server, callWithRequestFactory, RollupSearchRequest); + constructor(elasticsearchService: ElasticsearchServiceSetup) { + super(elasticsearchService, callWithRequestFactory, RollupSearchRequest); } - getRollupData(req, indexPattern) { + getRollupData(req: KibanaRequest, indexPattern: string) { const callWithRequest = this.getCallWithRequestInstance(req); return callWithRequest(ROLLUP_INDEX_CAPABILITIES_METHOD, { @@ -36,7 +37,7 @@ export const getRollupSearchStrategy = ( }).catch(() => Promise.resolve({})); } - async checkForViability(req, indexPattern) { + async checkForViability(req: KibanaRequest, indexPattern: string) { let isViable = false; let capabilities = null; @@ -60,7 +61,14 @@ export const getRollupSearchStrategy = ( }; } - async getFieldsForWildcard(req, indexPattern, { fieldsCapabilities, rollupIndex }) { + async getFieldsForWildcard( + req: KibanaRequest, + indexPattern: string, + { + fieldsCapabilities, + rollupIndex, + }: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string } + ) { const fields = await super.getFieldsForWildcard(req, indexPattern); const fieldsFromFieldCapsApi = indexBy(fields, 'name'); const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs; diff --git a/x-pack/legacy/plugins/rollup/server/plugin.ts b/x-pack/legacy/plugins/rollup/server/plugin.ts new file mode 100644 index 00000000000000..52b1e31af4eb22 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/plugin.ts @@ -0,0 +1,95 @@ +/* + * 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 { 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, + metrics, + }: { + __LEGACY: ServerShim; + usageCollection?: UsageCollectionSetup; + metrics?: VisTypeTimeseriesSetup; + } + ) { + 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 ( + serverShim.plugins.index_management && + serverShim.plugins.index_management.addIndexManagementDataEnricher + ) { + serverShim.plugins.index_management.addIndexManagementDataEnricher(rollupDataEnricher); + } + + if (metrics) { + const { addSearchStrategy } = metrics; + registerRollupSearchStrategy(routeDependencies, addSearchStrategy); + } + } + + start() {} + + stop() {} +} diff --git a/x-pack/legacy/plugins/rollup/rollup_data_enricher.js b/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts similarity index 77% rename from x-pack/legacy/plugins/rollup/rollup_data_enricher.js rename to x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts index e92cd3b0b4fbc6..7c5e160c54a31a 100644 --- a/x-pack/legacy/plugins/rollup/rollup_data_enricher.js +++ b/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts @@ -4,14 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -export const rollupDataEnricher = async (indicesList, callWithRequest) => { +interface Index { + name: string; + [key: string]: unknown; +} + +export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: any) => { if (!indicesList || !indicesList.length) { return indicesList; } + const params = { path: '/_all/_rollup/data', method: 'GET', }; + try { const rollupJobData = await callWithRequest('transport.request', params); return indicesList.map(index => { @@ -22,7 +29,7 @@ export const rollupDataEnricher = async (indicesList, callWithRequest) => { }; }); } catch (e) { - //swallow exceptions and return original list + // swallow exceptions and return original list return indicesList; } }; diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index.js b/x-pack/legacy/plugins/rollup/server/routes/api/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/routes/api/index.js rename to x-pack/legacy/plugins/rollup/server/routes/api/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.js b/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.js deleted file mode 100644 index dfc486c030812e..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.js +++ /dev/null @@ -1,93 +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 Joi from 'joi'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import indexBy from 'lodash/collection/indexBy'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; -import { mergeCapabilitiesWithFields } from '../../lib/merge_capabilities_with_fields'; -import querystring from 'querystring'; - -/** - * Get list of fields for rollup index pattern, in the format of regular index pattern fields - */ -export function registerFieldsForWildcardRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/index_patterns/rollup/_fields_for_wildcard', - method: 'GET', - config: { - pre: [licensePreRouting], - validate: { - query: Joi.object() - .keys({ - pattern: Joi.string().required(), - meta_fields: Joi.array() - .items(Joi.string()) - .default([]), - params: Joi.object() - .keys({ - rollup_index: Joi.string().required(), - }) - .required(), - }) - .default(), - }, - }, - handler: async request => { - const { pattern, meta_fields: metaFields, params } = request.query; - - // Format call to standard index pattern `fields for wildcard` - const standardRequestQuery = querystring.stringify({ pattern, meta_fields: metaFields }); - const standardRequest = { - url: `${request.getBasePath()}/api/index_patterns/_fields_for_wildcard?${standardRequestQuery}`, - method: 'GET', - headers: request.headers, - }; - - try { - // Make call and use field information from response - const standardResponse = await server.inject(standardRequest); - const fields = standardResponse.result && standardResponse.result.fields; - - const rollupIndex = params.rollup_index; - const callWithRequest = callWithRequestFactory(server, request); - - const rollupFields = []; - const fieldsFromFieldCapsApi = indexBy(fields, 'name'); - const rollupIndexCapabilities = getCapabilitiesForRollupIndices( - await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: rollupIndex, - }) - )[rollupIndex].aggs; - - // Keep meta fields - metaFields.forEach( - field => fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) - ); - - const mergedRollupFields = mergeCapabilitiesWithFields( - rollupIndexCapabilities, - fieldsFromFieldCapsApi, - rollupFields - ); - - return { - fields: mergedRollupFields, - }; - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - return wrapUnknownError(err); - } - }, - }); -} 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 new file mode 100644 index 00000000000000..2516840bd95375 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts @@ -0,0 +1,131 @@ +/* + * 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.js b/x-pack/legacy/plugins/rollup/server/routes/api/indices.js deleted file mode 100644 index 3d1c6932575bc8..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/indices.js +++ /dev/null @@ -1,128 +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 { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../lib/is_es_error_factory'; -import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; - -function isNumericField(fieldCapability) { - const numericTypes = [ - 'long', - 'integer', - 'short', - 'byte', - 'double', - 'float', - 'half_float', - 'scaled_float', - ]; - return numericTypes.some(numericType => fieldCapability[numericType] != null); -} - -export function registerIndicesRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - /** - * Returns a list of all rollup index names - */ - server.route({ - path: '/api/rollup/indices', - method: 'GET', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - try { - const data = await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: '_all', - }); - return getCapabilitiesForRollupIndices(data); - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - return wrapUnknownError(err); - } - }, - }); - - /** - * 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? - */ - server.route({ - path: '/api/rollup/index_pattern_validity/{indexPattern}', - method: 'GET', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - - try { - const { indexPattern } = request.params; - const [fieldCapabilities, rollupIndexCapabilities] = 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 = []; - const numericFields = []; - const keywordFields = []; - - const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields); - fieldCapabilitiesEntries.forEach(([fieldName, fieldCapability]) => { - if (fieldCapability.date) { - dateFields.push(fieldName); - return; - } - - if (isNumericField(fieldCapability)) { - numericFields.push(fieldName); - return; - } - - if (fieldCapability.keyword) { - keywordFields.push(fieldName); - } - }); - - return { - doesMatchIndices, - doesMatchRollupIndices, - dateFields, - numericFields, - keywordFields, - }; - } catch (err) { - // 404s are still valid results. - if (err.statusCode === 404) { - return { - doesMatchIndices: false, - doesMatchRollupIndices: false, - dateFields: [], - numericFields: [], - keywordFields: [], - }; - } - - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts b/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts new file mode 100644 index 00000000000000..e78f09a71876b1 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts @@ -0,0 +1,175 @@ +/* + * 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.js b/x-pack/legacy/plugins/rollup/server/routes/api/jobs.js deleted file mode 100644 index 1a9a402ad65180..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.js +++ /dev/null @@ -1,153 +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 { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../lib/is_es_error_factory'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; - -export function registerJobsRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/rollup/jobs', - method: 'GET', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - try { - const callWithRequest = callWithRequestFactory(server, request); - return await callWithRequest('rollup.jobs'); - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); - - server.route({ - path: '/api/rollup/create', - method: 'PUT', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - try { - const { id, ...rest } = request.payload.job; - - const callWithRequest = callWithRequestFactory(server, request); - - // Create job. - await callWithRequest('rollup.createJob', { - id, - body: rest, - }); - - // Then request the newly created job. - const results = await callWithRequest('rollup.job', { id }); - return results.jobs[0]; - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); - - server.route({ - path: '/api/rollup/start', - method: 'POST', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - try { - const { jobIds } = request.payload; - - const callWithRequest = callWithRequestFactory(server, request); - return await Promise.all( - jobIds.map(id => callWithRequest('rollup.startJob', { id })) - ).then(() => ({ success: true })); - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); - - server.route({ - path: '/api/rollup/stop', - method: 'POST', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - try { - const { jobIds } = request.payload; - // 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(server, request); - - const stopRollupJob = id => - callWithRequest('rollup.stopJob', { - id, - waitForCompletion: waitForCompletion === 'true', - }); - - return await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true })); - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); - - server.route({ - path: '/api/rollup/delete', - method: 'POST', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - try { - const { jobIds } = request.payload; - - const callWithRequest = callWithRequestFactory(server, request); - return await Promise.all( - jobIds.map(id => callWithRequest('rollup.deleteJob', { id })) - ).then(() => ({ success: true })); - } 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)) { - throw wrapEsError(err); - } - - throw wrapUnknownError(err); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts b/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts new file mode 100644 index 00000000000000..e58bc95b9a3753 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts @@ -0,0 +1,178 @@ +/* + * 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(), + }, + { allowUnknowns: true } + ), + }), + }, + }, + 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.js b/x-pack/legacy/plugins/rollup/server/routes/api/search.js deleted file mode 100644 index 58098421f0a8fb..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/search.js +++ /dev/null @@ -1,44 +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 { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../lib/is_es_error_factory'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; - -export function registerSearchRoute(server) { - const isEsError = isEsErrorFactory(server); - const licensePreRouting = licensePreRoutingFactory(server); - - server.route({ - path: '/api/rollup/search', - method: 'POST', - config: { - pre: [licensePreRouting], - }, - handler: async request => { - const callWithRequest = callWithRequestFactory(server, request); - - try { - const requests = request.payload.map(({ index, query }) => - callWithRequest('rollup.search', { - index, - rest_total_hits_as_int: true, - body: query, - }) - ); - - return await Promise.all(requests); - } catch (err) { - if (isEsError(err)) { - return wrapEsError(err); - } - - return wrapUnknownError(err); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/search.ts b/x-pack/legacy/plugins/rollup/server/routes/api/search.ts new file mode 100644 index 00000000000000..97999a4b5ce8d2 --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/routes/api/search.ts @@ -0,0 +1,50 @@ +/* + * 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/lib/register_license_checker/index.js b/x-pack/legacy/plugins/rollup/server/shared_imports.ts similarity index 75% rename from x-pack/legacy/plugins/rollup/server/lib/register_license_checker/index.js rename to x-pack/legacy/plugins/rollup/server/shared_imports.ts index 7b0f97c38d1292..941610b97707f8 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/register_license_checker/index.js +++ b/x-pack/legacy/plugins/rollup/server/shared_imports.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerLicenseChecker } from './register_license_checker'; +export { IndexPatternsFetcher } from '../../../../../src/plugins/data/server'; diff --git a/x-pack/legacy/plugins/rollup/server/types.ts b/x-pack/legacy/plugins/rollup/server/types.ts new file mode 100644 index 00000000000000..62a4841133cffb --- /dev/null +++ b/x-pack/legacy/plugins/rollup/server/types.ts @@ -0,0 +1,22 @@ +/* + * 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; + index_management: any; + }; +} + +export interface RouteDependencies { + router: IRouter; + elasticsearchService: ElasticsearchServiceSetup; + elasticsearch: IClusterClient; +} diff --git a/x-pack/legacy/plugins/rollup/server/usage/index.js b/x-pack/legacy/plugins/rollup/server/usage/index.js deleted file mode 100644 index 9304b35aeb6c76..00000000000000 --- a/x-pack/legacy/plugins/rollup/server/usage/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerRollupUsageCollector } from './collector'; diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json new file mode 100644 index 00000000000000..6ab2fc8907c0db --- /dev/null +++ b/x-pack/plugins/rollup/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "rollup", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true +} diff --git a/x-pack/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/index.ts new file mode 100644 index 00000000000000..40568424537765 --- /dev/null +++ b/x-pack/plugins/rollup/server/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 { PluginInitializerContext } from 'src/core/server'; +import { RollupPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => new RollupPlugin(initContext); + +export { RollupSetup } from './plugin'; diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts new file mode 100644 index 00000000000000..fa05b8d1307d61 --- /dev/null +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -0,0 +1,35 @@ +/* + * 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 { Plugin, PluginInitializerContext } from 'src/core/server'; + +export class RollupPlugin implements Plugin { + private readonly initContext: PluginInitializerContext; + + constructor(initContext: PluginInitializerContext) { + this.initContext = initContext; + } + + public setup() { + return { + __legacy: { + config: this.initContext.config, + logger: this.initContext.logger, + }, + }; + } + + public start() {} + public stop() {} +} + +export interface RollupSetup { + /** @deprecated */ + __legacy: { + config: PluginInitializerContext['config']; + logger: PluginInitializerContext['logger']; + }; +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b558e892895e72..1c2a0fc3d5ac84 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10253,9 +10253,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.rollupJobs.appTitle": "ロールアップジョブ", - "xpack.rollupJobs.checkLicense.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません", - "xpack.rollupJobs.checkLicense.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", - "xpack.rollupJobs.checkLicense.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。", "xpack.rollupJobs.create.backButton.label": "戻る", "xpack.rollupJobs.create.dateTypeField": "日付", "xpack.rollupJobs.create.errors.dateHistogramFieldMissing": "日付フィールドが必要です。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 412e31763986c5..c24f2952ef2ac7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10252,9 +10252,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.rollupJobs.appTitle": "汇总/打包作业", - "xpack.rollupJobs.checkLicense.errorExpiredMessage": "您不能使用 {pluginName},因为您的 {licenseType} 许可证已过期", - "xpack.rollupJobs.checkLicense.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", - "xpack.rollupJobs.checkLicense.errorUnsupportedMessage": "您的 {licenseType} 许可证不支持 {pluginName}。请升级您的许可。", "xpack.rollupJobs.create.backButton.label": "上一步", "xpack.rollupJobs.create.dateTypeField": "日期", "xpack.rollupJobs.create.errors.dateHistogramFieldMissing": "“日期”字段必填。", diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index 8eb084f24c52f9..be2af7cb76fd54 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -32,28 +32,42 @@ export default function({ getService }) { it('"pattern" is required', async () => { uri = `${BASE_URI}`; ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('"pattern" is required'); + expect(body.message).to.contain( + '[request query.pattern]: expected value of type [string]' + ); }); it('"params" is required', async () => { params = { pattern: 'foo' }; uri = `${BASE_URI}?${querystring.stringify(params)}`; ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('"params" is required'); + expect(body.message).to.contain( + '[request query.params]: expected value of type [string]' + ); }); - it('"params" must be an object', async () => { - params = { pattern: 'foo', params: 'bar' }; + it('"params" must be a valid JSON string', async () => { + params = { pattern: 'foo', params: 'foobarbaz' }; uri = `${BASE_URI}?${querystring.stringify(params)}`; ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('"params" must be an object'); + expect(body.message).to.contain('[request query.params]: expected JSON string'); }); - it('"params" must be an object that only accepts a "rollup_index" property', async () => { - params = { pattern: 'foo', params: JSON.stringify({ someProp: 'bar' }) }; + it('"params" requires a "rollup_index" property', async () => { + params = { pattern: 'foo', params: JSON.stringify({}) }; uri = `${BASE_URI}?${querystring.stringify(params)}`; ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('"someProp" is not allowed'); + expect(body.message).to.contain('[request query.params]: "rollup_index" is required'); + }); + + it('"params" only accepts a "rollup_index" property', async () => { + params = { + pattern: 'foo', + params: JSON.stringify({ rollup_index: 'my_index', someProp: 'bar' }), + }; + uri = `${BASE_URI}?${querystring.stringify(params)}`; + ({ body } = await supertest.get(uri).expect(400)); + expect(body.message).to.contain('[request query.params]: someProp is not allowed'); }); it('"meta_fields" must be an Array', async () => { @@ -64,7 +78,9 @@ export default function({ getService }) { }; uri = `${BASE_URI}?${querystring.stringify(params)}`; ({ body } = await supertest.get(uri).expect(400)); - expect(body.message).to.contain('"meta_fields" must be an array'); + expect(body.message).to.contain( + '[request query.meta_fields]: expected value of type [array]' + ); }); it('should return 404 the rollup index to query does not exist', async () => { @@ -73,7 +89,7 @@ export default function({ getService }) { params: JSON.stringify({ rollup_index: 'bar' }), })}`; ({ body } = await supertest.get(uri).expect(404)); - expect(body.message).to.contain('no such index [bar]'); + expect(body.message).to.contain('[index_not_found_exception] no such index [bar]'); }); }); From d949886d8a795e36c835d949991e9122667ff694 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Fri, 7 Feb 2020 16:02:38 +0100 Subject: [PATCH 03/10] Disabled categorization stats validation (#57087) This PR disabled the categorization stats validation after job creation. --- .../functional/services/machine_learning/job_table.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts index 153a0ac477b47b..dc401ca4548354 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -208,6 +208,15 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte expect(modelSizeStats).to.have.property('model_bytes'); delete modelSizeStats.model_bytes; + // remove categorization fields from validation until + // the ES version is updated + delete modelSizeStats.categorization_status; + delete modelSizeStats.categorized_doc_count; + delete modelSizeStats.dead_category_count; + delete modelSizeStats.frequent_category_count; + delete modelSizeStats.rare_category_count; + delete modelSizeStats.total_category_count; + expect(modelSizeStats).to.eql(expectedModelSizeStats); } From ff608998cdc58ec403a08b6de90462f2b12b5fac Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Fri, 7 Feb 2020 16:03:58 +0100 Subject: [PATCH 04/10] Saved Objects testing (#56965) * Expose core/public savedObjectsServiceMock * Test docs for Saved Objects unit and integration tests * Review comments * Update api types / docs --- ...-public.simplesavedobject._constructor_.md | 4 +- src/core/TESTING.md | 262 +++++++++++++++++- src/core/public/legacy/legacy_service.test.ts | 4 +- src/core/public/mocks.ts | 5 +- .../public/plugins/plugins_service.test.ts | 4 +- src/core/public/public.api.md | 2 +- .../saved_objects_service.mock.ts | 2 +- .../saved_objects/simple_saved_object.ts | 4 +- 8 files changed, 264 insertions(+), 23 deletions(-) diff --git a/docs/development/core/public/kibana-plugin-public.simplesavedobject._constructor_.md b/docs/development/core/public/kibana-plugin-public.simplesavedobject._constructor_.md index f0769c0124d638..87d317da7a9368 100644 --- a/docs/development/core/public/kibana-plugin-public.simplesavedobject._constructor_.md +++ b/docs/development/core/public/kibana-plugin-public.simplesavedobject._constructor_.md @@ -9,13 +9,13 @@ Constructs a new instance of the `SimpleSavedObject` class Signature: ```typescript -constructor(client: SavedObjectsClient, { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType); +constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| client | SavedObjectsClient | | +| client | SavedObjectsClientContract | | | { id, type, version, attributes, error, references, migrationVersion } | SavedObjectType<T> | | diff --git a/src/core/TESTING.md b/src/core/TESTING.md index 4dfab8830a5069..aac54a4a14680b 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -2,15 +2,34 @@ This document outlines best practices and patterns for testing Kibana Plugins. -- [Strategy](#strategy) -- [Core Integrations](#core-integrations) - - [Core Mocks](#core-mocks) +- [Testing Kibana Plugins](#testing-kibana-plugins) + - [Strategy](#strategy) + - [New concerns in the Kibana Platform](#new-concerns-in-the-kibana-platform) + - [Core Integrations](#core-integrations) + - [Core Mocks](#core-mocks) + - [Example](#example) - [Strategies for specific Core APIs](#strategies-for-specific-core-apis) - - [HTTP Routes](#http-routes) - - [SavedObjects](#savedobjects) - - [Elasticsearch](#elasticsearch) -- [Plugin Integrations](#plugin-integrations) -- [Plugin Contracts](#plugin-contracts) + - [HTTP Routes](#http-routes) + - [Preconditions](#preconditions) + - [Unit testing](#unit-testing) + - [Example](#example-1) + - [Integration tests](#integration-tests) + - [Functional Test Runner](#functional-test-runner) + - [Example](#example-2) + - [TestUtils](#testutils) + - [Example](#example-3) + - [Applications](#applications) + - [Example](#example-4) + - [SavedObjects](#savedobjects) + - [Unit Tests](#unit-tests) + - [Integration Tests](#integration-tests-1) + - [Elasticsearch](#elasticsearch) + - [Plugin integrations](#plugin-integrations) + - [Preconditions](#preconditions-1) + - [Testing dependencies usages](#testing-dependencies-usages) + - [Testing components consuming the dependencies](#testing-components-consuming-the-dependencies) + - [Testing optional plugin dependencies](#testing-optional-plugin-dependencies) + - [Plugin Contracts](#plugin-contracts) ## Strategy @@ -540,11 +559,232 @@ describe('renderApp', () => { }); ``` -#### SavedObjects +### SavedObjects -_How to test SO operations_ +#### Unit Tests -#### Elasticsearch +To unit test code that uses the Saved Objects client mock the client methods +and make assertions against the behaviour you would expect to see. + +Since the Saved Objects client makes network requests to an external +Elasticsearch cluster, it's important to include failure scenarios in your +test cases. + +When writing a view with which a user might interact, it's important to ensure +your code can recover from exceptions and provide a way for the user to +proceed. This behaviour should be tested as well. + +Below is an example of a Jest Unit test suite that mocks the server-side Saved +Objects client: + +```typescript +// src/plugins/myplugin/server/lib/short_url_lookup.ts +import crypto from 'crypto'; +import { SavedObjectsClientContract } from 'kibana/server'; + +export const shortUrlLookup = { + generateUrlId(url: string, savedObjectsClient: SavedObjectsClientContract) { + const id = crypto + .createHash('md5') + .update(url) + .digest('hex'); + + return savedObjectsClient + .create( + 'url', + { + url, + accessCount: 0, + createDate: new Date().valueOf(), + accessDate: new Date().valueOf(), + }, + { id } + ) + .then(doc => doc.id) + .catch(err => { + if (savedObjectsClient.errors.isConflictError(err)) { + return id; + } else { + throw err; + } + }); + }, +}; + +``` + +```typescript +// src/plugins/myplugin/server/lib/short_url_lookup.test.ts +import { shortUrlLookup } from './short_url_lookup'; +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; + +describe('shortUrlLookup', () => { + const ID = 'bf00ad16941fc51420f91a93428b27a0'; + const TYPE = 'url'; + const URL = 'http://elastic.co'; + + const mockSavedObjectsClient = savedObjectsClientMock.create(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('generateUrlId', () => { + it('provides correct arguments to savedObjectsClient', async () => { + const ATTRIBUTES = { + url: URL, + accessCount: 0, + createDate: new Date().valueOf(), + accessDate: new Date().valueOf(), + }; + mockSavedObjectsClient.create.mockResolvedValueOnce({ + id: ID, + type: TYPE, + references: [], + attributes: ATTRIBUTES, + }); + await shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient); + + expect(mockSavedObjectsClient.create).toHaveBeenCalledTimes(1); + const [type, attributes, options] = mockSavedObjectsClient.create.mock.calls[0]; + expect(type).toBe(TYPE); + expect(attributes).toStrictEqual(ATTRIBUTES); + expect(options).toStrictEqual({ id: ID }); + }); + + it('ignores version conflict and returns id', async () => { + mockSavedObjectsClient.create.mockRejectedValueOnce( + mockSavedObjectsClient.errors.decorateConflictError(new Error()) + ); + const id = await shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient); + expect(id).toEqual(ID); + }); + + it('rejects with passed through savedObjectsClient errors', () => { + const error = new Error('oops'); + mockSavedObjectsClient.create.mockRejectedValueOnce(error); + return expect(shortUrlLookup.generateUrlId(URL, mockSavedObjectsClient)).rejects.toBe(error); + }); + }); +}); +``` + +The following is an example of a public saved object unit test. The biggest +difference with the server-side test is the slightly different Saved Objects +client API which returns `SimpleSavedObject` instances which needs to be +reflected in the mock. + +```typescript +// src/plugins/myplugin/public/saved_query_service.ts +import { + SavedObjectsClientContract, + SavedObjectAttributes, + SimpleSavedObject, +} from 'src/core/public'; + +export type SavedQueryAttributes = SavedObjectAttributes & { + title: string; + description: 'bar'; + query: { + language: 'kuery'; + query: 'response:200'; + }; +}; + +export const createSavedQueryService = (savedObjectsClient: SavedObjectsClientContract) => { + const saveQuery = async ( + attributes: SavedQueryAttributes + ): Promise> => { + try { + return await savedObjectsClient.create('query', attributes, { + id: attributes.title as string, + }); + } catch (err) { + throw new Error('Unable to create saved query, please try again.'); + } + }; + + return { + saveQuery, + }; +}; +``` + +```typescript +// src/plugins/myplugin/public/saved_query_service.test.ts +import { createSavedQueryService, SavedQueryAttributes } from './saved_query_service'; +import { savedObjectsServiceMock } from '../../../../../core/public/mocks'; +import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; + +describe('saved query service', () => { + const savedQueryAttributes: SavedQueryAttributes = { + title: 'foo', + description: 'bar', + query: { + language: 'kuery', + query: 'response:200', + }, + }; + + const mockSavedObjectsClient = savedObjectsServiceMock.createStartContract() + .client as jest.Mocked; + + const savedQueryService = createSavedQueryService(mockSavedObjectsClient); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('saveQuery', function() { + it('should create a saved object for the given attributes', async () => { + // The public Saved Objects client returns instances of + // SimpleSavedObject, so we create an instance to return from our mock. + const mockReturnValue = new SimpleSavedObject(mockSavedObjectsClient, { + type: 'query', + id: 'foo', + attributes: savedQueryAttributes, + references: [], + }); + mockSavedObjectsClient.create.mockResolvedValue(mockReturnValue); + + const response = await savedQueryService.saveQuery(savedQueryAttributes); + expect(mockSavedObjectsClient.create).toHaveBeenCalledWith('query', savedQueryAttributes, { + id: 'foo', + }); + expect(response).toBe(mockReturnValue); + }); + + it('should reject with an error when saved objects client errors', async done => { + mockSavedObjectsClient.create.mockRejectedValue(new Error('timeout')); + + try { + await savedQueryService.saveQuery(savedQueryAttributes); + } catch (err) { + expect(err).toMatchInlineSnapshot( + `[Error: Unable to create saved query, please try again.]` + ); + done(); + } + }); + }); +}); +``` + +#### Integration Tests +To get the highest confidence in how your code behaves when using the Saved +Objects client, you should write at least a few integration tests which loads +data into and queries a real Elasticsearch database. + +To do that we'll write a Jest integration test using `TestUtils` to start +Kibana and esArchiver to load fixture data into Elasticsearch. + +1. Create the fixtures data you need in Elasticsearch +2. Create a fixtures archive with `node scripts/es_archiver save [index patterns...]` +3. Load the fixtures in your test using esArchiver `esArchiver.load('name')`; + +_todo: fully worked out example_ + +### Elasticsearch _How to test ES clients_ diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index d08c8b52e39c9b..c3de645c6b17e4 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -58,7 +58,7 @@ import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { LegacyPlatformService } from './legacy_service'; import { applicationServiceMock } from '../application/application_service.mock'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; -import { savedObjectsMock } from '../saved_objects/saved_objects_service.mock'; +import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; const applicationSetup = applicationServiceMock.createInternalSetupContract(); @@ -97,7 +97,7 @@ const injectedMetadataStart = injectedMetadataServiceMock.createStartContract(); const notificationsStart = notificationServiceMock.createStartContract(); const overlayStart = overlayServiceMock.createStartContract(); const uiSettingsStart = uiSettingsServiceMock.createStartContract(); -const savedObjectsStart = savedObjectsMock.createStartContract(); +const savedObjectsStart = savedObjectsServiceMock.createStartContract(); const fatalErrorsStart = fatalErrorsServiceMock.createStartContract(); const mockStorage = { getItem: jest.fn() } as any; diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index ce90d49065ad4e..3301d71e2cdaf4 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -26,7 +26,7 @@ import { i18nServiceMock } from './i18n/i18n_service.mock'; import { notificationServiceMock } from './notifications/notifications_service.mock'; import { overlayServiceMock } from './overlays/overlay_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; -import { savedObjectsMock } from './saved_objects/saved_objects_service.mock'; +import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { contextServiceMock } from './context/context_service.mock'; import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; @@ -40,6 +40,7 @@ export { legacyPlatformServiceMock } from './legacy/legacy_service.mock'; export { notificationServiceMock } from './notifications/notifications_service.mock'; export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; function createCoreSetupMock({ basePath = '' } = {}) { const mock = { @@ -70,7 +71,7 @@ function createCoreStartMock({ basePath = '' } = {}) { notifications: notificationServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), - savedObjects: savedObjectsMock.createStartContract(), + savedObjects: savedObjectsServiceMock.createStartContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar, }, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index dbbcda8d60e128..688eaf4f2bfc7a 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -44,7 +44,7 @@ import { injectedMetadataServiceMock } from '../injected_metadata/injected_metad import { httpServiceMock } from '../http/http_service.mock'; import { CoreSetup, CoreStart, PluginInitializerContext } from '..'; import { docLinksServiceMock } from '../doc_links/doc_links_service.mock'; -import { savedObjectsMock } from '../saved_objects/saved_objects_service.mock'; +import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; export let mockPluginInitializers: Map; @@ -110,7 +110,7 @@ describe('PluginsService', () => { notifications: notificationServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), - savedObjects: savedObjectsMock.createStartContract(), + savedObjects: savedObjectsServiceMock.createStartContract(), fatalErrors: fatalErrorsServiceMock.createStartContract(), }; mockStartContext = { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 91b4e9be58973a..fb48524c20fb94 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1170,7 +1170,7 @@ export interface SavedObjectsUpdateOptions { // @public export class SimpleSavedObject { - constructor(client: SavedObjectsClient, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); + constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); // (undocumented) attributes: T; // (undocumented) diff --git a/src/core/public/saved_objects/saved_objects_service.mock.ts b/src/core/public/saved_objects/saved_objects_service.mock.ts index 247e684a24b928..855bdf8314ec86 100644 --- a/src/core/public/saved_objects/saved_objects_service.mock.ts +++ b/src/core/public/saved_objects/saved_objects_service.mock.ts @@ -45,7 +45,7 @@ const createMock = () => { return mocked; }; -export const savedObjectsMock = { +export const savedObjectsServiceMock = { create: createMock, createStartContract: createStartContractMock, }; diff --git a/src/core/public/saved_objects/simple_saved_object.ts b/src/core/public/saved_objects/simple_saved_object.ts index 7978708c9eabbb..8e464680bcf174 100644 --- a/src/core/public/saved_objects/simple_saved_object.ts +++ b/src/core/public/saved_objects/simple_saved_object.ts @@ -19,7 +19,7 @@ import { get, has, set } from 'lodash'; import { SavedObject as SavedObjectType, SavedObjectAttributes } from '../../server'; -import { SavedObjectsClient } from './saved_objects_client'; +import { SavedObjectsClientContract } from './saved_objects_client'; /** * This class is a very simple wrapper for SavedObjects loaded from the server @@ -41,7 +41,7 @@ export class SimpleSavedObject { public references: SavedObjectType['references']; constructor( - private client: SavedObjectsClient, + private client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObjectType ) { this.id = id; From dd8ccc45df2db5bd410b0d3802ca42b3617a24ed Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Fri, 7 Feb 2020 09:17:41 -0600 Subject: [PATCH 05/10] [Metrics-UI] Fix toolbar popover for metrics table row (#56796) * Disable scroll when popover is open. Switch to hooks * Remove unused prop * UseEffect to update styles * Remove ununsed import * Rename variables for clarity Co-authored-by: Elastic Machine --- .../components/nodes_overview/table.tsx | 250 +++++++++--------- .../components/waffle/node_context_menu.tsx | 7 +- 2 files changed, 128 insertions(+), 129 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx index dc0de6f6e9c69a..5c793f670119c7 100644 --- a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx +++ b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx @@ -8,29 +8,24 @@ import { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip, EuiBasicTableColumn } fro import { i18n } from '@kbn/i18n'; import { last } from 'lodash'; -import React from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { createWaffleMapNode } from '../../containers/waffle/nodes_to_wafflemap'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { fieldToName } from '../waffle/lib/field_to_display_name'; import { NodeContextMenu } from '../waffle/node_context_menu'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { SnapshotNode, SnapshotNodePath } from '../../../common/http_api/snapshot_api'; +import { ROOT_ELEMENT_ID } from '../../app'; interface Props { nodes: SnapshotNode[]; nodeType: InventoryItemType; options: InfraWaffleMapOptions; - formatter: (subject: string | number) => string; currentTime: number; + formatter: (subject: string | number) => string; onFilter: (filter: string) => void; } -const initialState = { - isPopoverOpen: [] as string[], -}; - -type State = Readonly; - const getGroupPaths = (path: SnapshotNodePath[]) => { switch (path.length) { case 3: @@ -42,126 +37,131 @@ const getGroupPaths = (path: SnapshotNodePath[]) => { } }; -export const TableView = class extends React.PureComponent { - public readonly state: State = initialState; - public render() { - const { nodes, options, formatter, currentTime, nodeType } = this.props; - const columns: Array> = [ - { - field: 'name', - name: i18n.translate('xpack.infra.tableView.columnName.name', { defaultMessage: 'Name' }), - sortable: true, - truncateText: true, - textOnly: true, - render: (value: string, item: { node: InfraWaffleMapNode }) => { - const tooltipText = item.node.id === value ? `${value}` : `${value} (${item.node.id})`; - // For the table we need to create a UniqueID that takes into to account the groupings - // as well as the node name. There is the possibility that a node can be present in two - // different groups and be on the screen at the same time. - const uniqueID = [...item.node.path.map(p => p.value), item.node.name].join(':'); - return ( - - - {value} - - - ); - }, - }, - ...options.groupBy.map((grouping, index) => ({ - field: `group_${index}`, - name: fieldToName((grouping && grouping.field) || ''), - sortable: true, - truncateText: true, - textOnly: true, - render: (value: string) => { - const handleClick = () => this.props.onFilter(`${grouping.field}:"${value}"`); - return ( - - {value} +export const TableView = (props: Props) => { + const { nodes, options, formatter, currentTime, nodeType } = props; + const [openPopovers, setOpenPopovers] = useState([]); + const openPopoverFor = useCallback( + (id: string) => () => { + setOpenPopovers([...openPopovers, id]); + }, + [openPopovers] + ); + + const closePopoverFor = useCallback( + (id: string) => () => { + if (openPopovers.includes(id)) { + setOpenPopovers(openPopovers.filter(subject => subject !== id)); + } + }, + [openPopovers] + ); + + useEffect(() => { + if (openPopovers.length > 0) { + document.getElementById(ROOT_ELEMENT_ID)!.style.overflowY = 'hidden'; + } else { + document.getElementById(ROOT_ELEMENT_ID)!.style.overflowY = 'auto'; + } + }, [openPopovers]); + + const columns: Array> = [ + { + field: 'name', + name: i18n.translate('xpack.infra.tableView.columnName.name', { defaultMessage: 'Name' }), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string, item: { node: InfraWaffleMapNode }) => { + const tooltipText = item.node.id === value ? `${value}` : `${value} (${item.node.id})`; + // For the table we need to create a UniqueID that takes into to account the groupings + // as well as the node name. There is the possibility that a node can be present in two + // different groups and be on the screen at the same time. + const uniqueID = [...item.node.path.map(p => p.value), item.node.name].join(':'); + return ( + + + {value} - ); - }, - })), - { - field: 'value', - name: i18n.translate('xpack.infra.tableView.columnName.last1m', { - defaultMessage: 'Last 1m', - }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, - }, - { - field: 'avg', - name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, + + ); }, - { - field: 'max', - name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, + }, + ...options.groupBy.map((grouping, index) => ({ + field: `group_${index}`, + name: fieldToName((grouping && grouping.field) || ''), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string) => { + const handleClick = () => props.onFilter(`${grouping.field}:"${value}"`); + return ( + + {value} + + ); }, - ]; - const items = nodes.map(node => { - const name = last(node.path); - return { - name: (name && name.label) || 'unknown', - ...getGroupPaths(node.path).reduce( - (acc, path, index) => ({ - ...acc, - [`group_${index}`]: path.label, - }), - {} - ), - value: node.metric.value, - avg: node.metric.avg, - max: node.metric.max, - node: createWaffleMapNode(node), - }; - }); - const initialSorting = { - sort: { - field: 'value', - direction: 'desc', - }, - } as const; - return ( - - ); - } + })), + { + field: 'value', + name: i18n.translate('xpack.infra.tableView.columnName.last1m', { + defaultMessage: 'Last 1m', + }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'avg', + name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'max', + name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + ]; - private openPopoverFor = (id: string) => () => { - this.setState(prevState => ({ isPopoverOpen: [...prevState.isPopoverOpen, id] })); - }; + const items = nodes.map(node => { + const name = last(node.path); + return { + name: (name && name.label) || 'unknown', + ...getGroupPaths(node.path).reduce( + (acc, path, index) => ({ + ...acc, + [`group_${index}`]: path.label, + }), + {} + ), + value: node.metric.value, + avg: node.metric.avg, + max: node.metric.max, + node: createWaffleMapNode(node), + }; + }); + const initialSorting = { + sort: { + field: 'value', + direction: 'desc', + }, + } as const; - private closePopoverFor = (id: string) => () => { - if (this.state.isPopoverOpen.includes(id)) { - this.setState(prevState => { - return { - isPopoverOpen: prevState.isPopoverOpen.filter(subject => subject !== id), - }; - }); - } - }; + return ( + + ); }; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx index 86a22c358b4d51..97b85a7e6e17d4 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -28,7 +28,6 @@ import { interface Props { options: InfraWaffleMapOptions; currentTime: number; - children: any; node: InfraWaffleMapNode; nodeType: InventoryItemType; isPopoverOpen: boolean; @@ -36,7 +35,7 @@ interface Props { popoverPosition: EuiPopoverProps['anchorPosition']; } -export const NodeContextMenu = ({ +export const NodeContextMenu: React.FC = ({ options, currentTime, children, @@ -45,7 +44,7 @@ export const NodeContextMenu = ({ closePopover, nodeType, popoverPosition, -}: Props) => { +}) => { const uiCapabilities = useKibana().services.application?.capabilities; const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; @@ -132,7 +131,7 @@ export const NodeContextMenu = ({ closePopover={closePopover} id={`${node.pathId}-popover`} isOpen={isPopoverOpen} - button={children} + button={children!} anchorPosition={popoverPosition} >
From 20782f62dfa8bb34e74679d8dea937d70295b113 Mon Sep 17 00:00:00 2001 From: Phillip Burch Date: Fri, 7 Feb 2020 09:18:21 -0600 Subject: [PATCH 06/10] Add new config for filebeat index name (#56920) --- x-pack/legacy/plugins/monitoring/common/constants.ts | 2 -- x-pack/legacy/plugins/monitoring/config.js | 3 +++ x-pack/legacy/plugins/monitoring/index.js | 1 + .../monitoring/server/lib/logs/init_infra_source.js | 8 ++++++-- .../server/routes/api/v1/cluster/cluster.js | 4 ++-- .../server/routes/api/v1/cluster/clusters.js | 4 ++-- .../routes/api/v1/elasticsearch/index_detail.js | 11 ++++++----- .../server/routes/api/v1/elasticsearch/node_detail.js | 11 ++++++----- .../server/routes/api/v1/elasticsearch/overview.js | 11 ++++++----- 9 files changed, 32 insertions(+), 23 deletions(-) diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 53764f592dc151..1fc322a0de395f 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -159,8 +159,6 @@ export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7' + INDEX_ export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*' + INDEX_PATTERN_ELASTICSEARCH_NEW; -export const INDEX_PATTERN_FILEBEAT = 'filebeat-*'; - // This is the unique token that exists in monitoring indices collected by metricbeat export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-'; diff --git a/x-pack/legacy/plugins/monitoring/config.js b/x-pack/legacy/plugins/monitoring/config.js index 778b656c056f20..ec4c00ccbea113 100644 --- a/x-pack/legacy/plugins/monitoring/config.js +++ b/x-pack/legacy/plugins/monitoring/config.js @@ -18,6 +18,9 @@ export const config = Joi => { enabled: Joi.boolean().default(true), ui: Joi.object({ enabled: Joi.boolean().default(true), + logs: Joi.object({ + index: Joi.string().default('filebeat-*'), + }).default(), ccs: Joi.object({ enabled: Joi.boolean().default(true), }).default(), diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js index ade172f527dabd..25b88958c116f5 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -50,6 +50,7 @@ export const monitoring = kibana => 'monitoring.cluster_alerts.email_notifications.email_address', 'monitoring.ui.ccs.enabled', 'monitoring.ui.elasticsearch.logFetchCount', + 'monitoring.ui.logs.index', ]; const serverConfig = server.config(); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/logs/init_infra_source.js b/x-pack/legacy/plugins/monitoring/server/lib/logs/init_infra_source.js index f2b179fe014b34..7ca36e8b295530 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/logs/init_infra_source.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/logs/init_infra_source.js @@ -5,11 +5,15 @@ */ import { prefixIndexPattern } from '../ccs_utils'; -import { INDEX_PATTERN_FILEBEAT, INFRA_SOURCE_ID } from '../../../common/constants'; +import { INFRA_SOURCE_ID } from '../../../common/constants'; export const initInfraSource = (config, infraPlugin) => { if (infraPlugin) { - const filebeatIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_FILEBEAT, '*'); + const filebeatIndexPattern = prefixIndexPattern( + config, + config.get('monitoring.ui.logs.index'), + '*' + ); infraPlugin.defineInternalSourceConfiguration(INFRA_SOURCE_ID, { name: 'Elastic Stack Logs', logAlias: filebeatIndexPattern, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/cluster.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/cluster.js index 5c85a10edbc29f..b2c2db7ea793ea 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/cluster.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/cluster.js @@ -9,7 +9,6 @@ import { getClustersFromRequest } from '../../../../lib/cluster/get_clusters_fro import { handleError } from '../../../../lib/errors'; import { getIndexPatterns } from '../../../../lib/cluster/get_index_patterns'; import { verifyCcsAvailability } from '../../../../lib/elasticsearch/verify_ccs_availability'; -import { INDEX_PATTERN_FILEBEAT } from '../../../../../common/constants'; export function clusterRoute(server) { /* @@ -37,9 +36,10 @@ export function clusterRoute(server) { }, handler: async req => { await verifyCcsAvailability(req); + const config = server.config(); const indexPatterns = getIndexPatterns(server, { - filebeatIndexPattern: INDEX_PATTERN_FILEBEAT, + filebeatIndexPattern: config.get('monitoring.ui.logs.index'), }); const options = { clusterUuid: req.params.clusterUuid, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/clusters.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/clusters.js index 6342a9436f6c72..92f03670972287 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/clusters.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/cluster/clusters.js @@ -9,7 +9,6 @@ import { getClustersFromRequest } from '../../../../lib/cluster/get_clusters_fro import { verifyMonitoringAuth } from '../../../../lib/elasticsearch/verify_monitoring_auth'; import { verifyCcsAvailability } from '../../../../lib/elasticsearch/verify_ccs_availability'; import { handleError } from '../../../../lib/errors'; -import { INDEX_PATTERN_FILEBEAT } from '../../../../../common/constants'; import { getIndexPatterns } from '../../../../lib/cluster/get_index_patterns'; export function clustersRoute(server) { @@ -34,6 +33,7 @@ export function clustersRoute(server) { }, }, handler: async req => { + const config = server.config(); let clusters = []; // NOTE using try/catch because checkMonitoringAuth is expected to throw @@ -43,7 +43,7 @@ export function clustersRoute(server) { await verifyMonitoringAuth(req); await verifyCcsAvailability(req); const indexPatterns = getIndexPatterns(server, { - filebeatIndexPattern: INDEX_PATTERN_FILEBEAT, + filebeatIndexPattern: config.get('monitoring.ui.logs.index'), }); clusters = await getClustersFromRequest(req, indexPatterns, { codePaths: req.payload.codePaths, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js index c32e25d9f20d19..6f03459acf2856 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js @@ -13,10 +13,7 @@ import { getShardAllocation, getShardStats } from '../../../../lib/elasticsearch import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_index_detail'; -import { - INDEX_PATTERN_ELASTICSEARCH, - INDEX_PATTERN_FILEBEAT, -} from '../../../../../common/constants'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs/get_logs'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSet; @@ -50,7 +47,11 @@ export function esIndexRoute(server) { const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); - const filebeatIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_FILEBEAT, ccs); + const filebeatIndexPattern = prefixIndexPattern( + config, + config.get('monitoring.ui.logs.index'), + ccs + ); const isAdvanced = req.payload.is_advanced; const metricSet = isAdvanced ? metricSetAdvanced : metricSetOverview; diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js index 25ead723e3ddb8..364214d45c2da2 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js @@ -13,10 +13,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSets } from './metric_set_node_detail'; -import { - INDEX_PATTERN_ELASTICSEARCH, - INDEX_PATTERN_FILEBEAT, -} from '../../../../../common/constants'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs/get_logs'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSets; @@ -51,7 +48,11 @@ export function esNodeRoute(server) { const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); - const filebeatIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_FILEBEAT, '*'); + const filebeatIndexPattern = prefixIndexPattern( + config, + config.get('monitoring.ui.logs.index'), + '*' + ); const isAdvanced = req.payload.is_advanced; let metricSet; diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js index b0045502fa228c..df1e847c16606a 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js @@ -12,10 +12,7 @@ import { getMetrics } from '../../../../lib/details/get_metrics'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_overview'; -import { - INDEX_PATTERN_ELASTICSEARCH, - INDEX_PATTERN_FILEBEAT, -} from '../../../../../common/constants'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; @@ -42,7 +39,11 @@ export function esOverviewRoute(server) { const ccs = req.payload.ccs; const clusterUuid = req.params.clusterUuid; const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); - const filebeatIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_FILEBEAT, '*'); + const filebeatIndexPattern = prefixIndexPattern( + config, + config.get('monitoring.ui.logs.index'), + '*' + ); const start = req.payload.timeRange.min; const end = req.payload.timeRange.max; From 12fcf015cb19ed47098fb690cc28f8a9eff4bd0e Mon Sep 17 00:00:00 2001 From: Chris Koehnke Date: Fri, 7 Feb 2020 10:19:32 -0500 Subject: [PATCH 07/10] [docs] Fix spaces api example json (#50411) Make this example properly formatted json. Co-authored-by: Elastic Machine --- docs/api/spaces-management/get_all.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/spaces-management/get_all.asciidoc b/docs/api/spaces-management/get_all.asciidoc index 4ae67a0dcca8b4..f7fb92baa165f2 100644 --- a/docs/api/spaces-management/get_all.asciidoc +++ b/docs/api/spaces-management/get_all.asciidoc @@ -42,7 +42,7 @@ The API returns the following: "color": "#aabbcc", "disabledFeatures": ["apm"], "initials": "MK", - "imageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSU", + "imageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSU" }, { "id": "sales", @@ -50,6 +50,6 @@ The API returns the following: "initials": "MK", "disabledFeatures": ["discover", "timelion"], "imageUrl": "" - }, + } ] -------------------------------------------------- From 13944fadaea43d050cf68df6d772ff2f71ac1cbb Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Fri, 7 Feb 2020 09:38:51 -0600 Subject: [PATCH 08/10] [Logs UI] Set streamLive false in URL state when arriving from link-to (#56329) * [Logs UI] Remove streamLive false from URL state * Force false livestream boolean from link-to * Update type def * Update snapshots * Fix snapshot typo * Fix snapshot again * Fix node test snapshot Co-authored-by: Elastic Machine --- .../logs/log_position/with_log_position_url_state.tsx | 5 +++-- .../infra/public/pages/link_to/redirect_to_logs.test.tsx | 4 ++-- .../public/pages/link_to/redirect_to_node_logs.test.tsx | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx index a877750587be4c..221dac95ef5f07 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx @@ -16,7 +16,7 @@ import { LogPositionState, LogPositionStateParams } from './log_position_state'; interface LogPositionUrlState { position: LogPositionStateParams['visibleMidpoint'] | undefined; - streamLive?: boolean | undefined; + streamLive: boolean; } export const WithLogPositionUrlState = () => { @@ -81,7 +81,7 @@ const mapToPositionUrlState = (value: any) => ? pickTimeKey(value) : undefined; -const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); +const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : false); export const replaceLogPositionInQueryString = (time: number) => Number.isNaN(time) @@ -91,4 +91,5 @@ export const replaceLogPositionInQueryString = (time: number) => time, tiebreaker: 0, }, + streamLive: false, }); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx index a800b6421c0276..a418be01d1ed2d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx @@ -19,7 +19,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -33,7 +33,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx index 5fa80c8efee73f..2d1f3a32988aab 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx @@ -73,7 +73,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -89,7 +89,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); From e3535b1b1c7f66251a2d422a218615449fe8190d Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Fri, 7 Feb 2020 10:55:29 -0500 Subject: [PATCH 09/10] Remove Kibana a11y guide in favor of EUI (#57021) * Remove Kibana a11y styleguide in favor of EUI and moving auxilary content to CONTRIBUTING.md and STYLEGUIDE.md Co-Authored-By: Tim Roes --- CONTRIBUTING.md | 18 +++++ STYLEGUIDE.md | 111 +++++++++++++++++++++------- style_guides/accessibility_guide.md | 73 ------------------ 3 files changed, 101 insertions(+), 101 deletions(-) delete mode 100644 style_guides/accessibility_guide.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59e3bd9232d8b8..9cba1ee909f6da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,7 @@ A high level overview of our contributing guidelines. - [Instrumenting with Elastic APM](#instrumenting-with-elastic-apm) - [Debugging Unit Tests](#debugging-unit-tests) - [Unit Testing Plugins](#unit-testing-plugins) + - [Automated Accessibility Testing](#automated-accessibility-testing) - [Cross-browser compatibility](#cross-browser-compatibility) - [Testing compatibility locally](#testing-compatibility-locally) - [Running Browser Automation Tests](#running-browser-automation-tests) @@ -553,6 +554,23 @@ yarn test:mocha yarn test:browser --dev # remove the --dev flag to run them once and close ``` +### Automated Accessibility Testing + +To run the tests locally: + +1. In one terminal window run `node scripts/functional_tests_server --config test/accessibility/config.ts` +2. In another terminal window run `node scripts/functional_test_runner.js --config test/accessibility/config.ts` + +To run the x-pack tests, swap the config file out for `x-pack/test/accessibility/config.ts`. + +After the server is up, you can go to this instance of Kibana at `localhost:5620`. + +The testing is done using [axe](https://github.com/dequelabs/axe-core). The same thing that runs in CI, +can be run locally using their browser plugins: + +- [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US) +- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) + ### Cross-browser Compatibility #### Testing Compatibility Locally diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 461d51a3e76e30..48d4f929b68518 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -6,7 +6,7 @@ recommended for the development of all Kibana plugins. Besides the content in this style guide, the following style guides may also apply to all development within the Kibana project. Please make sure to also read them: -- [Accessibility style guide](style_guides/accessibility_guide.md) +- [Accessibility style guide](https://elastic.github.io/eui/#/guidelines/accessibility) - [SASS style guide](https://elastic.github.io/eui/#/guidelines/sass) ## General @@ -45,10 +45,7 @@ This part contains style guide rules around general (framework agnostic) HTML us Use camel case for the values of attributes such as `id` and `data-test-subj` selectors. ```html - ``` @@ -74,6 +71,59 @@ It's important that when you write CSS/SASS selectors using classes, IDs, and at capitalization in the CSS matches that used in the HTML. HTML and CSS follow different case sensitivity rules, and we can avoid subtle gotchas by ensuring we use the same capitalization in both of them. +### How to generate ids? + +When labeling elements (and for some other accessibility tasks) you will often need +ids. Ids must be unique within the page i.e. no duplicate ids in the rendered DOM +at any time. + +Since we have some components that are used multiple times on the page, you must +make sure every instance of that component has a unique `id`. To make the generation +of those `id`s easier, you can use the `htmlIdGenerator` service in the `@elastic/eui`. + +A React component could use it as follows: + +```jsx +import { htmlIdGenerator } from '@elastic/eui'; + +render() { + // Create a new generator that will create ids deterministic + const htmlId = htmlIdGenerator(); + return (
+ + +
); +} +``` + +Each id generator you create by calling `htmlIdGenerator()` will generate unique but +deterministic ids. As you can see in the above example, that single generator +created the same id in the label's `htmlFor` as well as the input's `id`. + +A single generator instance will create the same id when passed the same argument +to the function multiple times. But two different generators will produce two different +ids for the same argument to the function, as you can see in the following example: + +```js +const generatorOne = htmlIdGenerator(); +const generatorTwo = htmlIdGenerator(); + +// Those statements are always true: +// Same generator +generatorOne('foo') === generatorOne('foo'); +generatorOne('foo') !== generatorOne('bar'); + +// Different generator +generatorOne('foo') !== generatorTwo('foo'); +``` + +This allows multiple instances of a single React component to now have different ids. +If you include the above React component multiple times in the same page, +each component instance will have a unique id, because each render method will use a different +id generator. + +You can also use this service outside of React. + ## API endpoints The following style guide rules are targeting development of server side API endpoints. @@ -90,7 +140,8 @@ API routes must start with the `/api/` path segment, and should be followed by t Kibana uses `snake_case` for the entire API, just like Elasticsearch. All urls, paths, query string parameters, values, and bodies should be `snake_case` formatted. -*Right:* +_Right:_ + ``` POST /api/kibana/index_patterns { @@ -108,19 +159,19 @@ The following style guide rules apply for working with TypeScript/JavaScript fil ### TypeScript vs. JavaScript -Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code. +Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code. Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process. ### Prefer modern JavaScript/TypeScript syntax You should prefer modern language features in a lot of cases, e.g.: -* Prefer `class` over `prototype` inheritance -* Prefer arrow function over function expressions -* Prefer arrow function over storing `this` (no `const self = this;`) -* Prefer template strings over string concatenation -* Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()` -* Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities) +- Prefer `class` over `prototype` inheritance +- Prefer arrow function over function expressions +- Prefer arrow function over storing `this` (no `const self = this;`) +- Prefer template strings over string concatenation +- Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()` +- Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities) ### Avoid mutability and state @@ -131,7 +182,7 @@ Instead, create new variables, and shallow copies of objects and arrays: ```js // good function addBar(foos, foo) { - const newFoo = {...foo, name: 'bar'}; + const newFoo = { ...foo, name: 'bar' }; return [...foos, newFoo]; } @@ -250,8 +301,8 @@ const second = arr[1]; ### Magic numbers/strings -These are numbers (or other values) simply used in line in your code. *Do not -use these*, give them a variable name so they can be understood and changed +These are numbers (or other values) simply used in line in your code. _Do not +use these_, give them a variable name so they can be understood and changed easily. ```js @@ -325,19 +376,18 @@ import inSibling from '../foo/child'; Don't do this. Everything should be wrapped in a module that can be depended on by other modules. Even things as simple as a single value should be a module. - ### Only use ternary operators for small, simple code -And *never* use multiple ternaries together, because they make it more +And _never_ use multiple ternaries together, because they make it more difficult to reason about how different values flow through the conditions involved. Instead, structure the logic for maximum readability. ```js // good, a situation where only 1 ternary is needed -const foo = (a === b) ? 1 : 2; +const foo = a === b ? 1 : 2; // bad -const foo = (a === b) ? 1 : (a === c) ? 2 : 3; +const foo = a === b ? 1 : a === c ? 2 : 3; ``` ### Use descriptive conditions @@ -475,13 +525,12 @@ setTimeout(() => { Use slashes for both single line and multi line comments. Try to write comments that explain higher level mechanisms or clarify difficult -segments of your code. *Don't use comments to restate trivial things*. +segments of your code. _Don't use comments to restate trivial things_. -*Exception:* Comment blocks describing a function and its arguments +_Exception:_ Comment blocks describing a function and its arguments (docblock) should start with `/**`, contain a single `*` at the beginning of each line, and end with `*/`. - ```js // good @@ -546,11 +595,17 @@ You can read more about these two ngReact methods [here](https://github.com/ngRe Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated, and is also a more succinct syntax. **Good:** + ```html - + ``` **Bad:** + ```html ``` @@ -564,9 +619,9 @@ Name action functions in the form of a strong verb and passed properties in the ``` -## Attribution +## Attribution -Parts of the JavaScript style guide were initially forked from the -[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is -licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) +Parts of the JavaScript style guide were initially forked from the +[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is +licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) license. diff --git a/style_guides/accessibility_guide.md b/style_guides/accessibility_guide.md deleted file mode 100644 index 4827d939915105..00000000000000 --- a/style_guides/accessibility_guide.md +++ /dev/null @@ -1,73 +0,0 @@ -# Accessibility (A11Y) Guide - -[EUI's accessibility guidelines](https://elastic.github.io/eui/#/guidelines/accessibility) should be your first stop for all things. - -## Automated accessibility testing - -To run the tests locally: - -1. In one terminal window run `node scripts/functional_tests_server --config test/accessibility/config.ts` -2. In another terminal window run `node scripts/functional_test_runner.js --config test/accessibility/config.ts` - -To run the x-pack tests, swap the config file out for `x-pack/test/accessibility/config.ts`. - -After the server is up, you can go to this instance of Kibana at `localhost:5020`. - -The testing is done using [axe](https://github.com/dequelabs/axe-core). The same thing that runs in CI, -can be run locally using their browser plugins: - -- [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US) -- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/) - -## How to generate ids? - -When labeling elements (and for some other accessibility tasks) you will often need -ids. Ids must be unique within the page i.e. no duplicate ids in the rendered DOM -at any time. - -Since we have some components that are used multiple times on the page, you must -make sure every instance of that component has a unique `id`. To make the generation -of those `id`s easier, you can use the `htmlIdGenerator` service in the `@elastic/eui`. - -A react component could use it as follows: - -```jsx -import { htmlIdGenerator } from '@elastic/eui'; - -render() { - // Create a new generator that will create ids deterministic - const htmlId = htmlIdGenerator(); - return (
- - -
); -} -``` - -Each id generator you create by calling `htmlIdGenerator()` will generate unique but -deterministic ids. As you can see in the above example, that single generator -created the same id in the label's `htmlFor` as well as the input's `id`. - -A single generator instance will create the same id when passed the same argument -to the function multiple times. But two different generators will produce two different -ids for the same argument to the function, as you can see in the following example: - -```js -const generatorOne = htmlIdGenerator(); -const generatorTwo = htmlIdGenerator(); - -// Those statements are always true: -// Same generator -generatorOne('foo') === generatorOne('foo'); -generatorOne('foo') !== generatorOne('bar'); - -// Different generator -generatorOne('foo') !== generatorTwo('foo'); -``` - -This allows multiple instances of a single react component to now have different ids. -If you include the above react component multiple times in the same page, -each component instance will have a unique id, because each render method will use a different -id generator. - -You can use this service of course also outside of react. From 693255c92d855ff33f9650bdbd1cb1ea71c544c5 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Fri, 7 Feb 2020 12:27:18 -0500 Subject: [PATCH 10/10] [ML] DF Analytics creation: update schema definition for create route (#56979) * fix df analytics create route schema definition * add all optional params to schema definiton --- .../ml/server/new_platform/data_analytics_schema.ts | 10 +++++++++- .../plugins/ml/server/routes/data_frame_analytics.ts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts b/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts index f5d72c51dc070d..21454fa884b82c 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts +++ b/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts @@ -13,8 +13,16 @@ export const dataAnalyticsJobConfigSchema = { results_field: schema.maybe(schema.string()), }), source: schema.object({ - index: schema.string(), + index: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + query: schema.maybe(schema.any()), + _source: schema.maybe( + schema.object({ + includes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + excludes: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + }) + ), }), + allow_lazy_start: schema.maybe(schema.boolean()), analysis: schema.any(), analyzed_fields: schema.any(), model_memory_limit: schema.string(), diff --git a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts index 67fa2fba46f1a9..f134820adbb48d 100644 --- a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts @@ -156,7 +156,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti params: schema.object({ analyticsId: schema.string(), }), - body: schema.object({ ...dataAnalyticsJobConfigSchema }), + body: schema.object(dataAnalyticsJobConfigSchema), }, }, licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {