From 1c718d676049d77168b5c326a4a92543ce158e8d Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Wed, 8 Apr 2020 11:30:12 -0600 Subject: [PATCH 01/46] Add --filter option to API docs script (#62888) --- src/dev/run_check_published_api_changes.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/dev/run_check_published_api_changes.ts b/src/dev/run_check_published_api_changes.ts index 8bb3fb20cea8..ba3cd1280f34 100644 --- a/src/dev/run_check_published_api_changes.ts +++ b/src/dev/run_check_published_api_changes.ts @@ -163,6 +163,7 @@ interface Options { accept: boolean; docs: boolean; help: boolean; + filter: string; } async function run( @@ -205,6 +206,7 @@ async function run( const extraFlags: string[] = []; const opts = (getopts(process.argv.slice(2), { boolean: ['accept', 'docs', 'help'], + string: ['filter'], default: { project: undefined, }, @@ -222,6 +224,8 @@ async function run( opts.help = true; } + const folders = ['core/public', 'core/server', 'plugins/data/server', 'plugins/data/public']; + if (opts.help) { process.stdout.write( dedent(chalk` @@ -240,9 +244,13 @@ async function run( {dim # Checks for and automatically accepts and updates documentation for any changes to the Kibana Core API} {dim $} node scripts/check_published_api_changes --accept + {dim # Only checks the core/public directory} + {dim $} node scripts/check_published_api_changes --filter=core/public + Options: --accept {dim Accepts all changes by updating the API Review files and documentation} --docs {dim Updates the Core API documentation} + --only {dim RegExp that folder names must match, folders: [${folders.join(', ')}]} --help {dim Show this message} `) ); @@ -258,9 +266,11 @@ async function run( return false; } - const folders = ['core/public', 'core/server', 'plugins/data/server', 'plugins/data/public']; - - const results = await Promise.all(folders.map(folder => run(folder, { log, opts }))); + const results = await Promise.all( + folders + .filter(folder => (opts.filter.length ? folder.match(opts.filter) : true)) + .map(folder => run(folder, { log, opts })) + ); if (results.find(r => r === false) !== undefined) { process.exitCode = 1; From cbe479b8bd1297a5f54b2b9a3f8ea9f480cbb713 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Wed, 8 Apr 2020 12:39:59 -0500 Subject: [PATCH 02/46] [Metrics UI] Invalidate non-count alerts which have no metrics (#62837) --- .../public/components/alerting/metrics/validation.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx index 55365c3d4c2b..d84e46d08a28 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx @@ -23,6 +23,7 @@ export function validateMetricThreshold({ timeWindowSize: string[]; threshold0: string[]; threshold1: string[]; + metric: string[]; }; } = {}; validationResult.errors = errors; @@ -41,6 +42,7 @@ export function validateMetricThreshold({ timeWindowSize: [], threshold0: [], threshold1: [], + metric: [], }; if (!c.aggType) { errors[id].aggField.push( @@ -73,6 +75,14 @@ export function validateMetricThreshold({ }) ); } + + if (!c.metric && c.aggType !== 'count') { + errors[id].metric.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', { + defaultMessage: 'Metric is required.', + }) + ); + } }); return validationResult; From 941a4879ae2072344cc3be5846c47c8e2340d153 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Wed, 8 Apr 2020 12:40:27 -0500 Subject: [PATCH 03/46] [Alerting] Fix validation support for nested IErrorObjects (#62833) * [Alerting] Add validation support for nested IErrorObjects * Typecheck fix * Fix recursion crash when errors are strings * Typecheck fix --- .../public/application/sections/alert_form/alert_add.tsx | 9 ++++++++- .../public/common/expression_items/of.tsx | 3 ++- .../public/common/expression_items/threshold.tsx | 3 ++- x-pack/plugins/triggers_actions_ui/public/types.ts | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index e44e20751b31..e025248fad52 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { useCallback, useReducer, useState } from 'react'; +import { isObject } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -83,7 +84,7 @@ export const AlertAdd = ({ ...(alertType ? alertType.validate(alert.params).errors : []), ...validateBaseProperties(alert).errors, } as IErrorObject; - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + const hasErrors = parseErrors(errors); const actionsErrors: Array<{ errors: IErrorObject; @@ -213,3 +214,9 @@ export const AlertAdd = ({ ); }; + +const parseErrors: (errors: IErrorObject) => boolean = errors => + !!Object.values(errors).find(errorList => { + if (isObject(errorList)) return parseErrors(errorList as IErrorObject); + return errorList.length >= 1; + }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx index d399f6136690..3de0a99ccbbd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx @@ -17,13 +17,14 @@ import { } from '@elastic/eui'; import { builtInAggregationTypes } from '../constants'; import { AggregationType } from '../types'; +import { IErrorObject } from '../../types'; import { ClosablePopoverTitle } from './components'; import './of.scss'; interface OfExpressionProps { aggType: string; aggField?: string; - errors: { [key: string]: string[] }; + errors: IErrorObject; onChangeSelectedAggField: (selectedAggType?: string) => void; fields: Record; customAggTypesOptions?: { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index fb3ff9ceb092..99dd7b63383f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -18,11 +18,12 @@ import { } from '@elastic/eui'; import { builtInComparators } from '../constants'; import { Comparator } from '../types'; +import { IErrorObject } from '../../types'; import { ClosablePopoverTitle } from './components'; interface ThresholdExpressionProps { thresholdComparator: string; - errors: { [key: string]: string[] }; + errors: IErrorObject; onChangeSelectedThresholdComparator: (selectedThresholdComparator?: string) => void; onChangeSelectedThreshold: (selectedThreshold?: number[]) => void; customComparators?: { diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 31c77833cc0e..7f78d327d012 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -112,5 +112,5 @@ export interface AlertTypeModel { } export interface IErrorObject { - [key: string]: string[]; + [key: string]: string | string[] | IErrorObject; } From d4f2bd744dc64eb530277927c67807c2a5fb9ba2 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 8 Apr 2020 10:45:15 -0700 Subject: [PATCH 04/46] Exclude disabled datasources and streams from agent config (#62869) --- .../datasource_to_agent_datasource.test.ts | 42 ++++++++++++++++++- .../datasource_to_agent_datasource.ts | 18 ++++---- .../server/services/agent_config.ts | 6 +-- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts index ab039be8e7c2..b897c03e89f8 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts @@ -41,7 +41,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }, { id: 'test-logs-bar', - enabled: false, + enabled: true, dataset: 'bar', config: { barVar: { value: 'bar-value' }, @@ -119,7 +119,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }, { id: 'test-logs-bar', - enabled: false, + enabled: true, dataset: 'bar', barVar: 'bar-value', barVar2: [1, 2], @@ -140,6 +140,44 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }); }); + it('returns agent datasource config without disabled streams', () => { + expect( + storedDatasourceToAgentDatasource({ + ...mockDatasource, + inputs: [ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ], + }) + ).toEqual({ + id: 'mock-datasource', + namespace: 'default', + enabled: true, + use_output: 'default', + inputs: [ + { + type: 'test-logs', + enabled: true, + inputVar: 'input-value', + inputVar3: { + testField: 'test', + }, + streams: [ + { + id: 'test-logs-foo', + enabled: true, + dataset: 'foo', + fooVar: 'foo-value', + fooVar2: [1, 2], + }, + ], + }, + ], + }); + }); + it('returns agent datasource config without disabled inputs', () => { expect( storedDatasourceToAgentDatasource({ diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts index f58eaacb7be6..20bbbec8919d 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts @@ -51,14 +51,16 @@ export const storedDatasourceToAgentDatasource = ( const fullInput = { ...input, ...Object.entries(input.config || {}).reduce(configReducer, {}), - streams: input.streams.map(stream => { - const fullStream = { - ...stream, - ...Object.entries(stream.config || {}).reduce(configReducer, {}), - }; - delete fullStream.config; - return fullStream; - }), + streams: input.streams + .filter(stream => stream.enabled) + .map(stream => { + const fullStream = { + ...stream, + ...Object.entries(stream.config || {}).reduce(configReducer, {}), + }; + delete fullStream.config; + return fullStream; + }), }; delete fullInput.config; return fullInput; diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index a941494072ae..309ddca3784c 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -319,9 +319,9 @@ class AgentConfigService { return outputs; }, {} as FullAgentConfig['outputs']), }, - datasources: (config.datasources as Datasource[]).map(ds => - storedDatasourceToAgentDatasource(ds) - ), + datasources: (config.datasources as Datasource[]) + .filter(datasource => datasource.enabled) + .map(ds => storedDatasourceToAgentDatasource(ds)), revision: config.revision, }; From 184f59447bfb1cb5ffc83e0e8d1df8bfe282f9d8 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 8 Apr 2020 11:13:39 -0700 Subject: [PATCH 05/46] [APM] Service map - fixes layout issues for maps with no rum services (#62887) * Closes #62878 in Service Maps by improving the selection algorithm for root nodes * Fixes some latent centering issues when navigating in the service map. * Removes unused imports * Added layoutstopDelayTimeout to cleanup step --- .../components/app/ServiceMap/Cytoscape.tsx | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 21bedc204f48..e4b656ae8160 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -14,8 +14,6 @@ import React, { useState } from 'react'; import { debounce } from 'lodash'; -import { isRumAgentName } from '../../../../../../../plugins/apm/common/agent_name'; -import { AGENT_NAME } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { animationOptions, cytoscapeOptions, @@ -96,10 +94,15 @@ function getLayoutOptions( } function selectRoots(cy: cytoscape.Core): string[] { - const nodes = cy.nodes(); - const roots = nodes.roots(); - const rumNodes = nodes.filter(node => isRumAgentName(node.data(AGENT_NAME))); - return rumNodes.union(roots).map(node => node.id()); + const bfs = cy.elements().bfs({ + roots: cy.elements().leaves() + }); + const furthestNodeFromLeaves = bfs.path.last(); + return cy + .elements() + .roots() + .union(furthestNodeFromLeaves) + .map(el => el.id()); } export function Cytoscape({ @@ -168,15 +171,26 @@ export function Cytoscape({ layout.run(); } }; + let layoutstopDelayTimeout: NodeJS.Timeout; const layoutstopHandler: cytoscape.EventHandler = event => { - event.cy.animate({ - ...animationOptions, - center: { - eles: serviceName - ? event.cy.getElementById(serviceName) - : event.cy.collection() + // This 0ms timer is necessary to prevent a race condition + // between the layout finishing rendering and viewport centering + layoutstopDelayTimeout = setTimeout(() => { + if (serviceName) { + event.cy.animate({ + ...animationOptions, + fit: { + eles: event.cy.elements(), + padding: nodeHeight + }, + center: { + eles: event.cy.getElementById(serviceName) + } + }); + } else { + event.cy.fit(undefined, nodeHeight); } - }); + }, 0); }; // debounce hover tracking so it doesn't spam telemetry with redundant events const trackNodeEdgeHover = debounce( @@ -231,6 +245,7 @@ export function Cytoscape({ cy.removeListener('select', 'node', selectHandler); cy.removeListener('unselect', 'node', unselectHandler); } + clearTimeout(layoutstopDelayTimeout); }; }, [cy, height, serviceName, trackApmEvent, width]); From 369ddff95192373923fb5323ef06b70868303c4c Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 8 Apr 2020 12:15:48 -0700 Subject: [PATCH 06/46] [kbn/optimizer] link to kibanaReact/kibanaUtils plugins (#62720) * [kbn/optimizer] link to data/kibanaReact/kibanaUtils plugins * depend on normalize-path package * typos * avoid loading kibanaUtils and kibanaReact from urls * update types and tests, now that whole plugin is exported to window * update snapshot, removed export of `plugins` property * fix condition, ignore things NOT in data/react/utils * make es_ui_shared a "static bundle" too * move kibana_utils/common usage to /public * convert some more /common usage to /public * use async-download/ordered-execution for bootstrap script * fix typo * remove kibanaUtils bundle * remove kibanaReact bundle * Revert "remove kibanaReact bundle" This reverts commit f14e9ee604ae8f0bb664a04c6966e337254d2659. * Revert "remove kibanaUtils bundle" This reverts commit a64b2a7f647f44f6d1941dba4ba39076ce5d74d9. * stop linking to the data plugin * add comment pointing to async-download info Co-authored-by: spalger Co-authored-by: Elastic Machine --- packages/kbn-optimizer/package.json | 1 + .../basic_optimization.test.ts.snap | 4 +- .../src/worker/webpack.config.ts | 68 ++++++++++++++++-- packages/kbn-ui-shared-deps/polyfills.js | 7 ++ src/core/public/plugins/plugin_loader.test.ts | 2 +- src/core/public/plugins/plugin_loader.ts | 33 ++++++--- .../public/input_control_vis_type.ts | 2 +- .../kibana/public/discover/kibana_services.ts | 4 +- .../vis_type_metric/public/services.ts | 2 +- .../vis_type_table/public/services.ts | 2 +- .../vis_type_tagcloud/public/services.ts | 2 +- .../public/metrics_type.ts | 2 +- .../public/__mocks__/services.ts | 2 +- .../vis_type_vega/public/vega_type.ts | 2 +- .../vis_type_vislib/public/services.ts | 2 +- .../ui/ui_render/bootstrap/template.js.hbs | 69 ++++++++----------- src/plugins/discover/public/services.ts | 2 +- .../embeddable_action_storage.test.ts | 2 +- src/plugins/es_ui_shared/kibana.json | 5 ++ src/plugins/es_ui_shared/public/index.ts | 8 +++ src/plugins/kibana_react/kibana.json | 5 ++ .../public/adapters/react_to_ui_component.ts | 2 +- .../adapters/ui_to_react_component.test.tsx | 2 +- .../public/adapters/ui_to_react_component.ts | 2 +- src/plugins/kibana_react/public/index.ts | 8 +++ src/plugins/kibana_utils/kibana.json | 5 ++ src/plugins/kibana_utils/public/index.ts | 11 ++- .../ui_actions/public/actions/action.ts | 2 +- .../public/actions/action_definition.ts | 2 +- 29 files changed, 182 insertions(+), 78 deletions(-) create mode 100644 src/plugins/es_ui_shared/kibana.json create mode 100644 src/plugins/kibana_react/kibana.json create mode 100644 src/plugins/kibana_utils/kibana.json diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index b648004760d7..b3e5a8c51868 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -32,6 +32,7 @@ "json-stable-stringify": "^1.0.1", "loader-utils": "^1.2.3", "node-sass": "^4.13.0", + "normalize-path": "^3.0.0", "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", "resolve-url-loader": "^3.1.1", diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index d52d89eebe2f..4b4bb1282d93 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -57,6 +57,6 @@ OptimizerConfig { } `; -exports[`builds expected bundles, saves bundle counts to metadata: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i bundle.id === p.id)) { + return; + } + + // ignore requests that don't include a /data/public, /kibana_react/public, or + // /kibana_utils/public segment as a cheap way to avoid doing path resolution + // for paths that couldn't possibly resolve to what we're looking for + const reqToStaticBundle = STATIC_BUNDLE_PLUGINS.some(p => + request.includes(`/${p.dirname}/public`) + ); + if (!reqToStaticBundle) { + return; + } + + // determine the most acurate resolution string we can without running full resolution + const rootRelative = normalizePath( + Path.relative(bundle.sourceRoot, Path.resolve(context, request)) + ); + for (const { id, dirname } of STATIC_BUNDLE_PLUGINS) { + if (rootRelative === `src/plugins/${dirname}/public`) { + return `__kbnBundles__['plugin/${id}']`; + } + } + + // import doesn't match a root public import + return undefined; +} + export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { const commonConfig: webpack.Configuration = { node: { fs: 'empty' }, @@ -63,7 +117,6 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { // When the entry point is loaded, assign it's exported `plugin` // value to a key on the global `__kbnBundles__` object. library: ['__kbnBundles__', `plugin/${bundle.id}`], - libraryExport: 'plugin', } : {}), }, @@ -72,9 +125,16 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { noEmitOnErrors: true, }, - externals: { - ...UiSharedDeps.externals, - }, + externals: [ + UiSharedDeps.externals, + function(context, request, cb) { + try { + cb(undefined, dynamicExternals(bundle, context, request)); + } catch (error) { + cb(error, undefined); + } + }, + ], plugins: [new CleanWebpackPlugin(), new DisallowedSyntaxPlugin()], diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/polyfills.js index d2305d643e4d..1f1392b02baf 100644 --- a/packages/kbn-ui-shared-deps/polyfills.js +++ b/packages/kbn-ui-shared-deps/polyfills.js @@ -20,6 +20,13 @@ require('core-js/stable'); require('regenerator-runtime/runtime'); require('custom-event-polyfill'); + +if (typeof window.Event === 'object') { + // IE11 doesn't support unknown event types, required by react-use + // https://github.com/streamich/react-use/issues/73 + window.Event = CustomEvent; +} + require('whatwg-fetch'); require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); require('./vendor/childnode_remove_polyfill'); diff --git a/src/core/public/plugins/plugin_loader.test.ts b/src/core/public/plugins/plugin_loader.test.ts index e5cbffc3e2d9..b4e2c3095f14 100644 --- a/src/core/public/plugins/plugin_loader.test.ts +++ b/src/core/public/plugins/plugin_loader.test.ts @@ -71,7 +71,7 @@ test('`loadPluginBundles` creates a script tag and loads initializer', async () // Setup a fake initializer as if a plugin bundle had actually been loaded. const fakeInitializer = jest.fn(); - coreWindow.__kbnBundles__['plugin/plugin-a'] = fakeInitializer; + coreWindow.__kbnBundles__['plugin/plugin-a'] = { plugin: fakeInitializer }; // Call the onload callback fakeScriptTag.onload(); await expect(loadPromise).resolves.toEqual(fakeInitializer); diff --git a/src/core/public/plugins/plugin_loader.ts b/src/core/public/plugins/plugin_loader.ts index 63aba0dde2af..bf7711055e97 100644 --- a/src/core/public/plugins/plugin_loader.ts +++ b/src/core/public/plugins/plugin_loader.ts @@ -32,7 +32,7 @@ export type UnknownPluginInitializer = PluginInitializer new Promise>( (resolve, reject) => { - const script = document.createElement('script'); const coreWindow = (window as unknown) as CoreWindow; + const exportId = `plugin/${pluginName}`; + const readPluginExport = () => { + const PluginExport: any = coreWindow.__kbnBundles__[exportId]; + if (typeof PluginExport?.plugin !== 'function') { + reject( + new Error(`Definition of plugin "${pluginName}" should be a function (${bundlePath}).`) + ); + } else { + resolve( + PluginExport.plugin as PluginInitializer + ); + } + }; + + if (coreWindow.__kbnBundles__[exportId]) { + readPluginExport(); + return; + } + + const script = document.createElement('script'); // Assumes that all plugin bundles get put into the bundles/plugins subdirectory const bundlePath = addBasePath(`/bundles/plugin/${pluginName}/${pluginName}.plugin.js`); script.setAttribute('src', bundlePath); @@ -89,15 +108,7 @@ export const loadPluginBundle: LoadPluginBundle = < // Wire up resolve and reject script.onload = () => { cleanupTag(); - - const initializer = coreWindow.__kbnBundles__[`plugin/${pluginName}`]; - if (!initializer || typeof initializer !== 'function') { - reject( - new Error(`Definition of plugin "${pluginName}" should be a function (${bundlePath}).`) - ); - } else { - resolve(initializer as PluginInitializer); - } + readPluginExport(); }; script.onerror = () => { diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts index 023e6ebb7125..badea68eec19 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -23,7 +23,7 @@ import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; import { InputControlVisDependencies } from './plugin'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { const InputControlVisController = createInputControlVisController(deps); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index e6421142f666..98679a8f24d1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -17,6 +17,8 @@ * under the License. */ import { DiscoverServices } from './build_services'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; +import { search } from '../../../../../plugins/data/public'; let angularModule: any = null; let services: DiscoverServices | null = null; @@ -50,8 +52,6 @@ export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; }>('urlTracker'); -import { search } from '../../../../../plugins/data/public'; -import { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search; export { unhashUrl, diff --git a/src/legacy/core_plugins/vis_type_metric/public/services.ts b/src/legacy/core_plugins/vis_type_metric/public/services.ts index 5af11bc7f0b0..b303ccd5aeed 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/services.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_table/public/services.ts b/src/legacy/core_plugins/vis_type_table/public/services.ts index 08efed733caf..b4b491ac7a55 100644 --- a/src/legacy/core_plugins/vis_type_table/public/services.ts +++ b/src/legacy/core_plugins/vis_type_table/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts index fef46282eb8d..272bed3e91a0 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts index 30c62d778933..1db35c406eb1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,7 +25,7 @@ import { metricsRequestHandler } from './request_handler'; import { EditorController } from './editor_controller'; // @ts-ignore import { PANEL_TYPES } from '../../../../plugins/vis_type_timeseries/common/panel_types'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; export const metricsVisDefinition = { name: 'metrics', diff --git a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts index 64a9aaaf3b7a..b2f3e5b2241e 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { IUiSettingsClient, NotificationsStart, SavedObjectsStart } from 'kibana/public'; import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index 5d9ed5c8df91..f56d7682efc6 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { DefaultEditorSize } from '../../../../plugins/vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; import { createVegaRequestHandler } from './vega_request_handler'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vislib/public/services.ts b/src/legacy/core_plugins/vis_type_vislib/public/services.ts index da50e227d84d..0d6b1b5e8de5 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/services.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getDataActions, setDataActions] = createGetterSetter< diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index ad4aa97d8ea7..1093153edbbf 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -30,33 +30,27 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { function loadStyleSheet(url, cb) { var dom = document.createElement('link'); + dom.rel = 'stylesheet'; + dom.type = 'text/css'; + dom.href = url; dom.addEventListener('error', failure); - dom.setAttribute('rel', 'stylesheet'); - dom.setAttribute('type', 'text/css'); - dom.setAttribute('href', url); dom.addEventListener('load', cb); document.head.appendChild(dom); } function loadScript(url, cb) { var dom = document.createElement('script'); - dom.setAttribute('async', ''); + {{!-- NOTE: async = false is used to trigger async-download/ordered-execution as outlined here: https://www.html5rocks.com/en/tutorials/speed/script-loading/ --}} + dom.async = false; + dom.src = url; dom.addEventListener('error', failure); - dom.setAttribute('src', url); dom.addEventListener('load', cb); document.head.appendChild(dom); } - function load(urlSet, cb) { - if (urlSet.deps) { - load({ urls: urlSet.deps }, function () { - load({ urls: urlSet.urls }, cb); - }); - return; - } - - var pending = urlSet.urls.length; - urlSet.urls.forEach(function (url) { + function load(urls, cb) { + var pending = urls.length; + urls.forEach(function (url) { var innerCb = function () { pending = pending - 1; if (pending === 0 && typeof cb === 'function') { @@ -74,36 +68,27 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { }); } - load({ - deps: [ + load([ {{#each sharedJsDepFilenames}} '{{../regularBundlePath}}/kbn-ui-shared-deps/{{this}}', {{/each}} - ], - urls: [ - { - deps: [ - '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}', - { - deps: [ - '{{dllBundlePath}}/vendors_runtime.bundle.dll.js' - ], - urls: [ - {{#each dllJsChunks}} - '{{this}}', - {{/each}} - ] - }, - '{{regularBundlePath}}/commons.bundle.js', - ], - urls: [ - '{{regularBundlePath}}/{{appId}}.bundle.js', - {{#each styleSheetPaths}} - '{{this}}', - {{/each}} - ] - } - ] + '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}', + '{{dllBundlePath}}/vendors_runtime.bundle.dll.js', + {{#each dllJsChunks}} + '{{this}}', + {{/each}} + '{{regularBundlePath}}/commons.bundle.js', + {{!-- '{{regularBundlePath}}/plugin/data/data.plugin.js', --}} + '{{regularBundlePath}}/plugin/kibanaUtils/kibanaUtils.plugin.js', + '{{regularBundlePath}}/plugin/esUiShared/esUiShared.plugin.js', + '{{regularBundlePath}}/plugin/kibanaReact/kibanaReact.plugin.js' + ], function () { + load([ + '{{regularBundlePath}}/{{appId}}.bundle.js', + {{#each styleSheetPaths}} + '{{this}}', + {{/each}} + ]) }); }; } diff --git a/src/plugins/discover/public/services.ts b/src/plugins/discover/public/services.ts index 3a28759d82b7..37e2144800ea 100644 --- a/src/plugins/discover/public/services.ts +++ b/src/plugins/discover/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../kibana_utils/common'; +import { createGetterSetter } from '../../kibana_utils/public'; import { DocViewsRegistry } from './doc_views/doc_views_registry'; export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter( diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts index 56facc37fc66..ddd84b054434 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts @@ -21,7 +21,7 @@ import { Embeddable } from './embeddable'; import { EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; import { EmbeddableActionStorage, SerializedEvent } from './embeddable_action_storage'; -import { of } from '../../../../kibana_utils/common'; +import { of } from '../../../../kibana_utils/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/src/plugins/es_ui_shared/kibana.json b/src/plugins/es_ui_shared/kibana.json new file mode 100644 index 000000000000..5a3db3b34409 --- /dev/null +++ b/src/plugins/es_ui_shared/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "esUiShared", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 944b800c66a2..6db6248f4c68 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -35,3 +35,11 @@ export { export { indices } from './indices'; export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode'; + +/** dummy plugin, we just want esUiShared to have its own bundle */ +export function plugin() { + return new (class EsUiSharedPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/kibana_react/kibana.json b/src/plugins/kibana_react/kibana.json new file mode 100644 index 000000000000..0add1bee84ae --- /dev/null +++ b/src/plugins/kibana_react/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "kibanaReact", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts b/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts index b4007b30cf8c..21bba92ada4c 100644 --- a/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts +++ b/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts @@ -19,7 +19,7 @@ import { ComponentType, createElement as h } from 'react'; import { render as renderReact, unmountComponentAtNode } from 'react-dom'; -import { UiComponent, UiComponentInstance } from '../../../kibana_utils/common'; +import { UiComponent, UiComponentInstance } from '../../../kibana_utils/public'; /** * Transform a React component into a `UiComponent`. diff --git a/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx b/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx index 939d372b9997..aefbd66e50fc 100644 --- a/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx +++ b/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx @@ -19,7 +19,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { UiComponent } from '../../../kibana_utils/common'; +import { UiComponent } from '../../../kibana_utils/public'; import { uiToReactComponent } from './ui_to_react_component'; import { reactToUiComponent } from './react_to_ui_component'; diff --git a/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts b/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts index 9b34880cf4fe..ee99ea367267 100644 --- a/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts +++ b/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts @@ -18,7 +18,7 @@ */ import { FC, createElement as h, useRef, useLayoutEffect, useMemo } from 'react'; -import { UiComponent, UiComponentInstance } from '../../../kibana_utils/common'; +import { UiComponent, UiComponentInstance } from '../../../kibana_utils/public'; /** * Transforms `UiComponent` into a React component. diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index e1689e38dbfe..9ad9f14ac565 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -31,3 +31,11 @@ export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; export { useUrlTracker } from './use_url_tracker'; export { toMountPoint } from './util'; + +/** dummy plugin, we just want kibanaReact to have its own bundle */ +export function plugin() { + return new (class KibanaReactPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/kibana_utils/kibana.json b/src/plugins/kibana_utils/kibana.json new file mode 100644 index 000000000000..6fa39d82d102 --- /dev/null +++ b/src/plugins/kibana_utils/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "kibanaUtils", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 1876e688c989..2f139050e994 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -19,7 +19,6 @@ export { calculateObjectHash, - createGetterSetter, defer, Defer, Get, @@ -31,6 +30,8 @@ export { UiComponent, UiComponentInstance, url, + createGetterSetter, + defaultFeedbackMessage, } from '../common'; export * from './core'; export * from './errors'; @@ -75,3 +76,11 @@ export { } from './state_sync'; export { removeQueryParam, redirectWhenMissing, ensureDefaultIndexPattern } from './history'; export { applyDiff } from './state_management/utils/diff_object'; + +/** dummy plugin, we just want kibanaUtils to have its own bundle */ +export function plugin() { + return new (class KibanaUtilsPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 2b2fc004a84c..f532c2c8aa21 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; import { ActionType, ActionContextMapping } from '../types'; export type ActionByType = Action; diff --git a/src/plugins/ui_actions/public/actions/action_definition.ts b/src/plugins/ui_actions/public/actions/action_definition.ts index c590cf8f34ee..3eaa13572a82 100644 --- a/src/plugins/ui_actions/public/actions/action_definition.ts +++ b/src/plugins/ui_actions/public/actions/action_definition.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; import { ActionType, ActionContextMapping } from '../types'; export interface ActionDefinition { From f84773faed53cf0c3683406d5eaccb5f51975cda Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Wed, 8 Apr 2020 13:16:32 -0600 Subject: [PATCH 07/46] Add basic StatusService (#60335) --- .../kibana-plugin-core-server.coresetup.md | 1 + ...ana-plugin-core-server.coresetup.status.md | 13 + ...in-core-server.corestatus.elasticsearch.md | 11 + .../kibana-plugin-core-server.corestatus.md | 21 ++ ...gin-core-server.corestatus.savedobjects.md | 11 + ...asticsearchstatusmeta.incompatiblenodes.md | 11 + ...gin-core-server.elasticsearchstatusmeta.md | 20 ++ ...er.elasticsearchstatusmeta.warningnodes.md | 11 + .../core/server/kibana-plugin-core-server.md | 8 + ...sversioncompatibility.incompatiblenodes.md | 11 + ....nodesversioncompatibility.iscompatible.md | 11 + ...nodesversioncompatibility.kibanaversion.md | 11 + ...n-core-server.nodesversioncompatibility.md | 22 ++ ...erver.nodesversioncompatibility.message.md | 11 + ....nodesversioncompatibility.warningnodes.md | 11 + ...lugin-core-server.savedobjectstatusmeta.md | 20 ++ ...r.savedobjectstatusmeta.migratedindices.md | 15 ++ ...plugin-core-server.servicestatus.detail.md | 13 + ...e-server.servicestatus.documentationurl.md | 13 + ...-plugin-core-server.servicestatus.level.md | 13 + ...kibana-plugin-core-server.servicestatus.md | 24 ++ ...a-plugin-core-server.servicestatus.meta.md | 13 + ...lugin-core-server.servicestatus.summary.md | 13 + ...a-plugin-core-server.servicestatuslevel.md | 13 + ...-plugin-core-server.servicestatuslevels.md | 37 +++ ...in-core-server.statusservicesetup.core_.md | 13 + ...a-plugin-core-server.statusservicesetup.md | 20 ++ .../kibana-plugin-plugins-data-server.md | 1 - .../elasticsearch_service.mock.ts | 6 + .../elasticsearch/elasticsearch_service.ts | 2 + src/core/server/elasticsearch/index.ts | 1 + src/core/server/elasticsearch/status.test.ts | 222 +++++++++++++++++ src/core/server/elasticsearch/status.ts | 78 ++++++ src/core/server/elasticsearch/types.ts | 8 + .../version_check/ensure_es_version.ts | 2 +- src/core/server/index.ts | 18 +- src/core/server/internal_types.ts | 8 +- src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 3 + src/core/server/mocks.ts | 11 +- src/core/server/plugins/plugin_context.ts | 3 + src/core/server/saved_objects/index.ts | 6 +- .../saved_objects/migrations/core/index.ts | 2 +- .../migrations/core/migration_coordinator.ts | 2 + .../server/saved_objects/migrations/index.ts | 1 + .../saved_objects/migrations/kibana/index.ts | 2 +- .../migrations/kibana/kibana_migrator.mock.ts | 17 +- .../migrations/kibana/kibana_migrator.test.ts | 28 +++ .../migrations/kibana/kibana_migrator.ts | 29 ++- .../saved_objects_service.mock.ts | 7 + .../saved_objects/saved_objects_service.ts | 16 +- src/core/server/saved_objects/status.test.ts | 134 ++++++++++ src/core/server/saved_objects/status.ts | 84 +++++++ src/core/server/saved_objects/types.ts | 13 + src/core/server/server.api.md | 83 +++++++ src/core/server/server.test.mocks.ts | 6 + src/core/server/server.test.ts | 7 + src/core/server/server.ts | 20 +- .../server/status/get_summary_status.test.ts | 180 ++++++++++++++ src/core/server/status/get_summary_status.ts | 84 +++++++ src/core/server/status/index.ts | 21 ++ src/core/server/status/status_service.mock.ts | 71 ++++++ src/core/server/status/status_service.test.ts | 229 ++++++++++++++++++ src/core/server/status/status_service.ts | 78 ++++++ src/core/server/status/test_utils.ts | 25 ++ src/core/server/status/types.ts | 134 ++++++++++ src/core/server/test_utils.ts | 1 + 67 files changed, 2009 insertions(+), 27 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.status.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.corestatus.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md create mode 100644 src/core/server/elasticsearch/status.test.ts create mode 100644 src/core/server/elasticsearch/status.ts create mode 100644 src/core/server/saved_objects/status.test.ts create mode 100644 src/core/server/saved_objects/status.ts create mode 100644 src/core/server/status/get_summary_status.test.ts create mode 100644 src/core/server/status/get_summary_status.ts create mode 100644 src/core/server/status/index.ts create mode 100644 src/core/server/status/status_service.mock.ts create mode 100644 src/core/server/status/status_service.test.ts create mode 100644 src/core/server/status/status_service.ts create mode 100644 src/core/server/status/test_utils.ts create mode 100644 src/core/server/status/types.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 29fdc37a8117..c10b460da8b4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -23,6 +23,7 @@ export interface CoreSetupHttpServiceSetup | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | | [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | +| [status](./kibana-plugin-core-server.coresetup.status.md) | StatusServiceSetup | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | | [uiSettings](./kibana-plugin-core-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-core-server.uisettingsservicesetup.md) | | [uuid](./kibana-plugin-core-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md new file mode 100644 index 000000000000..f5ea627a9f00 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [status](./kibana-plugin-core-server.coresetup.status.md) + +## CoreSetup.status property + +[StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) + +Signature: + +```typescript +status: StatusServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md new file mode 100644 index 000000000000..b41e7020c38e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) > [elasticsearch](./kibana-plugin-core-server.corestatus.elasticsearch.md) + +## CoreStatus.elasticsearch property + +Signature: + +```typescript +elasticsearch: ServiceStatus; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.md new file mode 100644 index 000000000000..3fde86a18c58 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) + +## CoreStatus interface + +Status of core services. + +Signature: + +```typescript +export interface CoreStatus +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [elasticsearch](./kibana-plugin-core-server.corestatus.elasticsearch.md) | ServiceStatus | | +| [savedObjects](./kibana-plugin-core-server.corestatus.savedobjects.md) | ServiceStatus | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md new file mode 100644 index 000000000000..d554c6f70d72 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) > [savedObjects](./kibana-plugin-core-server.corestatus.savedobjects.md) + +## CoreStatus.savedObjects property + +Signature: + +```typescript +savedObjects: ServiceStatus; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md new file mode 100644 index 000000000000..f8a45fe9a5a9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) > [incompatibleNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md) + +## ElasticsearchStatusMeta.incompatibleNodes property + +Signature: + +```typescript +incompatibleNodes: NodesVersionCompatibility['incompatibleNodes']; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md new file mode 100644 index 000000000000..2398410fa4b8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) + +## ElasticsearchStatusMeta interface + + +Signature: + +```typescript +export interface ElasticsearchStatusMeta +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [incompatibleNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md) | NodesVersionCompatibility['incompatibleNodes'] | | +| [warningNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md) | NodesVersionCompatibility['warningNodes'] | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md new file mode 100644 index 000000000000..7374ccd9e7fa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) > [warningNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md) + +## ElasticsearchStatusMeta.warningNodes property + +Signature: + +```typescript +warningNodes: NodesVersionCompatibility['warningNodes']; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 793684c1b379..accab9bf0cb3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -66,6 +66,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-core-server.corestart.md) | Context passed to the plugins start method. | +| [CoreStatus](./kibana-plugin-core-server.corestatus.md) | Status of core services. | | [CustomHttpResponseOptions](./kibana-plugin-core-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | @@ -75,6 +76,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchError](./kibana-plugin-core-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | +| [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | @@ -101,6 +103,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LogMeta](./kibana-plugin-core-server.logmeta.md) | Contextual metadata | | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | APIs to retrieves metrics gathered and exposed by the core platform. | +| [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) | | | [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | | [OnPreResponseExtensions](./kibana-plugin-core-server.onpreresponseextensions.md) | Additional data to extend a response. | @@ -162,15 +165,18 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-core-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for registering Saved Object types, creating and registering Saved Object client wrappers and factories. | | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | +| [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) | Meta information about the SavedObjectService's status. Available to plugins via [CoreSetup.status](./kibana-plugin-core-server.coresetup.status.md). | | [SavedObjectsType](./kibana-plugin-core-server.savedobjectstype.md) | | | [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) | Configuration options for the [type](./kibana-plugin-core-server.savedobjectstype.md)'s management section. | | [SavedObjectsTypeMappingDefinition](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) | Describe a saved object type mapping. | | [SavedObjectsUpdateOptions](./kibana-plugin-core-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-core-server.savedobjectsupdateresponse.md) | | +| [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) | The current status of a service at a point in time. | | [SessionCookieValidationResult](./kibana-plugin-core-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | | [SessionStorage](./kibana-plugin-core-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-core-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-core-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. | | [StringValidationRegex](./kibana-plugin-core-server.stringvalidationregex.md) | StringValidation with regex object | | [StringValidationRegexString](./kibana-plugin-core-server.stringvalidationregexstring.md) | StringValidation as regex string | | [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | @@ -184,6 +190,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | | [kibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution. | +| [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. | | [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output | ## Type Aliases @@ -256,6 +263,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [ScopeableRequest](./kibana-plugin-core-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md). | +| [ServiceStatusLevel](./kibana-plugin-core-server.servicestatuslevel.md) | A convenience type that represents the union of each value in [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md). | | [SharedGlobalConfig](./kibana-plugin-core-server.sharedglobalconfig.md) | | | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | | [StringValidation](./kibana-plugin-core-server.stringvalidation.md) | Allows regex objects or a regex string | diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md new file mode 100644 index 000000000000..8e7298d28801 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [incompatibleNodes](./kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md) + +## NodesVersionCompatibility.incompatibleNodes property + +Signature: + +```typescript +incompatibleNodes: NodeInfo[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md new file mode 100644 index 000000000000..82a4800a3b4b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [isCompatible](./kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md) + +## NodesVersionCompatibility.isCompatible property + +Signature: + +```typescript +isCompatible: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md new file mode 100644 index 000000000000..347f2d3474b1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [kibanaVersion](./kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md) + +## NodesVersionCompatibility.kibanaVersion property + +Signature: + +```typescript +kibanaVersion: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md new file mode 100644 index 000000000000..6fcfacc3bc90 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) + +## NodesVersionCompatibility interface + +Signature: + +```typescript +export interface NodesVersionCompatibility +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [incompatibleNodes](./kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md) | NodeInfo[] | | +| [isCompatible](./kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md) | boolean | | +| [kibanaVersion](./kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md) | string | | +| [message](./kibana-plugin-core-server.nodesversioncompatibility.message.md) | string | | +| [warningNodes](./kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md) | NodeInfo[] | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md new file mode 100644 index 000000000000..415a7825ee2b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [message](./kibana-plugin-core-server.nodesversioncompatibility.message.md) + +## NodesVersionCompatibility.message property + +Signature: + +```typescript +message?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md new file mode 100644 index 000000000000..6c017e9fc800 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [warningNodes](./kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md) + +## NodesVersionCompatibility.warningNodes property + +Signature: + +```typescript +warningNodes: NodeInfo[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md new file mode 100644 index 000000000000..3a0b23d18632 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) + +## SavedObjectStatusMeta interface + +Meta information about the SavedObjectService's status. Available to plugins via [CoreSetup.status](./kibana-plugin-core-server.coresetup.status.md). + +Signature: + +```typescript +export interface SavedObjectStatusMeta +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [migratedIndices](./kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md) | {
[status: string]: number;
skipped: number;
migrated: number;
} | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md new file mode 100644 index 000000000000..6a29623b2f12 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) > [migratedIndices](./kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md) + +## SavedObjectStatusMeta.migratedIndices property + +Signature: + +```typescript +migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md new file mode 100644 index 000000000000..fa369aa0bdfb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [detail](./kibana-plugin-core-server.servicestatus.detail.md) + +## ServiceStatus.detail property + +A more detailed description of the service status. + +Signature: + +```typescript +detail?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md new file mode 100644 index 000000000000..5ef8c1251a60 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [documentationUrl](./kibana-plugin-core-server.servicestatus.documentationurl.md) + +## ServiceStatus.documentationUrl property + +A URL to open in a new tab about how to resolve or troubleshoot the problem. + +Signature: + +```typescript +documentationUrl?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md new file mode 100644 index 000000000000..551c10c9bff8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [level](./kibana-plugin-core-server.servicestatus.level.md) + +## ServiceStatus.level property + +The current availability level of the service. + +Signature: + +```typescript +level: ServiceStatusLevel; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.md new file mode 100644 index 000000000000..d35fc951c57f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) + +## ServiceStatus interface + +The current status of a service at a point in time. + +Signature: + +```typescript +export interface ServiceStatus | unknown = unknown> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [detail](./kibana-plugin-core-server.servicestatus.detail.md) | string | A more detailed description of the service status. | +| [documentationUrl](./kibana-plugin-core-server.servicestatus.documentationurl.md) | string | A URL to open in a new tab about how to resolve or troubleshoot the problem. | +| [level](./kibana-plugin-core-server.servicestatus.level.md) | ServiceStatusLevel | The current availability level of the service. | +| [meta](./kibana-plugin-core-server.servicestatus.meta.md) | Meta | Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, machine-readable information about the service status. May include status information for underlying features. | +| [summary](./kibana-plugin-core-server.servicestatus.summary.md) | string | A high-level summary of the service status. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md new file mode 100644 index 000000000000..a48994daa5a4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [meta](./kibana-plugin-core-server.servicestatus.meta.md) + +## ServiceStatus.meta property + +Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, machine-readable information about the service status. May include status information for underlying features. + +Signature: + +```typescript +meta?: Meta; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md new file mode 100644 index 000000000000..db90afd6f74a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [summary](./kibana-plugin-core-server.servicestatus.summary.md) + +## ServiceStatus.summary property + +A high-level summary of the service status. + +Signature: + +```typescript +summary: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md new file mode 100644 index 000000000000..5f995ff5e13e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatusLevel](./kibana-plugin-core-server.servicestatuslevel.md) + +## ServiceStatusLevel type + +A convenience type that represents the union of each value in [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md). + +Signature: + +```typescript +export declare type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md new file mode 100644 index 000000000000..a66cec78c736 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md @@ -0,0 +1,37 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) + +## ServiceStatusLevels variable + +The current "level" of availability of a service. + +Signature: + +```typescript +ServiceStatusLevels: Readonly<{ + available: Readonly<{ + toString: () => "available"; + valueOf: () => 0; + }>; + degraded: Readonly<{ + toString: () => "degraded"; + valueOf: () => 1; + }>; + unavailable: Readonly<{ + toString: () => "unavailable"; + valueOf: () => 2; + }>; + critical: Readonly<{ + toString: () => "critical"; + valueOf: () => 3; + }>; +}> +``` + +## Remarks + +The values implement `valueOf` to allow for easy comparisons between status levels with <, >, etc. Higher values represent higher severities. Note that the default `Array.prototype.sort` implementation does not correctly sort these values. + +A snapshot serializer is available in `src/core/server/test_utils` to ease testing of these values with Jest. + diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md new file mode 100644 index 000000000000..6662e68b44d3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) > [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) + +## StatusServiceSetup.core$ property + +Current status for all Core services. + +Signature: + +```typescript +core$: Observable; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md new file mode 100644 index 000000000000..0551a217520a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) + +## StatusServiceSetup interface + +API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. + +Signature: + +```typescript +export interface StatusServiceSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) | Observable<CoreStatus> | Current status for all Core services. | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 259d725b3bf0..e756eb9b7290 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -23,7 +23,6 @@ | Function | Description | | --- | --- | | [getDefaultSearchParams(config)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | -| [getTotalLoaded({ total, failed, successful })](./kibana-plugin-plugins-data-server.gettotalloaded.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index 389d98a0818c..da8846f6dddb 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -26,8 +26,10 @@ import { InternalElasticsearchServiceSetup, ElasticsearchServiceSetup, ElasticsearchServiceStart, + ElasticsearchStatusMeta, } from './types'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; const createScopedClusterClientMock = (): jest.Mocked => ({ callAsInternalUser: jest.fn(), @@ -102,6 +104,10 @@ const createInternalSetupContractMock = () => { warningNodes: [], kibanaVersion: '8.0.0', }), + status$: new BehaviorSubject>({ + level: ServiceStatusLevels.available, + summary: 'Elasticsearch is available', + }), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), }, diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index b92a6edf778e..684f6e15caff 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -40,6 +40,7 @@ import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; import { InternalElasticsearchServiceSetup, ElasticsearchServiceStart } from './types'; import { CallAPIOptions } from './api_types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; +import { calculateStatus$ } from './status'; /** @internal */ interface CoreClusterClients { @@ -186,6 +187,7 @@ export class ElasticsearchService adminClient: this.adminClient, dataClient, createClient: this.createClient, + status$: calculateStatus$(esNodesCompatibility$), }; } diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index cfd72a6fd5e4..2e45f710c4dc 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -31,3 +31,4 @@ export { config, configSchema, ElasticsearchConfig } from './elasticsearch_confi export { ElasticsearchError, ElasticsearchErrorHelpers } from './errors'; export * from './api_types'; export * from './types'; +export { NodesVersionCompatibility } from './version_check/ensure_es_version'; diff --git a/src/core/server/elasticsearch/status.test.ts b/src/core/server/elasticsearch/status.test.ts new file mode 100644 index 000000000000..dd5fb04bfd1c --- /dev/null +++ b/src/core/server/elasticsearch/status.test.ts @@ -0,0 +1,222 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { take } from 'rxjs/operators'; +import { Subject, of } from 'rxjs'; + +import { calculateStatus$ } from './status'; +import { ServiceStatusLevels, ServiceStatus } from '../status'; +import { ServiceStatusLevelSnapshotSerializer } from '../status/test_utils'; +import { NodesVersionCompatibility } from './version_check/ensure_es_version'; + +expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer); + +const nodeInfo = { + version: '1.1.1', + ip: '1.1.1.1', + http: { + publish_address: 'https://1.1.1.1:9200', + }, + name: 'node1', +}; + +describe('calculateStatus', () => { + it('starts in unavailable', async () => { + expect( + await calculateStatus$(new Subject()) + .pipe(take(1)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: 'Waiting for Elasticsearch', + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }); + }); + + it('changes to available when isCompatible and no warningNodes', async () => { + expect( + await calculateStatus$( + of({ isCompatible: true, kibanaVersion: '1.1.1', warningNodes: [], incompatibleNodes: [] }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.available, + summary: 'Elasticsearch is available', + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }); + }); + + it('changes to degraded when isCompatible and warningNodes present', async () => { + expect( + await calculateStatus$( + of({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [], + // this isn't the real message, just used to test that the message + // is forwarded to the status + message: 'Some nodes are a different version', + }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.degraded, + summary: 'Some nodes are a different version', + meta: { + incompatibleNodes: [], + warningNodes: [nodeInfo], + }, + }); + }); + + it('changes to critical when isCompatible is false', async () => { + expect( + await calculateStatus$( + of({ + isCompatible: false, + kibanaVersion: '2.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [nodeInfo], + // this isn't the real message, just used to test that the message + // is forwarded to the status + message: 'Incompatible with Elasticsearch', + }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.critical, + summary: 'Incompatible with Elasticsearch', + meta: { + incompatibleNodes: [nodeInfo], + warningNodes: [nodeInfo], + }, + }); + }); + + it('emits status updates when node compatibility changes', () => { + const nodeCompat$ = new Subject(); + + const statusUpdates: ServiceStatus[] = []; + const subscription = calculateStatus$(nodeCompat$).subscribe(status => + statusUpdates.push(status) + ); + + nodeCompat$.next({ + isCompatible: false, + kibanaVersion: '2.1.1', + incompatibleNodes: [], + warningNodes: [], + message: 'Unable to retrieve version info', + }); + nodeCompat$.next({ + isCompatible: false, + kibanaVersion: '2.1.1', + incompatibleNodes: [nodeInfo], + warningNodes: [], + message: 'Incompatible with Elasticsearch', + }); + nodeCompat$.next({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [], + message: 'Some nodes are incompatible', + }); + nodeCompat$.next({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [], + incompatibleNodes: [], + }); + + subscription.unsubscribe(); + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "level": unavailable, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Waiting for Elasticsearch", + }, + Object { + "level": critical, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Unable to retrieve version info", + }, + Object { + "level": critical, + "meta": Object { + "incompatibleNodes": Array [ + Object { + "http": Object { + "publish_address": "https://1.1.1.1:9200", + }, + "ip": "1.1.1.1", + "name": "node1", + "version": "1.1.1", + }, + ], + "warningNodes": Array [], + }, + "summary": "Incompatible with Elasticsearch", + }, + Object { + "level": degraded, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [ + Object { + "http": Object { + "publish_address": "https://1.1.1.1:9200", + }, + "ip": "1.1.1.1", + "name": "node1", + "version": "1.1.1", + }, + ], + }, + "summary": "Some nodes are incompatible", + }, + Object { + "level": available, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Elasticsearch is available", + }, + ] + `); + }); +}); diff --git a/src/core/server/elasticsearch/status.ts b/src/core/server/elasticsearch/status.ts new file mode 100644 index 000000000000..1eaa338af123 --- /dev/null +++ b/src/core/server/elasticsearch/status.ts @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable, merge, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { ElasticsearchStatusMeta } from './types'; +import { NodesVersionCompatibility } from './version_check/ensure_es_version'; + +export const calculateStatus$ = ( + esNodesCompatibility$: Observable +): Observable> => + merge( + of({ + level: ServiceStatusLevels.unavailable, + summary: `Waiting for Elasticsearch`, + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }), + esNodesCompatibility$.pipe( + map( + ({ + isCompatible, + message, + incompatibleNodes, + warningNodes, + }): ServiceStatus => { + if (!isCompatible) { + return { + level: ServiceStatusLevels.critical, + summary: + // Message should always be present, but this is a safe fallback + message ?? + `Some Elasticsearch nodes are not compatible with this version of Kibana`, + meta: { warningNodes, incompatibleNodes }, + }; + } else if (warningNodes.length > 0) { + return { + level: ServiceStatusLevels.degraded, + summary: + // Message should always be present, but this is a safe fallback + message ?? + `Some Elasticsearch nodes are running different versions than this version of Kibana`, + meta: { warningNodes, incompatibleNodes }, + }; + } + + return { + level: ServiceStatusLevels.available, + summary: `Elasticsearch is available`, + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }; + } + ) + ) + ); diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index ef8edecfd26e..3d38935e9fbf 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -22,6 +22,7 @@ import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; +import { ServiceStatus } from '../status'; /** * @public @@ -128,4 +129,11 @@ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceS readonly config$: Observable; }; esNodesCompatibility$: Observable; + status$: Observable>; +} + +/** @public */ +export interface ElasticsearchStatusMeta { + warningNodes: NodesVersionCompatibility['warningNodes']; + incompatibleNodes: NodesVersionCompatibility['incompatibleNodes']; } diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.ts index 3e760ec0efab..7bd6331978d1 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.ts @@ -142,7 +142,7 @@ export const pollEsNodesVersion = ({ kibanaVersion, ignoreVersionMismatch, esVersionCheckInterval: healthCheckInterval, -}: PollEsNodesVersionOptions): Observable => { +}: PollEsNodesVersionOptions): Observable => { log.debug('Checking Elasticsearch version'); return timer(0, healthCheckInterval).pipe( exhaustMap(() => { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 56ce16a951aa..a298f80f96d8 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -60,6 +60,7 @@ import { import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { UuidServiceSetup } from './uuid'; import { MetricsServiceSetup } from './metrics'; +import { StatusServiceSetup } from './status'; export { bootstrap } from './bootstrap'; export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; @@ -95,6 +96,8 @@ export { ElasticsearchErrorHelpers, ElasticsearchServiceSetup, ElasticsearchServiceStart, + ElasticsearchStatusMeta, + NodesVersionCompatibility, APICaller, FakeRequest, ScopeableRequest, @@ -226,6 +229,7 @@ export { SavedObjectsUpdateResponse, SavedObjectsServiceStart, SavedObjectsServiceSetup, + SavedObjectStatusMeta, SavedObjectsDeleteOptions, ISavedObjectsRepository, SavedObjectsRepository, @@ -294,6 +298,14 @@ export { LegacyInternals, } from './legacy'; +export { + CoreStatus, + ServiceStatus, + ServiceStatusLevel, + ServiceStatusLevels, + StatusServiceSetup, +} from './status'; + /** * Plugin specific context passed to a route handler. * @@ -348,14 +360,16 @@ export interface CoreSetup; } diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 825deea99bc2..ede0d3dc9fcc 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -31,6 +31,7 @@ import { import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings'; import { UuidServiceSetup } from './uuid'; import { InternalMetricsServiceSetup } from './metrics'; +import { InternalStatusServiceSetup } from './status'; /** @internal */ export interface InternalCoreSetup { @@ -38,10 +39,11 @@ export interface InternalCoreSetup { context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; - uiSettings: InternalUiSettingsServiceSetup; - savedObjects: InternalSavedObjectsServiceSetup; - uuid: UuidServiceSetup; metrics: InternalMetricsServiceSetup; + savedObjects: InternalSavedObjectsServiceSetup; + status: InternalStatusServiceSetup; + uiSettings: InternalUiSettingsServiceSetup; + uuid: UuidServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index c6860086e778..0cf2ebe55ea1 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -48,6 +48,7 @@ import { findLegacyPluginSpecs } from './plugins'; import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; import { LegacyService } from './legacy_service'; import { coreMock } from '../mocks'; +import { statusServiceMock } from '../status/status_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -106,6 +107,7 @@ beforeEach(() => { rendering: renderingServiceMock, metrics: metricsServiceMock.createInternalSetupContract(), uuid: uuidSetup, + status: statusServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, }; diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index bb5f6d5617aa..f77230301ce0 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -306,6 +306,9 @@ export class LegacyService implements CoreService { registerType: setupDeps.core.savedObjects.registerType, getImportExportObjectLimit: setupDeps.core.savedObjects.getImportExportObjectLimit, }, + status: { + core$: setupDeps.core.status.core$, + }, uiSettings: { register: setupDeps.core.uiSettings.register, }, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 31bf17da041a..faf73044cac4 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -33,6 +33,7 @@ import { InternalCoreSetup, InternalCoreStart } from './internal_types'; import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; import { metricsServiceMock } from './metrics/metrics_service.mock'; import { uuidServiceMock } from './uuid/uuid_service.mock'; +import { statusServiceMock } from './status/status_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -133,9 +134,10 @@ function createCoreSetupMock({ elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createSetupContract(), + metrics: metricsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, uuid: uuidServiceMock.createSetupContract(), - metrics: metricsServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -161,10 +163,11 @@ function createInternalCoreSetupMock() { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createInternalSetup(), http: httpServiceMock.createSetupContract(), - uiSettings: uiSettingsServiceMock.createSetupContract(), - savedObjects: savedObjectsServiceMock.createInternalSetupContract(), - uuid: uuidServiceMock.createSetupContract(), metrics: metricsServiceMock.createInternalSetupContract(), + savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createInternalSetupContract(), + uuid: uuidServiceMock.createSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 32662f07a86f..61d97aea9745 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -175,6 +175,9 @@ export function createPluginSetupContext( registerType: deps.savedObjects.registerType, getImportExportObjectLimit: deps.savedObjects.getImportExportObjectLimit, }, + status: { + core$: deps.status.core$, + }, uiSettings: { register: deps.uiSettings.register, }, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index b50e47b9eab7..fe4795cad11a 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -68,7 +68,11 @@ export { SavedObjectMigrationContext, } from './migrations'; -export { SavedObjectsType, SavedObjectsTypeManagementDefinition } from './types'; +export { + SavedObjectStatusMeta, + SavedObjectsType, + SavedObjectsTypeManagementDefinition, +} from './types'; export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config'; export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; diff --git a/src/core/server/saved_objects/migrations/core/index.ts b/src/core/server/saved_objects/migrations/core/index.ts index 4fbadf90f4b6..466d399f653c 100644 --- a/src/core/server/saved_objects/migrations/core/index.ts +++ b/src/core/server/saved_objects/migrations/core/index.ts @@ -22,4 +22,4 @@ export { IndexMigrator } from './index_migrator'; export { buildActiveMappings } from './build_active_mappings'; export { CallCluster } from './call_cluster'; export { LogFn } from './migration_logger'; -export { MigrationResult } from './migration_coordinator'; +export { MigrationResult, MigrationStatus } from './migration_coordinator'; diff --git a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts index ddd82edd9344..5ba2d0afc692 100644 --- a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts +++ b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts @@ -39,6 +39,8 @@ import { SavedObjectsMigrationLogger } from './migration_logger'; const DEFAULT_POLL_INTERVAL = 15000; +export type MigrationStatus = 'waiting' | 'running' | 'completed'; + export type MigrationResult = | { status: 'skipped' } | { status: 'patched' } diff --git a/src/core/server/saved_objects/migrations/index.ts b/src/core/server/saved_objects/migrations/index.ts index dc966f079782..8ddaed3707eb 100644 --- a/src/core/server/saved_objects/migrations/index.ts +++ b/src/core/server/saved_objects/migrations/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export { MigrationResult } from './core'; export { KibanaMigrator, IKibanaMigrator } from './kibana'; export { SavedObjectMigrationFn, diff --git a/src/core/server/saved_objects/migrations/kibana/index.ts b/src/core/server/saved_objects/migrations/kibana/index.ts index 25772c4c9b0b..df4751521ac5 100644 --- a/src/core/server/saved_objects/migrations/kibana/index.ts +++ b/src/core/server/saved_objects/migrations/kibana/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { KibanaMigrator, IKibanaMigrator } from './kibana_migrator'; +export { KibanaMigrator, IKibanaMigrator, KibanaMigratorStatus } from './kibana_migrator'; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts index 2ee656721abd..257b32c1e4c2 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts @@ -17,10 +17,11 @@ * under the License. */ -import { KibanaMigrator } from './kibana_migrator'; +import { KibanaMigrator, KibanaMigratorStatus } from './kibana_migrator'; import { buildActiveMappings } from '../core'; const { mergeTypes } = jest.requireActual('./kibana_migrator'); import { SavedObjectsType } from '../../types'; +import { BehaviorSubject } from 'rxjs'; const defaultSavedObjectTypes: SavedObjectsType[] = [ { @@ -47,6 +48,20 @@ const createMigrator = ( runMigrations: jest.fn(), getActiveMappings: jest.fn(), migrateDocument: jest.fn(), + getStatus$: jest.fn( + () => + new BehaviorSubject({ + status: 'completed', + result: [ + { + status: 'migrated', + destIndex: '.test-kibana_2', + sourceIndex: '.test-kibana_1', + elapsedMs: 10, + }, + ], + }) + ), }; mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(mergeTypes(types))); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index fd82bf282266..336eeff99f47 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { take } from 'rxjs/operators'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; @@ -79,6 +80,33 @@ describe('KibanaMigrator', () => { .filter(callClusterPath => callClusterPath === 'cat.templates'); expect(callClusterCommands.length).toBe(1); }); + + it('emits results on getMigratorResult$()', async () => { + const options = mockOptions(); + const clusterStub = jest.fn(() => ({ status: 404 })); + + options.callCluster = clusterStub; + const migrator = new KibanaMigrator(options); + const migratorStatus = migrator + .getStatus$() + .pipe(take(3)) + .toPromise(); + await migrator.runMigrations(); + const { status, result } = await migratorStatus; + expect(status).toEqual('completed'); + expect(result![0]).toMatchObject({ + destIndex: '.my-index_1', + elapsedMs: expect.any(Number), + sourceIndex: '.my-index', + status: 'migrated', + }); + expect(result![1]).toMatchObject({ + destIndex: 'other-index_1', + elapsedMs: expect.any(Number), + sourceIndex: 'other-index', + status: 'migrated', + }); + }); }); }); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index bc29061b380b..dafd6c534119 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -24,10 +24,17 @@ import { Logger } from 'src/core/server/logging'; import { KibanaConfigType } from 'src/core/server/kibana_config'; +import { BehaviorSubject } from 'rxjs'; import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { docValidator, PropertyValidators } from '../../validation'; -import { buildActiveMappings, CallCluster, IndexMigrator } from '../core'; +import { + buildActiveMappings, + CallCluster, + IndexMigrator, + MigrationResult, + MigrationStatus, +} from '../core'; import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator'; import { createIndexMap } from '../core/build_index_map'; import { SavedObjectsMigrationConfigType } from '../../saved_objects_config'; @@ -46,6 +53,11 @@ export interface KibanaMigratorOptions { export type IKibanaMigrator = Pick; +export interface KibanaMigratorStatus { + status: MigrationStatus; + result?: MigrationResult[]; +} + /** * Manages the shape of mappings and documents in the Kibana index. */ @@ -58,7 +70,10 @@ export class KibanaMigrator { private readonly mappingProperties: SavedObjectsTypeMappingDefinitions; private readonly typeRegistry: ISavedObjectTypeRegistry; private readonly serializer: SavedObjectsSerializer; - private migrationResult?: Promise>; + private migrationResult?: Promise; + private readonly status$ = new BehaviorSubject({ + status: 'waiting', + }); /** * Creates an instance of KibanaMigrator. @@ -109,12 +124,20 @@ export class KibanaMigrator { Array<{ status: string }> > { if (this.migrationResult === undefined || rerun) { - this.migrationResult = this.runMigrationsInternal(); + this.status$.next({ status: 'running' }); + this.migrationResult = this.runMigrationsInternal().then(result => { + this.status$.next({ status: 'completed', result }); + return result; + }); } return this.migrationResult; } + public getStatus$() { + return this.status$.asObservable(); + } + private runMigrationsInternal() { const kibanaIndexName = this.kibanaConfig.index; const indexMap = createIndexMap({ diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 9fe32b14e645..7ba4613c857d 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -17,6 +17,8 @@ * under the License. */ +import { BehaviorSubject } from 'rxjs'; + import { SavedObjectsService, InternalSavedObjectsServiceSetup, @@ -29,6 +31,7 @@ import { savedObjectsClientProviderMock } from './service/lib/scoped_client_prov import { savedObjectsRepositoryMock } from './service/lib/repository.mock'; import { savedObjectsClientMock } from './service/saved_objects_client.mock'; import { typeRegistryMock } from './saved_objects_type_registry.mock'; +import { ServiceStatusLevels } from '../status'; type SavedObjectsServiceContract = PublicMethodsOf; @@ -75,6 +78,10 @@ const createSetupContractMock = () => { const createInternalSetupContractMock = () => { const internalSetupContract: jest.Mocked = { ...createSetupContractMock(), + status$: new BehaviorSubject({ + level: ServiceStatusLevels.available, + summary: `SavedObjects is available`, + }), }; return internalSetupContract; }; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index aa440c645456..62027928c0bb 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Subject } from 'rxjs'; -import { first, filter, take } from 'rxjs/operators'; +import { Subject, Observable } from 'rxjs'; +import { first, filter, take, switchMap } from 'rxjs/operators'; import { CoreService } from '../../types'; import { SavedObjectsClient, @@ -38,7 +38,7 @@ import { SavedObjectConfig, } from './saved_objects_config'; import { KibanaRequest, InternalHttpServiceSetup } from '../http'; -import { SavedObjectsClientContract, SavedObjectsType } from './types'; +import { SavedObjectsClientContract, SavedObjectsType, SavedObjectStatusMeta } from './types'; import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/repository'; import { SavedObjectsClientFactoryProvider, @@ -50,6 +50,8 @@ import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objec import { PropertyValidators } from './validation'; import { SavedObjectsSerializer } from './serialization'; import { registerRoutes } from './routes'; +import { ServiceStatus } from '../status'; +import { calculateStatus$ } from './status'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to @@ -164,7 +166,9 @@ export interface SavedObjectsServiceSetup { /** * @internal */ -export type InternalSavedObjectsServiceSetup = SavedObjectsServiceSetup; +export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { + status$: Observable>; +} /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to @@ -321,6 +325,10 @@ export class SavedObjectsService }); return { + status$: calculateStatus$( + this.migrator$.pipe(switchMap(migrator => migrator.getStatus$())), + setupDeps.elasticsearch.status$ + ), setClientFactoryProvider: provider => { if (this.started) { throw new Error('cannot call `setClientFactoryProvider` after service startup.'); diff --git a/src/core/server/saved_objects/status.test.ts b/src/core/server/saved_objects/status.test.ts new file mode 100644 index 000000000000..8efea1e2c00c --- /dev/null +++ b/src/core/server/saved_objects/status.test.ts @@ -0,0 +1,134 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { of, Observable } from 'rxjs'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { calculateStatus$ } from './status'; +import { take } from 'rxjs/operators'; + +describe('calculateStatus$', () => { + const expectUnavailableDueToEs = (status$: Observable) => + expect(status$.pipe(take(1)).toPromise()).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is not available without a healthy Elasticearch connection`, + }); + + const expectUnavailableDueToMigrations = (status$: Observable) => + expect(status$.pipe(take(1)).toPromise()).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }); + + describe('when elasticsearch is unavailable', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.unavailable, + summary: 'xxx', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToEs(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable after migrations have ran', async () => { + await expectUnavailableDueToEs( + calculateStatus$(of({ status: 'completed', result: [] }), esStatus$) + ); + }); + }); + + describe('when elasticsearch is critical', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.critical, + summary: 'xxx', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToEs(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable after migrations have ran', async () => { + await expectUnavailableDueToEs( + calculateStatus$( + of({ status: 'completed', result: [{ status: 'migrated' } as any] }), + esStatus$ + ) + ); + }); + }); + + describe('when elasticsearch is available', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.available, + summary: 'Available', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToMigrations(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable while migrations are running', async () => { + await expect( + calculateStatus$(of({ status: 'running' }), esStatus$) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is running migrations`, + }); + }); + it('is available after migrations have ran', async () => { + await expect( + calculateStatus$( + of({ status: 'completed', result: [{ status: 'skipped' }, { status: 'patched' }] }), + esStatus$ + ) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.available, + summary: `SavedObjects service has completed migrations and is available`, + meta: { + migratedIndices: { + migrated: 0, + patched: 1, + skipped: 1, + }, + }, + }); + }); + }); + + describe('when elasticsearch is degraded', () => { + const esStatus$ = of({ level: ServiceStatusLevels.degraded, summary: 'xxx' }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToMigrations(calculateStatus$(of(), esStatus$)); + }); + it('is degraded after migrations have ran', async () => { + await expect( + calculateStatus$( + of([{ status: 'skipped' }]), + esStatus$ + ) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.degraded, + summary: 'SavedObjects service is degraded due to Elasticsearch: [xxx]', + }); + }); + }); +}); diff --git a/src/core/server/saved_objects/status.ts b/src/core/server/saved_objects/status.ts new file mode 100644 index 000000000000..66a6e2baa17a --- /dev/null +++ b/src/core/server/saved_objects/status.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable, combineLatest } from 'rxjs'; +import { startWith, map } from 'rxjs/operators'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { SavedObjectStatusMeta } from './types'; +import { KibanaMigratorStatus } from './migrations/kibana'; + +export const calculateStatus$ = ( + rawMigratorStatus$: Observable, + elasticsearchStatus$: Observable +): Observable> => { + const migratorStatus$: Observable> = rawMigratorStatus$.pipe( + map(migrationStatus => { + if (migrationStatus.status === 'waiting') { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }; + } else if (migrationStatus.status === 'running') { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is running migrations`, + }; + } + + const statusCounts: SavedObjectStatusMeta['migratedIndices'] = { migrated: 0, skipped: 0 }; + if (migrationStatus.result) { + migrationStatus.result.forEach(({ status }) => { + statusCounts[status] = (statusCounts[status] ?? 0) + 1; + }); + } + + return { + level: ServiceStatusLevels.available, + summary: `SavedObjects service has completed migrations and is available`, + meta: { + migratedIndices: statusCounts, + }, + }; + }), + startWith({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }) + ); + + return combineLatest([elasticsearchStatus$, migratorStatus$]).pipe( + map(([esStatus, migratorStatus]) => { + if (esStatus.level >= ServiceStatusLevels.unavailable) { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is not available without a healthy Elasticearch connection`, + }; + } else if (migratorStatus.level === ServiceStatusLevels.unavailable) { + return migratorStatus; + } else if (esStatus.level === ServiceStatusLevels.degraded) { + return { + level: esStatus.level, + summary: `SavedObjects service is degraded due to Elasticsearch: [${esStatus.summary}]`, + }; + } else { + return migratorStatus; + } + }) + ); +}; diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 962965a08f8b..f14e9d9efb5e 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -46,6 +46,19 @@ export { SavedObjectsMigrationVersion, } from '../../types'; +/** + * Meta information about the SavedObjectService's status. Available to plugins via {@link CoreSetup.status}. + * + * @public + */ +export interface SavedObjectStatusMeta { + migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +} + /** * * @public diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index e4e2b8d7adbb..f3e3b7736d8d 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -638,6 +638,8 @@ export interface CoreSetup ISavedObjectTypeRegistry; } +// @public +export interface SavedObjectStatusMeta { + // (undocumented) + migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +} + // @public (undocumented) export interface SavedObjectsType { convertToAliasScript?: string; @@ -2237,6 +2283,38 @@ export class ScopedClusterClient implements IScopedClusterClient { callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } +// @public +export interface ServiceStatus | unknown = unknown> { + detail?: string; + documentationUrl?: string; + level: ServiceStatusLevel; + meta?: Meta; + summary: string; +} + +// @public +export type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; + +// @public +export const ServiceStatusLevels: Readonly<{ + available: Readonly<{ + toString: () => "available"; + valueOf: () => 0; + }>; + degraded: Readonly<{ + toString: () => "degraded"; + valueOf: () => 1; + }>; + unavailable: Readonly<{ + toString: () => "unavailable"; + valueOf: () => 2; + }>; + critical: Readonly<{ + toString: () => "critical"; + valueOf: () => 3; + }>; +}>; + // @public export interface SessionCookieValidationResult { isValid: boolean; @@ -2274,6 +2352,11 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ // @public export type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart, TStart]>; +// @public +export interface StatusServiceSetup { + core$: Observable; +} + // @public export type StringValidation = StringValidationRegex | StringValidationRegexString; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 53d1b742a649..5d535c984572 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -85,3 +85,9 @@ export const mockMetricsService = metricsServiceMock.create(); jest.doMock('./metrics/metrics_service', () => ({ MetricsService: jest.fn(() => mockMetricsService), })); + +import { statusServiceMock } from './status/status_service.mock'; +export const mockStatusService = statusServiceMock.create(); +jest.doMock('./status/status_service', () => ({ + StatusService: jest.fn(() => mockStatusService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index a4b5a9d81df2..24c41d511180 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -29,6 +29,7 @@ import { mockUiSettingsService, mockRenderingService, mockMetricsService, + mockStatusService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -63,6 +64,7 @@ test('sets up services on "setup"', async () => { expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -74,6 +76,7 @@ test('sets up services on "setup"', async () => { expect(mockUiSettingsService.setup).toHaveBeenCalledTimes(1); expect(mockRenderingService.setup).toHaveBeenCalledTimes(1); expect(mockMetricsService.setup).toHaveBeenCalledTimes(1); + expect(mockStatusService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -141,6 +144,7 @@ test('stops services on "stop"', async () => { expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); expect(mockUiSettingsService.stop).not.toHaveBeenCalled(); expect(mockMetricsService.stop).not.toHaveBeenCalled(); + expect(mockStatusService.stop).not.toHaveBeenCalled(); await server.stop(); @@ -151,6 +155,7 @@ test('stops services on "stop"', async () => { expect(mockSavedObjectsService.stop).toHaveBeenCalledTimes(1); expect(mockUiSettingsService.stop).toHaveBeenCalledTimes(1); expect(mockMetricsService.stop).toHaveBeenCalledTimes(1); + expect(mockStatusService.stop).toHaveBeenCalledTimes(1); }); test(`doesn't setup core services if config validation fails`, async () => { @@ -167,6 +172,7 @@ test(`doesn't setup core services if config validation fails`, async () => { expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); }); test(`doesn't setup core services if legacy config validation fails`, async () => { @@ -187,4 +193,5 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); }); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 222be572b75e..07ea431dd3a0 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -36,6 +36,9 @@ import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; import { MetricsService, opsConfig } from './metrics'; +import { CapabilitiesService } from './capabilities'; +import { UuidService } from './uuid'; +import { StatusService } from './status/status_service'; import { config as cspConfig } from './csp'; import { config as elasticsearchConfig } from './elasticsearch'; @@ -50,8 +53,6 @@ import { mapToObject } from '../utils'; import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart } from './internal_types'; -import { CapabilitiesService } from './capabilities'; -import { UuidService } from './uuid'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -70,6 +71,7 @@ export class Server { private readonly uiSettings: UiSettingsService; private readonly uuid: UuidService; private readonly metrics: MetricsService; + private readonly status: StatusService; private readonly coreApp: CoreApp; private pluginsInitialized?: boolean; @@ -95,6 +97,7 @@ export class Server { this.capabilities = new CapabilitiesService(core); this.uuid = new UuidService(core); this.metrics = new MetricsService(core); + this.status = new StatusService(core); this.coreApp = new CoreApp(core); } @@ -145,15 +148,21 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); + const statusSetup = this.status.setup({ + elasticsearch: elasticsearchServiceSetup, + savedObjects: savedObjectsSetup, + }); + const coreSetup: InternalCoreSetup = { capabilities: capabilitiesSetup, context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, - uiSettings: uiSettingsSetup, - savedObjects: savedObjectsSetup, - uuid: uuidSetup, metrics: metricsSetup, + savedObjects: savedObjectsSetup, + status: statusSetup, + uiSettings: uiSettingsSetup, + uuid: uuidSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -220,6 +229,7 @@ export class Server { await this.uiSettings.stop(); await this.rendering.stop(); await this.metrics.stop(); + await this.status.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup, rendering: RenderingServiceSetup) { diff --git a/src/core/server/status/get_summary_status.test.ts b/src/core/server/status/get_summary_status.test.ts new file mode 100644 index 000000000000..7516e82ee784 --- /dev/null +++ b/src/core/server/status/get_summary_status.test.ts @@ -0,0 +1,180 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ServiceStatus, ServiceStatusLevels } from './types'; +import { getSummaryStatus } from './get_summary_status'; + +describe('getSummaryStatus', () => { + const available: ServiceStatus = { level: ServiceStatusLevels.available, summary: 'Available' }; + const degraded: ServiceStatus = { + level: ServiceStatusLevels.degraded, + summary: 'This is degraded!', + }; + const unavailable: ServiceStatus = { + level: ServiceStatusLevels.unavailable, + summary: 'This is unavailable!', + }; + const critical: ServiceStatus = { + level: ServiceStatusLevels.critical, + summary: 'This is critical!', + }; + + it('returns available when all status are available', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: available, + s3: available, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.available, + }); + }); + + it('returns degraded when the worst status is degraded', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: degraded, + s3: available, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.degraded, + }); + }); + + it('returns unavailable when the worst status is unavailable', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: degraded, + s3: unavailable, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.unavailable, + }); + }); + + it('returns critical when the worst status is critical', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: critical, + s2: degraded, + s3: unavailable, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.critical, + }); + }); + + describe('summary', () => { + describe('when a single service is at highest level', () => { + it('returns all information about that single service', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[s2]: Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }); + }); + }); + + describe('when multiple services is at highest level', () => { + it('returns aggregated information about the affected services', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + s3: { + level: ServiceStatusLevels.unavailable, + summary: 'Proin mattis', + detail: 'Nunc quis nulla at mi lobortis pretium.', + documentationUrl: 'http://helpmenow.com/problem2', + meta: { + other: { data: 'over there' }, + }, + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[2] services are unavailable', + detail: 'See the status page for more information', + meta: { + affectedServices: { + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + s3: { + level: ServiceStatusLevels.unavailable, + summary: 'Proin mattis', + detail: 'Nunc quis nulla at mi lobortis pretium.', + documentationUrl: 'http://helpmenow.com/problem2', + meta: { + other: { data: 'over there' }, + }, + }, + }, + }, + }); + }); + }); + }); +}); diff --git a/src/core/server/status/get_summary_status.ts b/src/core/server/status/get_summary_status.ts new file mode 100644 index 000000000000..748a54f0bf8b --- /dev/null +++ b/src/core/server/status/get_summary_status.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ServiceStatus, ServiceStatusLevels, ServiceStatusLevel } from './types'; + +/** + * Returns a single {@link ServiceStatus} that summarizes the most severe status level from a group of statuses. + * @param statuses + */ +export const getSummaryStatus = (statuses: Array<[string, ServiceStatus]>): ServiceStatus => { + const grouped = groupByLevel(statuses); + const highestSeverityLevel = getHighestSeverityLevel(grouped.keys()); + const highestSeverityGroup = grouped.get(highestSeverityLevel)!; + + if (highestSeverityLevel === ServiceStatusLevels.available) { + return { + level: ServiceStatusLevels.available, + summary: `All services are available`, + }; + } else if (highestSeverityGroup.size === 1) { + const [serviceName, status] = [...highestSeverityGroup.entries()][0]; + return { + ...status, + summary: `[${serviceName}]: ${status.summary!}`, + }; + } else { + return { + level: highestSeverityLevel, + summary: `[${highestSeverityGroup.size}] services are ${highestSeverityLevel.toString()}`, + // TODO: include URL to status page + detail: `See the status page for more information`, + meta: { + affectedServices: Object.fromEntries([...highestSeverityGroup]), + }, + }; + } +}; + +const groupByLevel = ( + statuses: Array<[string, ServiceStatus]> +): Map> => { + const byLevel = new Map>(); + + for (const [serviceName, status] of statuses) { + let levelMap = byLevel.get(status.level); + if (!levelMap) { + levelMap = new Map(); + byLevel.set(status.level, levelMap); + } + + levelMap.set(serviceName, status); + } + + return byLevel; +}; + +const getHighestSeverityLevel = (levels: Iterable): ServiceStatusLevel => { + const sorted = [...levels].sort((a, b) => { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }); + return sorted[sorted.length - 1] ?? ServiceStatusLevels.available; +}; diff --git a/src/core/server/status/index.ts b/src/core/server/status/index.ts new file mode 100644 index 000000000000..c39115d55a68 --- /dev/null +++ b/src/core/server/status/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { StatusService } from './status_service'; +export * from './types'; diff --git a/src/core/server/status/status_service.mock.ts b/src/core/server/status/status_service.mock.ts new file mode 100644 index 000000000000..d550c2f06750 --- /dev/null +++ b/src/core/server/status/status_service.mock.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { StatusService } from './status_service'; +import { + InternalStatusServiceSetup, + StatusServiceSetup, + ServiceStatusLevels, + ServiceStatus, + CoreStatus, +} from './types'; +import { BehaviorSubject } from 'rxjs'; + +const available: ServiceStatus = { + level: ServiceStatusLevels.available, + summary: 'Service is working', +}; +const availableCoreStatus: CoreStatus = { + elasticsearch: available, + savedObjects: available, +}; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + core$: new BehaviorSubject(availableCoreStatus), + }; + + return setupContract; +}; + +const createInternalSetupContractMock = () => { + const setupContract: jest.Mocked = { + core$: new BehaviorSubject(availableCoreStatus), + overall$: new BehaviorSubject(available), + }; + + return setupContract; +}; + +type StatusServiceContract = PublicMethodsOf; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createInternalSetupContractMock()), + start: jest.fn(), + stop: jest.fn(), + }; + return mocked; +}; + +export const statusServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createInternalSetupContract: createInternalSetupContractMock, +}; diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts new file mode 100644 index 000000000000..6d92a266369b --- /dev/null +++ b/src/core/server/status/status_service.test.ts @@ -0,0 +1,229 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { of, BehaviorSubject } from 'rxjs'; + +import { ServiceStatus, ServiceStatusLevels, CoreStatus } from './types'; +import { StatusService } from './status_service'; +import { first } from 'rxjs/operators'; +import { mockCoreContext } from '../core_context.mock'; +import { ServiceStatusLevelSnapshotSerializer } from './test_utils'; + +expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer); + +describe('StatusService', () => { + const available: ServiceStatus = { + level: ServiceStatusLevels.available, + summary: 'Available', + }; + const degraded: ServiceStatus = { + level: ServiceStatusLevels.degraded, + summary: 'This is degraded!', + }; + + describe('setup', () => { + describe('core$', () => { + it('rolls up core status observables into single observable', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(available), + }, + savedObjects: { + status$: of(degraded), + }, + }); + expect(await setup.core$.pipe(first()).toPromise()).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + }); + + it('replays last event', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(available), + }, + savedObjects: { + status$: of(degraded), + }, + }); + const subResult1 = await setup.core$.pipe(first()).toPromise(); + const subResult2 = await setup.core$.pipe(first()).toPromise(); + const subResult3 = await setup.core$.pipe(first()).toPromise(); + expect(subResult1).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + expect(subResult2).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + expect(subResult3).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + }); + + it('does not emit duplicate events', () => { + const elasticsearch$ = new BehaviorSubject(available); + const savedObjects$ = new BehaviorSubject(degraded); + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: elasticsearch$, + }, + savedObjects: { + status$: savedObjects$, + }, + }); + + const statusUpdates: CoreStatus[] = []; + const subscription = setup.core$.subscribe(status => statusUpdates.push(status)); + + elasticsearch$.next(available); + elasticsearch$.next(available); + elasticsearch$.next({ + level: ServiceStatusLevels.available, + summary: `Wow another summary`, + }); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(available); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "elasticsearch": Object { + "level": available, + "summary": "Available", + }, + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + Object { + "elasticsearch": Object { + "level": available, + "summary": "Wow another summary", + }, + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + Object { + "elasticsearch": Object { + "level": available, + "summary": "Wow another summary", + }, + "savedObjects": Object { + "level": available, + "summary": "Available", + }, + }, + ] + `); + }); + }); + + describe('overall$', () => { + it('exposes an overall summary', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(degraded), + }, + savedObjects: { + status$: of(degraded), + }, + }); + expect(await setup.overall$.pipe(first()).toPromise()).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + }); + + it('replays last event', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(degraded), + }, + savedObjects: { + status$: of(degraded), + }, + }); + const subResult1 = await setup.overall$.pipe(first()).toPromise(); + const subResult2 = await setup.overall$.pipe(first()).toPromise(); + const subResult3 = await setup.overall$.pipe(first()).toPromise(); + expect(subResult1).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + expect(subResult2).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + expect(subResult3).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + }); + + it('does not emit duplicate events', () => { + const elasticsearch$ = new BehaviorSubject(available); + const savedObjects$ = new BehaviorSubject(degraded); + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: elasticsearch$, + }, + savedObjects: { + status$: savedObjects$, + }, + }); + + const statusUpdates: ServiceStatus[] = []; + const subscription = setup.overall$.subscribe(status => statusUpdates.push(status)); + + elasticsearch$.next(available); + elasticsearch$.next(available); + elasticsearch$.next({ + level: ServiceStatusLevels.available, + summary: `Wow another summary`, + }); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(available); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "level": degraded, + "summary": "[savedObjects]: This is degraded!", + }, + Object { + "level": available, + "summary": "All services are available", + }, + ] + `); + }); + }); + }); +}); diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts new file mode 100644 index 000000000000..b6697d822195 --- /dev/null +++ b/src/core/server/status/status_service.ts @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable max-classes-per-file */ + +import { Observable, combineLatest } from 'rxjs'; +import { map, distinctUntilChanged, shareReplay } from 'rxjs/operators'; +import { isDeepStrictEqual } from 'util'; + +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; +import { InternalElasticsearchServiceSetup } from '../elasticsearch'; +import { InternalSavedObjectsServiceSetup } from '../saved_objects'; + +import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types'; +import { getSummaryStatus } from './get_summary_status'; + +interface SetupDeps { + elasticsearch: Pick; + savedObjects: Pick; +} + +export class StatusService implements CoreService { + private readonly logger: Logger; + + constructor(coreContext: CoreContext) { + this.logger = coreContext.logger.get('status'); + } + + public setup(core: SetupDeps) { + const core$ = this.setupCoreStatus(core); + const overall$: Observable = core$.pipe( + map(coreStatus => { + const summary = getSummaryStatus(Object.entries(coreStatus)); + this.logger.debug(`Recalculated overall status`, { status: summary }); + return summary; + }), + distinctUntilChanged(isDeepStrictEqual) + ); + + return { + core$, + overall$, + }; + } + + public start() {} + + public stop() {} + + private setupCoreStatus({ elasticsearch, savedObjects }: SetupDeps): Observable { + return combineLatest([elasticsearch.status$, savedObjects.status$]).pipe( + map(([elasticsearchStatus, savedObjectsStatus]) => ({ + elasticsearch: elasticsearchStatus, + savedObjects: savedObjectsStatus, + })), + distinctUntilChanged(isDeepStrictEqual), + shareReplay(1) + ); + } +} diff --git a/src/core/server/status/test_utils.ts b/src/core/server/status/test_utils.ts new file mode 100644 index 000000000000..765fa8771f37 --- /dev/null +++ b/src/core/server/status/test_utils.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ServiceStatusLevels, ServiceStatusLevel } from './types'; + +export const ServiceStatusLevelSnapshotSerializer: jest.SnapshotSerializerPlugin = { + test: (val: any) => Object.values(ServiceStatusLevels).includes(val), + print: (val: ServiceStatusLevel) => val.toString(), +}; diff --git a/src/core/server/status/types.ts b/src/core/server/status/types.ts new file mode 100644 index 000000000000..84a7356c66bb --- /dev/null +++ b/src/core/server/status/types.ts @@ -0,0 +1,134 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { deepFreeze } from '../../utils'; + +/** + * The current status of a service at a point in time. + * + * @typeParam Meta - JSON-serializable object. Plugins should export this type to allow other plugins to read the `meta` + * field in a type-safe way. + * @public + */ +export interface ServiceStatus | unknown = unknown> { + /** + * The current availability level of the service. + */ + level: ServiceStatusLevel; + /** + * A high-level summary of the service status. + */ + summary: string; + /** + * A more detailed description of the service status. + */ + detail?: string; + /** + * A URL to open in a new tab about how to resolve or troubleshoot the problem. + */ + documentationUrl?: string; + /** + * Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, + * machine-readable information about the service status. May include status information for underlying features. + */ + meta?: Meta; +} + +/** + * The current "level" of availability of a service. + * + * @remarks + * The values implement `valueOf` to allow for easy comparisons between status levels with <, >, etc. Higher values + * represent higher severities. Note that the default `Array.prototype.sort` implementation does not correctly sort + * these values. + * + * A snapshot serializer is available in `src/core/server/test_utils` to ease testing of these values with Jest. + * + * @public + */ +export const ServiceStatusLevels = deepFreeze({ + /** + * Everything is working! + */ + available: { + toString: () => 'available', + valueOf: () => 0, + }, + /** + * Some features may not be working. + */ + degraded: { + toString: () => 'degraded', + valueOf: () => 1, + }, + /** + * The service is unavailable, but other functions that do not depend on this service should work. + */ + unavailable: { + toString: () => 'unavailable', + valueOf: () => 2, + }, + /** + * Block all user functions and display the status page, reserved for Core services only. + */ + critical: { + toString: () => 'critical', + valueOf: () => 3, + }, +}); + +/** + * A convenience type that represents the union of each value in {@link ServiceStatusLevels}. + * @public + */ +export type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; + +/** + * Status of core services. + * + * @internalRemarks + * Only contains entries for backend services that could have a non-available `status`. + * For example, `context` cannot possibly be broken, so it is not included. + * + * @public + */ +export interface CoreStatus { + elasticsearch: ServiceStatus; + savedObjects: ServiceStatus; +} + +/** + * API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. + * @public + */ +export interface StatusServiceSetup { + /** + * Current status for all Core services. + */ + core$: Observable; +} + +/** @internal */ +export interface InternalStatusServiceSetup extends StatusServiceSetup { + /** + * Overall system status used for HTTP API + */ + overall$: Observable; +} diff --git a/src/core/server/test_utils.ts b/src/core/server/test_utils.ts index 470b1c2d135b..f7e6fbcd0c13 100644 --- a/src/core/server/test_utils.ts +++ b/src/core/server/test_utils.ts @@ -18,3 +18,4 @@ */ export { createHttpServer } from './http/test_utils'; +export { ServiceStatusLevelSnapshotSerializer } from './status/test_utils'; From e0a519424fce5758434b914c38d877cbc7588f93 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 8 Apr 2020 15:10:44 -0500 Subject: [PATCH 08/46] Index pattern management plugin - src/legacy/core_plugins/management => new platform plugin (#62594) * implement index pattern management plugin in new platform --- .i18nrc.json | 1 + .../services => kibana/public}/index.ts | 5 +- .../step_index_pattern.test.tsx | 2 +- .../step_index_pattern/step_index_pattern.tsx | 2 +- .../step_time_field/step_time_field.test.tsx | 2 +- .../step_time_field/step_time_field.tsx | 2 +- .../create_index_pattern_wizard/index.js | 3 +- .../lib/get_indices.test.ts | 2 +- .../lib/get_indices.ts | 2 +- .../edit_index_pattern/edit_index_pattern.js | 13 ++--- .../sections/index_patterns/index.js | 5 +- .../__jest__/objects_table.test.js | 4 +- .../components/flyout/__jest__/flyout.test.js | 4 +- src/legacy/core_plugins/management/index.ts | 37 ------------- .../core_plugins/management/package.json | 5 -- .../core_plugins/management/public/index.ts | 38 -------------- .../core_plugins/management/public/legacy.ts | 45 ---------------- .../new_platform/new_platform.karma_mock.js | 15 ++++++ .../ui/public/new_platform/new_platform.ts | 6 +++ .../index_pattern_management/kibana.json | 7 +++ .../index_pattern_management/public}/index.ts | 11 ++-- .../index_pattern_management/public}/mocks.ts | 52 +++++++++---------- .../public}/plugin.ts | 43 +++++++-------- .../public/service}/creation/config.ts | 8 +-- .../public/service}/creation/index.ts | 0 .../public/service}/creation/manager.ts | 21 ++++++-- .../public/service}/index.ts | 0 .../index_pattern_management_service.ts | 51 ++++++++---------- .../public/service}/list/config.ts | 9 ++-- .../public/service}/list/index.ts | 0 .../public/service}/list/manager.ts | 18 +++++-- x-pack/legacy/plugins/rollup/kibana.json | 3 +- .../rollup_index_pattern_creation_config.js | 2 +- .../rollup_index_pattern_list_config.js | 2 +- x-pack/legacy/plugins/rollup/public/legacy.ts | 8 +-- x-pack/legacy/plugins/rollup/public/plugin.ts | 17 ++---- .../components/copy_to_space_flyout.test.tsx | 6 --- .../components/copy_to_space_flyout.tsx | 2 +- .../copy_to_space_flyout_footer.tsx | 2 +- .../components/processing_copy_to_space.tsx | 2 +- .../summarize_copy_result.test.ts | 2 +- .../summarize_copy_result.ts | 2 +- .../translations/translations/ja-JP.json | 10 ++-- .../translations/translations/zh-CN.json | 10 ++-- 44 files changed, 184 insertions(+), 297 deletions(-) rename src/legacy/core_plugins/{management/public/np_ready/services => kibana/public}/index.ts (86%) delete mode 100644 src/legacy/core_plugins/management/index.ts delete mode 100644 src/legacy/core_plugins/management/package.json delete mode 100644 src/legacy/core_plugins/management/public/index.ts delete mode 100644 src/legacy/core_plugins/management/public/legacy.ts create mode 100644 src/plugins/index_pattern_management/kibana.json rename src/{legacy/core_plugins/management/public/np_ready => plugins/index_pattern_management/public}/index.ts (83%) rename src/{legacy/core_plugins/management/public/np_ready => plugins/index_pattern_management/public}/mocks.ts (57%) rename src/{legacy/core_plugins/management/public/np_ready => plugins/index_pattern_management/public}/plugin.ts (60%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/creation/config.ts (88%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/creation/index.ts (100%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/creation/manager.ts (79%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/index.ts (100%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/index_pattern_management_service.ts (51%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/list/config.ts (87%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/list/index.ts (100%) rename src/{legacy/core_plugins/management/public/np_ready/services/index_pattern_management => plugins/index_pattern_management/public/service}/list/manager.ts (75%) diff --git a/.i18nrc.json b/.i18nrc.json index 3b0b40b40792..19d361aed934 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -24,6 +24,7 @@ "src/legacy/core_plugins/management", "src/plugins/management" ], + "indexPatternManagement": "src/plugins/index_pattern_management", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", "kibana_react": "src/legacy/core_plugins/kibana_react", diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index.ts b/src/legacy/core_plugins/kibana/public/index.ts similarity index 86% rename from src/legacy/core_plugins/management/public/np_ready/services/index.ts rename to src/legacy/core_plugins/kibana/public/index.ts index 9df010223542..a4fffc6eec26 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index.ts +++ b/src/legacy/core_plugins/kibana/public/index.ts @@ -17,4 +17,7 @@ * under the License. */ -export * from './index_pattern_management'; +export { + ProcessedImportResponse, + processImportResponse, +} from './management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx index 25bd36829b6d..40471b95d774 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { StepIndexPattern } from '../step_index_pattern'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { Header } from './components/header'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { coreMock } from '../../../../../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../../../../../../plugins/data/public/mocks'; import { SavedObjectsFindResponsePublic } from '../../../../../../../../../../core/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index bbb6bf26e5b3..648bf7f8f973 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -39,7 +39,7 @@ import { LoadingIndices } from './components/loading_indices'; import { StatusMessage } from './components/status_message'; import { IndicesList } from './components/indices_list'; import { Header } from './components/header'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { MatchedIndex } from '../../types'; interface StepIndexPatternProps { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx index e0c43105cb32..b23b1e3ad905 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { IFieldType } from '../../../../../../../../../../plugins/data/public'; import { StepTimeField } from '../step_time_field'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index 80582cc1fbd9..a58bf10c9dab 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -34,7 +34,7 @@ import { Header } from './components/header'; import { TimeField } from './components/time_field'; import { AdvancedOptions } from './components/advanced_options'; import { ActionButtons } from './components/action_buttons'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { DataPublicPluginStart } from '../../../../../../../../../../plugins/data/public'; interface StepTimeFieldProps { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index 50c5a58d35db..47cb773258cb 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -20,7 +20,6 @@ import uiRoutes from 'ui/routes'; import angularTemplate from './angular_template.html'; import { npStart } from 'ui/new_platform'; -import { setup as managementSetup } from '../../../../../../management/public/legacy'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render'; @@ -33,7 +32,7 @@ uiRoutes.when('/management/kibana/index_pattern', { const kbnUrl = $injector.get('kbnUrl'); $scope.$$postDigest(() => { const $routeParams = $injector.get('$routeParams'); - const indexPatternCreationType = managementSetup.indexPattern.creation.getType( + const indexPatternCreationType = npStart.plugins.indexPatternManagement.creation.getType( $routeParams.type ); const services = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts index 5a8460fcb51b..40583af7177f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts @@ -18,7 +18,7 @@ */ import { getIndices } from './get_indices'; -import { IndexPatternCreationConfig } from './../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../plugins/index_pattern_management/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LegacyApiCaller } from '../../../../../../../../../plugins/data/public/search'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts index 3848c425e2d4..3b1b7a3b52a5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts @@ -18,7 +18,7 @@ */ import { get, sortBy } from 'lodash'; -import { IndexPatternCreationConfig } from '../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../plugins/index_pattern_management/public'; import { DataPublicPluginStart } from '../../../../../../../../../plugins/data/public'; import { MatchedIndex } from '../types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 6d302ac5a74f..594430ca01f4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -29,7 +29,6 @@ import { uiModules } from 'ui/modules'; import template from './edit_index_pattern.html'; import { fieldWildcardMatcher } from '../../../../../../../../plugins/kibana_utils/public'; import { subscribeWithScope } from '../../../../../../../../plugins/kibana_legacy/public'; -import { setup as managementSetup } from '../../../../../../management/public/legacy'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SourceFiltersTable } from './source_filters_table'; @@ -239,14 +238,12 @@ uiModules $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = managementSetup.indexPattern.list; - $scope.indexPattern.tags = managementSetup.indexPattern.list.getIndexPatternTags( + $scope.indexPatternListProvider = npStart.plugins.indexPatternManagement.list; + $scope.indexPattern.tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags( $scope.indexPattern, $scope.indexPattern.id === config.get('defaultIndex') ); - $scope.getFieldInfo = managementSetup.indexPattern.list.getFieldInfo.bind( - managementSetup.indexPattern.list - ); + $scope.getFieldInfo = npStart.plugins.indexPatternManagement.list.getFieldInfo; docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { @@ -257,7 +254,7 @@ uiModules $scope.editSections = $scope.editSectionsProvider( $scope.indexPattern, $scope.fieldFilter, - managementSetup.indexPattern.list + npStart.plugins.indexPatternManagement.list ); $scope.refreshFilters(); $scope.fields = $scope.indexPattern.getNonScriptedFields(); @@ -363,7 +360,7 @@ uiModules $scope.editSections = $scope.editSectionsProvider( $scope.indexPattern, $scope.fieldFilter, - managementSetup.indexPattern.list + npStart.plugins.indexPatternManagement.list ); if ($scope.fieldFilter === undefined) { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 310797a7f3a0..a8376c0e84bf 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,7 +18,6 @@ */ import { management } from 'ui/management'; -import { setup as managementSetup } from '../../../../../management/public/legacy'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -111,7 +110,7 @@ uiModules transclude: true, template: indexTemplate, link: async function($scope) { - const indexPatternCreationOptions = await managementSetup.indexPattern.creation.getIndexPatternCreationOptions( + const indexPatternCreationOptions = await npStart.plugins.indexPatternManagement.creation.getIndexPatternCreationOptions( url => { $scope.$evalAsync(() => kbnUrl.change(url)); } @@ -124,7 +123,7 @@ uiModules const id = pattern.id; const title = pattern.get('title'); const isDefault = $scope.defaultIndex === id; - const tags = managementSetup.indexPattern.list.getIndexPatternTags( + const tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags( pattern, isDefault ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index a5e34f8955fe..7b9c17640a0f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../../../../plugins/index_pattern_management/public/mocks'; import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; @@ -30,7 +30,7 @@ import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); -jest.mock('../../../../../../../../management/public/legacy', () => ({ +jest.mock('../../../../../../../../../../plugins/index_pattern_management/public', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index 97c0d5b89d65..5d14c4609b91 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../../../../../../plugins/index_pattern_management/public/mocks'; import { Flyout } from '../flyout'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -48,7 +48,7 @@ jest.mock('../../../../../lib/resolve_saved_objects', () => ({ saveObjects: jest.fn(), })); -jest.mock('../../../../../../../../../../management/public/legacy', () => ({ +jest.mock('../../../../../../../../../../../../plugins/index_pattern_management/public', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); diff --git a/src/legacy/core_plugins/management/index.ts b/src/legacy/core_plugins/management/index.ts deleted file mode 100644 index 4962c948f842..000000000000 --- a/src/legacy/core_plugins/management/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from '../../../../kibana'; - -// eslint-disable-next-line import/no-default-export -export default function ManagementPlugin(kibana: any) { - const config: Legacy.PluginSpecOptions = { - id: 'stack-management', - publicDir: resolve(__dirname, 'public'), - config: (Joi: any) => { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - init: (server: Legacy.Server) => ({}), - }; - - return new kibana.Plugin(config); -} diff --git a/src/legacy/core_plugins/management/package.json b/src/legacy/core_plugins/management/package.json deleted file mode 100644 index 77d33a7bce3b..000000000000 --- a/src/legacy/core_plugins/management/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "management", - "version": "kibana" -} - \ No newline at end of file diff --git a/src/legacy/core_plugins/management/public/index.ts b/src/legacy/core_plugins/management/public/index.ts deleted file mode 100644 index bc3737524e12..000000000000 --- a/src/legacy/core_plugins/management/public/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Static np-ready code, re-exported here so consumers can import from - * `src/legacy/core_plugins/management/public` - * - * @public - */ - -export { - ManagementSetup, - ManagementStart, - plugin, - IndexPatternCreationConfig, - IndexPatternListConfig, -} from './np_ready'; - -export { - processImportResponse, - ProcessedImportResponse, -} from '../../kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts deleted file mode 100644 index 96d2c74398a0..000000000000 --- a/src/legacy/core_plugins/management/public/legacy.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * New Platform Shim - * - * In this file, we import any legacy dependencies we have, and shim them into - * our plugin by manually constructing the values that the new platform will - * eventually be passing to the `setup/start` method of our plugin definition. - * - * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy - * world code. Then when it comes time to migrate to the new platform, we can - * simply delete this shim file. - * - * We are also calling `setup/start` here and exporting our public contract so that - * other legacy plugins are able to import from '../core_plugins/management/legacy' - * and receive the response value of the `setup/start` contract, mimicking the - * data that will eventually be injected by the new platform. - */ - -import { PluginInitializerContext } from 'src/core/public'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { plugin } from '.'; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, { home: npSetup.plugins.home }); -export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index f70ef069dd13..0779d6472671 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -290,6 +290,10 @@ export const npSetup = { }), }, }, + indexPatternManagement: { + list: { addListConfig: sinon.fake() }, + creation: { addCreationConfig: sinon.fake() }, + }, discover: { docViews: { addDocView: sinon.fake(), @@ -325,6 +329,17 @@ export const npStart = { }), }, }, + indexPatternManagement: { + list: { + getType: sinon.fake(), + getIndexPatternCreationOptions: sinon.fake(), + }, + creation: { + getIndexPatternTags: sinon.fake(), + getFieldInfo: sinon.fake(), + areScriptedFieldsEnabled: sinon.fake(), + }, + }, embeddable: { getEmbeddableFactory: sinon.fake(), getEmbeddableFactories: sinon.fake(), diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index b4b509908175..cdd7e1a99491 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -47,6 +47,10 @@ import { AdvancedSettingsStart, } from '../../../../plugins/advanced_settings/public'; import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public'; +import { + IndexPatternManagementSetup, + IndexPatternManagementStart, +} from '../../../../plugins/index_pattern_management/public'; import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; import { TelemetryPluginSetup, TelemetryPluginStart } from '../../../../plugins/telemetry/public'; @@ -86,6 +90,7 @@ export interface PluginsSetup { visualizations: VisualizationsSetup; telemetry?: TelemetryPluginSetup; savedObjectsManagement: SavedObjectsManagementPluginSetup; + indexPatternManagement: IndexPatternManagementSetup; } export interface PluginsStart { @@ -107,6 +112,7 @@ export interface PluginsStart { telemetry?: TelemetryPluginStart; dashboard: DashboardStart; savedObjectsManagement: SavedObjectsManagementPluginStart; + indexPatternManagement: IndexPatternManagementStart; } export const npSetup = { diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json new file mode 100644 index 000000000000..d5397a11184a --- /dev/null +++ b/src/plugins/index_pattern_management/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "indexPatternManagement", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": [] +} diff --git a/src/legacy/core_plugins/management/public/np_ready/index.ts b/src/plugins/index_pattern_management/public/index.ts similarity index 83% rename from src/legacy/core_plugins/management/public/np_ready/index.ts rename to src/plugins/index_pattern_management/public/index.ts index bae0f1d3e23c..da482c0c51f0 100644 --- a/src/legacy/core_plugins/management/public/np_ready/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -29,14 +29,11 @@ * either types, or static code. */ import { PluginInitializerContext } from 'src/core/public'; -import { ManagementPlugin } from './plugin'; -export { ManagementSetup, ManagementStart } from './plugin'; +import { IndexPatternManagementPlugin } from './plugin'; +export { IndexPatternManagementSetup, IndexPatternManagementStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { - return new ManagementPlugin(initializerContext); + return new IndexPatternManagementPlugin(initializerContext); } -export { - IndexPatternCreationConfig, - IndexPatternListConfig, -} from './services/index_pattern_management'; +export { IndexPatternCreationConfig, IndexPatternListConfig } from './service'; diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts similarity index 57% rename from src/legacy/core_plugins/management/public/np_ready/mocks.ts rename to src/plugins/index_pattern_management/public/mocks.ts index ae0be98de63f..bc97f46c302e 100644 --- a/src/legacy/core_plugins/management/public/np_ready/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -18,42 +18,38 @@ */ import { PluginInitializerContext } from 'src/core/public'; -import { coreMock } from '../../../../../core/public/mocks'; +import { coreMock } from '../../../core/public/mocks'; import { - ManagementSetup, - ManagementStart, - ManagementPlugin, - ManagementPluginSetupDependencies, + IndexPatternManagementSetup, + IndexPatternManagementStart, + IndexPatternManagementPlugin, } from './plugin'; -const createSetupContract = (): ManagementSetup => ({ - indexPattern: { - creation: { - add: jest.fn(), - getType: jest.fn(), - getIndexPatternCreationOptions: jest.fn(), - } as any, - list: { - add: jest.fn(), - getIndexPatternTags: jest.fn(), - getFieldInfo: jest.fn(), - areScriptedFieldsEnabled: jest.fn(), - } as any, - }, +const createSetupContract = (): IndexPatternManagementSetup => ({ + creation: { + addCreationConfig: jest.fn(), + } as any, + list: { + addListConfig: jest.fn(), + } as any, }); -const createStartContract = (): ManagementStart => ({}); +const createStartContract = (): IndexPatternManagementStart => ({ + creation: { + getType: jest.fn(), + getIndexPatternCreationOptions: jest.fn(), + } as any, + list: { + getIndexPatternTags: jest.fn(), + getFieldInfo: jest.fn(), + areScriptedFieldsEnabled: jest.fn(), + } as any, +}); const createInstance = async () => { - const plugin = new ManagementPlugin({} as PluginInitializerContext); + const plugin = new IndexPatternManagementPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup(), ({ - home: { - featureCatalogue: { - register: jest.fn(), - }, - }, - } as unknown) as ManagementPluginSetupDependencies); + const setup = plugin.setup(coreMock.createSetup()); const doStart = () => plugin.start(coreMock.createStart(), {}); return { diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts similarity index 60% rename from src/legacy/core_plugins/management/public/np_ready/plugin.ts rename to src/plugins/index_pattern_management/public/plugin.ts index 2a8ef10c817c..93bb0ead1df4 100644 --- a/src/legacy/core_plugins/management/public/np_ready/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -17,43 +17,40 @@ * under the License. */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { HomePublicPluginSetup } from 'src/plugins/home/public'; -import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; - -export interface ManagementPluginSetupDependencies { - home: HomePublicPluginSetup; -} +import { + IndexPatternManagementService, + IndexPatternManagementServiceSetup, + IndexPatternManagementServiceStart, +} from './service'; // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface ManagementPluginStartDependencies {} - -export interface ManagementSetup { - indexPattern: IndexPatternManagementSetup; -} +export interface IndexPatternManagementSetupDependencies {} // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ManagementStart {} +export interface IndexPatternManagementStartDependencies {} -export class ManagementPlugin +export type IndexPatternManagementSetup = IndexPatternManagementServiceSetup; + +export type IndexPatternManagementStart = IndexPatternManagementServiceStart; + +export class IndexPatternManagementPlugin implements Plugin< - ManagementSetup, - ManagementStart, - ManagementPluginSetupDependencies, - ManagementPluginStartDependencies + IndexPatternManagementSetup, + IndexPatternManagementStart, + IndexPatternManagementSetupDependencies, + IndexPatternManagementStartDependencies > { private readonly indexPattern = new IndexPatternManagementService(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { home }: ManagementPluginSetupDependencies) { - return { - indexPattern: this.indexPattern.setup({ httpClient: core.http, home }), - }; + public setup(core: CoreSetup) { + return this.indexPattern.setup({ httpClient: core.http }); } - public start(core: CoreStart, plugins: ManagementPluginStartDependencies) { - return {}; + public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) { + return this.indexPattern.start(); } public stop() { diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts b/src/plugins/index_pattern_management/public/service/creation/config.ts similarity index 88% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts rename to src/plugins/index_pattern_management/public/service/creation/config.ts index 5714fa333896..29ab0ebfc3d5 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts +++ b/src/plugins/index_pattern_management/public/service/creation/config.ts @@ -18,20 +18,20 @@ */ import { i18n } from '@kbn/i18n'; -import { MatchedIndex } from '../../../../../../kibana/public/management/sections/index_patterns/create_index_pattern_wizard/types'; +import { MatchedIndex } from '../../../../../legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/types'; const indexPatternTypeName = i18n.translate( - 'management.editIndexPattern.createIndex.defaultTypeName', + 'indexPatternManagement.editIndexPattern.createIndex.defaultTypeName', { defaultMessage: 'index pattern' } ); const indexPatternButtonText = i18n.translate( - 'management.editIndexPattern.createIndex.defaultButtonText', + 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonText', { defaultMessage: 'Standard index pattern' } ); const indexPatternButtonDescription = i18n.translate( - 'management.editIndexPattern.createIndex.defaultButtonDescription', + 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription', { defaultMessage: 'Perform full aggregations against any data' } ); diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts b/src/plugins/index_pattern_management/public/service/creation/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts rename to src/plugins/index_pattern_management/public/service/creation/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/plugins/index_pattern_management/public/service/creation/manager.ts similarity index 79% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts rename to src/plugins/index_pattern_management/public/service/creation/manager.ts index e7fa13409ab0..32b3e7ee7a13 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts +++ b/src/plugins/index_pattern_management/public/service/creation/manager.ts @@ -17,23 +17,25 @@ * under the License. */ -import { HttpSetup } from '../../../../../../../../core/public'; +import { HttpSetup } from '../../../../../core/public'; import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; export class IndexPatternCreationManager { private configs: IndexPatternCreationConfig[]; - constructor(private readonly httpClient: HttpSetup) { + constructor() { this.configs = []; } - public add(Config: typeof IndexPatternCreationConfig) { - const config = new Config({ httpClient: this.httpClient }); + public addCreationConfig = (httpClient: HttpSetup) => ( + Config: typeof IndexPatternCreationConfig + ) => { + const config = new Config({ httpClient }); if (this.configs.findIndex(c => c.key === config.key) !== -1) { throw new Error(`${config.key} exists in IndexPatternCreationManager.`); } this.configs.push(config); - } + }; public getType(key: string | undefined): IndexPatternCreationConfig | null { if (key) { @@ -58,4 +60,13 @@ export class IndexPatternCreationManager { ); return options; } + + setup = (httpClient: HttpSetup) => ({ + addCreationConfig: this.addCreationConfig(httpClient).bind(this), + }); + + start = () => ({ + getType: this.getType.bind(this), + getIndexPatternCreationOptions: this.getIndexPatternCreationOptions.bind(this), + }); } diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts b/src/plugins/index_pattern_management/public/service/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts rename to src/plugins/index_pattern_management/public/service/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts similarity index 51% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts rename to src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts index 2b6f008dd928..4780fa00ed46 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts +++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts @@ -17,18 +17,12 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { - FeatureCatalogueCategory, - HomePublicPluginSetup, -} from '../../../../../../../plugins/home/public'; -import { HttpSetup } from '../../../../../../../core/public'; +import { HttpSetup } from '../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; interface SetupDependencies { httpClient: HttpSetup; - home: HomePublicPluginSetup; } /** @@ -37,31 +31,29 @@ interface SetupDependencies { * @internal */ export class IndexPatternManagementService { - public setup({ httpClient, home }: SetupDependencies) { - const creation = new IndexPatternCreationManager(httpClient); - const list = new IndexPatternListManager(); + indexPatternCreationManager: IndexPatternCreationManager; + indexPatternListConfig: IndexPatternListManager; - creation.add(IndexPatternCreationConfig); - list.add(IndexPatternListConfig); + constructor() { + this.indexPatternCreationManager = new IndexPatternCreationManager(); + this.indexPatternListConfig = new IndexPatternListManager(); + } - home.featureCatalogue.register({ - id: 'index_patterns', - title: i18n.translate('management.indexPatternHeader', { - defaultMessage: 'Index Patterns', - }), - description: i18n.translate('management.indexPatternLabel', { - defaultMessage: - 'Manage the index patterns that help retrieve your data from Elasticsearch.', - }), - icon: 'indexPatternApp', - path: '/app/kibana#/management/kibana/index_patterns', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }); + public setup({ httpClient }: SetupDependencies) { + const creationManagerSetup = this.indexPatternCreationManager.setup(httpClient); + creationManagerSetup.addCreationConfig(IndexPatternCreationConfig); + this.indexPatternListConfig.setup().addListConfig(IndexPatternListConfig); return { - creation, - list, + creation: creationManagerSetup, + list: this.indexPatternListConfig.setup(), + }; + } + + public start() { + return { + creation: this.indexPatternCreationManager.start(), + list: this.indexPatternListConfig.start(), }; } @@ -71,4 +63,5 @@ export class IndexPatternManagementService { } /** @internal */ -export type IndexPatternManagementSetup = ReturnType; +export type IndexPatternManagementServiceSetup = ReturnType; +export type IndexPatternManagementServiceStart = ReturnType; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts b/src/plugins/index_pattern_management/public/service/list/config.ts similarity index 87% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts rename to src/plugins/index_pattern_management/public/service/list/config.ts index dd4d77a68117..87c246e8913e 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts +++ b/src/plugins/index_pattern_management/public/service/list/config.ts @@ -33,9 +33,12 @@ export class IndexPatternListConfig { ? [ { key: 'default', - name: i18n.translate('management.editIndexPattern.list.defaultIndexPatternListName', { - defaultMessage: 'Default', - }), + name: i18n.translate( + 'indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName', + { + defaultMessage: 'Default', + } + ), }, ] : []; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts b/src/plugins/index_pattern_management/public/service/list/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts rename to src/plugins/index_pattern_management/public/service/list/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts b/src/plugins/index_pattern_management/public/service/list/manager.ts similarity index 75% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts rename to src/plugins/index_pattern_management/public/service/list/manager.ts index 73ca33ae914a..3a2910a222cd 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts +++ b/src/plugins/index_pattern_management/public/service/list/manager.ts @@ -27,7 +27,7 @@ export class IndexPatternListManager { this.configs = []; } - public add(Config: typeof IndexPatternListConfig) { + private addListConfig(Config: typeof IndexPatternListConfig) { const config = new Config(); if (this.configs.findIndex(c => c.key === config.key) !== -1) { throw new Error(`${config.key} exists in IndexPatternListManager.`); @@ -35,7 +35,7 @@ export class IndexPatternListManager { this.configs.push(config); } - public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { + private getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { return this.configs.reduce((tags: IndexPatternTag[], config) => { return config.getIndexPatternTags ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) @@ -43,15 +43,25 @@ export class IndexPatternListManager { }, []); } - public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + private getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { return this.configs.reduce((info: string[], config) => { return config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info; }, []); } - public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + private areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { return this.configs.every(config => { return config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true; }); } + + setup = () => ({ + addListConfig: this.addListConfig.bind(this), + }); + + start = () => ({ + getIndexPatternTags: this.getIndexPatternTags.bind(this), + getFieldInfo: this.getFieldInfo.bind(this), + areScriptedFieldsEnabled: this.areScriptedFieldsEnabled.bind(this), + }); } diff --git a/x-pack/legacy/plugins/rollup/kibana.json b/x-pack/legacy/plugins/rollup/kibana.json index 3781d59d8c0f..3df8bd7c187d 100644 --- a/x-pack/legacy/plugins/rollup/kibana.json +++ b/x-pack/legacy/plugins/rollup/kibana.json @@ -4,7 +4,8 @@ "requiredPlugins": [ "home", "index_management", - "metrics" + "metrics", + "indexPatternManagement" ], "optionalPlugins": [ "usageCollection" diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index f0eb21a21944..f4de2a309812 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { RollupPrompt } from './components/rollup_prompt'; -import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; +import { IndexPatternCreationConfig } from '../../../../../../src/plugins/index_pattern_management/public'; const rollupIndexPatternTypeName = i18n.translate( 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index fbf2612b83aa..809a76d1868b 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -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 { IndexPatternListConfig } from '../../../../../../src/legacy/core_plugins/management/public'; +import { IndexPatternListConfig } from '../../../../../../src/plugins/index_pattern_management/public'; function isRollup(indexPattern) { return ( diff --git a/x-pack/legacy/plugins/rollup/public/legacy.ts b/x-pack/legacy/plugins/rollup/public/legacy.ts index ec530e63408f..83945110c2c7 100644 --- a/x-pack/legacy/plugins/rollup/public/legacy.ts +++ b/x-pack/legacy/plugins/rollup/public/legacy.ts @@ -6,14 +6,8 @@ import { npSetup, npStart } from 'ui/new_platform'; import { RollupPlugin } from './plugin'; -import { setup as management } from '../../../../../src/legacy/core_plugins/management/public/legacy'; const plugin = new RollupPlugin(); -export const setup = plugin.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - managementLegacy: management, - }, -}); +export const setup = plugin.setup(npSetup.core, npSetup.plugins); export const start = plugin.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/rollup/public/plugin.ts b/x-pack/legacy/plugins/rollup/public/plugin.ts index c58975419e20..5782e88c3448 100644 --- a/x-pack/legacy/plugins/rollup/public/plugin.ts +++ b/x-pack/legacy/plugins/rollup/public/plugin.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { PluginsStart } from './legacy_imports'; -import { ManagementSetup as ManagementSetupLegacy } from '../../../../../src/legacy/core_plugins/management/public/np_ready'; import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_management'; // @ts-ignore import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config'; @@ -26,6 +25,7 @@ import { import { CRUD_APP_BASE_PATH } from './crud_app/constants'; import { ManagementSetup } from '../../../../../src/plugins/management/public'; import { IndexMgmtSetup } from '../../../../plugins/index_management/public'; +import { IndexPatternManagementSetup } from '../../../../../src/plugins/index_pattern_management/public'; import { search } from '../../../../../src/plugins/data/public'; // @ts-ignore import { setEsBaseAndXPackBase, setHttp } from './crud_app/services'; @@ -33,23 +33,16 @@ import { setNotifications, setFatalErrors } from './kibana_services'; import { renderApp } from './application'; export interface RollupPluginSetupDependencies { - __LEGACY: { - managementLegacy: ManagementSetupLegacy; - }; home?: HomePublicPluginSetup; management: ManagementSetup; indexManagement?: IndexMgmtSetup; + indexPatternManagement: IndexPatternManagementSetup; } export class RollupPlugin implements Plugin { setup( core: CoreSetup, - { - __LEGACY: { managementLegacy }, - home, - management, - indexManagement, - }: RollupPluginSetupDependencies + { home, management, indexManagement, indexPatternManagement }: RollupPluginSetupDependencies ) { setFatalErrors(core.fatalErrors); @@ -61,8 +54,8 @@ export class RollupPlugin implements Plugin { const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS); if (isRollupIndexPatternsEnabled) { - managementLegacy.indexPattern.creation.add(RollupIndexPatternCreationConfig); - managementLegacy.indexPattern.list.add(RollupIndexPatternListConfig); + indexPatternManagement.creation.addCreationConfig(RollupIndexPatternCreationConfig); + indexPatternManagement.list.addListConfig(RollupIndexPatternListConfig); } if (home) { diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index 7809b511adda..99b4e184c071 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -19,11 +18,6 @@ import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { ToastsApi } from 'src/core/public'; -jest.mock('../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ - setup: mockManagementPlugin.createSetupContract(), - start: mockManagementPlugin.createStartContract(), -})); - interface SetupOpts { mockSpaces?: Space[]; returnBeforeSpacesLoad?: boolean; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index 4d92505c4aeb..fee41fc7e36d 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -25,7 +25,7 @@ import { ToastsStart } from 'src/core/public'; import { ProcessedImportResponse, processImportResponse, -} from '../../../../../../src/legacy/core_plugins/management/public'; +} from '../../../../../../src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../spaces_manager'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index b22cec0af5ea..4f6ff55dbfbb 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; import { ImportRetry } from '../types'; interface Props { diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index 96cbac4b4806..ea74fc92b95e 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,7 +13,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { CopyOptions, ImportRetry } from '../types'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index fb2616619c64..65a0cabfeb71 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 96e642b0f45d..44c9e9993bf1 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ProcessedImportResponse } from 'src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from 'src/plugins/saved_objects_management/public'; export interface SummarizedSavedObjectResult { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d07026c0883b..00ac5b77d00f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2523,12 +2523,10 @@ "management.breadcrumb": "管理", "management.connectDataDisplayName": "データに接続", "management.displayName": "管理", - "management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", - "management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", - "management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", - "management.editIndexPattern.list.defaultIndexPatternListName": "デフォルト", - "management.indexPatternHeader": "インデックスパターン", - "management.indexPatternLabel": "Elasticsearch からのデータの取得に役立つインデックスパターンを管理します。", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", + "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", + "indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName": "デフォルト", "management.nav.label": "管理", "management.nav.menu": "管理メニュー", "management.stackManagement.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7e64ff6301fa..f6d84431bef7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2524,12 +2524,10 @@ "management.breadcrumb": "管理", "management.connectDataDisplayName": "连接数据", "management.displayName": "管理", - "management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", - "management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", - "management.editIndexPattern.createIndex.defaultTypeName": "索引模式", - "management.editIndexPattern.list.defaultIndexPatternListName": "默认值", - "management.indexPatternHeader": "索引模式", - "management.indexPatternLabel": "管理帮助从 Elasticsearch 检索数据的索引模式。", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", + "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "索引模式", + "indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName": "默认值", "management.nav.label": "管理", "management.nav.menu": "管理菜单", "management.stackManagement.managementDescription": "您用于管理 Elastic Stack 的中心控制台。", From fdb4a37a6016c8afce52203ecccbe03e6fc4064e Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Wed, 8 Apr 2020 20:33:21 +0000 Subject: [PATCH 09/46] restore empty_kibana after saved objects test (#62951) --- test/functional/apps/management/_mgmt_import_saved_objects.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.js index 53b7e7062ee2..2f9d9f9bfb17 100644 --- a/test/functional/apps/management/_mgmt_import_saved_objects.js +++ b/test/functional/apps/management/_mgmt_import_saved_objects.js @@ -35,6 +35,7 @@ export default function({ getService, getPageObjects }) { afterEach(async function() { await esArchiver.unload('discover'); + await esArchiver.load('empty_kibana'); }); it('should import saved objects mgmt', async function() { From 86a25876601053604d08ad4884db796066b819b2 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 8 Apr 2020 13:33:51 -0700 Subject: [PATCH 10/46] [Ingest] Data source configuration validation UI (#61180) * Initial pass at datasource configuration validation * Show error icon and red text at input and stream levels * Add tests, fix bugs in validation method * Fix typings --- .../components/datasource_input_config.tsx | 63 ++- .../components/datasource_input_panel.tsx | 50 +- .../datasource_input_stream_config.tsx | 55 +- .../components/datasource_input_var_field.tsx | 28 +- .../components/index.ts | 1 + .../create_datasource_page/index.tsx | 16 +- .../create_datasource_page/services/index.ts | 7 + .../services/validate_datasource.test.ts | 504 ++++++++++++++++++ .../services/validate_datasource.ts | 232 ++++++++ .../step_configure_datasource.tsx | 169 ++++-- .../ingest_manager/services/index.ts | 2 + .../ingest_manager/types/index.ts | 3 + 12 files changed, 1038 insertions(+), 92 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts create mode 100644 x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx index 356739af1ff9..0e8763cb2d4c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx @@ -6,26 +6,38 @@ import React, { useState, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiText, + EuiTextColor, EuiSpacer, EuiButtonEmpty, EuiTitle, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInput, RegistryVarsEntry } from '../../../../types'; -import { isAdvancedVar } from '../services'; +import { isAdvancedVar, DatasourceConfigValidationResults, validationHasErrors } from '../services'; import { DatasourceInputVarField } from './datasource_input_var_field'; export const DatasourceInputConfig: React.FunctionComponent<{ packageInputVars?: RegistryVarsEntry[]; datasourceInput: DatasourceInput; updateDatasourceInput: (updatedInput: Partial) => void; -}> = ({ packageInputVars, datasourceInput, updateDatasourceInput }) => { + inputVarsValidationResults: DatasourceConfigValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInputVars, + datasourceInput, + updateDatasourceInput, + inputVarsValidationResults, + forceShowErrors, +}) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputVarsValidationResults); + const requiredVars: RegistryVarsEntry[] = []; const advancedVars: RegistryVarsEntry[] = []; @@ -40,15 +52,36 @@ export const DatasourceInputConfig: React.FunctionComponent<{ } return ( - - + + -

- -

+ + +

+ + + +

+
+ {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} +
@@ -60,7 +93,7 @@ export const DatasourceInputConfig: React.FunctionComponent<{

- + {requiredVars.map(varDef => { const { name: varName, type: varType } = varDef; @@ -81,6 +114,8 @@ export const DatasourceInputConfig: React.FunctionComponent<{ }, }); }} + errors={inputVarsValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -123,6 +158,8 @@ export const DatasourceInputConfig: React.FunctionComponent<{ }, }); }} + errors={inputVarsValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -132,6 +169,6 @@ export const DatasourceInputConfig: React.FunctionComponent<{ ) : null}
-
+ ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx index 74b08f48df12..6b0c68ccb7d3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx @@ -17,8 +17,10 @@ import { EuiButtonIcon, EuiHorizontalRule, EuiSpacer, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInput, DatasourceInputStream, RegistryInput } from '../../../../types'; +import { DatasourceInputValidationResults, validationHasErrors } from '../services'; import { DatasourceInputConfig } from './datasource_input_config'; import { DatasourceInputStreamConfig } from './datasource_input_stream_config'; @@ -32,10 +34,21 @@ export const DatasourceInputPanel: React.FunctionComponent<{ packageInput: RegistryInput; datasourceInput: DatasourceInput; updateDatasourceInput: (updatedInput: Partial) => void; -}> = ({ packageInput, datasourceInput, updateDatasourceInput }) => { + inputValidationResults: DatasourceInputValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInput, + datasourceInput, + updateDatasourceInput, + inputValidationResults, + forceShowErrors, +}) => { // Showing streams toggle state const [isShowingStreams, setIsShowingStreams] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputValidationResults); + return ( {/* Header / input-level toggle */} @@ -43,9 +56,32 @@ export const DatasourceInputPanel: React.FunctionComponent<{ -

{packageInput.title || packageInput.type}

- + + + +

+ + {packageInput.title || packageInput.type} + +

+
+
+ {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} +
} checked={datasourceInput.enabled} onChange={e => { @@ -122,6 +158,8 @@ export const DatasourceInputPanel: React.FunctionComponent<{ packageInputVars={packageInput.vars} datasourceInput={datasourceInput} updateDatasourceInput={updateDatasourceInput} + inputVarsValidationResults={{ config: inputValidationResults.config }} + forceShowErrors={forceShowErrors} /> @@ -165,6 +203,10 @@ export const DatasourceInputPanel: React.FunctionComponent<{ updateDatasourceInput(updatedInput); }} + inputStreamValidationResults={ + inputValidationResults.streams![datasourceInputStream.id] + } + forceShowErrors={forceShowErrors} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx index 3bf5b2bb4c0f..43e8f5a2c060 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx @@ -7,26 +7,38 @@ import React, { useState, Fragment } from 'react'; import ReactMarkdown from 'react-markdown'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiText, EuiSpacer, EuiButtonEmpty, + EuiTextColor, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; -import { isAdvancedVar } from '../services'; +import { isAdvancedVar, DatasourceConfigValidationResults, validationHasErrors } from '../services'; import { DatasourceInputVarField } from './datasource_input_var_field'; export const DatasourceInputStreamConfig: React.FunctionComponent<{ packageInputStream: RegistryStream; datasourceInputStream: DatasourceInputStream; updateDatasourceInputStream: (updatedStream: Partial) => void; -}> = ({ packageInputStream, datasourceInputStream, updateDatasourceInputStream }) => { + inputStreamValidationResults: DatasourceConfigValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInputStream, + datasourceInputStream, + updateDatasourceInputStream, + inputStreamValidationResults, + forceShowErrors, +}) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputStreamValidationResults); + const requiredVars: RegistryVarsEntry[] = []; const advancedVars: RegistryVarsEntry[] = []; @@ -41,10 +53,33 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ } return ( - - + + + + + {packageInputStream.title || packageInputStream.dataset} + + + {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} + + } checked={datasourceInputStream.enabled} onChange={e => { const enabled = e.target.checked; @@ -62,7 +97,7 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ ) : null} - + {requiredVars.map(varDef => { const { name: varName, type: varType } = varDef; @@ -83,6 +118,8 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ }, }); }} + errors={inputStreamValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -125,6 +162,8 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ }, }); }} + errors={inputStreamValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -134,6 +173,6 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ ) : null}
- + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx index bcb99eed88ac..846a807f9240 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx @@ -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 React from 'react'; +import React, { useState } from 'react'; import ReactMarkdown from 'react-markdown'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiFieldText, EuiComboBox, EuiText, EuiCodeEditor } from '@elastic/eui'; @@ -16,12 +16,20 @@ export const DatasourceInputVarField: React.FunctionComponent<{ varDef: RegistryVarsEntry; value: any; onChange: (newValue: any) => void; -}> = ({ varDef, value, onChange }) => { + errors?: string[] | null; + forceShowErrors?: boolean; +}> = ({ varDef, value, onChange, errors: varErrors, forceShowErrors }) => { + const [isDirty, setIsDirty] = useState(false); + const { multi, required, type, title, name, description } = varDef; + const isInvalid = (isDirty || forceShowErrors) && !!varErrors; + const errors = isInvalid ? varErrors : null; + const renderField = () => { - if (varDef.multi) { + if (multi) { return ( ({ label: val }))} onCreateOption={(newVal: any) => { onChange([...value, newVal]); @@ -29,10 +37,11 @@ export const DatasourceInputVarField: React.FunctionComponent<{ onChange={(newVals: any[]) => { onChange(newVals.map(val => val.label)); }} + onBlur={() => setIsDirty(true)} /> ); } - if (varDef.type === 'yaml') { + if (type === 'yaml') { return ( onChange(newVal)} + onBlur={() => setIsDirty(true)} /> ); } return ( onChange(e.target.value)} + onBlur={() => setIsDirty(true)} /> ); }; return ( ) : null } - helpText={} + helpText={} > {renderField()} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts index e5f18e1449d1..3bfca7566891 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts @@ -5,3 +5,4 @@ */ export { CreateDatasourcePageLayout } from './layout'; export { DatasourceInputPanel } from './datasource_input_panel'; +export { DatasourceInputVarField } from './datasource_input_var_field'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx index 23d0f3317a66..7815ab9cd1d6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx @@ -21,6 +21,7 @@ import { useLinks as useEPMLinks } from '../../epm/hooks'; import { CreateDatasourcePageLayout } from './components'; import { CreateDatasourceFrom, CreateDatasourceStep } from './types'; import { CREATE_DATASOURCE_STEP_PATHS } from './constants'; +import { DatasourceValidationResults, validateDatasource } from './services'; import { StepSelectPackage } from './step_select_package'; import { StepSelectConfig } from './step_select_config'; import { StepConfigureDatasource } from './step_configure_datasource'; @@ -51,6 +52,9 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { inputs: [], }); + // Datasource validation state + const [validationResults, setValidationResults] = useState(); + // Update package info method const updatePackageInfo = (updatedPackageInfo: PackageInfo | undefined) => { if (updatedPackageInfo) { @@ -84,9 +88,18 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { ...updatedFields, }; setDatasource(newDatasource); - // eslint-disable-next-line no-console console.debug('Datasource updated', newDatasource); + updateDatasourceValidation(newDatasource); + }; + + const updateDatasourceValidation = (newDatasource?: NewDatasource) => { + if (packageInfo) { + const newValidationResult = validateDatasource(newDatasource || datasource, packageInfo); + setValidationResults(newValidationResult); + // eslint-disable-next-line no-console + console.debug('Datasource validation results', newValidationResult); + } }; // Cancel url @@ -202,6 +215,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { packageInfo={packageInfo} datasource={datasource} updateDatasource={updateDatasource} + validationResults={validationResults!} backLink={ {from === 'config' ? ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts index 44e5bfa41cb9..d99f0712db3c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts @@ -4,3 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ export { isAdvancedVar } from './is_advanced_var'; +export { + DatasourceValidationResults, + DatasourceConfigValidationResults, + DatasourceInputValidationResults, + validateDatasource, + validationHasErrors, +} from './validate_datasource'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts new file mode 100644 index 000000000000..a45fabeb5ed6 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts @@ -0,0 +1,504 @@ +/* + * 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 { + PackageInfo, + InstallationStatus, + NewDatasource, + RegistryDatasource, +} from '../../../../types'; +import { validateDatasource, validationHasErrors } from './validate_datasource'; + +describe('Ingest Manager - validateDatasource()', () => { + const mockPackage = ({ + name: 'mock-package', + title: 'Mock package', + version: '0.0.0', + description: 'description', + type: 'mock', + categories: [], + requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } }, + format_version: '', + download: '', + path: '', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + 'index-pattern': [], + }, + }, + status: InstallationStatus.notInstalled, + datasources: [ + { + name: 'datasource1', + title: 'Datasource 1', + description: 'test datasource', + inputs: [ + { + type: 'foo', + title: 'Foo', + vars: [ + { default: 'foo-input-var-value', name: 'foo-input-var-name', type: 'text' }, + { + default: 'foo-input2-var-value', + name: 'foo-input2-var-name', + required: true, + type: 'text', + }, + { name: 'foo-input3-var-name', type: 'text', required: true, multi: true }, + ], + streams: [ + { + dataset: 'foo', + input: 'foo', + title: 'Foo', + vars: [{ name: 'var-name', type: 'yaml' }], + }, + ], + }, + { + type: 'bar', + title: 'Bar', + vars: [ + { + default: ['value1', 'value2'], + name: 'bar-input-var-name', + type: 'text', + multi: true, + }, + { name: 'bar-input2-var-name', required: true, type: 'text' }, + ], + streams: [ + { + dataset: 'bar', + input: 'bar', + title: 'Bar', + vars: [{ name: 'var-name', type: 'yaml', required: true }], + }, + { + dataset: 'bar2', + input: 'bar2', + title: 'Bar 2', + vars: [{ default: 'bar2-var-value', name: 'var-name', type: 'text' }], + }, + ], + }, + { + type: 'with-no-config-or-streams', + title: 'With no config or streams', + streams: [], + }, + { + type: 'with-disabled-streams', + title: 'With disabled streams', + streams: [ + { + dataset: 'disabled', + input: 'disabled', + title: 'Disabled', + enabled: false, + vars: [{ multi: true, required: true, name: 'var-name', type: 'text' }], + }, + { dataset: 'disabled2', input: 'disabled2', title: 'Disabled 2', enabled: false }, + ], + }, + ], + }, + ], + } as unknown) as PackageInfo; + + const validDatasource: NewDatasource = { + name: 'datasource1-1', + config_id: 'test-config', + enabled: true, + output_id: 'test-output', + inputs: [ + { + type: 'foo', + enabled: true, + config: { + 'foo-input-var-name': { value: 'foo-input-var-value', type: 'text' }, + 'foo-input2-var-name': { value: 'foo-input2-var-value', type: 'text' }, + 'foo-input3-var-name': { value: ['test'], type: 'text' }, + }, + streams: [ + { + id: 'foo-foo', + dataset: 'foo', + enabled: true, + config: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + enabled: true, + config: { + 'bar-input-var-name': { value: ['value1', 'value2'], type: 'text' }, + 'bar-input2-var-name': { value: 'test', type: 'text' }, + }, + streams: [ + { + id: 'bar-bar', + dataset: 'bar', + enabled: true, + config: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + { + id: 'bar-bar2', + dataset: 'bar2', + enabled: true, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + enabled: true, + streams: [ + { + id: 'with-disabled-streams-disabled', + dataset: 'disabled', + enabled: false, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + { + id: 'with-disabled-streams-disabled2', + dataset: 'disabled2', + enabled: false, + }, + ], + }, + ], + }; + + const invalidDatasource: NewDatasource = { + ...validDatasource, + name: '', + inputs: [ + { + type: 'foo', + enabled: true, + config: { + 'foo-input-var-name': { value: undefined, type: 'text' }, + 'foo-input2-var-name': { value: '', type: 'text' }, + 'foo-input3-var-name': { value: [], type: 'text' }, + }, + streams: [ + { + id: 'foo-foo', + dataset: 'foo', + enabled: true, + config: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + enabled: true, + config: { + 'bar-input-var-name': { value: 'invalid value for multi', type: 'text' }, + 'bar-input2-var-name': { value: undefined, type: 'text' }, + }, + streams: [ + { + id: 'bar-bar', + dataset: 'bar', + enabled: true, + config: { 'var-name': { value: ' \n\n', type: 'yaml' } }, + }, + { + id: 'bar-bar2', + dataset: 'bar2', + enabled: true, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + enabled: true, + streams: [ + { + id: 'with-disabled-streams-disabled', + dataset: 'disabled', + enabled: false, + config: { + 'var-name': { + value: 'invalid value but not checked due to not enabled', + type: 'text', + }, + }, + }, + { + id: 'with-disabled-streams-disabled2', + dataset: 'disabled2', + enabled: false, + }, + ], + }, + ], + }; + + const noErrorsValidationResults = { + name: null, + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': null, + 'foo-input3-var-name': null, + }, + streams: { 'foo-foo': { config: { 'var-name': null } } }, + }, + bar: { + config: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, + streams: { + 'bar-bar': { config: { 'var-name': null } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }; + + it('returns no errors for valid datasource configuration', () => { + expect(validateDatasource(validDatasource, mockPackage)).toEqual(noErrorsValidationResults); + }); + + it('returns errors for invalid datasource configuration', () => { + expect(validateDatasource(invalidDatasource, mockPackage)).toEqual({ + name: ['Name is required'], + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { 'foo-foo': { config: { 'var-name': ['Invalid YAML format'] } } }, + }, + bar: { + config: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + 'bar-bar': { config: { 'var-name': ['var-name is required'] } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }); + }); + + it('returns no errors for disabled inputs', () => { + const disabledInputs = invalidDatasource.inputs.map(input => ({ ...input, enabled: false })); + expect(validateDatasource({ ...validDatasource, inputs: disabledInputs }, mockPackage)).toEqual( + noErrorsValidationResults + ); + }); + + it('returns only datasource and input-level errors for disabled streams', () => { + const inputsWithDisabledStreams = invalidDatasource.inputs.map(input => + input.streams + ? { + ...input, + streams: input.streams.map(stream => ({ ...stream, enabled: false })), + } + : input + ); + expect( + validateDatasource({ ...invalidDatasource, inputs: inputsWithDisabledStreams }, mockPackage) + ).toEqual({ + name: ['Name is required'], + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { 'foo-foo': { config: { 'var-name': null } } }, + }, + bar: { + config: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + 'bar-bar': { config: { 'var-name': null } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }); + }); + + it('returns no errors for packages with no datasources', () => { + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: undefined, + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + }); + + it('returns no errors for packages with no inputs', () => { + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [{} as RegistryDatasource], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [({ inputs: [] } as unknown) as RegistryDatasource], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + }); +}); + +describe('Ingest Manager - validationHasErrors()', () => { + it('returns true for stream validation results with errors', () => { + expect( + validationHasErrors({ + config: { foo: ['foo error'], bar: null }, + }) + ).toBe(true); + }); + + it('returns false for stream validation results with no errors', () => { + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + }) + ).toBe(false); + }); + + it('returns true for input validation results with errors', () => { + expect( + validationHasErrors({ + config: { foo: ['foo error'], bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }) + ).toBe(true); + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: ['foo error'], bar: null } } }, + }) + ).toBe(true); + }); + + it('returns false for input validation results with no errors', () => { + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }) + ).toBe(false); + }); + + it('returns true for datasource validation results with errors', () => { + expect( + validationHasErrors({ + name: ['name error'], + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: ['foo error'], bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: ['foo error'], bar: null } } }, + }, + }, + }) + ).toBe(true); + }); + + it('returns false for datasource validation results with no errors', () => { + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(false); + }); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts new file mode 100644 index 000000000000..518e2bfc1af0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts @@ -0,0 +1,232 @@ +/* + * 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'; +import { safeLoad } from 'js-yaml'; +import { getFlattenedObject } from '../../../../services'; +import { + NewDatasource, + DatasourceInput, + DatasourceInputStream, + DatasourceConfigRecordEntry, + PackageInfo, + RegistryInput, + RegistryVarsEntry, +} from '../../../../types'; + +type Errors = string[] | null; + +type ValidationEntry = Record; + +export interface DatasourceConfigValidationResults { + config?: ValidationEntry; +} + +export type DatasourceInputValidationResults = DatasourceConfigValidationResults & { + streams?: Record; +}; + +export interface DatasourceValidationResults { + name: Errors; + description: Errors; + inputs: Record | null; +} + +/* + * Returns validation information for a given datasource configuration and package info + * Note: this method assumes that `datasource` is correctly structured for the given package + */ +export const validateDatasource = ( + datasource: NewDatasource, + packageInfo: PackageInfo +): DatasourceValidationResults => { + const validationResults: DatasourceValidationResults = { + name: null, + description: null, + inputs: {}, + }; + + if (!datasource.name.trim()) { + validationResults.name = [ + i18n.translate('xpack.ingestManager.datasourceValidation.nameRequiredErrorMessage', { + defaultMessage: 'Name is required', + }), + ]; + } + + if ( + !packageInfo.datasources || + packageInfo.datasources.length === 0 || + !packageInfo.datasources[0] || + !packageInfo.datasources[0].inputs || + packageInfo.datasources[0].inputs.length === 0 + ) { + validationResults.inputs = null; + return validationResults; + } + + const registryInputsByType: Record< + string, + RegistryInput + > = packageInfo.datasources[0].inputs.reduce((inputs, registryInput) => { + inputs[registryInput.type] = registryInput; + return inputs; + }, {} as Record); + + // Validate each datasource input with either its own config fields or streams + datasource.inputs.forEach(input => { + if (!input.config && !input.streams) { + return; + } + + const inputValidationResults: DatasourceInputValidationResults = { + config: undefined, + streams: {}, + }; + + const inputVarsByName = (registryInputsByType[input.type].vars || []).reduce( + (vars, registryVar) => { + vars[registryVar.name] = registryVar; + return vars; + }, + {} as Record + ); + + // Validate input-level config fields + const inputConfigs = Object.entries(input.config || {}); + if (inputConfigs.length) { + inputValidationResults.config = inputConfigs.reduce((results, [name, configEntry]) => { + results[name] = input.enabled + ? validateDatasourceConfig(configEntry, inputVarsByName[name]) + : null; + return results; + }, {} as ValidationEntry); + } else { + delete inputValidationResults.config; + } + + // Validate each input stream with config fields + if (input.streams.length) { + input.streams.forEach(stream => { + if (!stream.config) { + return; + } + + const streamValidationResults: DatasourceConfigValidationResults = { + config: undefined, + }; + + const streamVarsByName = ( + ( + registryInputsByType[input.type].streams.find( + registryStream => registryStream.dataset === stream.dataset + ) || {} + ).vars || [] + ).reduce((vars, registryVar) => { + vars[registryVar.name] = registryVar; + return vars; + }, {} as Record); + + // Validate stream-level config fields + streamValidationResults.config = Object.entries(stream.config).reduce( + (results, [name, configEntry]) => { + results[name] = + input.enabled && stream.enabled + ? validateDatasourceConfig(configEntry, streamVarsByName[name]) + : null; + return results; + }, + {} as ValidationEntry + ); + + inputValidationResults.streams![stream.id] = streamValidationResults; + }); + } else { + delete inputValidationResults.streams; + } + + if (inputValidationResults.config || inputValidationResults.streams) { + validationResults.inputs![input.type] = inputValidationResults; + } + }); + + if (Object.entries(validationResults.inputs!).length === 0) { + validationResults.inputs = null; + } + return validationResults; +}; + +const validateDatasourceConfig = ( + configEntry: DatasourceConfigRecordEntry, + varDef: RegistryVarsEntry +): string[] | null => { + const errors = []; + const { value } = configEntry; + let parsedValue: any = value; + + if (typeof value === 'string') { + parsedValue = value.trim(); + } + + if (varDef.required) { + if (parsedValue === undefined || (typeof parsedValue === 'string' && !parsedValue)) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.requiredErrorMessage', { + defaultMessage: '{fieldName} is required', + values: { + fieldName: varDef.title || varDef.name, + }, + }) + ); + } + } + + if (varDef.type === 'yaml') { + try { + parsedValue = safeLoad(value); + } catch (e) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.invalidYamlFormatErrorMessage', { + defaultMessage: 'Invalid YAML format', + }) + ); + } + } + + if (varDef.multi) { + if (parsedValue && !Array.isArray(parsedValue)) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.invalidArrayErrorMessage', { + defaultMessage: 'Invalid format', + }) + ); + } + if ( + varDef.required && + (!parsedValue || (Array.isArray(parsedValue) && parsedValue.length === 0)) + ) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.requiredErrorMessage', { + defaultMessage: '{fieldName} is required', + values: { + fieldName: varDef.title || varDef.name, + }, + }) + ); + } + } + + return errors.length ? errors : null; +}; + +export const validationHasErrors = ( + validationResults: + | DatasourceValidationResults + | DatasourceInputValidationResults + | DatasourceConfigValidationResults +) => { + const flattenedValidation = getFlattenedObject(validationResults); + return !!Object.entries(flattenedValidation).find(([, value]) => !!value); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx index b45beef4a8b5..105d6c66a570 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx @@ -9,17 +9,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSteps, EuiPanel, - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiFieldText, EuiButtonEmpty, EuiSpacer, EuiEmptyPrompt, EuiText, EuiButton, EuiComboBox, + EuiCallOut, } from '@elastic/eui'; import { AgentConfig, @@ -28,21 +27,37 @@ import { NewDatasource, DatasourceInput, } from '../../../types'; +import { Loading } from '../../../components'; import { packageToConfigDatasourceInputs } from '../../../services'; -import { DatasourceInputPanel } from './components'; +import { DatasourceValidationResults, validationHasErrors } from './services'; +import { DatasourceInputPanel, DatasourceInputVarField } from './components'; export const StepConfigureDatasource: React.FunctionComponent<{ agentConfig: AgentConfig; packageInfo: PackageInfo; datasource: NewDatasource; updateDatasource: (fields: Partial) => void; + validationResults: DatasourceValidationResults; backLink: JSX.Element; cancelUrl: string; onNext: () => void; -}> = ({ agentConfig, packageInfo, datasource, updateDatasource, backLink, cancelUrl, onNext }) => { +}> = ({ + agentConfig, + packageInfo, + datasource, + updateDatasource, + validationResults, + backLink, + cancelUrl, + onNext, +}) => { // Form show/hide states const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState(false); + // Form submit state + const [submitAttempted, setSubmitAttempted] = useState(false); + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + // Update datasource's package and config info useEffect(() => { const dsPackage = datasource.package; @@ -81,56 +96,56 @@ export const StepConfigureDatasource: React.FunctionComponent<{ }, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]); // Step A, define datasource - const DefineDatasource = ( + const renderDefineDatasource = () => ( - - - - } - > - - updateDatasource({ - name: e.target.value, - }) - } - /> - + + + { + updateDatasource({ + name: newValue, + }); + }} + errors={validationResults!.name} + forceShowErrors={submitAttempted} + /> - - - } - labelAppend={ - - - - } - > - - updateDatasource({ - description: e.target.value, - }) - } - /> - + + { + updateDatasource({ + description: newValue, + }); + }} + errors={validationResults!.description} + forceShowErrors={submitAttempted} + /> - + - - + + - + + ) : null} @@ -182,7 +198,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{ // Step B, configure inputs (and their streams) // Assume packages only export one datasource for now - const ConfigureInputs = + const renderConfigureInputs = () => packageInfo.datasources && packageInfo.datasources[0] && packageInfo.datasources[0].inputs && @@ -208,6 +224,8 @@ export const StepConfigureDatasource: React.FunctionComponent<{ inputs: newInputs, }); }} + inputValidationResults={validationResults!.inputs![datasourceInput.type]} + forceShowErrors={submitAttempted} /> ) : null; @@ -232,7 +250,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{
); - return ( + return validationResults ? ( @@ -251,7 +269,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{ defaultMessage: 'Define your datasource', } ), - children: DefineDatasource, + children: renderDefineDatasource(), }, { title: i18n.translate( @@ -260,13 +278,34 @@ export const StepConfigureDatasource: React.FunctionComponent<{ defaultMessage: 'Choose the data you want to collect', } ), - children: ConfigureInputs, + children: renderConfigureInputs(), }, ]} /> + {hasErrors && submitAttempted ? ( + + +

+ +

+
+ +
+ ) : null} @@ -278,7 +317,17 @@ export const StepConfigureDatasource: React.FunctionComponent<{ - onNext()}> + { + setSubmitAttempted(true); + if (!hasErrors) { + onNext(); + } + }} + > + ) : ( + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 0aa08602e4d4..5ebd1300baf6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export { getFlattenedObject } from '../../../../../../../src/core/utils'; + export { agentConfigRouteService, datasourceRouteService, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 333a9b049fa8..32615278b67d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -16,6 +16,7 @@ export { NewDatasource, DatasourceInput, DatasourceInputStream, + DatasourceConfigRecordEntry, // API schemas - Agent Config GetAgentConfigsResponse, GetAgentConfigsResponseItem, @@ -56,6 +57,7 @@ export { RegistryVarsEntry, RegistryInput, RegistryStream, + RegistryDatasource, PackageList, PackageListItem, PackagesGroupedByStatus, @@ -70,4 +72,5 @@ export { DeletePackageResponse, DetailViewPanelName, InstallStatus, + InstallationStatus, } from '../../../../common'; From 578e443bdd79ea6436289ee39703816fe7419ebd Mon Sep 17 00:00:00 2001 From: Dmitry Lemeshko Date: Thu, 9 Apr 2020 00:08:21 +0300 Subject: [PATCH 11/46] FTR: add chromium-based Edge browser support (#61684) * bump dependency, add edge support in ftr services * add config files * fix browser version for msedge * use npm ms-chromium-edge-driver * download edge driver aside from session creation * move dependency to dev * update dist/index file * bump edge-driver version * change type to msedge to match w3c spec * fix discover tests for Edge * Revert "fix discover tests for Edge" This reverts commit 87e7fdd256433ef1ad392147d6bd63b925552b37. * bump driver version up Co-authored-by: Elastic Machine --- package.json | 5 +- packages/kbn-pm/dist/index.js | 1105 +++-------------- .../lib/config/schema.ts | 2 +- test/functional/config.edge.js | 34 + test/functional/services/browser.ts | 4 +- .../web_element_wrapper.ts | 9 +- test/functional/services/remote/browsers.ts | 1 + test/functional/services/remote/remote.ts | 17 +- test/functional/services/remote/webdriver.ts | 50 +- x-pack/test/functional/config.edge.js | 21 + yarn.lock | 320 ++++- 11 files changed, 556 insertions(+), 1012 deletions(-) create mode 100644 test/functional/config.edge.js create mode 100644 x-pack/test/functional/config.edge.js diff --git a/package.json b/package.json index 4c5db5321c28..bd0fec3a5c11 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,7 @@ "@types/recompose": "^0.30.6", "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", - "@types/selenium-webdriver": "^4.0.5", + "@types/selenium-webdriver": "4.0.9", "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", @@ -462,6 +462,7 @@ "load-grunt-config": "^3.0.1", "mocha": "^7.1.1", "mock-http-server": "1.3.0", + "ms-chromium-edge-driver": "^0.2.0", "multistream": "^2.1.1", "murmurhash3js": "3.0.1", "mutation-observer": "^1.0.3", @@ -480,7 +481,7 @@ "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", - "selenium-webdriver": "^4.0.0-alpha.5", + "selenium-webdriver": "^4.0.0-alpha.7", "simple-git": "1.116.0", "simplebar-react": "^2.1.0", "sinon": "^7.4.2", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 0cc1ad632667..7a858deff41d 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -79260,7 +79260,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(705); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(928); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(923); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79445,9 +79445,9 @@ const pAll = __webpack_require__(707); const arrify = __webpack_require__(709); const globby = __webpack_require__(710); const isGlob = __webpack_require__(604); -const cpFile = __webpack_require__(913); -const junk = __webpack_require__(925); -const CpyError = __webpack_require__(926); +const cpFile = __webpack_require__(908); +const junk = __webpack_require__(920); +const CpyError = __webpack_require__(921); const defaultOptions = { ignoreJunk: true @@ -79697,8 +79697,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(711); const glob = __webpack_require__(713); const fastGlob = __webpack_require__(718); -const dirGlob = __webpack_require__(906); -const gitignore = __webpack_require__(909); +const dirGlob = __webpack_require__(901); +const gitignore = __webpack_require__(904); const DEFAULT_FILTER = () => false; @@ -81531,11 +81531,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(720); var taskManager = __webpack_require__(721); -var reader_async_1 = __webpack_require__(877); -var reader_stream_1 = __webpack_require__(901); -var reader_sync_1 = __webpack_require__(902); -var arrayUtils = __webpack_require__(904); -var streamUtils = __webpack_require__(905); +var reader_async_1 = __webpack_require__(872); +var reader_stream_1 = __webpack_require__(896); +var reader_sync_1 = __webpack_require__(897); +var arrayUtils = __webpack_require__(899); +var streamUtils = __webpack_require__(900); /** * Synchronous API. */ @@ -82175,9 +82175,9 @@ var extend = __webpack_require__(838); */ var compilers = __webpack_require__(841); -var parsers = __webpack_require__(873); -var cache = __webpack_require__(874); -var utils = __webpack_require__(875); +var parsers = __webpack_require__(868); +var cache = __webpack_require__(869); +var utils = __webpack_require__(870); var MAX_LENGTH = 1024 * 64; /** @@ -100710,9 +100710,9 @@ var toRegex = __webpack_require__(729); */ var compilers = __webpack_require__(858); -var parsers = __webpack_require__(869); -var Extglob = __webpack_require__(872); -var utils = __webpack_require__(871); +var parsers = __webpack_require__(864); +var Extglob = __webpack_require__(867); +var utils = __webpack_require__(866); var MAX_LENGTH = 1024 * 64; /** @@ -101222,7 +101222,7 @@ var parsers = __webpack_require__(862); * Module dependencies */ -var debug = __webpack_require__(864)('expand-brackets'); +var debug = __webpack_require__(801)('expand-brackets'); var extend = __webpack_require__(738); var Snapdragon = __webpack_require__(768); var toRegex = __webpack_require__(729); @@ -101816,839 +101816,12 @@ exports.createRegex = function(pattern, include) { /* 864 */ /***/ (function(module, exports, __webpack_require__) { -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(865); -} else { - module.exports = __webpack_require__(868); -} - - -/***/ }), -/* 865 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(866); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return; - - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} - - -/***/ }), -/* 866 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(867); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - -exports.formatters = {}; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ - -function selectColor(namespace) { - var hash = 0, i; - - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return exports.colors[Math.abs(hash) % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function createDebug(namespace) { - - function debug() { - // disabled? - if (!debug.enabled) return; - - var self = debug; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); - - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } - - return debug; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - exports.names = []; - exports.skips = []; - - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - - -/***/ }), -/* 867 */ -/***/ (function(module, exports) { - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; - } - if (ms >= m) { - return Math.round(ms / m) + 'm'; - } - if (ms >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; - } - return Math.ceil(ms / n) + ' ' + name + 's'; -} - - -/***/ }), -/* 868 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Module dependencies. - */ - -var tty = __webpack_require__(478); -var util = __webpack_require__(29); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(866); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); - - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); - - obj[prop] = val; - return obj; -}, {}); - -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ - -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} - -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; - -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ - -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; - - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} - -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. - */ - -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ - -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); - - // Note stream._type is used for test-module-load-list.js - - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - case 'FILE': - var fs = __webpack_require__(23); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - var net = __webpack_require__(806); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); - - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; - - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init (debug) { - debug.inspectOpts = {}; - - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); - - -/***/ }), -/* 869 */ -/***/ (function(module, exports, __webpack_require__) { - "use strict"; var brackets = __webpack_require__(859); -var define = __webpack_require__(870); -var utils = __webpack_require__(871); +var define = __webpack_require__(865); +var utils = __webpack_require__(866); /** * Characters to use in text regex (we want to "not" match @@ -102803,7 +101976,7 @@ module.exports = parsers; /***/ }), -/* 870 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102841,7 +102014,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 871 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102917,7 +102090,7 @@ utils.createRegex = function(str) { /***/ }), -/* 872 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102928,7 +102101,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(768); -var define = __webpack_require__(870); +var define = __webpack_require__(865); var extend = __webpack_require__(738); /** @@ -102936,7 +102109,7 @@ var extend = __webpack_require__(738); */ var compilers = __webpack_require__(858); -var parsers = __webpack_require__(869); +var parsers = __webpack_require__(864); /** * Customize Snapdragon parser and renderer @@ -103002,7 +102175,7 @@ module.exports = Extglob; /***/ }), -/* 873 */ +/* 868 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103092,14 +102265,14 @@ function textRegex(pattern) { /***/ }), -/* 874 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(850))(); /***/ }), -/* 875 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103117,7 +102290,7 @@ utils.define = __webpack_require__(837); utils.diff = __webpack_require__(854); utils.extend = __webpack_require__(838); utils.pick = __webpack_require__(855); -utils.typeOf = __webpack_require__(876); +utils.typeOf = __webpack_require__(871); utils.unique = __webpack_require__(741); /** @@ -103415,7 +102588,7 @@ utils.unixify = function(options) { /***/ }), -/* 876 */ +/* 871 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -103550,7 +102723,7 @@ function isBuffer(val) { /***/ }), -/* 877 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103569,9 +102742,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_stream_1 = __webpack_require__(895); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_stream_1 = __webpack_require__(890); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -103632,15 +102805,15 @@ exports.default = ReaderAsync; /***/ }), -/* 878 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(879); -const readdirAsync = __webpack_require__(887); -const readdirStream = __webpack_require__(890); +const readdirSync = __webpack_require__(874); +const readdirAsync = __webpack_require__(882); +const readdirStream = __webpack_require__(885); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -103724,7 +102897,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 879 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103732,11 +102905,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(880); +const DirectoryReader = __webpack_require__(875); let syncFacade = { - fs: __webpack_require__(885), - forEach: __webpack_require__(886), + fs: __webpack_require__(880), + forEach: __webpack_require__(881), sync: true }; @@ -103765,7 +102938,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 880 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103774,9 +102947,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(881); -const stat = __webpack_require__(883); -const call = __webpack_require__(884); +const normalizeOptions = __webpack_require__(876); +const stat = __webpack_require__(878); +const call = __webpack_require__(879); /** * Asynchronously reads the contents of a directory and streams the results @@ -104152,14 +103325,14 @@ module.exports = DirectoryReader; /***/ }), -/* 881 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(882); +const globToRegExp = __webpack_require__(877); module.exports = normalizeOptions; @@ -104336,7 +103509,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 882 */ +/* 877 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -104473,13 +103646,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 883 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(884); +const call = __webpack_require__(879); module.exports = stat; @@ -104554,7 +103727,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 884 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104615,14 +103788,14 @@ function callOnce (fn) { /***/ }), -/* 885 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(884); +const call = __webpack_require__(879); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -104686,7 +103859,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 886 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104715,7 +103888,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 887 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104723,12 +103896,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(888); -const DirectoryReader = __webpack_require__(880); +const maybe = __webpack_require__(883); +const DirectoryReader = __webpack_require__(875); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(889), + forEach: __webpack_require__(884), async: true }; @@ -104770,7 +103943,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 888 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104797,7 +103970,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 889 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104833,7 +104006,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 890 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104841,11 +104014,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(880); +const DirectoryReader = __webpack_require__(875); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(889), + forEach: __webpack_require__(884), async: true }; @@ -104865,16 +104038,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 891 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(892); -var entry_1 = __webpack_require__(894); -var pathUtil = __webpack_require__(893); +var deep_1 = __webpack_require__(887); +var entry_1 = __webpack_require__(889); +var pathUtil = __webpack_require__(888); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -104940,13 +104113,13 @@ exports.default = Reader; /***/ }), -/* 892 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(893); +var pathUtils = __webpack_require__(888); var patternUtils = __webpack_require__(722); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -105030,7 +104203,7 @@ exports.default = DeepFilter; /***/ }), -/* 893 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105061,13 +104234,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 894 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(893); +var pathUtils = __webpack_require__(888); var patternUtils = __webpack_require__(722); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -105153,7 +104326,7 @@ exports.default = EntryFilter; /***/ }), -/* 895 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105173,8 +104346,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(896); -var fs_1 = __webpack_require__(900); +var fsStat = __webpack_require__(891); +var fs_1 = __webpack_require__(895); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -105224,14 +104397,14 @@ exports.default = FileSystemStream; /***/ }), -/* 896 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(897); -const statProvider = __webpack_require__(899); +const optionsManager = __webpack_require__(892); +const statProvider = __webpack_require__(894); /** * Asynchronous API. */ @@ -105262,13 +104435,13 @@ exports.statSync = statSync; /***/ }), -/* 897 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(898); +const fsAdapter = __webpack_require__(893); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -105281,7 +104454,7 @@ exports.prepare = prepare; /***/ }), -/* 898 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105304,7 +104477,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 899 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105356,7 +104529,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 900 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105387,7 +104560,7 @@ exports.default = FileSystem; /***/ }), -/* 901 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105407,9 +104580,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_stream_1 = __webpack_require__(895); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_stream_1 = __webpack_require__(890); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -105477,7 +104650,7 @@ exports.default = ReaderStream; /***/ }), -/* 902 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105496,9 +104669,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_sync_1 = __webpack_require__(903); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_sync_1 = __webpack_require__(898); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -105558,7 +104731,7 @@ exports.default = ReaderSync; /***/ }), -/* 903 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105577,8 +104750,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(896); -var fs_1 = __webpack_require__(900); +var fsStat = __webpack_require__(891); +var fs_1 = __webpack_require__(895); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -105624,7 +104797,7 @@ exports.default = FileSystemSync; /***/ }), -/* 904 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105640,7 +104813,7 @@ exports.flatten = flatten; /***/ }), -/* 905 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105661,13 +104834,13 @@ exports.merge = merge; /***/ }), -/* 906 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(907); +const pathType = __webpack_require__(902); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -105733,13 +104906,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 907 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(908); +const pify = __webpack_require__(903); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -105782,7 +104955,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 908 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105873,7 +105046,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 909 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105881,9 +105054,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(718); -const gitIgnore = __webpack_require__(910); -const pify = __webpack_require__(911); -const slash = __webpack_require__(912); +const gitIgnore = __webpack_require__(905); +const pify = __webpack_require__(906); +const slash = __webpack_require__(907); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -105981,7 +105154,7 @@ module.exports.sync = options => { /***/ }), -/* 910 */ +/* 905 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -106450,7 +105623,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 911 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106525,7 +105698,7 @@ module.exports = (input, options) => { /***/ }), -/* 912 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106543,17 +105716,17 @@ module.exports = input => { /***/ }), -/* 913 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const pEvent = __webpack_require__(914); -const CpFileError = __webpack_require__(917); -const fs = __webpack_require__(921); -const ProgressEmitter = __webpack_require__(924); +const pEvent = __webpack_require__(909); +const CpFileError = __webpack_require__(912); +const fs = __webpack_require__(916); +const ProgressEmitter = __webpack_require__(919); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -106667,12 +105840,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 914 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(915); +const pTimeout = __webpack_require__(910); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -106963,12 +106136,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 915 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(916); +const pFinally = __webpack_require__(911); class TimeoutError extends Error { constructor(message) { @@ -107014,7 +106187,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 916 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -107036,12 +106209,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 917 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(918); +const NestedError = __webpack_require__(913); class CpFileError extends NestedError { constructor(message, nested) { @@ -107055,10 +106228,10 @@ module.exports = CpFileError; /***/ }), -/* 918 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(919); +var inherits = __webpack_require__(914); var NestedError = function (message, nested) { this.nested = nested; @@ -107109,7 +106282,7 @@ module.exports = NestedError; /***/ }), -/* 919 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -107117,12 +106290,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(920); + module.exports = __webpack_require__(915); } /***/ }), -/* 920 */ +/* 915 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -107151,16 +106324,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 921 */ +/* 916 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(29); const fs = __webpack_require__(22); -const makeDir = __webpack_require__(922); -const pEvent = __webpack_require__(914); -const CpFileError = __webpack_require__(917); +const makeDir = __webpack_require__(917); +const pEvent = __webpack_require__(909); +const CpFileError = __webpack_require__(912); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -107257,7 +106430,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 922 */ +/* 917 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -107265,7 +106438,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const {promisify} = __webpack_require__(29); -const semver = __webpack_require__(923); +const semver = __webpack_require__(918); const defaults = { mode: 0o777 & (~process.umask()), @@ -107414,7 +106587,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 923 */ +/* 918 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -109016,7 +108189,7 @@ function coerce (version, options) { /***/ }), -/* 924 */ +/* 919 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109057,7 +108230,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 925 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109103,12 +108276,12 @@ exports.default = module.exports; /***/ }), -/* 926 */ +/* 921 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(927); +const NestedError = __webpack_require__(922); class CpyError extends NestedError { constructor(message, nested) { @@ -109122,7 +108295,7 @@ module.exports = CpyError; /***/ }), -/* 927 */ +/* 922 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -109178,7 +108351,7 @@ module.exports = NestedError; /***/ }), -/* 928 */ +/* 923 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 66f17ab579ec..f4b91d154cbb 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -136,7 +136,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ type: Joi.string() - .valid('chrome', 'firefox', 'ie') + .valid('chrome', 'firefox', 'ie', 'msedge') .default('chrome'), logPollingMs: Joi.number().default(100), diff --git a/test/functional/config.edge.js b/test/functional/config.edge.js new file mode 100644 index 000000000000..ed68b41e8c89 --- /dev/null +++ b/test/functional/config.edge.js @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default async function({ readConfigFile }) { + const defaultConfig = await readConfigFile(require.resolve('./config')); + + return { + ...defaultConfig.getAll(), + + browser: { + type: 'msedge', + }, + + junit: { + reportName: 'MS Chromium Edge UI Functional Tests', + }, + }; +} diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 5017947e95d0..13d2365c0719 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -47,7 +47,9 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { */ public readonly browserType: string = browserType; - public readonly isChrome: boolean = browserType === Browsers.Chrome; + public readonly isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes( + browserType + ); public readonly isFirefox: boolean = browserType === Browsers.Firefox; diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 157918df874c..8b57ecd3c823 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -55,6 +55,7 @@ export class WebElementWrapper { private driver: WebDriver = this.webDriver.driver; private Keys = Key; public isW3CEnabled: boolean = (this.webDriver.driver as any).executor_.w3c === true; + public isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes(this.browserType); public static create( webElement: WebElement | WebElementWrapper, @@ -63,7 +64,7 @@ export class WebElementWrapper { timeout: number, fixedHeaderHeight: number, logger: ToolingLog, - browserType: string + browserType: Browsers ): WebElementWrapper { if (webElement instanceof WebElementWrapper) { return webElement; @@ -87,7 +88,7 @@ export class WebElementWrapper { private timeout: number, private fixedHeaderHeight: number, private logger: ToolingLog, - private browserType: string + private browserType: Browsers ) {} private async _findWithCustomTimeout( @@ -243,7 +244,7 @@ export class WebElementWrapper { return this.clearValueWithKeyboard(); } await this.retryCall(async function clearValue(wrapper) { - if (wrapper.browserType === Browsers.Chrome || options.withJS) { + if (wrapper.isChromium || options.withJS) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2702 await wrapper.driver.executeScript(`arguments[0].value=''`, wrapper._webElement); } else { @@ -275,7 +276,7 @@ export class WebElementWrapper { await delay(100); } } else { - if (this.browserType === Browsers.Chrome) { + if (this.isChromium) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=30 await this.retryCall(async function clearValueWithKeyboard(wrapper) { await wrapper.driver.executeScript(`arguments[0].select();`, wrapper._webElement); diff --git a/test/functional/services/remote/browsers.ts b/test/functional/services/remote/browsers.ts index 46d81f1737a5..aa6e364d0a09 100644 --- a/test/functional/services/remote/browsers.ts +++ b/test/functional/services/remote/browsers.ts @@ -21,4 +21,5 @@ export enum Browsers { Chrome = 'chrome', Firefox = 'firefox', InternetExplorer = 'ie', + ChromiumEdge = 'msedge', } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index e571a1a7e555..b0724488cb5d 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -64,18 +64,23 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { lifecycle, config.get('browser.logPollingMs') ); + const isW3CEnabled = (driver as any).executor_.w3c; const caps = await driver.getCapabilities(); - const browserVersion = caps.get(isW3CEnabled ? 'browserVersion' : 'version'); + const browserVersion = caps.get( + isW3CEnabled || browserType === Browsers.ChromiumEdge ? 'browserVersion' : 'version' + ); - log.info(`Remote initialized: ${caps.get('browserName')} ${browserVersion}`); + log.info( + `Remote initialized: ${caps.get( + 'browserName' + )} ${browserVersion}, w3c compliance=${isW3CEnabled}, collectingCoverage=${collectCoverage}` + ); - if (browserType === Browsers.Chrome) { + if ([Browsers.Chrome, Browsers.ChromiumEdge].includes(browserType)) { log.info( - `Chromedriver version: ${ - caps.get('chrome').chromedriverVersion - }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` + `${browserType}driver version: ${caps.get(browserType)[`${browserType}driverVersion`]}` ); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 3bf5b865aa7b..fc0b5bbb787c 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -31,10 +31,12 @@ import { Builder, Capabilities, By, logging, until } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; // @ts-ignore internal modules are not typed +import edge from 'selenium-webdriver/edge'; +import { installDriver } from 'ms-chromium-edge-driver'; +// @ts-ignore internal modules are not typed import { Executor } from 'selenium-webdriver/lib/http'; // @ts-ignore internal modules are not typed import { getLogger } from 'selenium-webdriver/lib/logging'; - import { pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -63,6 +65,7 @@ Executor.prototype.execute = preventParallelCalls( ); let attemptCounter = 0; +let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( log: ToolingLog, browserType: Browsers, @@ -74,6 +77,46 @@ async function attemptToCreateCommand( const buildDriverInstance = async () => { switch (browserType) { + case 'msedge': { + if (edgePaths && edgePaths.browserPath && edgePaths.driverPath) { + const edgeOptions = new edge.Options(); + if (headlessBrowser === '1') { + // @ts-ignore internal modules are not typed + edgeOptions.headless(); + } + // @ts-ignore internal modules are not typed + edgeOptions.setEdgeChromium(true); + // @ts-ignore internal modules are not typed + edgeOptions.setBinaryPath(edgePaths.browserPath); + const session = await new Builder() + .forBrowser('MicrosoftEdge') + .setEdgeOptions(edgeOptions) + .setEdgeService(new edge.ServiceBuilder(edgePaths.driverPath)) + .build(); + return { + session, + consoleLog$: pollForLogEntry$( + session, + logging.Type.BROWSER, + logPollingMs, + lifecycle.cleanup.after$ + ).pipe( + takeUntil(lifecycle.cleanup.after$), + map(({ message, level: { name: level } }) => ({ + message: message.replace(/\\n/g, '\n'), + level, + })) + ), + }; + } else { + throw new Error( + `Chromium Edge session requires browser or driver path to be defined: ${JSON.stringify( + edgePaths + )}` + ); + } + } + case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ @@ -265,6 +308,11 @@ export async function initWebDriver( log.verbose(entry.message); }); + // download Edge driver only in case of usage + if (browserType === Browsers.ChromiumEdge) { + edgePaths = await installDriver(); + } + return await Promise.race([ (async () => { await delay(2 * MINUTE); diff --git a/x-pack/test/functional/config.edge.js b/x-pack/test/functional/config.edge.js new file mode 100644 index 000000000000..882fb6fea368 --- /dev/null +++ b/x-pack/test/functional/config.edge.js @@ -0,0 +1,21 @@ +/* + * 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 default async function({ readConfigFile }) { + const chromeConfig = await readConfigFile(require.resolve('./config')); + + return { + ...chromeConfig.getAll(), + + browser: { + type: 'msedge', + }, + + junit: { + reportName: 'MS Chromium Edge XPack UI Functional Tests', + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 77ab69c71557..3f04b2d26a01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2499,6 +2499,11 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@sindresorhus/is@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.0.tgz#6ad4ca610f696098e92954ab431ff83bea0ce13f" + integrity sha512-lXKXfypKo644k4Da4yXkPCrwcvn6SlUW2X2zFbuflKHNjf0w9htru01bo26uMhleMXsDmnZ12eJLdrAZa9MANg== + "@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.6.0.tgz#ec7670432ae9c8eb710400d112c201a362d83393" @@ -3398,6 +3403,13 @@ "@svgr/plugin-svgo" "^4.2.0" loader-utils "^1.2.3" +"@szmarczak/http-timer@^4.0.0": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + "@testim/chrome-version@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" @@ -3646,6 +3658,16 @@ resolved "https://registry.yarnpkg.com/@types/browserslist-useragent/-/browserslist-useragent-3.0.0.tgz#d425c9818182ce71ce53866798cee9c7d41d6e53" integrity sha512-ZBvKzg3yyWNYEkwxAzdmUzp27sFvw+1m080/+2lwrt+eltNefn1f4fnpMyrjOla31p8zLleCYqQXw+3EETfn0w== +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/caseless@*": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" @@ -4015,6 +4037,11 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + "@types/indent-string@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-3.0.0.tgz#9ebb391ceda548926f5819ad16405349641b999f" @@ -4146,6 +4173,13 @@ dependencies: "@types/node" "*" +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + "@types/license-checker@15.0.0": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/license-checker/-/license-checker-15.0.0.tgz#685d69e2cf61ffd862320434601f51c85e28bba1" @@ -4617,6 +4651,13 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/responselike@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/retry@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" @@ -4632,10 +4673,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.5.tgz#23041a4948c82daf2df9836e4d2358fec10d3e24" - integrity sha512-ma1aL1znI3ptEbSQgbywgadrRCJouPIACSfOl/bPwu/TPNSyyE/+o9jZ6+bpDVTtIdksZuVKpq4SR1ip3DRduw== +"@types/selenium-webdriver@4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.9.tgz#12621e55b2ef8f6c98bd17fe23fa720c6cba16bd" + integrity sha512-HopIwBE7GUXsscmt/J0DhnFXLSmO04AfxT6b8HAprknwka7pqEWquWDMXxCjd+NUHK9MkCe1SDKKsMiNmCItbQ== "@types/semver@^5.5.0": version "5.5.0" @@ -7358,13 +7399,18 @@ binaryextensions@2: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935" integrity sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA== -bindings@^1.5.0: +bindings@1, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" +bindings@~1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" + integrity sha1-FK1hE4EtLTfXLme0ystLtyZQXxE= + bit-twiddle@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz#0c6c1fabe2b23d17173d9a61b7b7093eb9e1769e" @@ -7898,7 +7944,7 @@ buffer@^5.1.0, buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= @@ -8045,6 +8091,13 @@ cache-loader@^4.1.0: neo-async "^2.6.1" schema-utils "^2.0.0" +cacheable-lookup@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-2.0.0.tgz#33b1e56f17507f5cf9bb46075112d65473fb7713" + integrity sha512-s2piO6LvA7xnL1AR03wuEdSx3BZT3tIJpZ56/lcJwzO/6DTJZlTs7X3lrvPxk6d1PlDe6PrVe2TjlUIZNFglAQ== + dependencies: + keyv "^4.0.0" + cacheable-request@^2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" @@ -8058,6 +8111,19 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + cachedir@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -8886,7 +8952,7 @@ clone-regexp@^1.0.0: is-regexp "^1.0.0" is-supported-regexp-flag "^1.0.0" -clone-response@1.0.2: +clone-response@1.0.2, clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -9150,16 +9216,16 @@ commander@4.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.0.tgz#545983a0603fe425bc672d66c9e3c89c42121a83" integrity sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw== +commander@^2.12.1, commander@^2.20.0, commander@^2.7.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.20.0, commander@^2.7.1: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commander@^2.8.1: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" @@ -10575,7 +10641,7 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: +debug@2, debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -10647,6 +10713,13 @@ decompress-response@^4.2.0: dependencies: mimic-response "^2.0.0" +decompress-response@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f" + integrity sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw== + dependencies: + mimic-response "^2.0.0" + decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" @@ -10798,6 +10871,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -13239,6 +13317,17 @@ fetch-mock@^7.3.9: path-to-regexp "^2.2.1" whatwg-url "^6.5.0" +ffi@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ffi/-/ffi-2.3.0.tgz#fa1a7b3d85c0fa8c83d96947a64b5192bc47f858" + integrity sha512-vkPA9Hf9CVuQ5HeMZykYvrZF2QNJ/iKGLkyDkisBnoOOFeFXZQhUPxBARPBIZMJVulvBI2R+jgofW03gyPpJcQ== + dependencies: + bindings "~1.2.0" + debug "2" + nan "2" + ref "1" + ref-struct "1" + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -14716,6 +14805,27 @@ got@5.6.0: unzip-response "^1.0.0" url-parse-lax "^1.0.0" +got@^10.6.0: + version "10.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-10.6.0.tgz#ac3876261a4d8e5fc4f81186f79955ce7b0501dc" + integrity sha512-3LIdJNTdCFbbJc+h/EH0V5lpNpbJ6Bfwykk21lcQvQsEcrzdi/ltCyQehFHLzJ/ka0UMH4Slg0hkYvAZN9qUDg== + dependencies: + "@sindresorhus/is" "^2.0.0" + "@szmarczak/http-timer" "^4.0.0" + "@types/cacheable-request" "^6.0.1" + cacheable-lookup "^2.0.0" + cacheable-request "^7.0.1" + decompress-response "^5.0.0" + duplexer3 "^0.1.4" + get-stream "^5.0.0" + lowercase-keys "^2.0.0" + mimic-response "^2.1.0" + p-cancelable "^2.0.0" + p-event "^4.0.0" + responselike "^2.0.0" + to-readable-stream "^2.0.0" + type-fest "^0.10.0" + got@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca" @@ -15816,6 +15926,11 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -16854,6 +16969,11 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e" integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g== +is-generator-function@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" + integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== + is-glob@4.0.0, is-glob@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" @@ -18124,6 +18244,11 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" @@ -18359,7 +18484,7 @@ jsx-to-string@^1.4.0: json-stringify-pretty-compact "^1.0.1" react "^0.14.0" -jszip@^3.1.5: +jszip@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d" integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA== @@ -18535,6 +18660,13 @@ keyv@3.0.0: dependencies: json-buffer "3.0.0" +keyv@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.0.tgz#2d1dab694926b2d427e4c74804a10850be44c12f" + integrity sha512-U7ioE8AimvRVLfw4LffyOIRhL2xVgmE8T22L6i0BucSnBUyv4w+I7VN/zVZwRKHOI6ZRUcdMdWHQ8KSUvGpEog== + dependencies: + json-buffer "3.0.1" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -19503,6 +19635,11 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lowlight@~1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.1.tgz#ed7c3dffc36f8c1f263735c0fe0c907847c11250" @@ -20163,6 +20300,11 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== +mimic-response@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + mimos@4.x.x: version "4.0.0" resolved "https://registry.yarnpkg.com/mimos/-/mimos-4.0.0.tgz#76e3d27128431cb6482fd15b20475719ad626a5a" @@ -20591,6 +20733,19 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +ms-chromium-edge-driver@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ms-chromium-edge-driver/-/ms-chromium-edge-driver-0.2.0.tgz#0e0c6fd9fd1d1d36db97b2b3d7e9d4ba4d2de456" + integrity sha512-RkDsBPnMLjRna7q4LlvtLb+CHPei9gZapnlxm3ayWKk3Ab6HmDsz/17xG2eyqkKX5UcKeo04YlLZ345tO7OolA== + dependencies: + extract-zip "^1.6.7" + got "^10.6.0" + lodash "4.17.15" + tslint "^6.1.0" + tslint-config-prettier "^1.18.0" + util "^0.12.2" + windows-registry "^0.1.5" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -20703,7 +20858,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.12.1, nan@^2.13.2: +nan@2, nan@^2.12.1, nan@^2.13.2: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -21206,6 +21361,11 @@ normalize-url@^3.3.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -21891,6 +22051,11 @@ p-cancelable@^0.4.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -21903,7 +22068,7 @@ p-each-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-event@^4.1.0: +p-event@^4.0.0, p-event@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== @@ -24952,6 +25117,31 @@ redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +ref-struct@1, ref-struct@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ref-struct/-/ref-struct-1.1.0.tgz#5d5ee65ad41cefc3a5c5feb40587261e479edc13" + integrity sha1-XV7mWtQc78Olxf60BYcmHkee3BM= + dependencies: + debug "2" + ref "1" + +ref-union@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ref-union/-/ref-union-1.0.1.tgz#3a2397f862f1e75171d687268f43b3f17729f120" + integrity sha1-OiOX+GLx51Fx1ocmj0Oz8Xcp8SA= + dependencies: + debug "2" + ref "1" + +ref@1, ref@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ref/-/ref-1.3.5.tgz#0e33f080cdb94a3d95312b2b3b1fd0f82044ca0f" + integrity sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA== + dependencies: + bindings "1" + debug "2" + nan "2" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -25691,6 +25881,13 @@ responselike@1.0.2: dependencies: lowercase-keys "^1.0.0" +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -26249,15 +26446,14 @@ select@^1.1.2: resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= -selenium-webdriver@^4.0.0-alpha.5: - version "4.0.0-alpha.5" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.5.tgz#e4683b3dbf827d70df09a7e43bf02ebad20fa7c1" - integrity sha512-hktl3DSrhzM59yLhWzDGHIX9o56DvA+cVK7Dw6FcJR6qQ4CGzkaHeXQPcdrslkWMTeq0Ci9AmCxq0EMOvm2Rkg== +selenium-webdriver@^4.0.0-alpha.7: + version "4.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" + integrity sha512-D4qnTsyTr91jT8f7MfN+OwY0IlU5+5FmlO5xlgRUV6hDEV8JyYx2NerdTEqDDkNq7RZDYc4VoPALk8l578RBHw== dependencies: - jszip "^3.1.5" - rimraf "^2.6.3" + jszip "^3.2.2" + rimraf "^2.7.1" tmp "0.0.30" - xml2js "^0.4.19" selfsigned@^1.10.7: version "1.10.7" @@ -28648,6 +28844,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-2.1.0.tgz#82880316121bea662cdc226adb30addb50cb06e8" + integrity sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -28927,6 +29128,37 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslint-config-prettier@^1.18.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37" + integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg== + +tslint@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.0.tgz#c6c611b8ba0eed1549bf5a59ba05a7732133d851" + integrity sha512-fXjYd/61vU6da04E505OZQGb2VCN2Mq3doeWcOIryuG+eqdmFUXTYVwdhnbEu2k46LNLgUYt9bI5icQze/j0bQ== + dependencies: + "@babel/code-frame" "^7.0.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^4.0.1" + glob "^7.1.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + mkdirp "^0.5.1" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.10.0" + tsutils "^2.29.0" + +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -29431,6 +29663,11 @@ type-detect@^1.0.0: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" integrity sha1-diIXzAbbJY7EiQihKY6LlRIejqI= +type-fest@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.10.0.tgz#7f06b2b9fbfc581068d1341ffabd0349ceafc642" + integrity sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw== + type-fest@^0.3.0, type-fest@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" @@ -30121,6 +30358,16 @@ util@^0.11.0: dependencies: inherits "2.0.3" +util@^0.12.2: + version "0.12.2" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.2.tgz#54adb634c9e7c748707af2bf5a8c7ab640cbba2b" + integrity sha512-XE+MkWQvglYa+IOfBt5UFG93EmncEMP23UqpgDvVZVFBPxwmkK10QRp6pgU4xICPnWRf/t0zPv4noYSUq9gqUQ== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + safe-buffer "^5.1.2" + utila@^0.4.0, utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -31277,6 +31524,17 @@ window-size@^0.2.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= +windows-registry@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/windows-registry/-/windows-registry-0.1.5.tgz#92c25c960884b0d215e69395f52d8dfaa0ba4ad0" + integrity sha512-gMN3ets1fbdP+TApEbbX2TIfBK3MIH5+p9GMvIFS3CNLr7U0Khe5mRj/T5zvwo/pKdhJgDrCLYyaNSs7HYiBCw== + dependencies: + debug "^2.2.0" + ffi "^2.0.0" + ref "^1.2.0" + ref-struct "^1.0.2" + ref-union "^1.0.0" + windows-release@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" @@ -31575,14 +31833,6 @@ xml-parse-from-string@^1.0.0: resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= -xml2js@^0.4.19, xml2js@^0.4.5: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== - dependencies: - sax ">=0.6.0" - xmlbuilder "~9.0.1" - xml2js@^0.4.22: version "0.4.22" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02" @@ -31592,6 +31842,14 @@ xml2js@^0.4.22: util.promisify "~1.0.0" xmlbuilder "~11.0.0" +xml2js@^0.4.5: + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" + integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q== + dependencies: + sax ">=0.6.0" + xmlbuilder "~9.0.1" + xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" From e3bd04fcb078a8fd01d315bbde68781bdd8a3cfd Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 8 Apr 2020 22:36:33 +0100 Subject: [PATCH 12/46] [Alerting] Displays warning when a permanent encryption key is missing and hides alerting UI appropriately (#62772) Removes the Security flyout and instead replaces the Alerting List, Connectors List and Alert Flyout with suitable messaging. Verifies that a permanent Encryption Key has been configured and if it hasn't displays a suitable warning in place, or along side the TLS warning, as needed. --- x-pack/plugins/alerting/common/index.ts | 1 + x-pack/plugins/alerting/server/plugin.ts | 2 +- .../alerting/server/routes/health.test.ts | 60 +++++- .../plugins/alerting/server/routes/health.ts | 8 +- .../translations/translations/ja-JP.json | 17 +- .../translations/translations/zh-CN.json | 17 +- .../alert_action_security_call_out.test.tsx | 78 ------- .../alert_action_security_call_out.tsx | 78 ------- .../application/components/health_check.scss | 13 ++ .../components/health_check.test.tsx | 131 ++++++++++++ .../application/components/health_check.tsx | 197 ++++++++++++++++++ .../prompts/empty_connectors_prompt.scss | 3 + .../prompts/empty_connectors_prompt.tsx | 55 +++++ .../components/prompts/empty_prompt.tsx | 47 +++++ .../components/security_call_out.test.tsx | 72 ------- .../components/security_call_out.tsx | 75 ------- .../public/application/home.tsx | 25 ++- .../components/actions_connectors_list.scss | 4 - .../components/actions_connectors_list.tsx | 53 +---- .../sections/alert_form/alert_add.test.tsx | 5 +- .../sections/alert_form/alert_add.tsx | 104 +++++---- .../sections/alert_form/alert_edit.test.tsx | 5 +- .../sections/alert_form/alert_edit.tsx | 134 ++++++------ .../alerts_list/components/alerts_list.tsx | 42 +--- 24 files changed, 663 insertions(+), 563 deletions(-) delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/health_check.scss create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.scss create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_prompt.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.test.tsx delete mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.tsx diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 9d4ea69a6360..2574e73dd4f9 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -17,6 +17,7 @@ export interface ActionGroup { export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; + hasPermanentEncryptionKey: boolean; } export const BASE_ALERT_API_PATH = '/api/alert'; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 172a10622634..fdca6c0a9b50 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -190,7 +190,7 @@ export class AlertingPlugin { unmuteAllAlertRoute(router, this.licenseState); muteAlertInstanceRoute(router, this.licenseState); unmuteAlertInstanceRoute(router, this.licenseState); - healthRoute(router, this.licenseState); + healthRoute(router, this.licenseState, plugins.encryptedSavedObjects); return { registerType: alertTypeRegistry.register.bind(alertTypeRegistry), diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index 9efe020bc10c..42c83a7c04de 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -10,6 +10,7 @@ import { mockHandlerArguments } from './_mock_handler_arguments'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockLicenseState } from '../lib/license_state.mock'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; jest.mock('../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -24,7 +25,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [config] = router.get.mock.calls[0]; @@ -35,7 +38,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -58,11 +63,13 @@ describe('healthRoute', () => { `); }); - it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { + it('evaluates whether Encrypted Saved Objects is using an ephemeral encryption key', async () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = true; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -73,6 +80,31 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": false, + "isSufficientlySecure": true, + }, + } + `); + }); + + it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { + const router: RouterMock = mockRouter.create(); + + const licenseState = mockLicenseState(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const elasticsearch = elasticsearchServiceMock.createSetup(); + elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); + + const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } @@ -83,7 +115,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -94,6 +128,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } @@ -104,7 +139,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -117,6 +154,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": false, }, } @@ -127,7 +165,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -140,6 +180,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": false, }, } @@ -150,7 +191,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -163,6 +206,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index 29c2f3c5730f..fa2358a1f181 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -14,6 +14,7 @@ import { import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { AlertingFrameworkHealth } from '../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; interface XPackUsageSecurity { security?: { @@ -26,7 +27,11 @@ interface XPackUsageSecurity { }; } -export function healthRoute(router: IRouter, licenseState: LicenseState) { +export function healthRoute( + router: IRouter, + licenseState: LicenseState, + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +) { router.get( { path: '/api/alert/_health', @@ -54,6 +59,7 @@ export function healthRoute(router: IRouter, licenseState: LicenseState) { const frameworkHealth: AlertingFrameworkHealth = { isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), + hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey, }; return res.ok({ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 00ac5b77d00f..e63e1c8ad2c9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15864,8 +15864,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.andLabel": "AND", "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "タイミング", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "タイミング", - "xpack.triggersActionsUI.components.alertActionSecurityCallOut.enableTlsCta": "TLS を有効にする", - "xpack.triggersActionsUI.components.alertActionSecurityCallOut.tlsDisabledTitle": "アラート {action} を実行するには Elasticsearch と Kibana の間に TLS が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "メールに送信", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton": "変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "サーバーからメールを送信します。", @@ -15960,9 +15958,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch": "HTTP ヘッダーを追加", "xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText": "{numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}を削除できませんでした", "xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText": "{numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}を削除しました", - "xpack.triggersActionsUI.components.securityCallOut.enableTlsCta": "TLS を有効にする", - "xpack.triggersActionsUI.components.securityCallOut.tlsDisabledDescription": "アラートは API キー に依存し、キーを使用するには Elasticsearch と Kibana の間に TLS が必要です。", - "xpack.triggersActionsUI.components.securityCallOut.tlsDisabledTitle": "トランスポートレイヤーセキュリティを有効にする", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "コネクター", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "{numIdsToDelete, plural, one {{singleTitle}} other {# {multipleTitle}}}を削除 ", @@ -15986,8 +15981,8 @@ "xpack.triggersActionsUI.sections.actionConnectorForm.error.requiredNameText": "名前が必要です。", "xpack.triggersActionsUI.sections.actionForm.getMoreActionsTitle": "さらにアクションを表示", "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionButtonLabel": "コネクターを作成", - "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionEmptyBody": "Kibana でトリガーできるメール、Slack, Elasticsearch、およびサードパーティサービスを構成します。", - "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionEmptyTitle": "初めてのコネクターを作成する", + "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addActionEmptyBody": "Kibana でトリガーできるメール、Slack, Elasticsearch、およびサードパーティサービスを構成します。", + "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addActionEmptyTitle": "初めてのコネクターを作成する", "xpack.triggersActionsUI.sections.actionsConnectorsList.buttons.deleteDisabledTitle": "コネクターを削除できません", "xpack.triggersActionsUI.sections.actionsConnectorsList.buttons.deleteLabel": "{count} 件を削除", "xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription": "このコネクターを削除", @@ -16037,7 +16032,6 @@ "xpack.triggersActionsUI.sections.alertAdd.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "アラートを作成できません。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "「{alertName}」 を保存しました", - "xpack.triggersActionsUI.sections.alertAdd.securityCalloutAction": "作成", "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "インデックスを選択してください。", "xpack.triggersActionsUI.sections.alertAdd.threshold.closeIndexPopoverLabel": "閉じる", "xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", @@ -16074,7 +16068,6 @@ "xpack.triggersActionsUI.sections.alertEdit.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertEdit.saveErrorNotificationText": "アラートを更新できません。", "xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText": "「{alertName}」 を更新しました", - "xpack.triggersActionsUI.sections.alertEdit.securityCalloutAction": "編集中", "xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel": "削除", "xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle": "このアクションは無効です", "xpack.triggersActionsUI.sections.alertForm.actionIdLabel": "{connectorInstance} コネクター", @@ -16126,9 +16119,9 @@ "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.enableTitle": "有効にする", "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.muteTitle": "ミュート", "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.popoverButtonTitle": "アクション", - "xpack.triggersActionsUI.sections.alertsList.emptyButton": "アラートの作成", - "xpack.triggersActionsUI.sections.alertsList.emptyDesc": "トリガーが起きたときにメール、Slack、または別のコネクターを通してアラートを受信します。", - "xpack.triggersActionsUI.sections.alertsList.emptyTitle": "初めてのアラートを作成する", + "xpack.triggersActionsUI.components.emptyPrompt.emptyButton": "アラートの作成", + "xpack.triggersActionsUI.components.emptyPrompt.emptyDesc": "トリガーが起きたときにメール、Slack、または別のコネクターを通してアラートを受信します。", + "xpack.triggersActionsUI.components.emptyPrompt.emptyTitle": "初めてのアラートを作成する", "xpack.triggersActionsUI.sections.alertsList.multipleTitle": "アラート", "xpack.triggersActionsUI.sections.alertsList.searchPlaceholderTitle": "検索", "xpack.triggersActionsUI.sections.alertsList.singleTitle": "アラート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f6d84431bef7..cc75ceb988d9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15868,8 +15868,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.andLabel": "且", "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "当", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "当", - "xpack.triggersActionsUI.components.alertActionSecurityCallOut.enableTlsCta": "启用 TLS", - "xpack.triggersActionsUI.components.alertActionSecurityCallOut.tlsDisabledTitle": "告警 {action} 在 Elasticsearch 和 Kibana 之间需要 TLS。", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "发送到电子邮件", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton": "添加变量", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "从您的服务器发送电子邮件。", @@ -15964,9 +15962,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.viewHeadersSwitch": "添加 HTTP 标头", "xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText": "无法删除 {numErrors, number} 个{numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}", "xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText": "已删除 {numSuccesses, number} 个{numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}", - "xpack.triggersActionsUI.components.securityCallOut.enableTlsCta": "启用 TLS", - "xpack.triggersActionsUI.components.securityCallOut.tlsDisabledDescription": "Alerting 依赖于在 Elasticsearch 和 Kibana 之间需要 TLS 的 API 密钥。", - "xpack.triggersActionsUI.components.securityCallOut.tlsDisabledTitle": "启用传输层安全", "xpack.triggersActionsUI.connectors.breadcrumbTitle": "连接器", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "取消", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "删除{numIdsToDelete, plural, one {{singleTitle}} other { # 个{multipleTitle}}} ", @@ -15991,8 +15986,8 @@ "xpack.triggersActionsUI.sections.actionConnectorForm.error.requiredNameText": "名称必填。", "xpack.triggersActionsUI.sections.actionForm.getMoreActionsTitle": "获取更多的操作", "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionButtonLabel": "创建连接器", - "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionEmptyBody": "配置电子邮件、Slack、Elasticsearch 和 Kibana 可以触发的第三方服务。", - "xpack.triggersActionsUI.sections.actionsConnectorsList.addActionEmptyTitle": "创建您的首个连接器", + "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addActionEmptyBody": "配置电子邮件、Slack、Elasticsearch 和 Kibana 可以触发的第三方服务。", + "xpack.triggersActionsUI.components.emptyConnectorsPrompt.addActionEmptyTitle": "创建您的首个连接器", "xpack.triggersActionsUI.sections.actionsConnectorsList.buttons.deleteDisabledTitle": "无法删除连接器", "xpack.triggersActionsUI.sections.actionsConnectorsList.buttons.deleteLabel": "删除 {count} 个", "xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription": "删除此连接器", @@ -16042,7 +16037,6 @@ "xpack.triggersActionsUI.sections.alertAdd.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "无法创建告警。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "已保存“{alertName}”", - "xpack.triggersActionsUI.sections.alertAdd.securityCalloutAction": "创建", "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "选择索引。", "xpack.triggersActionsUI.sections.alertAdd.threshold.closeIndexPopoverLabel": "关闭", "xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", @@ -16079,7 +16073,6 @@ "xpack.triggersActionsUI.sections.alertEdit.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertEdit.saveErrorNotificationText": "无法更新告警。", "xpack.triggersActionsUI.sections.alertEdit.saveSuccessNotificationText": "已更新“{alertName}”", - "xpack.triggersActionsUI.sections.alertEdit.securityCalloutAction": "正在编辑", "xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel": "删除", "xpack.triggersActionsUI.sections.alertForm.actionDisabledTitle": "此操作已禁用", "xpack.triggersActionsUI.sections.alertForm.actionIdLabel": "{connectorInstance} 连接器", @@ -16131,9 +16124,9 @@ "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.enableTitle": "启用", "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.muteTitle": "静音", "xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.popoverButtonTitle": "操作", - "xpack.triggersActionsUI.sections.alertsList.emptyButton": "创建告警", - "xpack.triggersActionsUI.sections.alertsList.emptyDesc": "触发条件满足时通过电子邮件、Slack 或其他连接器接收告警。", - "xpack.triggersActionsUI.sections.alertsList.emptyTitle": "创建您的首个告警", + "xpack.triggersActionsUI.components.emptyPrompt.emptyButton": "创建告警", + "xpack.triggersActionsUI.components.emptyPrompt.emptyDesc": "触发条件满足时通过电子邮件、Slack 或其他连接器接收告警。", + "xpack.triggersActionsUI.components.emptyPrompt.emptyTitle": "创建您的首个告警", "xpack.triggersActionsUI.sections.alertsList.multipleTitle": "告警", "xpack.triggersActionsUI.sections.alertsList.searchPlaceholderTitle": "搜索", "xpack.triggersActionsUI.sections.alertsList.singleTitle": "告警", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.test.tsx deleted file mode 100644 index 85699cfbd750..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React, { Fragment } from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { AlertActionSecurityCallOut } from './alert_action_security_call_out'; - -import { EuiCallOut, EuiButton } from '@elastic/eui'; -import { act } from 'react-dom/test-utils'; -import { httpServiceMock } from '../../../../../../src/core/public/mocks'; - -const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' }; - -const http = httpServiceMock.createStartContract(); - -describe('alert action security call out', () => { - let useEffect: any; - - const mockUseEffect = () => { - // make react execute useEffects despite shallow rendering - useEffect.mockImplementationOnce((f: Function) => f()); - }; - - beforeEach(() => { - jest.resetAllMocks(); - useEffect = jest.spyOn(React, 'useEffect'); - mockUseEffect(); - }); - - test('renders nothing while health is loading', async () => { - http.get.mockImplementationOnce(() => new Promise(() => {})); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow( - - ); - }); - - expect(component?.is(Fragment)).toBeTruthy(); - expect(component?.html()).toBe(''); - }); - - test('renders nothing if keys are enabled', async () => { - http.get.mockResolvedValue({ isSufficientlySecure: true }); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow( - - ); - }); - - expect(component?.is(Fragment)).toBeTruthy(); - expect(component?.html()).toBe(''); - }); - - test('renders the callout if keys are disabled', async () => { - http.get.mockResolvedValue({ isSufficientlySecure: false }); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow( - - ); - }); - - expect(component?.find(EuiCallOut).prop('title')).toMatchInlineSnapshot( - `"Alert creation requires TLS between Elasticsearch and Kibana."` - ); - - expect(component?.find(EuiButton).prop('href')).toMatchInlineSnapshot( - `"elastic.co/guide/en/kibana/current/configuring-tls.html"` - ); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.tsx deleted file mode 100644 index f7a80202dff8..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/alert_action_security_call_out.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Fragment } from 'react'; -import { Option, none, some, fold, filter } from 'fp-ts/lib/Option'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { EuiCallOut, EuiButton, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { DocLinksStart, HttpSetup } from 'kibana/public'; -import { AlertingFrameworkHealth } from '../../types'; -import { health } from '../lib/alert_api'; - -interface Props { - docLinks: Pick; - action: string; - http: HttpSetup; -} - -export const AlertActionSecurityCallOut: React.FunctionComponent = ({ - http, - action, - docLinks, -}) => { - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - - const [alertingHealth, setAlertingHealth] = React.useState>(none); - - React.useEffect(() => { - async function fetchSecurityConfigured() { - setAlertingHealth(some(await health({ http }))); - } - - fetchSecurityConfigured(); - }, [http]); - - return pipe( - alertingHealth, - filter(healthCheck => !healthCheck.isSufficientlySecure), - fold( - () => , - () => ( - - - - - - - - - ) - ) - ); -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.scss b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.scss new file mode 100644 index 000000000000..c4d12221e3a0 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.scss @@ -0,0 +1,13 @@ +@mixin padBannerWith($size) { + padding-left: $size; + padding-right: $size; +} + +.alertingHealthCheck__body { + @include padBannerWith(2 * $euiSize); +} + +.alertingFlyoutHealthCheck__body { + @include padBannerWith(2 * $euiSize); + margin-top: $euiSize; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx new file mode 100644 index 000000000000..5156a6146f3a --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -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 React from 'react'; +import { render } from '@testing-library/react'; + +import { HealthCheck } from './health_check'; + +import { act } from 'react-dom/test-utils'; +import { httpServiceMock } from '../../../../../../src/core/public/mocks'; + +const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' }; + +const http = httpServiceMock.createStartContract(); + +describe('health check', () => { + test('renders spinner while health is loading', async () => { + http.get.mockImplementationOnce(() => new Promise(() => {})); + + const { queryByText, container } = render( + +

{'shouldnt render'}

+
+ ); + await act(async () => { + // wait for useEffect to run + }); + + expect(container.getElementsByClassName('euiLoadingSpinner').length).toBe(1); + expect(queryByText('shouldnt render')).not.toBeInTheDocument(); + }); + + it('renders children if keys are enabled', async () => { + http.get.mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: true }); + + const { queryByText } = render( + +

{'should render'}

+
+ ); + await act(async () => { + // wait for useEffect to run + }); + expect(queryByText('should render')).toBeInTheDocument(); + }); + + test('renders warning if keys are disabled', async () => { + http.get.mockImplementationOnce(async () => ({ + isSufficientlySecure: false, + hasPermanentEncryptionKey: true, + })); + + const { queryAllByText } = render( + +

{'should render'}

+
+ ); + await act(async () => { + // wait for useEffect to run + }); + + const [description, action] = queryAllByText(/TLS/i); + + expect(description.textContent).toMatchInlineSnapshot( + `"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to enable TLS."` + ); + + expect(action.textContent).toMatchInlineSnapshot(`"Learn how to enable TLS."`); + + expect(action.getAttribute('href')).toMatchInlineSnapshot( + `"elastic.co/guide/en/kibana/current/configuring-tls.html"` + ); + }); + + test('renders warning if encryption key is ephemeral', async () => { + http.get.mockImplementationOnce(async () => ({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: false, + })); + + const { queryByText, queryByRole } = render( + +

{'should render'}

+
+ ); + await act(async () => { + // wait for useEffect to run + }); + + const description = queryByRole(/banner/i); + expect(description!.textContent).toMatchInlineSnapshot( + `"To create an alert, set a value for xpack.encrypted_saved_objects.encryptionKey in your kibana.yml file. Learn how."` + ); + + const action = queryByText(/Learn/i); + expect(action!.textContent).toMatchInlineSnapshot(`"Learn how."`); + expect(action!.getAttribute('href')).toMatchInlineSnapshot( + `"elastic.co/guide/en/kibana/current/alert-action-settings-kb.html#general-alert-action-settings"` + ); + }); + + test('renders warning if encryption key is ephemeral and keys are disabled', async () => { + http.get.mockImplementationOnce(async () => ({ + isSufficientlySecure: false, + hasPermanentEncryptionKey: false, + })); + + const { queryByText } = render( + +

{'should render'}

+
+ ); + await act(async () => { + // wait for useEffect to run + }); + + const description = queryByText(/Transport Layer Security/i); + + expect(description!.textContent).toMatchInlineSnapshot( + `"You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. Learn how"` + ); + + const action = queryByText(/Learn/i); + expect(action!.textContent).toMatchInlineSnapshot(`"Learn how"`); + expect(action!.getAttribute('href')).toMatchInlineSnapshot( + `"elastic.co/guide/en/kibana/current/alerting-getting-started.html#alerting-setup-prerequisites"` + ); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx new file mode 100644 index 000000000000..c967cf5de077 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -0,0 +1,197 @@ +/* + * 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 React, { Fragment } from 'react'; +import { Option, none, some, fold } from 'fp-ts/lib/Option'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiLink, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DocLinksStart, HttpSetup } from 'kibana/public'; + +import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { AlertingFrameworkHealth } from '../../types'; +import { health } from '../lib/alert_api'; +import './health_check.scss'; + +interface Props { + docLinks: Pick; + http: HttpSetup; + inFlyout?: boolean; +} + +export const HealthCheck: React.FunctionComponent = ({ + docLinks, + http, + children, + inFlyout = false, +}) => { + const [alertingHealth, setAlertingHealth] = React.useState>(none); + + React.useEffect(() => { + (async function() { + setAlertingHealth(some(await health({ http }))); + })(); + }, [http]); + + const className = inFlyout ? 'alertingFlyoutHealthCheck' : 'alertingHealthCheck'; + + return pipe( + alertingHealth, + fold( + () => , + healthCheck => { + return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? ( + {children} + ) : !healthCheck.isSufficientlySecure && !healthCheck.hasPermanentEncryptionKey ? ( + + ) : !healthCheck.hasPermanentEncryptionKey ? ( + + ) : ( + + ); + } + ) + ); +}; + +type PromptErrorProps = Pick & { + className?: string; +}; + +const TlsAndEncryptionError = ({ + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + className, +}: PromptErrorProps) => ( + + + + } + body={ +
+

+ {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError', { + defaultMessage: + 'You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. ', + })} + + {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction', + { + defaultMessage: 'Learn how', + } + )} + +

+
+ } + /> +); + +const EncryptionError = ({ + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + className, +}: PromptErrorProps) => ( + + + + } + body={ +
+

+ {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey', + { + defaultMessage: 'To create an alert, set a value for ', + } + )} + {'xpack.encrypted_saved_objects.encryptionKey'} + {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey', + { + defaultMessage: ' in your kibana.yml file. ', + } + )} + + {i18n.translate( + 'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction', + { + defaultMessage: 'Learn how.', + } + )} + +

+
+ } + /> +); + +const TlsError = ({ + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + className, +}: PromptErrorProps) => ( + + + + } + body={ +
+

+ {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsError', { + defaultMessage: + 'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. ', + })} + + {i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsErrorAction', { + defaultMessage: 'Learn how to enable TLS.', + })} + +

+
+ } + /> +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.scss b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.scss new file mode 100644 index 000000000000..fe001ce294ef --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.scss @@ -0,0 +1,3 @@ +.actEmptyConnectorsPrompt__logo + .actEmptyConnectorsPrompt__logo { + margin-left: $euiSize; +} \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx new file mode 100644 index 000000000000..0e956ea56faa --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx @@ -0,0 +1,55 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; +import React, { Fragment } from 'react'; +import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui'; +import './empty_connectors_prompt.scss'; + +export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => void }) => ( + + + + + + +

+ +

+
+
+ } + body={ +

+ +

+ } + actions={ + + + + } + /> +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_prompt.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_prompt.tsx new file mode 100644 index 000000000000..df593d587de3 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_prompt.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + +export const EmptyPrompt = ({ onCTAClicked }: { onCTAClicked: () => void }) => ( + + + + } + body={ +

+ +

+ } + actions={ + + + + } + /> +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.test.tsx deleted file mode 100644 index 28bc02ec3392..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.test.tsx +++ /dev/null @@ -1,72 +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 React, { Fragment } from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { SecurityEnabledCallOut } from './security_call_out'; - -import { EuiCallOut, EuiButton } from '@elastic/eui'; -import { act } from 'react-dom/test-utils'; -import { httpServiceMock } from '../../../../../../src/core/public/mocks'; - -const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' }; - -const http = httpServiceMock.createStartContract(); - -describe('security call out', () => { - let useEffect: any; - - const mockUseEffect = () => { - // make react execute useEffects despite shallow rendering - useEffect.mockImplementationOnce((f: Function) => f()); - }; - - beforeEach(() => { - jest.resetAllMocks(); - useEffect = jest.spyOn(React, 'useEffect'); - mockUseEffect(); - }); - - test('renders nothing while health is loading', async () => { - http.get.mockImplementationOnce(() => new Promise(() => {})); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow(); - }); - - expect(component?.is(Fragment)).toBeTruthy(); - expect(component?.html()).toBe(''); - }); - - test('renders nothing if keys are enabled', async () => { - http.get.mockResolvedValue({ isSufficientlySecure: true }); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow(); - }); - - expect(component?.is(Fragment)).toBeTruthy(); - expect(component?.html()).toBe(''); - }); - - test('renders the callout if keys are disabled', async () => { - http.get.mockImplementationOnce(async () => ({ isSufficientlySecure: false })); - - let component: ShallowWrapper | undefined; - await act(async () => { - component = shallow(); - }); - - expect(component?.find(EuiCallOut).prop('title')).toMatchInlineSnapshot( - `"Enable Transport Layer Security"` - ); - - expect(component?.find(EuiButton).prop('href')).toMatchInlineSnapshot( - `"elastic.co/guide/en/kibana/current/configuring-tls.html"` - ); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.tsx deleted file mode 100644 index 9874a3a0697d..000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/security_call_out.tsx +++ /dev/null @@ -1,75 +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 React, { Fragment } from 'react'; -import { Option, none, some, fold, filter } from 'fp-ts/lib/Option'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { EuiCallOut, EuiButton, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { DocLinksStart, HttpSetup } from 'kibana/public'; - -import { AlertingFrameworkHealth } from '../../types'; -import { health } from '../lib/alert_api'; - -interface Props { - docLinks: Pick; - http: HttpSetup; -} - -export const SecurityEnabledCallOut: React.FunctionComponent = ({ docLinks, http }) => { - const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; - - const [alertingHealth, setAlertingHealth] = React.useState>(none); - - React.useEffect(() => { - async function fetchSecurityConfigured() { - setAlertingHealth(some(await health({ http }))); - } - - fetchSecurityConfigured(); - }, [http]); - - return pipe( - alertingHealth, - filter(healthCheck => !healthCheck?.isSufficientlySecure), - fold( - () => , - () => ( - - -

- -

- - - -
- -
- ) - ) - ); -}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 7c8d798984bf..4d0a9980f223 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -29,8 +29,8 @@ import { hasShowActionsCapability, hasShowAlertsCapability } from './lib/capabil import { ActionsConnectorsList } from './sections/actions_connectors_list/components/actions_connectors_list'; import { AlertsList } from './sections/alerts_list/components/alerts_list'; -import { SecurityEnabledCallOut } from './components/security_call_out'; import { PLUGIN } from './constants/plugin'; +import { HealthCheck } from './components/health_check'; interface MatchParams { section: Section; @@ -88,7 +88,6 @@ export const TriggersActionsUIHome: React.FunctionComponent - @@ -142,9 +141,27 @@ export const TriggersActionsUIHome: React.FunctionComponent {canShowActions && ( - + ( + + + + )} + /> + )} + {canShowAlerts && ( + ( + + + + )} + /> )} - {canShowAlerts && } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss index 3d65b8a799b1..70ad1cae6c1d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss @@ -1,7 +1,3 @@ -.actConnectorsList__logo + .actConnectorsList__logo { - margin-left: $euiSize; -} - .actConnectorsList__tableRowDisabled { background-color: $euiColorLightestShade; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 81693e1d2d9d..47e058f47394 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -10,9 +10,6 @@ import { EuiInMemoryTable, EuiSpacer, EuiButton, - EuiIcon, - EuiEmptyPrompt, - EuiTitle, EuiLink, EuiLoadingSpinner, EuiIconTip, @@ -30,6 +27,7 @@ import { ActionsConnectorsContextProvider } from '../../../context/actions_conne import { checkActionTypeEnabled } from '../../../lib/check_action_type_enabled'; import './actions_connectors_list.scss'; import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../../../../types'; +import { EmptyConnectorsPrompt } from '../../../components/prompts/empty_connectors_prompt'; export const ActionsConnectorsList: React.FunctionComponent = () => { const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies(); @@ -324,51 +322,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> ); - const emptyPrompt = ( - - - - - - -

- -

-
-
- } - body={ -

- -

- } - actions={ - setAddFlyoutVisibility(true)} - > - - - } - /> - ); - const noPermissionPrompt = (

{ )} {data.length !== 0 && table} - {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && emptyPrompt} + {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && ( + setAddFlyoutVisibility(true)} /> + )} {data.length === 0 && !canSave && noPermissionPrompt} { docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; - mockes.http.get.mockResolvedValue({ isSufficientlySecure: true }); + mockes.http.get.mockResolvedValue({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: true, + }); const alertType = { id: 'my-alert-type', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index e025248fad52..0620ced6365a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -25,7 +25,7 @@ import { Alert, AlertAction, IErrorObject } from '../../../types'; import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; import { createAlert } from '../../lib/alert_api'; -import { AlertActionSecurityCallOut } from '../../components/alert_action_security_call_out'; +import { HealthCheck } from '../../components/health_check'; import { PLUGIN } from '../../constants/plugin'; interface AlertAddProps { @@ -154,62 +154,54 @@ export const AlertAdd = ({

- - - - - - - - - {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} - - - - { - setIsSaving(true); - const savedAlert = await onSaveAlert(); - setIsSaving(false); - if (savedAlert) { - closeFlyout(); - if (reloadAlerts) { - reloadAlerts(); + + + + + + + + + {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + { + setIsSaving(true); + const savedAlert = await onSaveAlert(); + setIsSaving(false); + if (savedAlert) { + closeFlyout(); + if (reloadAlerts) { + reloadAlerts(); + } } - } - }} - > - - - - - + }} + > + + + + + + ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 6fcfb463c4c7..916ba368e073 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -36,7 +36,10 @@ describe('alert_edit', () => { docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; - mockedCoreSetup.http.get.mockResolvedValue({ isSufficientlySecure: true }); + mockedCoreSetup.http.get.mockResolvedValue({ + isSufficientlySecure: true, + hasPermanentEncryptionKey: true, + }); const alertType = { id: 'my-alert-type', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 3f27a7860baf..4255eca83be4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -26,7 +26,7 @@ import { Alert, AlertAction, IErrorObject } from '../../../types'; import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; import { updateAlert } from '../../lib/alert_api'; -import { AlertActionSecurityCallOut } from '../../components/alert_action_security_call_out'; +import { HealthCheck } from '../../components/health_check'; import { PLUGIN } from '../../constants/plugin'; interface AlertEditProps { @@ -137,77 +137,69 @@ export const AlertEdit = ({ - - - {hasActionsDisabled && ( - - - - - )} - - - - - - - {i18n.translate('xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} - - - - { - setIsSaving(true); - const savedAlert = await onSaveAlert(); - setIsSaving(false); - if (savedAlert) { - closeFlyout(); - if (reloadAlerts) { - reloadAlerts(); - } - } - }} - > - + + {hasActionsDisabled && ( + + - - - - + + + )} + + + + + + + {i18n.translate('xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + { + setIsSaving(true); + const savedAlert = await onSaveAlert(); + setIsSaving(false); + if (savedAlert) { + closeFlyout(); + if (reloadAlerts) { + reloadAlerts(); + } + } + }} + > + + + + + + ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index afd3299f0c2b..5d59180ff572 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -15,7 +15,6 @@ import { EuiFlexItem, EuiIcon, EuiSpacer, - EuiEmptyPrompt, EuiLink, EuiLoadingSpinner, } from '@elastic/eui'; @@ -36,6 +35,7 @@ import { loadActionTypes } from '../../../lib/action_connector_api'; import { hasDeleteAlertsCapability, hasSaveAlertsCapability } from '../../../lib/capabilities'; import { routeToAlertDetails, DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; +import { EmptyPrompt } from '../../../components/prompts/empty_prompt'; const ENTER_KEY = 13; @@ -292,44 +292,6 @@ export const AlertsList: React.FunctionComponent = () => { ); } - const emptyPrompt = ( - - - - } - body={ -

- -

- } - actions={ - setAlertFlyoutVisibility(true)} - > - - - } - /> - ); - const table = ( @@ -473,7 +435,7 @@ export const AlertsList: React.FunctionComponent = () => {
) : ( - emptyPrompt + setAlertFlyoutVisibility(true)} /> )} Date: Thu, 9 Apr 2020 00:56:52 +0300 Subject: [PATCH 13/46] [APM] Agent remote configuration: changes in Java property descriptions (#62282) * [APM] Agent remote configuration: changes in Java property descriptions * Removing newlines * Update snapshot Co-authored-by: Elastic Machine Co-authored-by: Brandon Morelli Co-authored-by: Nathan L Smith --- .../__snapshots__/index.test.ts.snap | 5 +++-- .../setting_definitions/java_settings.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index bc435179762a..49840d2157af 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -153,8 +153,9 @@ Array [ }, Object { "key": "stress_monitor_gc_stress_threshold", - "type": "boolean", - "validationName": "(\\"true\\" | \\"false\\")", + "type": "float", + "validationError": "Must be a number between 0.000 and 1", + "validationName": "numberFloatRt", }, Object { "key": "stress_monitor_system_cpu_relief_threshold", diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index bb050076b9f9..2e10c7437854 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -20,7 +20,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.enableLogCorrelation.description', { defaultMessage: - "A boolean specifying if the agent should integrate into SLF4J's MDC to enable trace-log correlation. If set to `true`, the agent will set the `trace.id` and `transaction.id` for the currently active spans and transactions to the MDC. While it's allowed to enable this setting at runtime, you can't disable it without a restart." + "A boolean specifying if the agent should integrate into SLF4J's MDC to enable trace-log correlation. If set to `true`, the agent will set the `trace.id` and `transaction.id` for the currently active spans and transactions to the MDC. Since Java agent version 1.16.0, the agent also adds `error.id` of captured error to the MDC just before the error message is logged. NOTE: While it's allowed to enable this setting at runtime, you can't disable it without a restart." } ), includeAgents: ['java'] @@ -41,7 +41,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.circuitBreakerEnabled.description', { defaultMessage: - 'A boolean specifying whether the circuit breaker should be enabled or not. When enabled, the agent periodically polls stress monitors to detect system/process/JVM stress state. If ANY of the monitors detects a stress indication, the agent will become inactive, as if the `active` configuration option has been set to `false`, thus reducing resource consumption to a minimum. When inactive, the agent continues polling the same monitors in order to detect whether the stress state has been relieved. If ALL monitors approve that the system/process/JVM is not under stress anymore, the agent will resume and become fully functional.' + 'A boolean specifying whether the circuit breaker should be enabled or not. When enabled, the agent periodically polls stress monitors to detect system/process/JVM stress state. If ANY of the monitors detects a stress indication, the agent will pause, as if the `recording` configuration option has been set to `false`, thus reducing resource consumption to a minimum. When paused, the agent continues polling the same monitors in order to detect whether the stress state has been relieved. If ALL monitors approve that the system/process/JVM is not under stress anymore, the agent will resume and become fully functional.' } ), includeAgents: ['java'] @@ -52,7 +52,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.stressMonitorGcStressThreshold.label', { defaultMessage: 'Stress monitor gc stress threshold' } ), - type: 'boolean', + type: 'float', category: 'Circuit-Breaker', defaultValue: '0.95', description: i18n.translate( @@ -155,7 +155,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansEnabled.description', { defaultMessage: - 'Set to `true` to make the agent create spans for method executions based on async-profiler, a sampling aka statistical profiler. Due to the nature of how sampling profilers work, the duration of the inferred spans are not exact, but only estimations. The `profiling_inferred_spans_sampling_interval` lets you fine tune the trade-off between accuracy and overhead. The inferred spans are created after a profiling session has ended. This means there is a delay between the regular and the inferred spans being visible in the UI. This feature is not available on Windows' + 'Set to `true` to make the agent create spans for method executions based on async-profiler, a sampling aka statistical profiler. Due to the nature of how sampling profilers work, the duration of the inferred spans are not exact, but only estimations. The `profiling_inferred_spans_sampling_interval` lets you fine tune the trade-off between accuracy and overhead. The inferred spans are created after a profiling session has ended. This means there is a delay between the regular and the inferred spans being visible in the UI. NOTE: This feature is not available on Windows.' } ), includeAgents: ['java'] @@ -209,7 +209,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansIncludedClasses.description', { defaultMessage: - 'If set, the agent will only create inferred spans for methods which match this list. Setting a value may slightly increase performance and can reduce clutter by only creating spans for the classes you are interested in. Example: `org.example.myapp.*` This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' + 'If set, the agent will only create inferred spans for methods which match this list. Setting a value may slightly reduce overhead and can reduce clutter by only creating spans for the classes you are interested in. This option supports the wildcard `*`, which matches zero or more characters. Example: `org.example.myapp.*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' } ), includeAgents: ['java'] @@ -228,7 +228,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansExcludedClasses.description', { defaultMessage: - 'Excludes classes for which no profiler-inferred spans should be created. This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' + 'Excludes classes for which no profiler-inferred spans should be created. This option supports the wildcard `*`, which matches zero or more characters. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' } ), includeAgents: ['java'] From 0c35762f2702c813dfb74c37bae4364eecfc95c4 Mon Sep 17 00:00:00 2001 From: Brittany Joiner Date: Wed, 8 Apr 2020 18:08:13 -0500 Subject: [PATCH 14/46] Add Error Exception Type Column (#59596) * start of error exception type * width and link * removed extra line * updated snapshot * updated snapshots * updated snapshots * Update snapshots Co-authored-by: Elastic Machine Co-authored-by: Nathan L Smith --- .../__test__/__snapshots__/List.test.tsx.snap | 287 ++++++++++++++++-- .../app/ErrorGroupOverview/List/index.tsx | 36 ++- .../elasticsearch_fieldnames.test.ts.snap | 6 + .../apm/common/elasticsearch_fieldnames.ts | 1 + .../errors/__snapshots__/queries.test.ts.snap | 2 + .../apm/server/lib/errors/get_error_groups.ts | 6 +- 6 files changed, 306 insertions(+), 32 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 205a303bcf47..afa0cb51cd10 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -11,6 +11,12 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` "sortable": false, "width": "96px", }, + Object { + "field": "type", + "name": "Type", + "render": [Function], + "sortable": false, + }, Object { "field": "message", "name": "Error message and culprit", @@ -142,7 +148,28 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` +
+ + Type + +
+ + List should render empty state 1`] = ` List should render empty state 1`] = ` aria-live="polite" aria-sort="descending" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_occurrenceCount_3" + data-test-subj="tableHeaderCell_occurrenceCount_4" role="columnheader" scope="col" style={ @@ -225,7 +252,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` aria-live="polite" aria-sort="none" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_latestOccurrenceAt_4" + data-test-subj="tableHeaderCell_latestOccurrenceAt_5" role="columnheader" scope="col" style={ @@ -264,7 +291,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > List should render with data 1`] = ` font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } +.c2 { + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .c1 { max-width: 100%; white-space: nowrap; @@ -301,7 +335,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` text-overflow: ellipsis; } -.c2 { +.c3 { font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; font-size: 16px; max-width: 100%; @@ -310,7 +344,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` text-overflow: ellipsis; } -.c3 { +.c4 { font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } @@ -324,6 +358,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` "sortable": false, "width": "96px", }, + Object { + "field": "type", + "name": "Type", + "render": [Function], + "sortable": false, + }, Object { "field": "message", "name": "Error message and culprit", @@ -486,7 +526,28 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` +
+ + Type + +
+ + List should render with data 1`] = ` List should render with data 1`] = ` aria-live="polite" aria-sort="descending" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_occurrenceCount_3" + data-test-subj="tableHeaderCell_occurrenceCount_4" role="columnheader" scope="col" style={ @@ -569,7 +630,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` aria-live="polite" aria-sort="none" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_latestOccurrenceAt_4" + data-test-subj="tableHeaderCell_latestOccurrenceAt_5" role="columnheader" scope="col" style={ @@ -642,6 +703,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` + +
+ Type +
+ + List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -812,6 +916,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -982,6 +1129,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -1152,6 +1342,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index b26833c02fe2..250b9a5d188d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -23,6 +23,8 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; import { ManagedTable } from '../../../shared/ManagedTable'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; +import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink'; +import { APMQueryParams } from '../../../shared/Links/url_helpers'; const GroupIdLink = styled(ErrorDetailLink)` font-family: ${fontFamilyCode}; @@ -32,6 +34,10 @@ const MessageAndCulpritCell = styled.div` ${truncate('100%')}; `; +const ErrorLink = styled(ErrorOverviewLink)` + ${truncate('100%')}; +`; + const MessageLink = styled(ErrorDetailLink)` font-family: ${fontFamilyCode}; font-size: ${fontSizes.large}; @@ -48,9 +54,8 @@ interface Props { const ErrorGroupList: React.FC = props => { const { items } = props; - const { - urlParams: { serviceName } - } = useUrlParams(); + const { urlParams } = useUrlParams(); + const { serviceName } = urlParams; if (!serviceName) { throw new Error('Service name is required'); @@ -73,6 +78,29 @@ const ErrorGroupList: React.FC = props => { ); } }, + { + name: i18n.translate('xpack.apm.errorsTable.typeColumnLabel', { + defaultMessage: 'Type' + }), + field: 'type', + sortable: false, + render: (type: string, item: ErrorGroupListAPIResponse[0]) => { + return ( + + {type} + + ); + } + }, { name: i18n.translate( 'xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel', @@ -150,7 +178,7 @@ const ErrorGroupList: React.FC = props => { ) } ], - [serviceName] + [serviceName, urlParams] ); return ( diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 5de82a9ee878..54dd4704edfc 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -16,6 +16,8 @@ exports[`Error ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Error ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Error ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Error ERROR_GROUP_ID 1`] = `"grouping key"`; exports[`Error ERROR_LOG_LEVEL 1`] = `undefined`; @@ -144,6 +146,8 @@ exports[`Span ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Span ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Span ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Span ERROR_GROUP_ID 1`] = `undefined`; exports[`Span ERROR_LOG_LEVEL 1`] = `undefined`; @@ -272,6 +276,8 @@ exports[`Transaction ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Transaction ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Transaction ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Transaction ERROR_GROUP_ID 1`] = `undefined`; exports[`Transaction ERROR_LOG_LEVEL 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index bc1b346f50da..d5c3f91eb924 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -60,6 +60,7 @@ export const ERROR_LOG_LEVEL = 'error.log.level'; export const ERROR_LOG_MESSAGE = 'error.log.message'; export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_TYPE = 'error.exception.type'; export const ERROR_PAGE_URL = 'error.page.url'; // METRICS diff --git a/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap index b9ac9d543170..982ad558dc91 100644 --- a/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap @@ -73,6 +73,7 @@ Object { "error.log.message", "error.exception.message", "error.exception.handled", + "error.exception.type", "error.culprit", "error.grouping_key", "@timestamp", @@ -148,6 +149,7 @@ Object { "error.log.message", "error.exception.message", "error.exception.handled", + "error.exception.type", "error.culprit", "error.grouping_key", "@timestamp", diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index 8ea6df5a9898..5221d737866f 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -8,6 +8,7 @@ import { ERROR_CULPRIT, ERROR_EXC_HANDLED, ERROR_EXC_MESSAGE, + ERROR_EXC_TYPE, ERROR_GROUP_ID, ERROR_LOG_MESSAGE } from '../../../common/elasticsearch_fieldnames'; @@ -67,6 +68,7 @@ export async function getErrorGroups({ ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, ERROR_CULPRIT, ERROR_GROUP_ID, '@timestamp' @@ -99,6 +101,7 @@ export async function getErrorGroups({ exception?: Array<{ handled?: boolean; message?: string; + type?: string; }>; culprit: APMError['error']['culprit']; grouping_key: APMError['error']['grouping_key']; @@ -120,7 +123,8 @@ export async function getErrorGroups({ culprit: source.error.culprit, groupId: source.error.grouping_key, latestOccurrenceAt: source['@timestamp'], - handled: source.error.exception?.[0].handled + handled: source.error.exception?.[0].handled, + type: source.error.exception?.[0].type }; }); From c643148f3613df41565a25e49b0f8c88fb17876a Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Wed, 8 Apr 2020 17:36:20 -0600 Subject: [PATCH 15/46] [SIEM][Detection Engine] Fix rule notification critical bugs ## Summary Fixes critical bugs found during testing of the rule notification. * Fixes a bug where when you turn on rules quickly such as ML rules you would see these message below. This message can also be seen when you first create a rule with an action notification. This is a race condition with how we update rules multiple times when we really should only update it once and do it before enabling a rule ``` server log [12:18:35.986] [error][alerting][alerting][plugins][plugins] Executing Alert "63b828b5-24b9-4d55-83ee-8a8201fe2d76" has resulted in Error: [security_exception] missing authentication credentials for REST request [/_security/user/_has_privileges], with { header={ WWW-Authenticate={ 0="Bearer realm=\"security\"" & 1="ApiKey" & 2="Basic realm=\"security\" charset=\"UTF-8\"" } } ``` * Fixes a bug where we were using `ruleParams.interval` when we should have been using `ruleAlertSavedObject.attributes.schedule.interval`. When changing rule notifications to run daily, weekly, etc.. you would see this exception being thrown: ``` server log [21:23:08.028] [error][alerting][alerting][plugins][plugins] Executing Alert "fedcccc0-7c69-4e2f-83f8-d8ee88ab5484" has resulted in Error: "from" or "to" was not provided to signals count query ``` * Fixes misc typing issues found * Fixes it to where we no longer make multiple DB calls but rather pass down objects we already have. * Changes the work flow to where we only update, create, or patch the alerting object once which fixes the race condition and improves the backend performance. * Removes left over unused code * Applied https://en.wikipedia.org/wiki/Single-entry_single-exit to functions where it made sense and easier to read. ### Checklist - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios --- .../legacy/plugins/siem/common/constants.ts | 2 - .../notifications/add_tags.ts | 2 +- .../create_notifications.test.ts | 2 - .../notifications/create_notifications.ts | 5 +- .../rules_notification_alert_type.ts | 4 +- .../detection_engine/notifications/types.ts | 9 ++-- .../update_notifications.test.ts | 9 +--- .../notifications/update_notifications.ts | 27 ++++------ .../routes/rules/create_rules_bulk_route.ts | 1 + .../routes/rules/create_rules_route.ts | 1 + .../routes/rules/import_rules_route.ts | 1 + .../routes/rules/update_rules_bulk_route.ts | 1 + .../routes/rules/update_rules_route.ts | 1 + .../create_rule_actions_saved_object.ts | 8 +-- .../delete_rule_actions_saved_object.ts | 9 ++-- .../get_rule_actions_saved_object.ts | 16 +++--- ...ate_or_create_rule_actions_saved_object.ts | 11 ++-- .../update_rule_actions_saved_object.ts | 15 ++---- .../rules/create_rules.test.ts | 1 + .../detection_engine/rules/create_rules.ts | 4 +- .../rules/install_prepacked_rules.ts | 1 + .../lib/detection_engine/rules/patch_rules.ts | 2 +- .../lib/detection_engine/rules/types.ts | 6 +-- .../rules/update_rule_actions.ts | 54 ------------------- .../rules/update_rules.test.ts | 3 ++ .../detection_engine/rules/update_rules.ts | 6 ++- .../rules/update_rules_notifications.ts | 9 +--- .../lib/detection_engine/signals/types.ts | 7 ++- 28 files changed, 75 insertions(+), 142 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 662fb8fb8ef6..22f1b3beffa3 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -65,8 +65,6 @@ export const INTERNAL_IDENTIFIER = '__internal'; export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id`; export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id`; export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable`; -export const INTERNAL_NOTIFICATION_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_id`; -export const INTERNAL_NOTIFICATION_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_rule_id`; /** * Detection engine routes diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts index 6955e57d099b..14b2e1ae9e36 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts @@ -6,5 +6,5 @@ import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; -export const addTags = (tags: string[] = [], ruleAlertId: string): string[] => +export const addTags = (tags: string[], ruleAlertId: string): string[] => Array.from(new Set([...tags, `${INTERNAL_RULE_ALERT_ID_KEY}:${ruleAlertId}`])); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts index 073251b68f41..3878f5dae888 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts @@ -24,7 +24,6 @@ describe('createNotifications', () => { enabled: true, interval: '', name: '', - tags: [], }); expect(alertsClient.create).toHaveBeenCalledWith( @@ -52,7 +51,6 @@ describe('createNotifications', () => { enabled: true, interval: '', name: '', - tags: [], }); expect(alertsClient.create).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts index 3a1697f1c8af..ccd7576255d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts @@ -17,12 +17,11 @@ export const createNotifications = async ({ ruleAlertId, interval, name, - tags, }: CreateNotificationParams): Promise => alertsClient.create({ data: { name, - tags: addTags(tags, ruleAlertId), + tags: addTags([], ruleAlertId), alertTypeId: NOTIFICATIONS_ID, consumer: APP_ID, params: { @@ -30,7 +29,7 @@ export const createNotifications = async ({ }, schedule: { interval }, enabled, - actions: actions?.map(transformRuleToAlertAction), + actions: actions.map(transformRuleToAlertAction), throttle: null, }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index ced81098c9f8..e4ad53de742d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -45,7 +45,9 @@ export const rulesNotificationAlertType = ({ const ruleParams = { ...ruleAlertParams, name: ruleName, id: ruleAlertSavedObject.id }; const fromInMs = parseScheduleDates( - previousStartedAt ? previousStartedAt.toISOString() : `now-${ruleParams.interval}` + previousStartedAt + ? previousStartedAt.toISOString() + : `now-${ruleAlertSavedObject.attributes.schedule.interval}` )?.format('x'); const toInMs = parseScheduleDates(startedAt.toISOString())?.format('x'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts index 128a7965cd7d..32a8737adc7c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts @@ -45,10 +45,11 @@ export interface Clients { alertsClient: AlertsClient; } -export type UpdateNotificationParams = Omit & { +export type UpdateNotificationParams = Omit< + NotificationAlertParams, + 'interval' | 'actions' | 'tags' +> & { actions: RuleAlertAction[]; - id?: string; - tags?: string[]; interval: string | null | undefined; ruleAlertId: string; } & Clients; @@ -64,8 +65,6 @@ export interface NotificationAlertParams { ruleAlertId: string; interval: string; name: string; - tags?: string[]; - throttle?: null; } export type CreateNotificationParams = NotificationAlertParams & Clients; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts index 4c077dd9fc1f..e1f7526438c3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts @@ -9,6 +9,7 @@ import { updateNotifications } from './update_notifications'; import { readNotifications } from './read_notifications'; import { createNotifications } from './create_notifications'; import { getNotificationResult } from '../routes/__mocks__/request_responses'; +import { UpdateNotificationParams } from './types'; jest.mock('./read_notifications'); jest.mock('./create_notifications'); @@ -30,7 +31,6 @@ describe('updateNotifications', () => { enabled: true, interval: '10m', name: '', - tags: [], }); expect(alertsClient.update).toHaveBeenCalledWith( @@ -48,14 +48,13 @@ describe('updateNotifications', () => { it('should create a new notification if did not exist', async () => { (readNotifications as jest.Mock).mockResolvedValue(null); - const params = { + const params: UpdateNotificationParams = { alertsClient, actions: [], ruleAlertId: 'new-rule-id', enabled: true, interval: '10m', name: '', - tags: [], }; await updateNotifications(params); @@ -73,7 +72,6 @@ describe('updateNotifications', () => { enabled: true, interval: null, name: '', - tags: [], }); expect(alertsClient.delete).toHaveBeenCalledWith( @@ -98,7 +96,6 @@ describe('updateNotifications', () => { enabled: true, interval: '10m', name: '', - tags: [], }); expect(alertsClient.update).toHaveBeenCalledWith( @@ -125,10 +122,8 @@ describe('updateNotifications', () => { alertsClient, actions: [], enabled: true, - id: notification.id, ruleAlertId, name: notification.name, - tags: notification.tags, interval: null, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts index 3197d21c0e95..ac0de406aceb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts @@ -15,50 +15,41 @@ export const updateNotifications = async ({ alertsClient, actions, enabled, - id, ruleAlertId, name, - tags, interval, }: UpdateNotificationParams): Promise => { - const notification = await readNotifications({ alertsClient, id, ruleAlertId }); + const notification = await readNotifications({ alertsClient, id: undefined, ruleAlertId }); if (interval && notification) { - const result = await alertsClient.update({ + return alertsClient.update({ id: notification.id, data: { - tags: addTags(tags, ruleAlertId), + tags: addTags([], ruleAlertId), name, schedule: { interval, }, - actions: actions?.map(transformRuleToAlertAction), + actions: actions.map(transformRuleToAlertAction), params: { ruleAlertId, }, throttle: null, }, }); - return result; - } - - if (interval && !notification) { - const result = await createNotifications({ + } else if (interval && !notification) { + return createNotifications({ alertsClient, enabled, - tags, name, interval, actions, ruleAlertId, }); - return result; - } - - if (!interval && notification) { + } else if (!interval && notification) { await alertsClient.delete({ id: notification.id }); return null; + } else { + return null; } - - return null; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index d0e36515946a..5377e9039785 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -144,6 +144,7 @@ export const createRulesBulkRoute = (router: IRouter) => { note, version, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is set to rule, otherwise we are a notification and should not enable it, }); const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 6038ad209532..9a329b78b8f1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -132,6 +132,7 @@ export const createRulesRoute = (router: IRouter): void => { note, version: 1, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is rule, otherwise we are a notification and should not enable it, }); const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 43e970702ba7..29ae5056a3ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -196,6 +196,7 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config note, version, lists, + actions: [], // Actions are not imported nor exported at this time }); resolve({ rule_id: ruleId, status_code: 200 }); } else if (rule != null && request.query.overwrite) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 9916972f4184..36e15780f5cb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -122,6 +122,7 @@ export const updateRulesBulkRoute = (router: IRouter) => { note, version, lists, + actions, }); if (rule != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 21dd2a4429cc..0444c757a9b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -118,6 +118,7 @@ export const updateRulesRoute = (router: IRouter) => { note, version, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is rule, otherwise we are a notification and should not enable it }); if (rule != null) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts index 97cfc1d2d9ea..991690d901d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts @@ -9,6 +9,7 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getThrottleOptions, getRuleActionsFromSavedObject } from './utils'; +import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; interface CreateRuleActionsSavedObject { ruleAlertId: string; @@ -22,12 +23,7 @@ export const createRuleActionsSavedObject = async ({ savedObjectsClient, actions = [], throttle, -}: CreateRuleActionsSavedObject): Promise<{ - id: string; - actions: RuleAlertAction[]; - alertThrottle: string | null; - ruleThrottle: string; -}> => { +}: CreateRuleActionsSavedObject): Promise => { const ruleActionsSavedObject = await savedObjectsClient.create< IRuleActionsAttributesSavedObjectAttributes >(ruleActionsSavedObjectType, { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts index 864281da5baf..91489334940b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts @@ -18,8 +18,9 @@ export const deleteRuleActionsSavedObject = async ({ savedObjectsClient, }: DeleteRuleActionsSavedObject): Promise<{} | null> => { const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - - if (!ruleActions) return null; - - return savedObjectsClient.delete(ruleActionsSavedObjectType, ruleActions.id); + if (ruleActions != null) { + return savedObjectsClient.delete(ruleActionsSavedObjectType, ruleActions.id); + } else { + return null; + } }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts index 61b544db5a18..dad35f6cb1f9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts @@ -15,15 +15,17 @@ interface GetRuleActionsSavedObject { savedObjectsClient: AlertServices['savedObjectsClient']; } -export const getRuleActionsSavedObject = async ({ - ruleAlertId, - savedObjectsClient, -}: GetRuleActionsSavedObject): Promise<{ +export interface RulesActionsSavedObject { id: string; actions: RuleAlertAction[]; alertThrottle: string | null; ruleThrottle: string; -} | null> => { +} + +export const getRuleActionsSavedObject = async ({ + ruleAlertId, + savedObjectsClient, +}: GetRuleActionsSavedObject): Promise => { const { saved_objects } = await savedObjectsClient.find< IRuleActionsAttributesSavedObjectAttributes >({ @@ -35,7 +37,7 @@ export const getRuleActionsSavedObject = async ({ if (!saved_objects[0]) { return null; + } else { + return getRuleActionsFromSavedObject(saved_objects[0]); } - - return getRuleActionsFromSavedObject(saved_objects[0]); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts index adc87150f89a..d79c61f6200e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts @@ -24,16 +24,17 @@ export const updateOrCreateRuleActionsSavedObject = async ({ actions, throttle, }: UpdateOrCreateRuleActionsSavedObject): Promise => { - const currentRuleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); + const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - if (currentRuleActions) { + if (ruleActions != null) { return updateRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle, - }) as Promise; + ruleActions, + }); + } else { + return createRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle }); } - - return createRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts index a15005110c56..2a2c84838ed9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts @@ -6,7 +6,7 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; -import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; +import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { getThrottleOptions } from './utils'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; @@ -16,6 +16,7 @@ interface DeleteRuleActionsSavedObject { savedObjectsClient: AlertServices['savedObjectsClient']; actions: RuleAlertAction[] | undefined; throttle: string | null | undefined; + ruleActions: RulesActionsSavedObject; } export const updateRuleActionsSavedObject = async ({ @@ -23,16 +24,8 @@ export const updateRuleActionsSavedObject = async ({ savedObjectsClient, actions, throttle, -}: DeleteRuleActionsSavedObject): Promise<{ - ruleThrottle: string; - alertThrottle: string | null; - actions: RuleAlertAction[]; - id: string; -} | null> => { - const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - - if (!ruleActions) return null; - + ruleActions, +}: DeleteRuleActionsSavedObject): Promise => { const throttleOptions = throttle ? getThrottleOptions(throttle) : { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts index 4c8d0f51f251..a60f1d417797 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts @@ -34,6 +34,7 @@ describe('createRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.create).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index bebf4f350483..91effb4741b8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { Alert } from '../../../../../../../plugins/alerting/common'; import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRuleParams } from './types'; @@ -42,6 +43,7 @@ export const createRules = async ({ note, version, lists, + actions, }: CreateRuleParams): Promise => { // TODO: Remove this and use regular lists once the feature is stable for a release const listsParam = hasListsFeature() ? { lists } : {}; @@ -81,7 +83,7 @@ export const createRules = async ({ }, schedule: { interval }, enabled, - actions: [], + actions: actions.map(transformRuleToAlertAction), throttle: null, }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index bcbe460fb6a6..6d4bacb9cc24 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -83,6 +83,7 @@ export const installPrepackagedRules = ( note, version, lists, + actions: [], // At this time there is no pre-packaged actions }), ]; }, []); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index d7655a15499e..5c4889ec5fd6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -120,7 +120,7 @@ export const patchRules = async ({ id: rule.id, data: { tags: addTags(tags ?? rule.tags, rule.params.ruleId, immutable ?? rule.params.immutable), - throttle: rule.throttle, + throttle: null, name: calculateName({ updatedName: name, originalName: rule.name }), schedule: { interval: calculateInterval(interval, rule.schedule.interval), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index 38b1097a845f..b1bed5d71615 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -142,12 +142,12 @@ export interface Clients { actionsClient: ActionsClient; } -export type PatchRuleParams = Partial> & { +export type PatchRuleParams = Partial> & { id: string | undefined | null; savedObjectsClient: SavedObjectsClientContract; } & Clients; -export type UpdateRuleParams = Omit & { +export type UpdateRuleParams = Omit & { id: string | undefined | null; savedObjectsClient: SavedObjectsClientContract; } & Clients; @@ -157,7 +157,7 @@ export type DeleteRuleParams = Clients & { ruleId: string | undefined | null; }; -export type CreateRuleParams = Omit & { +export type CreateRuleParams = Omit & { ruleId: string; } & Clients; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts deleted file mode 100644 index e6ee1e6a2976..000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts +++ /dev/null @@ -1,54 +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 { - AlertsClient, - AlertServices, - PartialAlert, -} from '../../../../../../../plugins/alerting/server'; -import { getRuleActionsSavedObject } from '../rule_actions/get_rule_actions_saved_object'; -import { readRules } from './read_rules'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; - -interface UpdateRuleActions { - alertsClient: AlertsClient; - savedObjectsClient: AlertServices['savedObjectsClient']; - ruleAlertId: string; -} - -export const updateRuleActions = async ({ - alertsClient, - savedObjectsClient, - ruleAlertId, -}: UpdateRuleActions): Promise => { - const rule = await readRules({ alertsClient, id: ruleAlertId }); - if (rule == null) { - return null; - } - - const ruleActions = await getRuleActionsSavedObject({ - savedObjectsClient, - ruleAlertId, - }); - - if (!ruleActions) { - return null; - } - - return alertsClient.update({ - id: ruleAlertId, - data: { - actions: !ruleActions.alertThrottle - ? ruleActions.actions.map(transformRuleToAlertAction) - : [], - throttle: null, - name: rule.name, - tags: rule.tags, - schedule: rule.schedule, - params: rule.params, - }, - }); -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts index ca299db6ace5..72f4cbcbe68e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts @@ -35,6 +35,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.disable).toHaveBeenCalledWith( @@ -61,6 +62,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.enable).toHaveBeenCalledWith( @@ -89,6 +91,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.update).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index 0e70e05f4de7..99326768ed33 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PartialAlert } from '../../../../../../../plugins/alerting/server'; import { readRules } from './read_rules'; import { IRuleSavedAttributesSavedObjectAttributes, UpdateRuleParams } from './types'; @@ -46,6 +47,7 @@ export const updateRules = async ({ lists, anomalyThreshold, machineLearningJobId, + actions, }: UpdateRuleParams): Promise => { const rule = await readRules({ alertsClient, ruleId, id }); if (rule == null) { @@ -90,8 +92,8 @@ export const updateRules = async ({ tags: addTags(tags, rule.params.ruleId, rule.params.immutable), name, schedule: { interval }, - actions: rule.actions, - throttle: rule.throttle, + actions: actions.map(transformRuleToAlertAction), + throttle: null, params: { description, ruleId: rule.params.ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts index bb66a5ee1342..994a54048b71 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts @@ -8,7 +8,6 @@ import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { AlertsClient, AlertServices } from '../../../../../../../plugins/alerting/server'; import { updateOrCreateRuleActionsSavedObject } from '../rule_actions/update_or_create_rule_actions_saved_object'; import { updateNotifications } from '../notifications/update_notifications'; -import { updateRuleActions } from './update_rule_actions'; import { RuleActions } from '../rule_actions/types'; interface UpdateRulesNotifications { @@ -37,19 +36,13 @@ export const updateRulesNotifications = async ({ throttle, }); - await updateRuleActions({ - alertsClient, - savedObjectsClient, - ruleAlertId, - }); - await updateNotifications({ alertsClient, ruleAlertId, enabled, name, actions: ruleActions.actions, - interval: ruleActions?.alertThrottle, + interval: ruleActions.alertThrottle, }); return ruleActions; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index d4469351de54..040e32aa0d36 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -162,5 +162,10 @@ export interface AlertAttributes { } export interface RuleAlertAttributes extends AlertAttributes { - params: Omit & { ruleId: string }; + params: Omit< + RuleAlertParams, + 'ruleId' | 'name' | 'enabled' | 'interval' | 'tags' | 'actions' | 'throttle' + > & { + ruleId: string; + }; } From 274cb805e1ed5138b0e0cd285aa9d420be5ce2b4 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Wed, 8 Apr 2020 19:58:50 -0400 Subject: [PATCH 16/46] =?UTF-8?q?[SIEM]=20[Detection=20Engine]=20Fixes=20b?= =?UTF-8?q?ug=20when=20notification=20doesn't=E2=80=A6=20(#63013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set refresh on bulk create to 'wait_for' when actions are present, so we do not respond until the newly indexed signals are searchable. * set refresh on bulk create to 'wait_for' when actions are present, so we do not respond until the newly indexed signals are searchable * fix types in tests --- .../signals/bulk_create_ml_signals.ts | 3 +- .../signals/search_after_bulk_create.test.ts | 8 +++++ .../signals/search_after_bulk_create.ts | 6 +++- .../signals/signal_rule_alert_type.test.ts | 32 +++++++++++++++++++ .../signals/signal_rule_alert_type.ts | 3 ++ .../signals/single_bulk_create.test.ts | 6 ++++ .../signals/single_bulk_create.ts | 6 ++-- .../siem/server/lib/detection_engine/types.ts | 2 ++ 8 files changed, 62 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index 355041d9efbd..ba8938f116fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -10,7 +10,7 @@ import { SearchResponse } from 'elasticsearch'; import { Logger } from '../../../../../../../../src/core/server'; import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; import { AnomalyResults, Anomaly } from '../../machine_learning'; @@ -29,6 +29,7 @@ interface BulkCreateMlSignalsParams { updatedBy: string; interval: string; enabled: boolean; + refresh: RefreshTypes; tags: string[]; throttle: string; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 414270ffcdd5..81600b0b8dd9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -52,6 +52,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -126,6 +127,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -156,6 +158,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -198,6 +201,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -240,6 +244,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -284,6 +289,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -328,6 +334,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -374,6 +381,7 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index ff81730bc4a7..3a964cb91fbd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -6,7 +6,7 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { Logger } from '../../../../../../../../src/core/server'; import { singleSearchAfter } from './single_search_after'; import { singleBulkCreate } from './single_bulk_create'; @@ -30,6 +30,7 @@ interface SearchAfterAndBulkCreateParams { enabled: boolean; pageSize: number; filter: unknown; + refresh: RefreshTypes; tags: string[]; throttle: string; } @@ -61,6 +62,7 @@ export const searchAfterAndBulkCreate = async ({ interval, enabled, pageSize, + refresh, tags, throttle, }: SearchAfterAndBulkCreateParams): Promise => { @@ -92,6 +94,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }); @@ -179,6 +182,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index 3d6f443ce60d..03fb5832fdf4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -105,6 +105,7 @@ describe('rules_notification_alert_type', () => { }; (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ success: true, searchAfterTimes: [], @@ -149,6 +150,37 @@ describe('rules_notification_alert_type', () => { }); }); + it("should set refresh to 'wait_for' when actions are present", async () => { + const ruleAlert = getResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + await alert.executor(payload); + expect((searchAfterAndBulkCreate as jest.Mock).mock.calls[0][0].refresh).toEqual('wait_for'); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); + }); + + it('should set refresh to false when actions are not present', async () => { + await alert.executor(payload); + expect((searchAfterAndBulkCreate as jest.Mock).mock.calls[0][0].refresh).toEqual(false); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); + }); + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { const ruleAlert = getResult(); ruleAlert.actions = [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index faac4a547fc1..0357f906f803 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -98,6 +98,7 @@ export const signalRulesAlertType = ({ params: ruleParams, } = savedObject.attributes; const updatedAt = savedObject.updated_at ?? ''; + const refresh = actions.length ? 'wait_for' : false; const buildRuleMessage = buildRuleMessageFactory({ id: alertId, ruleId, @@ -181,6 +182,7 @@ export const signalRulesAlertType = ({ updatedAt, interval, enabled, + refresh, tags, }); result.success = success; @@ -241,6 +243,7 @@ export const signalRulesAlertType = ({ interval, enabled, pageSize: searchAfterSize, + refresh, tags, throttle, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 56f061cdfa3c..45365b446cbf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -159,6 +159,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -192,6 +193,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -217,6 +219,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -243,6 +246,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -271,6 +275,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); @@ -365,6 +370,7 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index 6dd8823b57e4..fc33d0e15e43 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -9,7 +9,7 @@ import { performance } from 'perf_hooks'; import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { SignalSearchResponse, BulkResponse } from './types'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { generateId, makeFloatString } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { Logger } from '../../../../../../../../src/core/server'; @@ -31,6 +31,7 @@ interface SingleBulkCreateParams { enabled: boolean; tags: string[]; throttle: string; + refresh: RefreshTypes; } /** @@ -77,6 +78,7 @@ export const singleBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }: SingleBulkCreateParams): Promise => { @@ -124,7 +126,7 @@ export const singleBulkCreate = async ({ const start = performance.now(); const response: BulkResponse = await services.callCluster('bulk', { index: signalsIndex, - refresh: false, + refresh, body: bulkBody, }); const end = performance.now(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index d3fa98fd73d3..035f1b10ff8b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -149,3 +149,5 @@ export type CallWithRequest, V> = ( params: T, options?: CallAPIOptions ) => Promise; + +export type RefreshTypes = false | 'wait_for'; From 82e048a5fb57de3afd309e301536a90971edd7de Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 9 Apr 2020 09:28:44 +0200 Subject: [PATCH 17/46] add embed flag to saved object url as well (#62926) --- src/plugins/share/public/components/url_panel_content.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index 2b77b6f4592a..2b1159be8900 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -166,7 +166,7 @@ export class UrlPanelContent extends Component { // Get the application route, after the hash, and remove the #. const parsedAppUrl = parseUrl(parsedUrl.hash.slice(1), true); - return formatUrl({ + let formattedUrl = formatUrl({ protocol: parsedUrl.protocol, auth: parsedUrl.auth, host: parsedUrl.host, @@ -180,6 +180,11 @@ export class UrlPanelContent extends Component { }, }), }); + if (this.props.isEmbedded) { + formattedUrl = this.makeUrlEmbeddable(url); + } + + return formattedUrl; }; private getSnapshotUrl = () => { From 7b0e9d00aafa995c4a6261be7a1446362647e170 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 9 Apr 2020 11:43:51 +0200 Subject: [PATCH 18/46] [ML] Functional transform tests - stabilize source selection (#63087) This PR adds a retry to the transform source selection service method for functional tests. --- .../functional/services/transform_ui/source_selection.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/services/transform_ui/source_selection.ts b/x-pack/test/functional/services/transform_ui/source_selection.ts index d2ef2c67f000..38a819e285d6 100644 --- a/x-pack/test/functional/services/transform_ui/source_selection.ts +++ b/x-pack/test/functional/services/transform_ui/source_selection.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function TransformSourceSelectionProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { async assertSourceListContainsEntry(sourceName: string) { @@ -23,8 +24,10 @@ export function TransformSourceSelectionProvider({ getService }: FtrProviderCont async selectSource(sourceName: string) { await this.filterSourceSelection(sourceName); - await testSubjects.clickWhenNotDisabled(`savedObjectTitle${sourceName}`); - await testSubjects.existOrFail('transformPageCreateTransform'); + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.clickWhenNotDisabled(`savedObjectTitle${sourceName}`); + await testSubjects.existOrFail('transformPageCreateTransform', { timeout: 10 * 1000 }); + }); }, }; } From 7ec635798cd2b7d659fc8a247c1e8f96a2cec678 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 9 Apr 2020 13:39:14 +0300 Subject: [PATCH 19/46] [data.search.aggs]: Clean up TimeBuckets implementation (#62123) * Created tests for time_buckets. Clean up code. Removed getConfig method. * Fixed comments * Fixed comments (2) * Fixes * Removed __cached__ * Fixes for comments * some refactoring * Fixed comment about config Co-authored-by: Elastic Machine --- .../search/aggs/buckets/date_histogram.ts | 29 ++-- .../lib/time_buckets/time_buckets.test.ts | 121 +++++++++++++ .../buckets/lib/time_buckets/time_buckets.ts | 161 +++--------------- .../utils/calculate_auto_time_expression.ts | 13 +- .../public/legacy/build_pipeline.ts | 7 +- 5 files changed, 178 insertions(+), 153 deletions(-) create mode 100644 src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index e6fd259fabc9..57f3aa85ad94 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -44,20 +44,15 @@ const updateTimeBuckets = ( timefilter: TimefilterContract, customBuckets?: IBucketDateHistogramAggConfig['buckets'] ) => { - const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; + const bounds = + agg.params.timeRange && agg.fieldIsTimeField() + ? timefilter.calculateBounds(agg.params.timeRange) + : undefined; const buckets = customBuckets || agg.buckets; - buckets.setBounds(agg.fieldIsTimeField() && bounds); + buckets.setBounds(bounds); buckets.setInterval(agg.params.interval); }; -// TODO: Need to incorporate these properly into TimeBuckets -interface ITimeBuckets { - setBounds: Function; - getScaledDateFormat: TimeBuckets['getScaledDateFormat']; - setInterval: Function; - getInterval: Function; -} - export interface DateHistogramBucketAggDependencies { uiSettings: IUiSettingsClient; query: QuerySetup; @@ -65,7 +60,7 @@ export interface DateHistogramBucketAggDependencies { } export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { - buckets: ITimeBuckets; + buckets: TimeBuckets; } export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHistogramAggConfig { @@ -113,7 +108,12 @@ export const getDateHistogramBucketAgg = ({ if (buckets) return buckets; const { timefilter } = query.timefilter; - buckets = new TimeBuckets({ uiSettings }); + buckets = new TimeBuckets({ + 'histogram:maxBars': uiSettings.get('histogram:maxBars'), + 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + dateFormat: uiSettings.get('dateFormat'), + 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), + }); updateTimeBuckets(this, timefilter, buckets); return buckets; @@ -206,7 +206,8 @@ export const getDateHistogramBucketAgg = ({ ...dateHistogramInterval(interval.expression), }; - const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; + const scaleMetrics = + scaleMetricValues && interval.scaled && interval.scale && interval.scale < 1; if (scaleMetrics && aggs) { const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); const all = every(metrics, (a: IBucketAggConfig) => { @@ -218,7 +219,7 @@ export const getDateHistogramBucketAgg = ({ }); if (all) { output.metricScale = interval.scale; - output.metricScaleText = interval.preScaled.description; + output.metricScaleText = interval.preScaled?.description || ''; } } }, diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts new file mode 100644 index 000000000000..af3c15167295 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.test.ts @@ -0,0 +1,121 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import moment from 'moment'; + +import { TimeBuckets, TimeBucketsConfig } from './time_buckets'; + +describe('TimeBuckets', () => { + const timeBucketConfig: TimeBucketsConfig = { + 'histogram:maxBars': 4, + 'histogram:barTarget': 3, + dateFormat: 'YYYY-MM-DD', + 'dateFormat:scaled': [ + ['', 'HH:mm:ss.SSS'], + ['PT1S', 'HH:mm:ss'], + ['PT1M', 'HH:mm'], + ['PT1H', 'YYYY-MM-DD HH:mm'], + ['P1DT', 'YYYY-MM-DD'], + ['P1YT', 'YYYY'], + ], + }; + + test('setBounds/getBounds - bounds is correct', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + const bounds = { + min: moment('2020-03-25'), + max: moment('2020-03-31'), + }; + timeBuckets.setBounds(bounds); + const timeBucketsBounds = timeBuckets.getBounds(); + + expect(timeBucketsBounds).toEqual(bounds); + }); + + test('setBounds/getBounds - bounds is undefined', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + const bounds = { + min: moment('2020-03-25'), + max: moment('2020-03-31'), + }; + timeBuckets.setBounds(bounds); + let timeBucketsBounds = timeBuckets.getBounds(); + + expect(timeBucketsBounds).toEqual(bounds); + + timeBuckets.setBounds(); + timeBucketsBounds = timeBuckets.getBounds(); + + expect(timeBucketsBounds).toBeUndefined(); + }); + + test('setInterval/getInterval - intreval is a string', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + timeBuckets.setInterval('20m'); + const interval = timeBuckets.getInterval(); + + expect(interval.description).toEqual('20 minutes'); + expect(interval.esValue).toEqual(20); + expect(interval.esUnit).toEqual('m'); + expect(interval.expression).toEqual('20m'); + }); + + test('setInterval/getInterval - intreval is a string and bounds is defined', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + const bounds = { + min: moment('2020-03-25'), + max: moment('2020-03-31'), + }; + timeBuckets.setBounds(bounds); + timeBuckets.setInterval('20m'); + const interval = timeBuckets.getInterval(); + + expect(interval.description).toEqual('day'); + expect(interval.esValue).toEqual(1); + expect(interval.esUnit).toEqual('d'); + expect(interval.expression).toEqual('1d'); + expect(interval.scaled).toBeTruthy(); + expect(interval.scale).toEqual(0.013888888888888888); + + if (interval.preScaled) { + expect(interval.preScaled.description).toEqual('20 minutes'); + expect(interval.preScaled.esValue).toEqual(20); + expect(interval.preScaled.esUnit).toEqual('m'); + expect(interval.preScaled.expression).toEqual('20m'); + } + }); + + test('setInterval/getInterval - intreval is a "auto"', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + timeBuckets.setInterval('auto'); + const interval = timeBuckets.getInterval(); + + expect(interval.description).toEqual('0 milliseconds'); + expect(interval.esValue).toEqual(0); + expect(interval.esUnit).toEqual('ms'); + expect(interval.expression).toEqual('0ms'); + }); + + test('getScaledDateFormat', () => { + const timeBuckets = new TimeBuckets(timeBucketConfig); + timeBuckets.setInterval('20m'); + timeBuckets.getScaledDateFormat(); + const format = timeBuckets.getScaledDateFormat(); + expect(format).toEqual('HH:mm'); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts index c14f02e7decd..b8d6586652d6 100644 --- a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts +++ b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts @@ -17,11 +17,11 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { isString, isObject as isObjectLodash, isPlainObject, sortBy } from 'lodash'; +import moment, { Moment } from 'moment'; -import { IUiSettingsClient } from 'src/core/public'; import { parseInterval } from '../../../../../../common'; +import { TimeRangeBounds } from '../../../../../query'; import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval'; import { convertDurationToNormalizedEsInterval, @@ -29,37 +29,30 @@ import { EsInterval, } from './calc_es_interval'; -interface Bounds { - min: Date | number | null; - max: Date | number | null; -} - interface TimeBucketsInterval extends moment.Duration { // TODO double-check whether all of these are needed description: string; esValue: EsInterval['value']; esUnit: EsInterval['unit']; expression: EsInterval['expression']; - overflow: moment.Duration | boolean; - preScaled?: moment.Duration; + preScaled?: TimeBucketsInterval; scale?: number; scaled?: boolean; } function isObject(o: any): o is Record { - return _.isObject(o); -} - -function isString(s: any): s is string { - return _.isString(s); + return isObjectLodash(o); } function isValidMoment(m: any): boolean { return m && 'isValid' in m && m.isValid(); } -interface TimeBucketsConfig { - uiSettings: IUiSettingsClient; +export interface TimeBucketsConfig { + 'histogram:maxBars': number; + 'histogram:barTarget': number; + dateFormat: string; + 'dateFormat:scaled': string[][]; } /** @@ -70,108 +63,17 @@ interface TimeBucketsConfig { * @param {[type]} display [description] */ export class TimeBuckets { - private getConfig: (key: string) => any; - - private _lb: Bounds['min'] = null; - private _ub: Bounds['max'] = null; + private _timeBucketConfig: TimeBucketsConfig; + private _lb: TimeRangeBounds['min']; + private _ub: TimeRangeBounds['max']; private _originalInterval: string | null = null; private _i?: moment.Duration | 'auto'; // because other parts of Kibana arbitrarily add properties [key: string]: any; - static __cached__(self: TimeBuckets) { - let cache: any = {}; - const sameMoment = same(moment.isMoment); - const sameDuration = same(moment.isDuration); - - const desc: Record = { - __cached__: { - value: self, - }, - }; - - const breakers: Record = { - setBounds: 'bounds', - clearBounds: 'bounds', - setInterval: 'interval', - }; - - const resources: Record = { - bounds: { - setup() { - return [self._lb, self._ub]; - }, - changes(prev: any) { - return !sameMoment(prev[0], self._lb) || !sameMoment(prev[1], self._ub); - }, - }, - interval: { - setup() { - return self._i; - }, - changes(prev: any) { - return !sameDuration(prev, self._i); - }, - }, - }; - - function cachedGetter(prop: string) { - return { - value: (...rest: any) => { - if (cache.hasOwnProperty(prop)) { - return cache[prop]; - } - - return (cache[prop] = self[prop](...rest)); - }, - }; - } - - function cacheBreaker(prop: string) { - const resource = resources[breakers[prop]]; - const setup = resource.setup; - const changes = resource.changes; - const fn = self[prop]; - - return { - value: (...args: any) => { - const prev = setup.call(self); - const ret = fn.apply(self, ...args); - - if (changes.call(self, prev)) { - cache = {}; - } - - return ret; - }, - }; - } - - function same(checkType: any) { - return function(a: any, b: any) { - if (a === b) return true; - if (checkType(a) === checkType(b)) return +a === +b; - return false; - }; - } - - _.forOwn(TimeBuckets.prototype, (fn, prop) => { - if (!prop || prop[0] === '_') return; - - if (breakers.hasOwnProperty(prop)) { - desc[prop] = cacheBreaker(prop); - } else { - desc[prop] = cachedGetter(prop); - } - }); - - return Object.create(self, desc); - } - - constructor({ uiSettings }: TimeBucketsConfig) { - this.getConfig = (key: string) => uiSettings.get(key); - return TimeBuckets.__cached__(this); + constructor(timeBucketConfig: TimeBucketsConfig) { + this._timeBucketConfig = timeBucketConfig; } /** @@ -182,10 +84,10 @@ export class TimeBuckets { * @return {moment.duration|undefined} */ private getDuration(): moment.Duration | undefined { - if (this._ub === null || this._lb === null || !this.hasBounds()) { + if (this._ub === undefined || this._lb === undefined || !this.hasBounds()) { return; } - const difference = (this._ub as number) - (this._lb as number); + const difference = this._ub.valueOf() - this._lb.valueOf(); return moment.duration(difference, 'ms'); } @@ -200,22 +102,20 @@ export class TimeBuckets { * * @returns {undefined} */ - setBounds(input?: Bounds | Bounds[]) { + setBounds(input?: TimeRangeBounds | TimeRangeBounds[]) { if (!input) return this.clearBounds(); let bounds; - if (_.isPlainObject(input) && !Array.isArray(input)) { + if (isPlainObject(input) && !Array.isArray(input)) { // accept the response from timefilter.getActiveBounds() bounds = [input.min, input.max]; } else { bounds = Array.isArray(input) ? input : []; } - const moments = _(bounds) - .map(_.ary(moment, 1)) - .sortBy(Number); + const moments: Moment[] = sortBy(bounds, Number); - const valid = moments.size() === 2 && moments.every(isValidMoment); + const valid = moments.length === 2 && moments.every(isValidMoment); if (!valid) { this.clearBounds(); throw new Error('invalid bounds set: ' + input); @@ -236,7 +136,7 @@ export class TimeBuckets { * @return {undefined} */ clearBounds() { - this._lb = this._ub = null; + this._lb = this._ub = undefined; } /** @@ -262,7 +162,7 @@ export class TimeBuckets { * object * */ - getBounds(): Bounds | undefined { + getBounds(): TimeRangeBounds | undefined { if (!this.hasBounds()) return; return { min: this._lb, @@ -278,11 +178,10 @@ export class TimeBuckets { * - Any object from src/legacy/ui/agg_types.js * - "auto" * - Pass a valid moment unit - * - a moment.duration object. * * @param {object|string|moment.duration} input - see desc */ - setInterval(input: null | string | Record | moment.Duration) { + setInterval(input: null | string | Record) { let interval = input; // selection object -> val @@ -351,7 +250,7 @@ export class TimeBuckets { const readInterval = () => { const interval = this._i; if (moment.isDuration(interval)) return interval; - return calcAutoIntervalNear(this.getConfig('histogram:barTarget'), Number(duration)); + return calcAutoIntervalNear(this._timeBucketConfig['histogram:barTarget'], Number(duration)); }; const parsedInterval = readInterval(); @@ -362,7 +261,7 @@ export class TimeBuckets { return interval; } - const maxLength: number = this.getConfig('histogram:maxBars'); + const maxLength: number = this._timeBucketConfig['histogram:maxBars']; const approxLen = Number(duration) / Number(interval); let scaled; @@ -396,10 +295,6 @@ export class TimeBuckets { esValue: esInterval.value, esUnit: esInterval.unit, expression: esInterval.expression, - overflow: - Number(duration) > Number(interval) - ? moment.duration(Number(interval) - Number(duration)) - : false, }); }; @@ -423,7 +318,7 @@ export class TimeBuckets { */ getScaledDateFormat() { const interval = this.getInterval(); - const rules = this.getConfig('dateFormat:scaled'); + const rules = this._timeBucketConfig['dateFormat:scaled']; for (let i = rules.length - 1; i >= 0; i--) { const rule = rules[i]; @@ -432,6 +327,6 @@ export class TimeBuckets { } } - return this.getConfig('dateFormat'); + return this._timeBucketConfig.dateFormat; } } diff --git a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts index 459de66d057d..9d976784329c 100644 --- a/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts +++ b/src/plugins/data/public/search/aggs/utils/calculate_auto_time_expression.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import moment from 'moment'; import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from '../buckets/lib/time_buckets'; import { toAbsoluteDates, TimeRange } from '../../../../common'; @@ -28,12 +28,17 @@ export function getCalculateAutoTimeExpression(uiSettings: IUiSettingsClient) { return; } - const buckets = new TimeBuckets({ uiSettings }); + const buckets = new TimeBuckets({ + 'histogram:maxBars': uiSettings.get('histogram:maxBars'), + 'histogram:barTarget': uiSettings.get('histogram:barTarget'), + dateFormat: uiSettings.get('dateFormat'), + 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), + }); buckets.setInterval('auto'); buckets.setBounds({ - min: dates.from, - max: dates.to, + min: moment(dates.from), + max: moment(dates.to), }); return buckets.getInterval().expression; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 18af94c91924..f3192ba3da81 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -94,8 +94,11 @@ const getSchemas = ( const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => { if (isDateHistogramBucketAggConfig(agg)) { agg.params.timeRange = timeRange; - const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; - agg.buckets.setBounds(agg.fieldIsTimeField() && bounds); + const bounds = + agg.params.timeRange && agg.fieldIsTimeField() + ? timefilter.calculateBounds(agg.params.timeRange) + : undefined; + agg.buckets.setBounds(bounds); agg.buckets.setInterval(agg.params.interval); } From 530732c9dd3fa9ab2db6af2c58b52f2a60288a36 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 9 Apr 2020 14:03:44 +0200 Subject: [PATCH 20/46] [ML] Functional tests - stabilize typing in mml input (#63091) This PR wraps the model memory value setting during anomaly detection wizards in a retry. --- .../services/machine_learning/job_wizard_common.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 36181b66786d..af33ec2301ed 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -330,9 +330,11 @@ export function MachineLearningJobWizardCommonProvider( await this.ensureAdvancedSectionOpen(); subj = advancedSectionSelector(subj); } - await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); - await this.assertModelMemoryLimitValue(modelMemoryLimit, { - withAdvancedSection: sectionOptions.withAdvancedSection, + await retry.tryForTime(15 * 1000, async () => { + await mlCommon.setValueWithChecks(subj, modelMemoryLimit, { clearWithKeyboard: true }); + await this.assertModelMemoryLimitValue(modelMemoryLimit, { + withAdvancedSection: sectionOptions.withAdvancedSection, + }); }); }, From 8d21b6b6f3ccd0388e3a325e96e90f05df0c42b5 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 9 Apr 2020 14:06:01 +0200 Subject: [PATCH 21/46] Move search source parsing and serializing to data (#59919) --- ...-plugins-data-public.createsearchsource.md | 15 ++ .../kibana-plugin-plugins-data-public.md | 1 + ...plugin-plugins-data-public.searchsource.md | 1 + ...gins-data-public.searchsource.serialize.md | 27 ++++ .../kibana/public/discover/build_services.ts | 1 + .../management/saved_object_registry.ts | 1 + .../components/flyout/__jest__/flyout.test.js | 3 +- .../objects_table/components/flyout/flyout.js | 3 +- .../objects/lib/resolve_saved_objects.test.ts | 93 ++++++----- .../objects/lib/resolve_saved_objects.ts | 59 ++++--- .../public/visualize/np_ready/legacy_app.js | 1 + .../timelion/public/services/saved_sheets.ts | 1 + .../ui/public/new_platform/set_services.ts | 2 + src/plugins/dashboard/public/plugin.tsx | 3 +- .../saved_dashboards/saved_dashboards.ts | 3 +- src/plugins/data/public/index.ts | 1 + src/plugins/data/public/plugin.ts | 2 +- src/plugins/data/public/public.api.md | 38 +++-- src/plugins/data/public/search/index.ts | 1 + src/plugins/data/public/search/mocks.ts | 1 + .../data/public/search/search_service.ts | 5 +- .../create_search_source.test.ts | 151 ++++++++++++++++++ .../search_source/create_search_source.ts | 113 +++++++++++++ .../data/public/search/search_source/index.ts | 1 + .../data/public/search/search_source/mocks.ts | 1 + .../search_source/search_source.test.ts | 75 ++++++++- .../search/search_source/search_source.ts | 82 ++++++++++ src/plugins/data/public/search/types.ts | 2 + src/plugins/saved_objects/public/plugin.ts | 1 + .../saved_object/helpers/apply_es_resp.ts | 32 +++- .../helpers/build_saved_object.ts | 3 +- .../helpers/hydrate_index_pattern.ts | 10 +- .../helpers/parse_search_source.ts | 97 ----------- .../helpers/serialize_saved_object.ts | 62 ++----- .../public/saved_object/saved_object.test.ts | 52 +++--- src/plugins/saved_objects/public/types.ts | 10 +- src/plugins/visualizations/public/plugin.ts | 3 + .../public/saved_visualizations/_saved_vis.ts | 3 +- src/plugins/visualizations/public/services.ts | 2 + .../services/gis_map_saved_object_loader.js | 1 + .../use_search_items/use_search_items.ts | 1 + 41 files changed, 688 insertions(+), 276 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md create mode 100644 src/plugins/data/public/search/search_source/create_search_source.test.ts create mode 100644 src/plugins/data/public/search/search_source/create_search_source.ts delete mode 100644 src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md new file mode 100644 index 000000000000..5c5aa348eecd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md) + +## createSearchSource variable + +Deserializes a json string and a set of referenced objects to a `SearchSource` instance. Use this method to re-create the search source serialized using `searchSource.serialize`. + +This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service + +Signature: + +```typescript +createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 6964c070097c..fc0dab94a0f6 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -102,6 +102,7 @@ | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container | | [createSavedQueryService](./kibana-plugin-plugins-data-public.createsavedqueryservice.md) | | +| [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md) | Deserializes a json string and a set of referenced objects to a SearchSource instance. Use this method to re-create the search source serialized using searchSource.serialize.This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service | | [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-public.es_search_strategy.md) | | | [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md index 8e1dbb6e2671..5f2fc809a559 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -38,6 +38,7 @@ export declare class SearchSource | [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | | [getSearchRequestBody()](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) | | | | [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start | +| [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index.Using createSearchSource, the instance can be re-created. | | [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | | | [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | | | [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md new file mode 100644 index 000000000000..52d25dec01df --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [serialize](./kibana-plugin-plugins-data-public.searchsource.serialize.md) + +## SearchSource.serialize() method + +Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object. + +The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index` and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`. + +Using `createSearchSource`, the instance can be re-created. + +Signature: + +```typescript +serialize(): { + searchSourceJSON: string; + references: SavedObjectReference[]; + }; +``` +Returns: + +`{ + searchSourceJSON: string; + references: SavedObjectReference[]; + }` + diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts index 180ff13cdddc..a3a99a0ded52 100644 --- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts @@ -72,6 +72,7 @@ export async function buildServices( const services = { savedObjectsClient: core.savedObjects.client, indexPatterns: plugins.data.indexPatterns, + search: plugins.data.search, chrome: core.chrome, overlays: core.overlays, }; diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index 7261b2ba0337..705be68a141e 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts @@ -56,6 +56,7 @@ export const savedObjectManagementRegistry: ISavedObjectsManagementRegistry = { const services = { savedObjectsClient: npStart.core.savedObjects.client, indexPatterns: npStart.plugins.data.indexPatterns, + search: npStart.plugins.data.search, chrome: npStart.core.chrome, overlays: npStart.core.overlays, }; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index 5d14c4609b91..0d16e0ae35dd 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -519,7 +519,8 @@ describe('Flyout', () => { expect(resolveIndexPatternConflicts).toHaveBeenCalledWith( component.instance().resolutions, mockConflictedIndexPatterns, - true + true, + defaultProps.indexPatterns ); expect(saveObjects).toHaveBeenCalledWith( mockConflictedSavedObjectsLinkedToSavedSearches, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js index 105c27921837..da2221bb5420 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js @@ -358,7 +358,8 @@ export class Flyout extends Component { importCount += await resolveIndexPatternConflicts( resolutions, conflictedIndexPatterns, - isOverwriteAllChecked + isOverwriteAllChecked, + this.props.indexPatterns ); } this.setState({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts index 8243aa69ac08..dc6d2643145f 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts @@ -84,7 +84,7 @@ describe('resolveSavedObjects', () => { }, } as unknown) as IndexPatternsContract; - const services = [ + const services = ([ { type: 'search', get: async () => { @@ -124,7 +124,7 @@ describe('resolveSavedObjects', () => { }; }, }, - ] as SavedObjectLoader[]; + ] as unknown) as SavedObjectLoader[]; const overwriteAll = false; @@ -176,7 +176,7 @@ describe('resolveSavedObjects', () => { }, } as unknown) as IndexPatternsContract; - const services = [ + const services = ([ { type: 'search', get: async () => { @@ -217,7 +217,7 @@ describe('resolveSavedObjects', () => { }; }, }, - ] as SavedObjectLoader[]; + ] as unknown) as SavedObjectLoader[]; const overwriteAll = false; @@ -237,33 +237,38 @@ describe('resolveSavedObjects', () => { describe('resolveIndexPatternConflicts', () => { it('should resave resolutions', async () => { - const hydrateIndexPattern = jest.fn(); const save = jest.fn(); - const conflictedIndexPatterns = [ + const conflictedIndexPatterns = ([ { obj: { - searchSource: { - getOwnField: (field: string) => { - return field === 'index' ? '1' : undefined; + save, + }, + doc: { + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: '1', + }), }, }, - hydrateIndexPattern, - save, }, }, { obj: { - searchSource: { - getOwnField: (field: string) => { - return field === 'index' ? '3' : undefined; - }, - }, - hydrateIndexPattern, save, }, + doc: { + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: '3', + }), + }, + }, + }, }, - ]; + ] as unknown) as Array<{ obj: SavedObject; doc: any }>; const resolutions = [ { @@ -282,43 +287,49 @@ describe('resolveSavedObjects', () => { const overwriteAll = false; - await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll); - expect(hydrateIndexPattern.mock.calls.length).toBe(2); + await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({ + get: (id: string) => Promise.resolve({ id }), + } as unknown) as IndexPatternsContract); + expect(conflictedIndexPatterns[0].obj.searchSource!.getField('index')!.id).toEqual('2'); + expect(conflictedIndexPatterns[1].obj.searchSource!.getField('index')!.id).toEqual('4'); expect(save.mock.calls.length).toBe(2); expect(save).toHaveBeenCalledWith({ confirmOverwrite: !overwriteAll }); - expect(hydrateIndexPattern).toHaveBeenCalledWith('2'); - expect(hydrateIndexPattern).toHaveBeenCalledWith('4'); }); it('should resolve filter index conflicts', async () => { - const hydrateIndexPattern = jest.fn(); const save = jest.fn(); - const conflictedIndexPatterns = [ + const conflictedIndexPatterns = ([ { obj: { - searchSource: { - getOwnField: (field: string) => { - return field === 'index' ? '1' : [{ meta: { index: 'filterIndex' } }]; - }, - setField: jest.fn(), - }, - hydrateIndexPattern, save, }, + doc: { + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: '1', + filter: [{ meta: { index: 'filterIndex' } }], + }), + }, + }, + }, }, { obj: { - searchSource: { - getOwnField: (field: string) => { - return field === 'index' ? '3' : undefined; - }, - }, - hydrateIndexPattern, save, }, + doc: { + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + index: '3', + }), + }, + }, + }, }, - ]; + ] as unknown) as Array<{ obj: SavedObject; doc: any }>; const resolutions = [ { @@ -337,9 +348,11 @@ describe('resolveSavedObjects', () => { const overwriteAll = false; - await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll); + await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({ + get: (id: string) => Promise.resolve({ id }), + } as unknown) as IndexPatternsContract); - expect(conflictedIndexPatterns[0].obj.searchSource.setField).toHaveBeenCalledWith('filter', [ + expect(conflictedIndexPatterns[0].obj.searchSource!.getField('filter')).toEqual([ { meta: { index: 'newFilterIndex' } }, ]); expect(save.mock.calls.length).toBe(2); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts index 902de654f5f8..d9473367f750 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts @@ -18,12 +18,17 @@ */ import { i18n } from '@kbn/i18n'; -import { OverlayStart } from 'src/core/public'; +import { cloneDeep } from 'lodash'; +import { OverlayStart, SavedObjectReference } from 'src/core/public'; import { SavedObject, SavedObjectLoader, } from '../../../../../../../../plugins/saved_objects/public'; -import { IndexPatternsContract, IIndexPattern } from '../../../../../../../../plugins/data/public'; +import { + IndexPatternsContract, + IIndexPattern, + createSearchSource, +} from '../../../../../../../../plugins/data/public'; type SavedObjectsRawDoc = Record; @@ -126,7 +131,7 @@ async function importIndexPattern( async function importDocument(obj: SavedObject, doc: SavedObjectsRawDoc, overwriteAll: boolean) { await obj.applyESResp({ references: doc._references || [], - ...doc, + ...cloneDeep(doc), }); return await obj.save({ confirmOverwrite: !overwriteAll }); } @@ -160,41 +165,57 @@ async function awaitEachItemInParallel(list: T[], op: (item: T) => R) { export async function resolveIndexPatternConflicts( resolutions: Array<{ oldId: string; newId: string }>, conflictedIndexPatterns: any[], - overwriteAll: boolean + overwriteAll: boolean, + indexPatterns: IndexPatternsContract ) { let importCount = 0; - await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj }) => { - // Resolve search index reference: - let oldIndexId = obj.searchSource.getOwnField('index'); - // Depending on the object, this can either be the raw id or the actual index pattern object - if (typeof oldIndexId !== 'string') { - oldIndexId = oldIndexId.id; - } - let resolution = resolutions.find(({ oldId }) => oldId === oldIndexId); - if (resolution) { - const newIndexId = resolution.newId; - await obj.hydrateIndexPattern(newIndexId); + await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj, doc }) => { + const serializedSearchSource = JSON.parse( + doc._source.kibanaSavedObjectMeta?.searchSourceJSON || '{}' + ); + const oldIndexId = serializedSearchSource.index; + let allResolved = true; + const inlineResolution = resolutions.find(({ oldId }) => oldId === oldIndexId); + if (inlineResolution) { + serializedSearchSource.index = inlineResolution.newId; + } else { + allResolved = false; } // Resolve filter index reference: - const filter = (obj.searchSource.getOwnField('filter') || []).map((f: any) => { + const filter = (serializedSearchSource.filter || []).map((f: any) => { if (!(f.meta && f.meta.index)) { return f; } - resolution = resolutions.find(({ oldId }) => oldId === f.meta.index); + const resolution = resolutions.find(({ oldId }) => oldId === f.meta.index); return resolution ? { ...f, ...{ meta: { ...f.meta, index: resolution.newId } } } : f; }); if (filter.length > 0) { - obj.searchSource.setField('filter', filter); + serializedSearchSource.filter = filter; } - if (!resolution) { + const replacedReferences = (doc._references || []).map((reference: SavedObjectReference) => { + const resolution = resolutions.find(({ oldId }) => oldId === reference.id); + if (resolution) { + return { ...reference, id: resolution.newId }; + } else { + allResolved = false; + } + + return reference; + }); + + if (!allResolved) { // The user decided to skip this conflict so do nothing return; } + obj.searchSource = await createSearchSource(indexPatterns)( + JSON.stringify(serializedSearchSource), + replacedReferences + ); if (await saveObject(obj, overwriteAll)) { importCount++; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index 7c9ab32ab2f7..a710d3e31874 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -71,6 +71,7 @@ const getResolvedResults = deps => { return createSavedSearchesLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: data.indexPatterns, + search: data.search, chrome: core.chrome, overlays: core.overlays, }).get(results.vis.data.savedSearchId); diff --git a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts index 201b21f93298..e7f431a178ea 100644 --- a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts +++ b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts @@ -28,6 +28,7 @@ const savedObjectsClient = npStart.core.savedObjects.client; const services = { savedObjectsClient, indexPatterns: npStart.plugins.data.indexPatterns, + search: npStart.plugins.data.search, chrome: npStart.core.chrome, overlays: npStart.core.overlays, }; diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts index 8cf015d5dff5..400f31e73ffa 100644 --- a/src/legacy/ui/public/new_platform/set_services.ts +++ b/src/legacy/ui/public/new_platform/set_services.ts @@ -72,9 +72,11 @@ export function setStartServices(npStart: NpStart) { visualizationsServices.setAggs(npStart.plugins.data.search.aggs); visualizationsServices.setOverlays(npStart.core.overlays); visualizationsServices.setChrome(npStart.core.chrome); + visualizationsServices.setSearch(npStart.plugins.data.search); const savedVisualizationsLoader = createSavedVisLoader({ savedObjectsClient: npStart.core.savedObjects.client, indexPatterns: npStart.plugins.data.indexPatterns, + search: npStart.plugins.data.search, chrome: npStart.core.chrome, overlays: npStart.core.overlays, visualizationTypes: visualizationsServices.getTypes(), diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index c98fa612dc7a..322d734d9f39 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -284,7 +284,7 @@ export class DashboardPlugin const { notifications } = core; const { uiActions, - data: { indexPatterns }, + data: { indexPatterns, search }, } = plugins; const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings); @@ -300,6 +300,7 @@ export class DashboardPlugin const savedDashboardLoader = createSavedDashboardLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns, + search, chrome: core.chrome, overlays: core.overlays, }); diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts index 2a1e64fa88a0..09357072a13a 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts @@ -18,13 +18,14 @@ */ import { SavedObjectsClientContract, ChromeStart, OverlayStart } from 'kibana/public'; -import { IndexPatternsContract } from '../../../../plugins/data/public'; +import { DataPublicPluginStart, IndexPatternsContract } from '../../../../plugins/data/public'; import { SavedObjectLoader } from '../../../../plugins/saved_objects/public'; import { createSavedDashboardClass } from './saved_dashboard'; interface Services { savedObjectsClient: SavedObjectsClientContract; indexPatterns: IndexPatternsContract; + search: DataPublicPluginStart['search']; chrome: ChromeStart; overlays: OverlayStart; } diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index efafea44167d..06a46065baa8 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -366,6 +366,7 @@ export { SearchStrategyProvider, ISearchSource, SearchSource, + createSearchSource, SearchSourceFields, EsQuerySortValue, SortDirection, diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 15067077afc4..2ebe377b3b32 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -155,7 +155,7 @@ export class DataPublicPlugin implements Plugin({ timefilter: { timefil // @public (undocumented) export const createSavedQueryService: (savedObjectsClient: Pick) => SavedQueryService; +// @public +export const createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise; + // Warning: (ae-missing-release-tag) "CustomFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1667,6 +1671,10 @@ export class SearchSource { // (undocumented) history: SearchRequest[]; onRequestStart(handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise): void; + serialize(): { + searchSourceJSON: string; + references: SavedObjectReference[]; + }; // (undocumented) setField(field: K, value: SearchSourceFields[K]): this; // (undocumented) @@ -1881,21 +1889,21 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 1687d749f46e..cce973d632f4 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -54,6 +54,7 @@ export { SearchSourceFields, EsQuerySortValue, SortDirection, + createSearchSource, } from './search_source'; export { SearchInterceptor } from './search_interceptor'; diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index b70e889066a4..cb1c625a7295 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -33,6 +33,7 @@ export const searchStartMock: jest.Mocked = { aggs: searchAggsStartMock(), setInterceptor: jest.fn(), search: jest.fn(), + createSearchSource: jest.fn(), __LEGACY: { AggConfig: jest.fn() as any, AggType: jest.fn(), diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 42f31ef450d2..612468218482 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -25,6 +25,8 @@ import { TStrategyTypes } from './strategy_types'; import { getEsClient, LegacyApiCaller } from './es_client'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search/es_search_strategy'; +import { IndexPatternsContract } from '../index_patterns/index_patterns'; +import { createSearchSource } from './search_source'; import { QuerySetup } from '../query/query_service'; import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; @@ -108,7 +110,7 @@ export class SearchService implements Plugin { }; } - public start(core: CoreStart): ISearchStart { + public start(core: CoreStart, indexPatterns: IndexPatternsContract): ISearchStart { /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. @@ -145,6 +147,7 @@ export class SearchService implements Plugin { // TODO: should an intercepror have a destroy method? this.searchInterceptor = searchInterceptor; }, + createSearchSource: createSearchSource(indexPatterns), __LEGACY: { esClient: this.esClient!, AggConfig, diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts new file mode 100644 index 000000000000..d49ce5a0d11f --- /dev/null +++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts @@ -0,0 +1,151 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { createSearchSource as createSearchSourceFactory } from './create_search_source'; +import { IIndexPattern } from '../../../common/index_patterns'; +import { IndexPatternsContract } from '../../index_patterns/index_patterns'; +import { Filter } from '../../../common/es_query/filters'; + +describe('createSearchSource', function() { + let createSearchSource: ReturnType; + const indexPatternMock: IIndexPattern = {} as IIndexPattern; + let indexPatternContractMock: jest.Mocked; + + beforeEach(() => { + indexPatternContractMock = ({ + get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)), + } as unknown) as jest.Mocked; + createSearchSource = createSearchSourceFactory(indexPatternContractMock); + }); + + it('should fail if JSON is invalid', () => { + expect(createSearchSource('{', [])).rejects.toThrow(); + expect(createSearchSource('0', [])).rejects.toThrow(); + expect(createSearchSource('"abcdefg"', [])).rejects.toThrow(); + }); + + it('should set fields', async () => { + const searchSource = await createSearchSource( + JSON.stringify({ + highlightAll: true, + query: { + query: '', + language: 'kuery', + }, + }), + [] + ); + expect(searchSource.getOwnField('highlightAll')).toBe(true); + expect(searchSource.getOwnField('query')).toEqual({ + query: '', + language: 'kuery', + }); + }); + + it('should resolve referenced index pattern', async () => { + const searchSource = await createSearchSource( + JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }), + [ + { + id: '123-456', + type: 'index-pattern', + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + }, + ] + ); + expect(indexPatternContractMock.get).toHaveBeenCalledWith('123-456'); + expect(searchSource.getOwnField('index')).toBe(indexPatternMock); + }); + + it('should set filters and resolve referenced index patterns', async () => { + const searchSource = await createSearchSource( + JSON.stringify({ + filter: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Clothing", + }, + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + }, + query: { + match_phrase: { + 'category.keyword': "Men's Clothing", + }, + }, + $state: { + store: 'appState', + }, + }, + ], + }), + [ + { + id: '123-456', + type: 'index-pattern', + name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + }, + ] + ); + const filters = searchSource.getOwnField('filter') as Filter[]; + expect(filters[0]).toMatchInlineSnapshot(` + Object { + "$state": Object { + "store": "appState", + }, + "meta": Object { + "alias": null, + "disabled": false, + "index": "123-456", + "key": "category.keyword", + "negate": false, + "params": Object { + "query": "Men's Clothing", + }, + "type": "phrase", + }, + "query": Object { + "match_phrase": Object { + "category.keyword": "Men's Clothing", + }, + }, + } + `); + }); + + it('should migrate legacy queries on the fly', async () => { + const searchSource = await createSearchSource( + JSON.stringify({ + highlightAll: true, + query: 'a:b', + }), + [] + ); + expect(searchSource.getOwnField('query')).toEqual({ + query: 'a:b', + language: 'lucene', + }); + }); +}); diff --git a/src/plugins/data/public/search/search_source/create_search_source.ts b/src/plugins/data/public/search/search_source/create_search_source.ts new file mode 100644 index 000000000000..35b7ac4eb976 --- /dev/null +++ b/src/plugins/data/public/search/search_source/create_search_source.ts @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import _ from 'lodash'; +import { SavedObjectReference } from 'kibana/public'; +import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; +import { InvalidJSONProperty } from '../../../../kibana_utils/public'; +import { SearchSource } from './search_source'; +import { IndexPatternsContract } from '../../index_patterns/index_patterns'; +import { SearchSourceFields } from './types'; + +/** + * Deserializes a json string and a set of referenced objects to a `SearchSource` instance. + * Use this method to re-create the search source serialized using `searchSource.serialize`. + * + * This function is a factory function that returns the actual utility when calling it with the + * required service dependency (index patterns contract). A pre-wired version is also exposed in + * the start contract of the data plugin as part of the search service + * + * @param indexPatterns The index patterns contract of the data plugin + * + * @return Wired utility function taking two parameters `searchSourceJson`, the json string + * returned by `serializeSearchSource` and `references`, a list of references including the ones + * returned by `serializeSearchSource`. + * + * @public */ +export const createSearchSource = (indexPatterns: IndexPatternsContract) => async ( + searchSourceJson: string, + references: SavedObjectReference[] +) => { + const searchSource = new SearchSource(); + + // if we have a searchSource, set its values based on the searchSourceJson field + let searchSourceValues: Record; + try { + searchSourceValues = JSON.parse(searchSourceJson); + } catch (e) { + throw new InvalidJSONProperty( + `Invalid JSON in search source. ${e.message} JSON: ${searchSourceJson}` + ); + } + + // This detects a scenario where documents with invalid JSON properties have been imported into the saved object index. + // (This happened in issue #20308) + if (!searchSourceValues || typeof searchSourceValues !== 'object') { + throw new InvalidJSONProperty('Invalid JSON in search source.'); + } + + // Inject index id if a reference is saved + if (searchSourceValues.indexRefName) { + const reference = references.find(ref => ref.name === searchSourceValues.indexRefName); + if (!reference) { + throw new Error(`Could not find reference for ${searchSourceValues.indexRefName}`); + } + searchSourceValues.index = reference.id; + delete searchSourceValues.indexRefName; + } + + if (searchSourceValues.filter && Array.isArray(searchSourceValues.filter)) { + searchSourceValues.filter.forEach((filterRow: any) => { + if (!filterRow.meta || !filterRow.meta.indexRefName) { + return; + } + const reference = references.find((ref: any) => ref.name === filterRow.meta.indexRefName); + if (!reference) { + throw new Error(`Could not find reference for ${filterRow.meta.indexRefName}`); + } + filterRow.meta.index = reference.id; + delete filterRow.meta.indexRefName; + }); + } + + if (searchSourceValues.index && typeof searchSourceValues.index === 'string') { + searchSourceValues.index = await indexPatterns.get(searchSourceValues.index); + } + + const searchSourceFields = searchSource.getFields(); + const fnProps = _.transform( + searchSourceFields, + function(dynamic, val, name) { + if (_.isFunction(val) && name) dynamic[name] = val; + }, + {} + ); + + // This assignment might hide problems because the type of values passed from the parsed JSON + // might not fit the SearchSourceFields interface. + const newFields: SearchSourceFields = _.defaults(searchSourceValues, fnProps); + + searchSource.setFields(newFields); + const query = searchSource.getOwnField('query'); + + if (typeof query !== 'undefined') { + searchSource.setField('query', migrateLegacyQuery(query)); + } + + return searchSource; +}; diff --git a/src/plugins/data/public/search/search_source/index.ts b/src/plugins/data/public/search/search_source/index.ts index 10f1b2bc332e..0e9f530d0968 100644 --- a/src/plugins/data/public/search/search_source/index.ts +++ b/src/plugins/data/public/search/search_source/index.ts @@ -18,4 +18,5 @@ */ export * from './search_source'; +export { createSearchSource } from './create_search_source'; export { SortDirection, EsQuerySortValue, SearchSourceFields } from './types'; diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 700bea741bd6..1ef7c1187a9e 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -37,4 +37,5 @@ export const searchSourceMock: MockedKeys = { getSearchRequestBody: jest.fn(), destroy: jest.fn(), history: [], + serialize: jest.fn(), }; diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts index fcd116a3f412..6bad093d3140 100644 --- a/src/plugins/data/public/search/search_source/search_source.test.ts +++ b/src/plugins/data/public/search/search_source/search_source.test.ts @@ -18,7 +18,7 @@ */ import { SearchSource } from './search_source'; -import { IndexPattern } from '../..'; +import { IndexPattern, SortDirection } from '../..'; import { mockDataServices } from '../aggs/test_helpers'; jest.mock('../fetch', () => ({ @@ -150,4 +150,77 @@ describe('SearchSource', function() { expect(parentFn).toBeCalledWith(searchSource, options); }); }); + + describe('#serialize', function() { + it('should reference index patterns', () => { + const indexPattern123 = { id: '123' } as IndexPattern; + const searchSource = new SearchSource(); + searchSource.setField('index', indexPattern123); + const { searchSourceJSON, references } = searchSource.serialize(); + expect(references[0].id).toEqual('123'); + expect(references[0].type).toEqual('index-pattern'); + expect(JSON.parse(searchSourceJSON).indexRefName).toEqual(references[0].name); + }); + + it('should add other fields', () => { + const searchSource = new SearchSource(); + searchSource.setField('highlightAll', true); + searchSource.setField('from', 123456); + const { searchSourceJSON } = searchSource.serialize(); + expect(JSON.parse(searchSourceJSON).highlightAll).toEqual(true); + expect(JSON.parse(searchSourceJSON).from).toEqual(123456); + }); + + it('should omit sort and size', () => { + const searchSource = new SearchSource(); + searchSource.setField('highlightAll', true); + searchSource.setField('from', 123456); + searchSource.setField('sort', { field: SortDirection.asc }); + searchSource.setField('size', 200); + const { searchSourceJSON } = searchSource.serialize(); + expect(Object.keys(JSON.parse(searchSourceJSON))).toEqual(['highlightAll', 'from']); + }); + + it('should serialize filters', () => { + const searchSource = new SearchSource(); + const filter = [ + { + query: 'query', + meta: { + alias: 'alias', + disabled: false, + negate: false, + }, + }, + ]; + searchSource.setField('filter', filter); + const { searchSourceJSON } = searchSource.serialize(); + expect(JSON.parse(searchSourceJSON).filter).toEqual(filter); + }); + + it('should reference index patterns in filters separately from index field', () => { + const searchSource = new SearchSource(); + const indexPattern123 = { id: '123' } as IndexPattern; + searchSource.setField('index', indexPattern123); + const filter = [ + { + query: 'query', + meta: { + alias: 'alias', + disabled: false, + negate: false, + index: '456', + }, + }, + ]; + searchSource.setField('filter', filter); + const { searchSourceJSON, references } = searchSource.serialize(); + expect(references[0].id).toEqual('123'); + expect(references[0].type).toEqual('index-pattern'); + expect(JSON.parse(searchSourceJSON).indexRefName).toEqual(references[0].name); + expect(references[1].id).toEqual('456'); + expect(references[1].type).toEqual('index-pattern'); + expect(JSON.parse(searchSourceJSON).filter[0].meta.indexRefName).toEqual(references[1].name); + }); + }); }); diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 0c3321f03dab..c70db7bb82ef 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -70,6 +70,7 @@ */ import _ from 'lodash'; +import { SavedObjectReference } from 'kibana/public'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; @@ -419,4 +420,85 @@ export class SearchSource { return searchRequest; } + + /** + * Serializes the instance to a JSON string and a set of referenced objects. + * Use this method to get a representation of the search source which can be stored in a saved object. + * + * The references returned by this function can be mixed with other references in the same object, + * however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index` + * and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`. + * + * Using `createSearchSource`, the instance can be re-created. + * @param searchSource The search source to serialize + * @public */ + public serialize() { + const references: SavedObjectReference[] = []; + + const { + filter: originalFilters, + ...searchSourceFields + }: Omit = _.omit(this.getFields(), ['sort', 'size']); + let serializedSearchSourceFields: Omit & { + indexRefName?: string; + filter?: Array & { meta: Filter['meta'] & { indexRefName?: string } }>; + } = searchSourceFields; + if (searchSourceFields.index) { + const indexId = searchSourceFields.index.id!; + const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + references.push({ + name: refName, + type: 'index-pattern', + id: indexId, + }); + serializedSearchSourceFields = { + ...serializedSearchSourceFields, + indexRefName: refName, + index: undefined, + }; + } + if (originalFilters) { + const filters = this.getFilters(originalFilters); + serializedSearchSourceFields = { + ...serializedSearchSourceFields, + filter: filters.map((filterRow, i) => { + if (!filterRow.meta || !filterRow.meta.index) { + return filterRow; + } + const refName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + references.push({ + name: refName, + type: 'index-pattern', + id: filterRow.meta.index, + }); + return { + ...filterRow, + meta: { + ...filterRow.meta, + indexRefName: refName, + index: undefined, + }, + }; + }), + }; + } + + return { searchSourceJSON: JSON.stringify(serializedSearchSourceFields), references }; + } + + private getFilters(filterField: SearchSourceFields['filter']): Filter[] { + if (!filterField) { + return []; + } + + if (Array.isArray(filterField)) { + return filterField; + } + + if (_.isFunction(filterField)) { + return this.getFilters(filterField()); + } + + return [filterField]; + } } diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 03cbfa9f8ed8..ba6e44f47b75 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -18,6 +18,7 @@ */ import { CoreStart } from 'kibana/public'; +import { createSearchSource } from './search_source'; import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs'; import { ISearch, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; @@ -89,5 +90,6 @@ export interface ISearchStart { aggs: SearchAggsStart; setInterceptor: (searchInterceptor: SearchInterceptor) => void; search: ISearchGeneric; + createSearchSource: ReturnType; __LEGACY: ISearchStartLegacy & SearchAggsStartLegacy; } diff --git a/src/plugins/saved_objects/public/plugin.ts b/src/plugins/saved_objects/public/plugin.ts index 0f5773c00283..7927238e1206 100644 --- a/src/plugins/saved_objects/public/plugin.ts +++ b/src/plugins/saved_objects/public/plugin.ts @@ -39,6 +39,7 @@ export class SavedObjectsPublicPlugin SavedObjectClass: createSavedObjectClass({ indexPatterns: data.indexPatterns, savedObjectsClient: core.savedObjects.client, + search: data.search, chrome: core.chrome, overlays: core.overlays, }), diff --git a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts index 2e965eaf1989..9776887b6d74 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts @@ -18,9 +18,8 @@ */ import _ from 'lodash'; import { EsResponse, SavedObject, SavedObjectConfig } from '../../types'; -import { parseSearchSource } from './parse_search_source'; import { expandShorthand, SavedObjectNotFound } from '../../../../kibana_utils/public'; -import { IndexPattern } from '../../../../data/public'; +import { DataPublicPluginStart, IndexPattern } from '../../../../data/public'; /** * A given response of and ElasticSearch containing a plain saved object is applied to the given @@ -29,13 +28,13 @@ import { IndexPattern } from '../../../../data/public'; export async function applyESResp( resp: EsResponse, savedObject: SavedObject, - config: SavedObjectConfig + config: SavedObjectConfig, + createSearchSource: DataPublicPluginStart['search']['createSearchSource'] ) { const mapping = expandShorthand(config.mapping); const esType = config.type || ''; savedObject._source = _.cloneDeep(resp._source); const injectReferences = config.injectReferences; - const hydrateIndexPattern = savedObject.hydrateIndexPattern!; if (typeof resp.found === 'boolean' && !resp.found) { throw new SavedObjectNotFound(esType, savedObject.id || ''); } @@ -64,13 +63,34 @@ export async function applyESResp( _.assign(savedObject, savedObject._source); savedObject.lastSavedTitle = savedObject.title; - await parseSearchSource(savedObject, esType, meta.searchSourceJSON, resp.references); - await hydrateIndexPattern(); + if (config.searchSource) { + try { + savedObject.searchSource = await createSearchSource(meta.searchSourceJSON, resp.references); + } catch (error) { + if ( + error.constructor.name === 'SavedObjectNotFound' && + error.savedObjectType === 'index-pattern' + ) { + // if parsing the search source fails because the index pattern wasn't found, + // remember the reference - this is required for error handling on legacy imports + savedObject.unresolvedIndexPatternReference = { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + id: JSON.parse(meta.searchSourceJSON).index, + type: 'index-pattern', + }; + } + + throw error; + } + } + if (injectReferences && resp.references && resp.references.length > 0) { injectReferences(savedObject, resp.references); } + if (typeof config.afterESResp === 'function') { savedObject = await config.afterESResp(savedObject); } + return savedObject; } diff --git a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts index b9043890e277..e8faef4e9e04 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts @@ -81,7 +81,8 @@ export function buildSavedObject( */ savedObject.init = _.once(() => intializeSavedObject(savedObject, savedObjectsClient, config)); - savedObject.applyESResp = (resp: EsResponse) => applyESResp(resp, savedObject, config); + savedObject.applyESResp = (resp: EsResponse) => + applyESResp(resp, savedObject, config, services.search.createSearchSource); /** * Serialize this object diff --git a/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts b/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts index b55538e4073b..84275cf35bef 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts @@ -31,25 +31,19 @@ export async function hydrateIndexPattern( indexPatterns: IndexPatternsContract, config: SavedObjectConfig ) { - const clearSavedIndexPattern = !!config.clearSavedIndexPattern; const indexPattern = config.indexPattern; if (!savedObject.searchSource) { return null; } - if (clearSavedIndexPattern) { - savedObject.searchSource!.setField('index', undefined); - return null; - } - - const index = id || indexPattern || savedObject.searchSource!.getOwnField('index'); + const index = id || indexPattern || savedObject.searchSource.getOwnField('index'); if (typeof index !== 'string' || !index) { return null; } const indexObj = await indexPatterns.get(index); - savedObject.searchSource!.setField('index', indexObj); + savedObject.searchSource.setField('index', indexObj); return indexObj; } diff --git a/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts b/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts deleted file mode 100644 index cdb191f9e7df..000000000000 --- a/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import _ from 'lodash'; -import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; -import { SavedObject } from '../../types'; -import { InvalidJSONProperty } from '../../../../kibana_utils/public'; - -export function parseSearchSource( - savedObject: SavedObject, - esType: string, - searchSourceJson: string, - references: any[] -) { - if (!savedObject.searchSource) return; - - // if we have a searchSource, set its values based on the searchSourceJson field - let searchSourceValues: Record; - try { - searchSourceValues = JSON.parse(searchSourceJson); - } catch (e) { - throw new InvalidJSONProperty( - `Invalid JSON in ${esType} "${savedObject.id}". ${e.message} JSON: ${searchSourceJson}` - ); - } - - // This detects a scenario where documents with invalid JSON properties have been imported into the saved object index. - // (This happened in issue #20308) - if (!searchSourceValues || typeof searchSourceValues !== 'object') { - throw new InvalidJSONProperty(`Invalid searchSourceJSON in ${esType} "${savedObject.id}".`); - } - - // Inject index id if a reference is saved - if (searchSourceValues.indexRefName) { - const reference = references.find( - (ref: Record) => ref.name === searchSourceValues.indexRefName - ); - if (!reference) { - throw new Error( - `Could not find reference for ${ - searchSourceValues.indexRefName - } on ${savedObject.getEsType()} ${savedObject.id}` - ); - } - searchSourceValues.index = reference.id; - delete searchSourceValues.indexRefName; - } - - if (searchSourceValues.filter) { - searchSourceValues.filter.forEach((filterRow: any) => { - if (!filterRow.meta || !filterRow.meta.indexRefName) { - return; - } - const reference = references.find((ref: any) => ref.name === filterRow.meta.indexRefName); - if (!reference) { - throw new Error( - `Could not find reference for ${ - filterRow.meta.indexRefName - } on ${savedObject.getEsType()}` - ); - } - filterRow.meta.index = reference.id; - delete filterRow.meta.indexRefName; - }); - } - - const searchSourceFields = savedObject.searchSource.getFields(); - const fnProps = _.transform( - searchSourceFields, - function(dynamic: Record, val: any, name: string | undefined) { - if (_.isFunction(val) && name) dynamic[name] = val; - }, - {} - ); - - savedObject.searchSource.setFields(_.defaults(searchSourceValues, fnProps)); - const query = savedObject.searchSource.getOwnField('query'); - - if (typeof query !== 'undefined') { - savedObject.searchSource.setField('query', migrateLegacyQuery(query)); - } -} diff --git a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts index 8a020ca03aea..78f9eeb8b5fb 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts @@ -17,7 +17,6 @@ * under the License. */ import _ from 'lodash'; -import angular from 'angular'; import { SavedObject, SavedObjectConfig } from '../../types'; import { expandShorthand } from '../../../../kibana_utils/public'; @@ -41,57 +40,16 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje }); if (savedObject.searchSource) { - let searchSourceFields: Record = _.omit(savedObject.searchSource.getFields(), [ - 'sort', - 'size', - ]); - if (searchSourceFields.index) { - // searchSourceFields.index will normally be an IndexPattern, but can be a string in two scenarios: - // (1) `init()` (and by extension `hydrateIndexPattern()`) hasn't been called on Saved Object - // (2) The IndexPattern doesn't exist, so we fail to resolve it in `hydrateIndexPattern()` - const indexId = - typeof searchSourceFields.index === 'string' - ? searchSourceFields.index - : searchSourceFields.index.id; - const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; - references.push({ - name: refName, - type: 'index-pattern', - id: indexId, - }); - searchSourceFields = { - ...searchSourceFields, - indexRefName: refName, - index: undefined, - }; - } - if (searchSourceFields.filter) { - searchSourceFields = { - ...searchSourceFields, - filter: searchSourceFields.filter.map((filterRow: any, i: number) => { - if (!filterRow.meta || !filterRow.meta.index) { - return filterRow; - } - const refName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; - references.push({ - name: refName, - type: 'index-pattern', - id: filterRow.meta.index, - }); - return { - ...filterRow, - meta: { - ...filterRow.meta, - indexRefName: refName, - index: undefined, - }, - }; - }), - }; - } - attributes.kibanaSavedObjectMeta = { - searchSourceJSON: angular.toJson(searchSourceFields), - }; + const { + searchSourceJSON, + references: searchSourceReferences, + } = savedObject.searchSource.serialize(); + attributes.kibanaSavedObjectMeta = { searchSourceJSON }; + references.push(...searchSourceReferences); + } + + if (savedObject.unresolvedIndexPatternReference) { + references.push(savedObject.unresolvedIndexPatternReference); } return { attributes, references }; diff --git a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts index 08389e9e3c97..60c66f84080b 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts +++ b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts @@ -103,9 +103,11 @@ describe('Saved Object', () => { } beforeEach(() => { + (dataStartMock.search.createSearchSource as jest.Mock).mockReset(); SavedObjectClass = createSavedObjectClass({ savedObjectsClient: savedObjectsClientStub, indexPatterns: dataStartMock.indexPatterns, + search: dataStartMock.search, } as SavedObjectKibanaServices); }); @@ -269,7 +271,7 @@ describe('Saved Object', () => { ); }); - it('when index exists in searchSourceJSON', () => { + it('when search source references saved object', () => { const id = '123'; stubESResponse(getMockedDocResponse(id)); return createInitializedSavedObject({ type: 'dashboard', searchSource: true }).then( @@ -409,18 +411,17 @@ describe('Saved Object', () => { }); }); - it('throws error invalid JSON is detected', async () => { + it('forwards thrown exceptions from createSearchSource', async () => { + (dataStartMock.search.createSearchSource as jest.Mock).mockImplementation(() => { + throw new InvalidJSONProperty(''); + }); const savedObject = await createInitializedSavedObject({ type: 'dashboard', searchSource: true, }); const response = { found: true, - _source: { - kibanaSavedObjectMeta: { - searchSourceJSON: '"{\\n \\"filter\\": []\\n}"', - }, - }, + _source: {}, }; try { @@ -586,23 +587,24 @@ describe('Saved Object', () => { }); }); - it('injects references from searchSourceJSON', async () => { + it('passes references to search source parsing function', async () => { const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true }); return savedObject.init!().then(() => { + const searchSourceJSON = JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + filter: [ + { + meta: { + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + }, + }, + ], + }); const response = { found: true, _source: { kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - filter: [ - { - meta: { - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', - }, - }, - ], - }), + searchSourceJSON, }, }, references: [ @@ -619,16 +621,10 @@ describe('Saved Object', () => { ], }; savedObject.applyESResp(response); - expect(savedObject.searchSource!.getFields()).toEqual({ - index: 'my-index-1', - filter: [ - { - meta: { - index: 'my-index-2', - }, - }, - ], - }); + expect(dataStartMock.search.createSearchSource).toBeCalledWith( + searchSourceJSON, + response.references + ); }); }); }); diff --git a/src/plugins/saved_objects/public/types.ts b/src/plugins/saved_objects/public/types.ts index 99088df84ec3..318403804095 100644 --- a/src/plugins/saved_objects/public/types.ts +++ b/src/plugins/saved_objects/public/types.ts @@ -24,7 +24,12 @@ import { SavedObjectAttributes, SavedObjectReference, } from 'kibana/public'; -import { IIndexPattern, IndexPatternsContract, ISearchSource } from '../../data/public'; +import { + DataPublicPluginStart, + IIndexPattern, + IndexPatternsContract, + ISearchSource, +} from '../../data/public'; export interface SavedObject { _serialize: () => { attributes: SavedObjectAttributes; references: SavedObjectReference[] }; @@ -49,6 +54,7 @@ export interface SavedObject { searchSource?: ISearchSource; showInRecentlyAccessed: boolean; title: string; + unresolvedIndexPatternReference?: SavedObjectReference; } export interface SavedObjectSaveOpts { @@ -65,6 +71,7 @@ export interface SavedObjectCreationOpts { export interface SavedObjectKibanaServices { savedObjectsClient: SavedObjectsClientContract; indexPatterns: IndexPatternsContract; + search: DataPublicPluginStart['search']; chrome: ChromeStart; overlays: OverlayStart; } @@ -72,7 +79,6 @@ export interface SavedObjectKibanaServices { export interface SavedObjectConfig { // is only used by visualize afterESResp?: (savedObject: SavedObject) => Promise; - clearSavedIndexPattern?: boolean; defaults?: any; extractReferences?: (opts: { attributes: SavedObjectAttributes; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 216defcee901..8fcb84b19a9b 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -26,6 +26,7 @@ import { setCapabilities, setHttp, setIndexPatterns, + setSearch, setSavedObjects, setUsageCollector, setFilterManager, @@ -140,6 +141,7 @@ export class VisualizationsPlugin setHttp(core.http); setSavedObjects(core.savedObjects); setIndexPatterns(data.indexPatterns); + setSearch(data.search); setFilterManager(data.query.filterManager); setExpressions(expressions); setUiActions(uiActions); @@ -150,6 +152,7 @@ export class VisualizationsPlugin const savedVisualizationsLoader = createSavedVisLoader({ savedObjectsClient: core.savedObjects.client, indexPatterns: data.indexPatterns, + search: data.search, chrome: core.chrome, overlays: core.overlays, visualizationTypes: types, diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index bc96e08f4b9d..c99c7a4c2caa 100644 --- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -35,7 +35,7 @@ import { extractReferences, injectReferences } from './saved_visualization_refer import { IIndexPattern, ISearchSource, SearchSource } from '../../../../plugins/data/public'; import { ISavedVis, SerializedVis } from '../types'; import { createSavedSearchesLoader } from '../../../../plugins/discover/public'; -import { getChrome, getOverlays, getIndexPatterns, getSavedObjects } from '../services'; +import { getChrome, getOverlays, getIndexPatterns, getSavedObjects, getSearch } from '../services'; export const convertToSerializedVis = async (savedVis: ISavedVis): Promise => { const { visState } = savedVis; @@ -87,6 +87,7 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: const savedSearch = await createSavedSearchesLoader({ savedObjectsClient: getSavedObjects().client, indexPatterns: getIndexPatterns(), + search: getSearch(), chrome: getChrome(), overlays: getOverlays(), }).get(savedSearchId); diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index c4668fa4b0c7..618c61dff176 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -63,6 +63,8 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('Search'); + export const [getUsageCollector, setUsageCollector] = createGetterSetter( 'UsageCollection' ); diff --git a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js index 252d602e8f56..bc636c0b200f 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js @@ -17,6 +17,7 @@ module.service('gisMapSavedObjectLoader', function() { const services = { savedObjectsClient, indexPatterns: npStart.plugins.data.indexPatterns, + search: npStart.plugins.data.search, chrome: npStart.core.chrome, overlays: npStart.core.overlays, }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts index f5f9e98fe659..feff17b81311 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts @@ -29,6 +29,7 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => { const savedSearches = createSavedSearchesLoader({ savedObjectsClient, indexPatterns, + search: appDeps.data.search, chrome: appDeps.chrome, overlays: appDeps.overlays, }); From 2a1c8d8de477f3aa94f010a8919ed8b9fb266117 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 9 Apr 2020 14:30:21 +0200 Subject: [PATCH 22/46] [Discover] Hide time picker when an indexpattern without timefield is selected (#62134) * Assign valid value whether the timepicker should be displayed * Add functional tests --- .../discover/np_ready/angular/discover.html | 2 +- .../_indexpattern_without_timefield.ts | 52 +++++++++++++++ test/functional/apps/discover/index.js | 1 + .../index_pattern_without_timefield/data.json | 65 +++++++++++++++++++ .../mappings.json | 39 +++++++++++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 test/functional/apps/discover/_indexpattern_without_timefield.ts create mode 100644 test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json create mode 100644 test/functional/fixtures/es_archiver/index_pattern_without_timefield/mappings.json diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index fb38f3e7d4c4..d068e824a3e0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -11,7 +11,7 @@ query="state.query" saved-query-id="state.savedQuery" screen-title="screenTitle" - show-date-picker="enableTimeRangeSelector" + show-date-picker="indexPattern.isTimeBased()" show-save-query="showSaveQuery" show-search-bar="true" use-default-behaviors="true" diff --git a/test/functional/apps/discover/_indexpattern_without_timefield.ts b/test/functional/apps/discover/_indexpattern_without_timefield.ts new file mode 100644 index 000000000000..87a2da7e44a5 --- /dev/null +++ b/test/functional/apps/discover/_indexpattern_without_timefield.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); + + describe('indexpattern without timefield', function() { + before(async function() { + await esArchiver.loadIfNeeded('index_pattern_without_timefield'); + }); + + beforeEach(async function() { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('without-timefield'); + }); + + after(async function unloadMakelogs() { + await esArchiver.unload('index_pattern_without_timefield'); + }); + + it('should not display a timepicker', async function() { + const timepickerExists = await PageObjects.timePicker.timePickerExists(); + expect(timepickerExists).to.be(false); + }); + + it('should display a timepicker after switching to an index pattern with timefield', async function() { + expect(await PageObjects.timePicker.timePickerExists()).to.be(false); + await PageObjects.discover.selectIndexPattern('with-timefield'); + expect(await PageObjects.timePicker.timePickerExists()).to.be(true); + }); + }); +} diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index 582c979a194f..50f140b99aa1 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -47,5 +47,6 @@ export default function({ getService, loadTestFile }) { loadTestFile(require.resolve('./_doc_navigation')); loadTestFile(require.resolve('./_date_nanos')); loadTestFile(require.resolve('./_date_nanos_mixed')); + loadTestFile(require.resolve('./_indexpattern_without_timefield')); }); } diff --git a/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json new file mode 100644 index 000000000000..9493408a3004 --- /dev/null +++ b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/data.json @@ -0,0 +1,65 @@ +{ + "type": "doc", + "value": { + "id": "index-pattern:without-timefield", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "title": "without-timefield" + }, + "type": "index-pattern" + } + } +} + +{ + "type": "doc", + "value": { + "id": "AU_x3-TaGFA8no6QjiSJ", + "index": "without-timefield", + "source": { + "@message" : "5", + "@timestamp": "2019-09-22T23:50:13.253Z", + "referer": "http://twitter.com/error/takuya-onishi", + "request": "/uploads/dafydd-williams.jpg", + "response": "200", + "type": "apache", + "url": "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/dafydd-williams.jpg" + } + } +} + +{ + "type": "doc", + "value": { + "id": "index-pattern:with-timefield", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", + "title": "with-timefield", + "timeFieldName": "@timestamp" + }, + "type": "index-pattern" + } + } +} + +{ + "type": "doc", + "value": { + "id": "AU_x3-TaGFA8no6QjiSJ", + "index": "with-timefield", + "source": { + "@message" : "5", + "@timestamp": "2019-09-22T23:50:13.253Z", + "referer": "http://twitter.com/error/takuya-onishi", + "request": "/uploads/dafydd-williams.jpg", + "response": "200", + "type": "apache", + "url": "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/dafydd-williams.jpg" + } + } +} + diff --git a/test/functional/fixtures/es_archiver/index_pattern_without_timefield/mappings.json b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/mappings.json new file mode 100644 index 000000000000..009611192395 --- /dev/null +++ b/test/functional/fixtures/es_archiver/index_pattern_without_timefield/mappings.json @@ -0,0 +1,39 @@ +{ + "type": "index", + "value": { + "index": "without-timefield", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "index": "with-timefield", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} From 883af7008934bc7d9bf26fc7c5663ff6a2ab8355 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Thu, 9 Apr 2020 09:39:33 -0400 Subject: [PATCH 23/46] [Remote clusters] Fix flaky jest tests (#58768) --- .../remote_clusters_add.test.js | 2 - .../remote_clusters_edit.test.js | 44 ++++++++----------- .../remote_clusters_list.test.js | 18 ++++---- .../remote_cluster_form.test.js.snap | 6 ++- .../remote_cluster_form.js | 2 +- .../remote_cluster_add/remote_cluster_add.js | 6 ++- 6 files changed, 40 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js index 78482198b1a5..569c9a6c56c5 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_add.test.js @@ -7,8 +7,6 @@ import { pageHelpers, nextTick, setupEnvironment } from './helpers'; import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './helpers/constants'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.remoteClustersAdd; describe('Create Remote cluster', () => { diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_edit.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_edit.test.js index f7625d9eec09..a5905227f49b 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_edit.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_edit.test.js @@ -4,29 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); +import { act } from 'react-dom/test-utils'; + import { RemoteClusterForm } from '../../public/application/sections/components/remote_cluster_form'; -// import { pageHelpers, setupEnvironment, nextTick } from './helpers'; -import { pageHelpers, nextTick } from './helpers'; +import { pageHelpers, setupEnvironment } from './helpers'; import { REMOTE_CLUSTER_EDIT, REMOTE_CLUSTER_EDIT_NAME } from './helpers/constants'; -// const { setup } = pageHelpers.remoteClustersEdit; +const { setup } = pageHelpers.remoteClustersEdit; const { setup: setupRemoteClustersAdd } = pageHelpers.remoteClustersAdd; -// FLAKY: https://github.com/elastic/kibana/issues/57762 -// FLAKY: https://github.com/elastic/kibana/issues/57997 -// FLAKY: https://github.com/elastic/kibana/issues/57998 -describe.skip('Edit Remote cluster', () => { - // let server; - // let httpRequestsMockHelpers; +describe('Edit Remote cluster', () => { + let server; + let httpRequestsMockHelpers; let component; let find; let exists; - - /** - * - * commented out due to hooks being called regardless of skip - * https://github.com/facebook/jest/issues/8379 + let waitFor; beforeAll(() => { ({ server, httpRequestsMockHelpers } = setupEnvironment()); @@ -39,13 +32,12 @@ describe.skip('Edit Remote cluster', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRemoteClustersResponse([REMOTE_CLUSTER_EDIT]); - ({ component, find, exists } = setup()); - await nextTick(100); // We need to wait next tick for the mock server response to kick in - component.update(); + await act(async () => { + ({ component, find, exists, waitFor } = setup()); + await waitFor('remoteClusterForm'); + }); }); - */ - test('should have the title of the page set correctly', () => { expect(exists('remoteClusterPageTitle')).toBe(true); expect(find('remoteClusterPageTitle').text()).toEqual('Edit remote cluster'); @@ -60,14 +52,16 @@ describe.skip('Edit Remote cluster', () => { * the "create" remote cluster, we won't test it again but simply make sure that * the form component is indeed shared between the 2 app sections. */ - test('should use the same Form component as the "" component', async () => { - const { component: addRemoteClusterComponent } = setupRemoteClustersAdd(); + test('should use the same Form component as the "" component', async () => { + let addRemoteClusterTestBed; - await nextTick(); - addRemoteClusterComponent.update(); + await act(async () => { + addRemoteClusterTestBed = setupRemoteClustersAdd(); + addRemoteClusterTestBed.waitFor('remoteClusterAddPage'); + }); const formEdit = component.find(RemoteClusterForm); - const formAdd = addRemoteClusterComponent.find(RemoteClusterForm); + const formAdd = addRemoteClusterTestBed.component.find(RemoteClusterForm); expect(formEdit.length).toBe(1); expect(formAdd.length).toBe(1); diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js index 954deb8b98d3..bc73387831c9 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js @@ -3,6 +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 { act } from 'react-dom/test-utils'; import { pageHelpers, @@ -17,8 +18,6 @@ import { getRemoteClusterMock } from '../../fixtures/remote_cluster'; import { PROXY_MODE } from '../../common/constants'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.remoteClustersList; describe('', () => { @@ -78,6 +77,7 @@ describe('', () => { let actions; let tableCellsValues; let rows; + let waitFor; // For deterministic tests, we need to make sure that remoteCluster1 comes before remoteCluster2 // in the table list that is rendered. As the table orders alphabetically by index name @@ -110,11 +110,11 @@ describe('', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters); - // Mount the component - ({ component, find, exists, table, actions } = setup()); + await act(async () => { + ({ component, find, exists, table, actions, waitFor } = setup()); - await nextTick(100); // Make sure that the Http request is fulfilled - component.update(); + await waitFor('remoteClusterListTable'); + }); // Read the remote clusters list table ({ rows, tableCellsValues } = table.getMetaData('remoteClusterListTable')); @@ -241,8 +241,10 @@ describe('', () => { actions.clickBulkDeleteButton(); actions.clickConfirmModalDeleteRemoteCluster(); - await nextTick(600); // there is a 500ms timeout in the api action - component.update(); + await act(async () => { + await nextTick(600); // there is a 500ms timeout in the api action + component.update(); + }); ({ rows } = table.getMetaData('remoteClusterListTable')); diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index 1e6c2c4d289a..35c566548f15 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -118,9 +118,12 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u } save={[Function]} > - +
{this.renderSaveErrorFeedback()} - + diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js index 0531310bd097..4f9c5dcd3825 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js @@ -57,7 +57,11 @@ export class RemoteClusterAdd extends PureComponent { const { isAddingCluster, addClusterError } = this.props; return ( - + Date: Thu, 9 Apr 2020 09:41:38 -0400 Subject: [PATCH 24/46] [Endpoint][EPM] Endpoint depending on ingest manager to initialize (#62871) * Endpoint successfully depending on ingest manager to initialize * Moving the endpoint functional tests to their own directory to avoid enabling ingest in the base tests * Removing page objects and other endpoint fields from base functional * Updating code owners with new functional location * Pointing resolver tests at endpoint functional tests * Pointing space tests at the endpoint functional directory * Adding jest test names --- .github/CODEOWNERS | 3 +- x-pack/plugins/endpoint/kibana.json | 2 +- .../public/applications/endpoint/index.tsx | 2 + .../endpoint/mocks/dependencies_start_mock.ts | 3 ++ .../applications/endpoint/view/setup.tsx | 52 +++++++++++++++++++ x-pack/plugins/endpoint/public/plugin.ts | 2 + .../ingest_manager/common/types/models/epm.ts | 1 + x-pack/plugins/ingest_manager/public/index.ts | 2 + .../plugins/ingest_manager/public/plugin.ts | 20 ++++++- x-pack/scripts/functional_tests.js | 2 + x-pack/test/functional/config.js | 5 -- .../endpoint/alerts/api_feature/mappings.json | 3 +- x-pack/test/functional/page_objects/index.ts | 4 -- .../apps/endpoint/alerts.ts | 0 .../feature_controls/endpoint_spaces.ts | 0 .../apps/endpoint/feature_controls/index.ts | 0 .../apps/endpoint/header_nav.ts | 0 .../apps/endpoint/host_list.ts | 0 .../apps/endpoint/index.ts | 0 .../apps/endpoint/landing_page.ts | 7 ++- .../apps/endpoint/policy_list.ts | 0 x-pack/test/functional_endpoint/config.ts | 37 +++++++++++++ .../ftr_provider_context.d.ts | 12 +++++ .../page_objects/endpoint_alerts_page.ts | 0 .../page_objects/endpoint_page.ts | 0 .../functional_endpoint/page_objects/index.ts | 15 ++++++ .../apps/endpoint/index.ts | 14 +++++ .../apps/endpoint/landing_page.ts | 22 ++++++++ .../config.ts | 30 +++++++++++ .../ftr_provider_context.d.ts | 12 +++++ x-pack/test/plugin_functional/config.ts | 4 +- 31 files changed, 238 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/applications/endpoint/view/setup.tsx rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/alerts.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/feature_controls/endpoint_spaces.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/feature_controls/index.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/header_nav.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/host_list.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/index.ts (100%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/landing_page.ts (72%) rename x-pack/test/{functional => functional_endpoint}/apps/endpoint/policy_list.ts (100%) create mode 100644 x-pack/test/functional_endpoint/config.ts create mode 100644 x-pack/test/functional_endpoint/ftr_provider_context.d.ts rename x-pack/test/{functional => functional_endpoint}/page_objects/endpoint_alerts_page.ts (100%) rename x-pack/test/{functional => functional_endpoint}/page_objects/endpoint_page.ts (100%) create mode 100644 x-pack/test/functional_endpoint/page_objects/index.ts create mode 100644 x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/index.ts create mode 100644 x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/landing_page.ts create mode 100644 x-pack/test/functional_endpoint_ingest_failure/config.ts create mode 100644 x-pack/test/functional_endpoint_ingest_failure/ftr_provider_context.d.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index feaf47e45fd6..e707250ff326 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -202,7 +202,8 @@ # Endpoint /x-pack/plugins/endpoint/ @elastic/endpoint-app-team /x-pack/test/api_integration/apis/endpoint/ @elastic/endpoint-app-team -/x-pack/test/functional/apps/endpoint/ @elastic/endpoint-app-team +/x-pack/test/functional_endpoint/ @elastic/endpoint-app-team +/x-pack/test/functional_endpoint_ingest_failure/ @elastic/endpoint-app-team /x-pack/test/functional/es_archives/endpoint/ @elastic/endpoint-app-team # SIEM diff --git a/x-pack/plugins/endpoint/kibana.json b/x-pack/plugins/endpoint/kibana.json index 5b8bec777740..4b48c83fb0e7 100644 --- a/x-pack/plugins/endpoint/kibana.json +++ b/x-pack/plugins/endpoint/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "endpoint"], - "requiredPlugins": ["features", "embeddable", "data", "dataEnhanced"], + "requiredPlugins": ["features", "embeddable", "data", "dataEnhanced", "ingestManager"], "server": true, "ui": true } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index 89a6302351a5..82ac95160519 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -18,6 +18,7 @@ import { PolicyList } from './view/policy'; import { PolicyDetails } from './view/policy'; import { HeaderNavigation } from './components/header_nav'; import { AppRootProvider } from './view/app_root_provider'; +import { Setup } from './view/setup'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. @@ -48,6 +49,7 @@ const AppRoot: React.FunctionComponent = React.memo( ({ history, store, coreStart, depsStart }) => { return ( + & { */ export interface DepsStartMock { data: DataMock; + ingestManager: IngestManagerStart; } /** @@ -54,5 +56,6 @@ export const depsStartMock: () => DepsStartMock = () => { return { data: dataMock, + ingestManager: { success: true }, }; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/setup.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/setup.tsx new file mode 100644 index 000000000000..a826e1f30f75 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/setup.tsx @@ -0,0 +1,52 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { NotificationsStart } from 'kibana/public'; +import { IngestManagerStart } from '../../../../../ingest_manager/public'; + +export const Setup: React.FunctionComponent<{ + ingestManager: IngestManagerStart; + notifications: NotificationsStart; +}> = ({ ingestManager, notifications }) => { + React.useEffect(() => { + const defaultText = i18n.translate('xpack.endpoint.ingestToastMessage', { + defaultMessage: 'Ingest Manager failed during its setup.', + }); + + const title = i18n.translate('xpack.endpoint.ingestToastTitle', { + defaultMessage: 'App failed to initialize', + }); + + const displayToastWithModal = (text: string) => { + const errorText = new Error(defaultText); + // we're leveraging the notification's error toast which is usually used for displaying stack traces of an + // actually Error. Instead of displaying a stack trace we'll display the more detailed error text when the + // user clicks `See the full error` button to see the modal + errorText.stack = text; + notifications.toasts.addError(errorText, { + title, + }); + }; + + const displayToast = () => { + notifications.toasts.addDanger({ + title, + text: defaultText, + }); + }; + + if (!ingestManager.success) { + if (ingestManager.error) { + displayToastWithModal(ingestManager.error.message); + } else { + displayToast(); + } + } + }, [ingestManager, notifications.toasts]); + + return null; +}; diff --git a/x-pack/plugins/endpoint/public/plugin.ts b/x-pack/plugins/endpoint/public/plugin.ts index ee5bbe71ae8a..9964454add80 100644 --- a/x-pack/plugins/endpoint/public/plugin.ts +++ b/x-pack/plugins/endpoint/public/plugin.ts @@ -8,6 +8,7 @@ import { Plugin, CoreSetup, AppMountParameters, CoreStart } from 'kibana/public' import { EmbeddableSetup } from 'src/plugins/embeddable/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { i18n } from '@kbn/i18n'; +import { IngestManagerStart } from '../../ingest_manager/public'; import { ResolverEmbeddableFactory } from './embeddables/resolver'; export type EndpointPluginStart = void; @@ -18,6 +19,7 @@ export interface EndpointPluginSetupDependencies { } export interface EndpointPluginStartDependencies { data: DataPublicPluginStart; + ingestManager: IngestManagerStart; } /** diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index efa662100103..5524e7505d74 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -252,6 +252,7 @@ export enum IngestAssetType { export enum DefaultPackages { base = 'base', system = 'system', + endpoint = 'endpoint', } export interface IndexTemplate { diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/ingest_manager/public/index.ts index aa1e0e79e548..c11ad60dffee 100644 --- a/x-pack/plugins/ingest_manager/public/index.ts +++ b/x-pack/plugins/ingest_manager/public/index.ts @@ -6,6 +6,8 @@ import { PluginInitializerContext } from 'src/core/public'; import { IngestManagerPlugin } from './plugin'; +export { IngestManagerStart } from './plugin'; + export const plugin = (initializerContext: PluginInitializerContext) => { return new IngestManagerPlugin(initializerContext); }; diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts index d7be1c1f1fe6..77bba0bb0f99 100644 --- a/x-pack/plugins/ingest_manager/public/plugin.ts +++ b/x-pack/plugins/ingest_manager/public/plugin.ts @@ -17,11 +17,20 @@ import { LicensingPluginSetup } from '../../licensing/public'; import { PLUGIN_ID } from '../common/constants'; import { IngestManagerConfigType } from '../common/types'; +import { setupRouteService } from '../common'; export { IngestManagerConfigType } from '../common/types'; export type IngestManagerSetup = void; -export type IngestManagerStart = void; +/** + * Describes public IngestManager plugin contract returned at the `start` stage. + */ +export interface IngestManagerStart { + success: boolean; + error?: { + message: string; + }; +} export interface IngestManagerSetupDeps { licensing: LicensingPluginSetup; @@ -61,7 +70,14 @@ export class IngestManagerPlugin }); } - public start(core: CoreStart) {} + public async start(core: CoreStart): Promise { + try { + const { isInitialized: success } = await core.http.post(setupRouteService.getSetupPath()); + return { success }; + } catch (error) { + return { success: false, error: { message: error.body?.message || 'Unknown error' } }; + } + } public stop() {} } diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 7943da07716a..061c9e4a0d92 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -43,6 +43,8 @@ const onlyNotInCoverageTests = [ require.resolve('../test/licensing_plugin/config.ts'), require.resolve('../test/licensing_plugin/config.public.ts'), require.resolve('../test/licensing_plugin/config.legacy.ts'), + require.resolve('../test/functional_endpoint_ingest_failure/config.ts'), + require.resolve('../test/functional_endpoint/config.ts'), ]; require('@kbn/plugin-helpers').babelRegister(); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index cff555feace1..bc9a67da731c 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -57,7 +57,6 @@ export default async function({ readConfigFile }) { resolve(__dirname, './apps/cross_cluster_replication'), resolve(__dirname, './apps/remote_clusters'), resolve(__dirname, './apps/transform'), - resolve(__dirname, './apps/endpoint'), // This license_management file must be last because it is destructive. resolve(__dirname, './apps/license_management'), ], @@ -88,7 +87,6 @@ export default async function({ readConfigFile }) { '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--telemetry.banner=false', '--timelion.ui.enabled=true', - '--xpack.endpoint.enabled=true', ], }, uiSettings: { @@ -199,9 +197,6 @@ export default async function({ readConfigFile }) { pathname: '/app/kibana/', hash: '/management/elasticsearch/transform', }, - endpoint: { - pathname: '/app/endpoint', - }, }, // choose where esArchiver should load archives from diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/mappings.json b/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/mappings.json index 64dc395ab69a..7068c24a4b26 100644 --- a/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/mappings.json +++ b/x-pack/test/functional/es_archives/endpoint/alerts/api_feature/mappings.json @@ -389,7 +389,8 @@ "type": "nested" }, "file_extension": { - "type": "long" + "ignore_above": 1024, + "type": "keyword" }, "project_file": { "properties": { diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 07c5719ae53c..782d57adea77 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -46,8 +46,6 @@ import { LensPageProvider } from './lens_page'; import { InfraMetricExplorerProvider } from './infra_metric_explorer'; import { RoleMappingsPageProvider } from './role_mappings_page'; import { SpaceSelectorPageProvider } from './space_selector_page'; -import { EndpointPageProvider } from './endpoint_page'; -import { EndpointAlertsPageProvider } from './endpoint_alerts_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones @@ -81,6 +79,4 @@ export const pageObjects = { copySavedObjectsToSpace: CopySavedObjectsToSpacePageProvider, lens: LensPageProvider, roleMappings: RoleMappingsPageProvider, - endpoint: EndpointPageProvider, - endpointAlerts: EndpointAlertsPageProvider, }; diff --git a/x-pack/test/functional/apps/endpoint/alerts.ts b/x-pack/test/functional_endpoint/apps/endpoint/alerts.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/alerts.ts rename to x-pack/test/functional_endpoint/apps/endpoint/alerts.ts diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts rename to x-pack/test/functional_endpoint/apps/endpoint/feature_controls/endpoint_spaces.ts diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/index.ts b/x-pack/test/functional_endpoint/apps/endpoint/feature_controls/index.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/feature_controls/index.ts rename to x-pack/test/functional_endpoint/apps/endpoint/feature_controls/index.ts diff --git a/x-pack/test/functional/apps/endpoint/header_nav.ts b/x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/header_nav.ts rename to x-pack/test/functional_endpoint/apps/endpoint/header_nav.ts diff --git a/x-pack/test/functional/apps/endpoint/host_list.ts b/x-pack/test/functional_endpoint/apps/endpoint/host_list.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/host_list.ts rename to x-pack/test/functional_endpoint/apps/endpoint/host_list.ts diff --git a/x-pack/test/functional/apps/endpoint/index.ts b/x-pack/test/functional_endpoint/apps/endpoint/index.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/index.ts rename to x-pack/test/functional_endpoint/apps/endpoint/index.ts diff --git a/x-pack/test/functional/apps/endpoint/landing_page.ts b/x-pack/test/functional_endpoint/apps/endpoint/landing_page.ts similarity index 72% rename from x-pack/test/functional/apps/endpoint/landing_page.ts rename to x-pack/test/functional_endpoint/apps/endpoint/landing_page.ts index 65af91feae40..b4da4631aa60 100644 --- a/x-pack/test/functional/apps/endpoint/landing_page.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/landing_page.ts @@ -7,8 +7,9 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -export default ({ getPageObjects }: FtrProviderContext) => { +export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'endpoint']); + const testSubjects = getService('testSubjects'); describe('Endpoint landing page', function() { this.tags('ciGroup7'); @@ -20,5 +21,9 @@ export default ({ getPageObjects }: FtrProviderContext) => { const welcomeEndpointMessage = await pageObjects.endpoint.welcomeEndpointTitle(); expect(welcomeEndpointMessage).to.be('Hello World'); }); + + it('Does not display a toast indicating that the ingest manager failed to initialize', async () => { + await testSubjects.missingOrFail('euiToastHeader'); + }); }); }; diff --git a/x-pack/test/functional/apps/endpoint/policy_list.ts b/x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts similarity index 100% rename from x-pack/test/functional/apps/endpoint/policy_list.ts rename to x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts diff --git a/x-pack/test/functional_endpoint/config.ts b/x-pack/test/functional_endpoint/config.ts new file mode 100644 index 000000000000..37bf57b67b47 --- /dev/null +++ b/x-pack/test/functional_endpoint/config.ts @@ -0,0 +1,37 @@ +/* + * 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 { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from './page_objects'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + return { + ...xpackFunctionalConfig.getAll(), + pageObjects, + testFiles: [resolve(__dirname, './apps/endpoint')], + junit: { + reportName: 'X-Pack Endpoint Functional Tests', + }, + apps: { + ...xpackFunctionalConfig.get('apps'), + endpoint: { + pathname: '/app/endpoint', + }, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.endpoint.enabled=true', + '--xpack.ingestManager.enabled=true', + '--xpack.ingestManager.fleet.enabled=true', + ], + }, + }; +} diff --git a/x-pack/test/functional_endpoint/ftr_provider_context.d.ts b/x-pack/test/functional_endpoint/ftr_provider_context.d.ts new file mode 100644 index 000000000000..21ab5d5a4e55 --- /dev/null +++ b/x-pack/test/functional_endpoint/ftr_provider_context.d.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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from './page_objects'; +import { services } from '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/functional/page_objects/endpoint_alerts_page.ts b/x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts similarity index 100% rename from x-pack/test/functional/page_objects/endpoint_alerts_page.ts rename to x-pack/test/functional_endpoint/page_objects/endpoint_alerts_page.ts diff --git a/x-pack/test/functional/page_objects/endpoint_page.ts b/x-pack/test/functional_endpoint/page_objects/endpoint_page.ts similarity index 100% rename from x-pack/test/functional/page_objects/endpoint_page.ts rename to x-pack/test/functional_endpoint/page_objects/endpoint_page.ts diff --git a/x-pack/test/functional_endpoint/page_objects/index.ts b/x-pack/test/functional_endpoint/page_objects/index.ts new file mode 100644 index 000000000000..8138ce2eeccb --- /dev/null +++ b/x-pack/test/functional_endpoint/page_objects/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; +import { EndpointPageProvider } from './endpoint_page'; +import { EndpointAlertsPageProvider } from './endpoint_alerts_page'; + +export const pageObjects = { + ...xpackFunctionalPageObjects, + endpoint: EndpointPageProvider, + endpointAlerts: EndpointAlertsPageProvider, +}; diff --git a/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/index.ts b/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/index.ts new file mode 100644 index 000000000000..ae35f3e52546 --- /dev/null +++ b/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('endpoint when the ingest manager fails to setup correctly', function() { + this.tags('ciGroup7'); + + loadTestFile(require.resolve('./landing_page')); + }); +} diff --git a/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/landing_page.ts b/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/landing_page.ts new file mode 100644 index 000000000000..d29250ca3bed --- /dev/null +++ b/x-pack/test/functional_endpoint_ingest_failure/apps/endpoint/landing_page.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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + describe('home page', function() { + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + before(async () => { + await pageObjects.common.navigateToApp('endpoint'); + }); + + it('displays an error toast', async () => { + await testSubjects.existOrFail('euiToastHeader'); + }); + }); +}; diff --git a/x-pack/test/functional_endpoint_ingest_failure/config.ts b/x-pack/test/functional_endpoint_ingest_failure/config.ts new file mode 100644 index 000000000000..a2055a4bbdc1 --- /dev/null +++ b/x-pack/test/functional_endpoint_ingest_failure/config.ts @@ -0,0 +1,30 @@ +/* + * 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 { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile( + require.resolve('../functional_endpoint/config.ts') + ); + + return { + ...xpackFunctionalConfig.getAll(), + testFiles: [resolve(__dirname, './apps/endpoint')], + junit: { + reportName: 'X-Pack Endpoint Without Ingest Functional Tests', + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + // use a bogus port so the ingest manager setup will fail + '--xpack.ingestManager.epm.registryUrl=http://127.0.0.1:12345', + ], + }, + }; +} diff --git a/x-pack/test/functional_endpoint_ingest_failure/ftr_provider_context.d.ts b/x-pack/test/functional_endpoint_ingest_failure/ftr_provider_context.d.ts new file mode 100644 index 000000000000..0e4b47471d41 --- /dev/null +++ b/x-pack/test/functional_endpoint_ingest_failure/ftr_provider_context.d.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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from '../functional_endpoint/page_objects'; +import { services } from '../functional/services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/plugin_functional/config.ts b/x-pack/test/plugin_functional/config.ts index 6c3c496da71f..aa3c9bd24842 100644 --- a/x-pack/test/plugin_functional/config.ts +++ b/x-pack/test/plugin_functional/config.ts @@ -14,7 +14,9 @@ import { pageObjects } from './page_objects'; /* eslint-disable import/no-default-export */ export default async function({ readConfigFile }: FtrConfigProviderContext) { - const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + const xpackFunctionalConfig = await readConfigFile( + require.resolve('../functional_endpoint/config.ts') + ); // Find all folders in ./plugins since we treat all them as plugin folder const allFiles = fs.readdirSync(resolve(__dirname, 'plugins')); From abe3ccf1cc45213c2991af83e261a12ce19e91d1 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Thu, 9 Apr 2020 17:00:45 +0300 Subject: [PATCH 25/46] [Table Vis] Fix visualization overflow (#62630) * Fix data table vis overflowing * Change overflow to hidden --- src/plugins/vis_default_editor/public/_default.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/vis_default_editor/public/_default.scss b/src/plugins/vis_default_editor/public/_default.scss index 985381eeb56a..2187baea547d 100644 --- a/src/plugins/vis_default_editor/public/_default.scss +++ b/src/plugins/vis_default_editor/public/_default.scss @@ -72,6 +72,7 @@ display: flex; flex-basis: 100%; flex: 1; + overflow: hidden; @include euiBreakpoint('xs', 's', 'm') { // If we are on a small screen we force the visualization to take 100% width. From f9ba963af90d41b74e8d7dde9981c9a5cee53127 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 9 Apr 2020 10:33:55 -0400 Subject: [PATCH 26/46] [ML] DF Analytics: update memory estimate after adding exclude fields (#62850) * update mml estimate when excludes changes * ensure manually input mml is validated once estimated mml loads --- .../create_analytics_form.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index 0c83dfb6a234..199100d8b5ab 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -55,7 +55,14 @@ export const CreateAnalyticsForm: FC = ({ actions, sta const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const { setFormState, setEstimatedModelMemoryLimit } = actions; const mlContext = useMlContext(); - const { form, indexPatternsMap, isAdvancedEditorEnabled, isJobCreated, requestMessages } = state; + const { + estimatedModelMemoryLimit, + form, + indexPatternsMap, + isAdvancedEditorEnabled, + isJobCreated, + requestMessages, + } = state; const forceInput = useRef(null); const firstUpdate = useRef(true); @@ -152,6 +159,9 @@ export const CreateAnalyticsForm: FC = ({ actions, sta const debouncedGetExplainData = debounce(async () => { const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; + const shouldUpdateEstimatedMml = + !firstUpdate.current || !modelMemoryLimit || estimatedModelMemoryLimit === ''; + if (firstUpdate.current) { firstUpdate.current = false; } @@ -167,13 +177,12 @@ export const CreateAnalyticsForm: FC = ({ actions, sta const jobConfig = getJobConfigFromFormState(form); delete jobConfig.dest; delete jobConfig.model_memory_limit; - delete jobConfig.analyzed_fields; const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics( jobConfig ); const expectedMemoryWithoutDisk = resp.memory_estimation?.expected_memory_without_disk; - if (shouldUpdateModelMemoryLimit) { + if (shouldUpdateEstimatedMml) { setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); } @@ -340,7 +349,14 @@ export const CreateAnalyticsForm: FC = ({ actions, sta return () => { debouncedGetExplainData.cancel(); }; - }, [jobType, sourceIndex, sourceIndexNameEmpty, dependentVariable, trainingPercent]); + }, [ + jobType, + sourceIndex, + sourceIndexNameEmpty, + dependentVariable, + trainingPercent, + JSON.stringify(excludes), + ]); // Temp effect to close the context menu popover on Clone button click useEffect(() => { From 90d2b18bc5c4db75311733aea305c87b06d43675 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Thu, 9 Apr 2020 07:43:36 -0700 Subject: [PATCH 27/46] Add Data - Adding cloud reset password link to cloud instructions (#62835) * Adding cloud reset password link to cloud filebeat instructions * Auditbeat gets the cool reset password link * And the other beats instructions get the awesome password reset link * Changing the i18n id to more closely match the on-prem cloud id * Changing text for forgot password * Removing now unused translations * "Forgot your password" -> "Forgot the password" * "Elastic Cloud UI" -> "Elastic Cloud" Co-authored-by: Elastic Machine --- .../instructions/auditbeat_instructions.ts | 33 +++---------------- .../instructions/cloud_instructions.ts | 31 +++++++++++++++++ .../instructions/filebeat_instructions.ts | 33 +++---------------- .../instructions/functionbeat_instructions.ts | 17 ++-------- .../instructions/heartbeat_instructions.ts | 33 +++---------------- .../instructions/metricbeat_instructions.ts | 33 +++---------------- .../instructions/winlogbeat_instructions.ts | 9 ++--- x-pack/plugins/cloud/public/plugin.ts | 5 +-- x-pack/plugins/cloud/server/config.ts | 2 ++ .../translations/translations/ja-JP.json | 19 ----------- .../translations/translations/zh-CN.json | 19 ----------- 11 files changed, 61 insertions(+), 173 deletions(-) create mode 100644 src/plugins/home/server/tutorials/instructions/cloud_instructions.ts diff --git a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts index 4c85ad3985b3..2a6cfa035870 100644 --- a/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/auditbeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createAuditbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -305,13 +306,7 @@ export const createAuditbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.auditbeatCloudInstructions.config.osxTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, DEB: { title: i18n.translate('home.tutorials.common.auditbeatCloudInstructions.config.debTitle', { @@ -327,13 +322,7 @@ export const createAuditbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.auditbeatCloudInstructions.config.debTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, RPM: { title: i18n.translate('home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle', { @@ -349,13 +338,7 @@ export const createAuditbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, WINDOWS: { title: i18n.translate( @@ -374,13 +357,7 @@ export const createAuditbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts new file mode 100644 index 000000000000..a18e21d2b43d --- /dev/null +++ b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { i18n } from '@kbn/i18n'; + +export const cloudPasswordAndResetLink = i18n.translate( + 'home.tutorials.common.cloudInstructions.passwordAndResetLink', + { + defaultMessage: + 'Where {passwordTemplate} is the password of the `elastic` user.' + + `\\{#config.cloud.resetPasswordUrl\\} + Forgot the password? [Reset in Elastic Cloud](\\{config.cloud.resetPasswordUrl\\}). + \\{/config.cloud.resetPasswordUrl\\}`, + values: { passwordTemplate: '``' }, + } +); diff --git a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts index 66efa36ec9bc..0e99033b2ea6 100644 --- a/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/filebeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createFilebeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -299,13 +300,7 @@ export const createFilebeatCloudInstructions = () => ({ }, }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.filebeatCloudInstructions.config.osxTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, DEB: { title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.debTitle', { @@ -318,13 +313,7 @@ export const createFilebeatCloudInstructions = () => ({ }, }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.filebeatCloudInstructions.config.debTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, RPM: { title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.rpmTitle', { @@ -337,13 +326,7 @@ export const createFilebeatCloudInstructions = () => ({ }, }), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.filebeatCloudInstructions.config.rpmTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, WINDOWS: { title: i18n.translate('home.tutorials.common.filebeatCloudInstructions.config.windowsTitle', { @@ -359,13 +342,7 @@ export const createFilebeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.filebeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts index ee13b9c5eefd..06ff84146b5d 100644 --- a/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/functionbeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createFunctionbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -200,13 +201,7 @@ export const createFunctionbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.functionbeatCloudInstructions.config.osxTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, WINDOWS: { title: i18n.translate( @@ -225,13 +220,7 @@ export const createFunctionbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts index 33f5defc0273..fa5bf5df13b6 100644 --- a/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/heartbeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { Platform, TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createHeartbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -280,13 +281,7 @@ export const createHeartbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.heartbeatCloudInstructions.config.osxTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, DEB: { title: i18n.translate('home.tutorials.common.heartbeatCloudInstructions.config.debTitle', { @@ -302,13 +297,7 @@ export const createHeartbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.heartbeatCloudInstructions.config.debTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, RPM: { title: i18n.translate('home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle', { @@ -324,13 +313,7 @@ export const createHeartbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, WINDOWS: { title: i18n.translate( @@ -349,13 +332,7 @@ export const createHeartbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts index 9fdc70e0703a..651405941610 100644 --- a/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/metricbeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createMetricbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -295,13 +296,7 @@ export const createMetricbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.metricbeatCloudInstructions.config.osxTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, DEB: { title: i18n.translate('home.tutorials.common.metricbeatCloudInstructions.config.debTitle', { @@ -317,13 +312,7 @@ export const createMetricbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.metricbeatCloudInstructions.config.debTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, RPM: { title: i18n.translate('home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle', { @@ -339,13 +328,7 @@ export const createMetricbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, WINDOWS: { title: i18n.translate( @@ -364,13 +347,7 @@ export const createMetricbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts index 9d7d0660d3d6..27d7822e080a 100644 --- a/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/winlogbeat_instructions.ts @@ -22,6 +22,7 @@ import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; import { createTrycloudOption1, createTrycloudOption2 } from './onprem_cloud_instructions'; import { getSpaceIdForBeatsTutorial } from './get_space_id_for_beats_tutorial'; import { TutorialContext } from '../../services/tutorials/lib/tutorials_registry_types'; +import { cloudPasswordAndResetLink } from './cloud_instructions'; export const createWinlogbeatInstructions = (context?: TutorialContext) => ({ INSTALL: { @@ -130,13 +131,7 @@ export const createWinlogbeatCloudInstructions = () => ({ } ), commands: ['cloud.id: "{config.cloud.id}"', 'cloud.auth: "elastic:"'], - textPost: i18n.translate( - 'home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPost', - { - defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.', - values: { passwordTemplate: '``' }, - } - ), + textPost: cloudPasswordAndResetLink, }, }, }); diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index 2b8247066bfc..62e21392f711 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -11,6 +11,7 @@ import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; interface CloudConfigType { id?: string; + resetPasswordUrl?: string; } interface CloudSetupDependencies { @@ -26,13 +27,13 @@ export class CloudPlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} public async setup(core: CoreSetup, { home }: CloudSetupDependencies) { - const { id } = this.initializerContext.config.get(); + const { id, resetPasswordUrl } = this.initializerContext.config.get(); const isCloudEnabled = getIsCloudEnabled(id); if (home) { home.environment.update({ cloud: isCloudEnabled }); if (isCloudEnabled) { - home.tutorials.setVariable('cloud', { id }); + home.tutorials.setVariable('cloud', { id, resetPasswordUrl }); } } diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index 77e493dc3b7d..d899b45aebdf 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -21,6 +21,7 @@ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), id: schema.maybe(schema.string()), apm: schema.maybe(apmConfigSchema), + resetPasswordUrl: schema.maybe(schema.string()), }); export type CloudConfigType = TypeOf; @@ -28,6 +29,7 @@ export type CloudConfigType = TypeOf; export const config: PluginConfigDescriptor = { exposeToBrowser: { id: true, + resetPasswordUrl: true, }, schema: configSchema, }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e63e1c8ad2c9..16c4b11bb6a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1196,16 +1196,12 @@ "home.tutorials.common.auditbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.auditbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.auditbeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.auditbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatCloudInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatCloudInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", @@ -1247,16 +1243,12 @@ "home.tutorials.common.filebeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.filebeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.filebeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.filebeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatCloudInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.filebeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatCloudInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatCloudInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.filebeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.filebeatEnableInstructions.debTextPost": "「/etc/filebeat/modules.d/{moduleName}.yml」ファイルで設定を変更します。", @@ -1311,10 +1303,8 @@ "home.tutorials.common.functionbeatAWSInstructions.textPost": "「」と「」がアカウント資格情報、「us-east-1」がご希望の地域です。", "home.tutorials.common.functionbeatAWSInstructions.textPre": "環境で AWS アカウント認証情報を設定します。", "home.tutorials.common.functionbeatAWSInstructions.title": "AWS 認証情報の設定", - "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.functionbeatCloudInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTextPost": "「」が投入するロググループの名前で、「」が Functionbeat デプロイのステージングに使用されるが有効な S3 バケット名です。", @@ -1345,16 +1335,12 @@ "home.tutorials.common.heartbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.heartbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.heartbeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.heartbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatCloudInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatCloudInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.heartbeatEnableCloudInstructions.debTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", @@ -1414,16 +1400,12 @@ "home.tutorials.common.metricbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.metricbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.metricbeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.metricbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatCloudInstructions.config.debTitle": "構成を編集する", - "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatCloudInstructions.config.osxTitle": "構成を編集する", - "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle": "構成を編集する", - "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.metricbeatEnableInstructions.debTextPost": "「/etc/metricbeat/modules.d/{moduleName}.yml」ファイルで設定を変更します。", @@ -1478,7 +1460,6 @@ "home.tutorials.common.winlogbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cc75ceb988d9..c81fad386ac2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1197,16 +1197,12 @@ "home.tutorials.common.auditbeat.cloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.auditbeat.premCloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.auditbeat.premInstructions.gettingStarted.title": "入门", - "home.tutorials.common.auditbeatCloudInstructions.config.debTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.auditbeatCloudInstructions.config.debTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.auditbeatCloudInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.auditbeatCloudInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.auditbeatInstructions.config.debTextPost": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。", @@ -1248,16 +1244,12 @@ "home.tutorials.common.filebeat.cloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.filebeat.premCloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.filebeat.premInstructions.gettingStarted.title": "入门", - "home.tutorials.common.filebeatCloudInstructions.config.debTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.filebeatCloudInstructions.config.debTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.filebeatCloudInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.filebeatCloudInstructions.config.osxTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.filebeatCloudInstructions.config.osxTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.filebeatCloudInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.filebeatCloudInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.filebeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.filebeatEnableInstructions.debTextPost": "在 `/etc/filebeat/modules.d/{moduleName}.yml` 文件中修改设置。", @@ -1312,10 +1304,8 @@ "home.tutorials.common.functionbeatAWSInstructions.textPost": "其中 `` 和 `` 是您的帐户凭据,`us-east-1` 是所需的地区。", "home.tutorials.common.functionbeatAWSInstructions.textPre": "在环境中设置您的 AWS 帐户凭据:", "home.tutorials.common.functionbeatAWSInstructions.title": "设置 AWS 凭据", - "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.functionbeatCloudInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTextPost": "其中 `` 是要采集的日志组名称,`` 是将用于暂存 Functionbeat 部署的有效 S3 存储桶名称。", @@ -1346,16 +1336,12 @@ "home.tutorials.common.heartbeat.cloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.heartbeat.premCloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.heartbeat.premInstructions.gettingStarted.title": "入门", - "home.tutorials.common.heartbeatCloudInstructions.config.debTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.heartbeatCloudInstructions.config.debTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.heartbeatCloudInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.heartbeatCloudInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.heartbeatEnableCloudInstructions.debTextPre": "在 `heartbeat.yml` 文件中编辑 `heartbeat.monitors` 设置。", @@ -1415,16 +1401,12 @@ "home.tutorials.common.metricbeat.cloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.metricbeat.premCloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.metricbeat.premInstructions.gettingStarted.title": "入门", - "home.tutorials.common.metricbeatCloudInstructions.config.debTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.metricbeatCloudInstructions.config.debTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.metricbeatCloudInstructions.config.debTitle": "编辑配置", - "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.metricbeatCloudInstructions.config.osxTitle": "编辑配置", - "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle": "编辑配置", - "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.metricbeatEnableInstructions.debTextPost": "在 `/etc/metricbeat/modules.d/{moduleName}.yml` 文件中修改设置。", @@ -1479,7 +1461,6 @@ "home.tutorials.common.winlogbeat.cloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.winlogbeat.premCloudInstructions.gettingStarted.title": "入门", "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "入门", - "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPost": "其中 {passwordTemplate} 是 `elastic` 用户的密码。", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "修改 {path} 以设置 Elastic Cloud 的连接信息:", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "编辑配置", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPost": "其中,{passwordTemplate} 是 `elastic` 用户的密码,{esUrlTemplate} 是 Elasticsearch 的 URL,{kibanaUrlTemplate} 是 Kibana 的 URL。", From 80384c3209d8b49ed43fd2b505cd37f8cbf38ac7 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Thu, 9 Apr 2020 07:50:11 -0700 Subject: [PATCH 28/46] Exposed AddMessageVariables as separate component (#63007) * Exposed AddMessageVariables as separate component and added styles to allow to handle bigger list of messageVariables * Fixed failing tests and styles * Fixed due to comments --- .../translations/translations/ja-JP.json | 15 -- .../translations/translations/zh-CN.json | 15 -- .../components/add_message_variables.scss | 4 + .../components/add_message_variables.tsx | 69 +++++++ .../components/builtin_action_types/email.tsx | 62 +++--- .../builtin_action_types/es_index.test.tsx | 4 +- .../builtin_action_types/es_index.tsx | 62 ++---- .../builtin_action_types/pagerduty.tsx | 182 ++++++------------ .../builtin_action_types/server_log.tsx | 63 ++---- .../components/builtin_action_types/slack.tsx | 66 ++----- .../builtin_action_types/webhook.test.tsx | 2 +- .../builtin_action_types/webhook.tsx | 54 +----- 12 files changed, 211 insertions(+), 387 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 16c4b11bb6a6..d357e40c0293 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15846,7 +15846,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "タイミング", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "タイミング", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "メールに送信", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton": "変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "サーバーからメールを送信します。", "xpack.triggersActionsUI.components.builtinActionTypes.error.formatFromText": "送信元は有効なメールアドレスではありません。", "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText": "To、Cc、または Bcc のエントリーがありません。 1 つ以上のエントリーが必要です。", @@ -15875,14 +15874,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip": "このチェックボックスは更新インデックス値を設定します。", "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText": "データを Elasticsearch にインデックスしてください。", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle": "PagerDuty に送信", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton1": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton2": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton3": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton4": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton5": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton6": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton7": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariableTitle": "アラート変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlTextFieldLabel": "API URL", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel": "クラス (任意)", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.componentTextFieldLabel": "コンポーネント(任意)", @@ -15906,14 +15897,10 @@ "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel": "まとめ", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel": "タイムスタンプ (任意)", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle": "サーバーログに送信", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariablePopoverButton": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariableTitle": "変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "レベル", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "メッセージ", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "Kibana ログにメッセージを追加します。", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "Slack に送信", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariablePopoverButton": "アラート変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariableTitle": "アラート変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.requiredWebhookUrlText": "Web フック URL が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel": "メッセージ", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText": "Slack チャネルにメッセージを送信します。", @@ -15922,8 +15909,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle": "Web フックデータ", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeader": "ヘッダーを追加", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButton": "追加", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariablePopoverButton": "変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariableTitle": "変数を追加", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyCodeEditorAriaLabel": "コードエディター", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyFieldLabel": "本文", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.deleteHeaderButton": "削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c81fad386ac2..839c89f3b1ca 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15850,7 +15850,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.descriptionLabel": "当", "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "当", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "发送到电子邮件", - "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton": "添加变量", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText": "从您的服务器发送电子邮件。", "xpack.triggersActionsUI.components.builtinActionTypes.error.formatFromText": "发送者电子邮件地址无效。", "xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText": "未输入收件人、抄送、密送。 至少需要输入一个。", @@ -15879,14 +15878,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.refreshTooltip": "此复选框设置刷新索引值。", "xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText": "将数据索引到 Elasticsearch 中。", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle": "发送到 PagerDuty", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton1": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton2": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton3": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton4": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton5": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton6": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariablePopoverButton7": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariableTitle": "添加告警变量", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.apiUrlTextFieldLabel": "API URL", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel": "类(可选)", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.componentTextFieldLabel": "组件(可选)", @@ -15910,14 +15901,10 @@ "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel": "摘要", "xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.timestampTextFieldLabel": "时间戳(可选)", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle": "发送到服务器日志", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariablePopoverButton": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariableTitle": "添加变量", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "级别", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "消息", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "将消息添加到 Kibana 日志。", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "发送到 Slack", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariablePopoverButton": "添加告警变量", - "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariableTitle": "添加告警变量", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.requiredWebhookUrlText": "Webhook URL 必填。", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel": "消息", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText": "向 Slack 频道或用户发送消息。", @@ -15926,8 +15913,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.actionTypeTitle": "Webhook 数据", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeader": "添加标头", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addHeaderButton": "添加", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariablePopoverButton": "添加变量", - "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariableTitle": "添加变量", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyCodeEditorAriaLabel": "代码编辑器", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.bodyFieldLabel": "正文", "xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.deleteHeaderButton": "删除", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss new file mode 100644 index 000000000000..996f21c4b6b0 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.scss @@ -0,0 +1,4 @@ +.messageVariablesPanel { + @include euiYScrollWithShadows; + max-height: $euiSize * 20; +} \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx new file mode 100644 index 000000000000..ab9b5c2586c1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.tsx @@ -0,0 +1,69 @@ +/* + * 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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import './add_message_variables.scss'; + +interface Props { + messageVariables: string[] | undefined; + paramsProperty: string; + onSelectEventHandler: (variable: string) => void; +} + +export const AddMessageVariables: React.FunctionComponent = ({ + messageVariables, + paramsProperty, + onSelectEventHandler, +}) => { + const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false); + + const getMessageVariables = () => + messageVariables?.map((variable: string) => ( + { + onSelectEventHandler(variable); + setIsVariablesPopoverOpen(false); + }} + > + {`{{${variable}}}`} + + )); + + const addVariableButtonTitle = i18n.translate( + 'xpack.triggersActionsUI.components.addMessageVariables.addVariableTitle', + { + defaultMessage: 'Add alert variable', + } + ); + + return ( + setIsVariablesPopoverOpen(true)} + iconType="indexOpen" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton', + { + defaultMessage: 'Add variable', + } + )} + /> + } + isOpen={isVariablesPopoverOpen} + closePopover={() => setIsVariablesPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.tsx index b4bbb8af36a1..dff697297f3e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.tsx @@ -16,10 +16,6 @@ import { EuiButtonEmpty, EuiSwitch, EuiFormRow, - EuiContextMenuItem, - EuiButtonIcon, - EuiContextMenuPanel, - EuiPopover, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { @@ -29,6 +25,7 @@ import { ActionParamsProps, } from '../../../types'; import { EmailActionParams, EmailActionConnector } from './types'; +import { AddMessageVariables } from '../add_message_variables'; export function getActionType(): ActionTypeModel { const mailformat = /^[^@\s]+@[^@\s]+$/; @@ -368,25 +365,21 @@ const EmailParamsFields: React.FunctionComponent(false); const [addBCC, setAddBCC] = useState(false); - const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false); useEffect(() => { if (!message && defaultMessage && defaultMessage.length > 0) { editAction('message', defaultMessage, index); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const messageVariablesItems = messageVariables?.map((variable: string) => ( - { - editAction('message', (message ?? '').concat(` {{${variable}}}`), index); - setIsVariablesPopoverOpen(false); - }} - > - {`{{${variable}}}`} - - )); + + const onSelectMessageVariable = (paramsProperty: string, variable: string) => { + editAction( + paramsProperty, + ((actionParams as any)[paramsProperty] ?? '').concat(` {{${variable}}}`), + index + ); + }; + return ( + onSelectMessageVariable('subject', variable) + } + paramsProperty="subject" + /> + } > setIsVariablesPopoverOpen(true)} - iconType="indexOpen" - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton', - { - defaultMessage: 'Add variable', - } - )} - /> + + onSelectMessageVariable('message', variable) } - isOpen={isVariablesPopoverOpen} - closePopover={() => setIsVariablesPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + paramsProperty="message" + /> } > { ).toBe(`{ "test": 123 }`); - expect( - wrapper.find('[data-test-subj="indexDocumentAddVariableButton"]').length > 0 - ).toBeTruthy(); + expect(wrapper.find('[data-test-subj="documentsAddVariableButton"]').length > 0).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx index 56d9f40e4002..9bd6a39d216e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx @@ -14,10 +14,6 @@ import { EuiSelect, EuiTitle, EuiIconTip, - EuiPopover, - EuiButtonIcon, - EuiContextMenuPanel, - EuiContextMenuItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -36,6 +32,7 @@ import { getIndexPatterns, } from '../../../common/index_controls'; import { useXJsonMode } from '../../lib/use_x_json_mode'; +import { AddMessageVariables } from '../add_message_variables'; export function getActionType(): ActionTypeModel { return { @@ -282,23 +279,13 @@ const IndexParamsFields: React.FunctionComponent 0 ? documents[0] : null ); - const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false); - const messageVariablesItems = messageVariables?.map((variable: string, i: number) => ( - { - const value = (xJson ?? '').concat(` {{${variable}}}`); - setXJson(value); - // Keep the documents in sync with the editor content - onDocumentsChange(convertToJson(value)); - setIsVariablesPopoverOpen(false); - }} - > - {`{{${variable}}}`} - - )); + const onSelectMessageVariable = (variable: string) => { + const value = (xJson ?? '').concat(` {{${variable}}}`); + setXJson(value); + // Keep the documents in sync with the editor content + onDocumentsChange(convertToJson(value)); + }; + function onDocumentsChange(updatedDocuments: string) { try { const documentsJSON = JSON.parse(updatedDocuments); @@ -317,34 +304,11 @@ const IndexParamsFields: React.FunctionComponent setIsVariablesPopoverOpen(true)} - iconType="indexOpen" - title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.addVariableTitle', - { - defaultMessage: 'Add variable', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.addVariablePopoverButton', - { - defaultMessage: 'Add variable', - } - )} - /> - } - isOpen={isVariablesPopoverOpen} - closePopover={() => setIsVariablesPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + onSelectMessageVariable(variable)} + paramsProperty="documents" + /> } > >({ - dedupKey: false, - summary: false, - source: false, - timestamp: false, - component: false, - group: false, - class: false, - }); - // TODO: replace this button with a proper Eui component, when it will be ready - const getMessageVariables = (paramsProperty: string) => - messageVariables?.map((variable: string) => ( - { - editAction( - paramsProperty, - ((actionParams as any)[paramsProperty] ?? '').concat(` {{${variable}}}`), - index - ); - setIsVariablesPopoverOpen({ ...isVariablesPopoverOpen, [paramsProperty]: false }); - }} - > - {`{{${variable}}}`} - - )); - const addVariableButtonTitle = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.addVariableTitle', - { - defaultMessage: 'Add alert variable', - } - ); - - const getAddVariableComponent = (paramsProperty: string, buttonName: string) => { - return ( - - setIsVariablesPopoverOpen({ ...isVariablesPopoverOpen, [paramsProperty]: true }) - } - iconType="indexOpen" - aria-label={buttonName} - /> - } - isOpen={isVariablesPopoverOpen[paramsProperty]} - closePopover={() => - setIsVariablesPopoverOpen({ ...isVariablesPopoverOpen, [paramsProperty]: false }) - } - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + const onSelectMessageVariable = (paramsProperty: string, variable: string) => { + editAction( + paramsProperty, + ((actionParams as any)[paramsProperty] ?? '').concat(` {{${variable}}}`), + index ); }; + return ( @@ -359,15 +305,15 @@ const PagerDutyParamsFields: React.FunctionComponent + onSelectMessageVariable('dedupKey', variable) } - ) - )} + paramsProperty="dedupKey" + /> + } > + onSelectMessageVariable('timestamp', variable) } - ) - )} + paramsProperty="timestamp" + /> + } > + onSelectMessageVariable('component', variable) } - ) - )} + paramsProperty="component" + /> + } > onSelectMessageVariable('group', variable)} + paramsProperty="group" + /> + } > onSelectMessageVariable('source', variable)} + paramsProperty="source" + /> + } > + onSelectMessageVariable('summary', variable) } - ) - )} + paramsProperty="summary" + /> + } > onSelectMessageVariable('class', variable)} + paramsProperty="class" + /> + } > (false); useEffect(() => { editAction('level', 'info', index); @@ -80,18 +72,11 @@ export const ServerLogParamsFields: React.FunctionComponent ( - { - editAction('message', (message ?? '').concat(` {{${variable}}}`), index); - setIsVariablesPopoverOpen(false); - }} - > - {`{{${variable}}}`} - - )); + + const onSelectMessageVariable = (paramsProperty: string, variable: string) => { + editAction(paramsProperty, (message ?? '').concat(` {{${variable}}}`), index); + }; + return ( setIsVariablesPopoverOpen(true)} - iconType="indexOpen" - title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariableTitle', - { - defaultMessage: 'Add variable', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariablePopoverButton', - { - defaultMessage: 'Add variable', - } - )} - /> + + onSelectMessageVariable('message', variable) } - isOpen={isVariablesPopoverOpen} - closePopover={() => setIsVariablesPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + paramsProperty="message" + /> } > { const { message } = actionParams; - const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false); useEffect(() => { if (!message && defaultMessage && defaultMessage.length > 0) { editAction('message', defaultMessage, index); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const messageVariablesItems = messageVariables?.map((variable: string, i: number) => ( - { - editAction('message', (message ?? '').concat(` {{${variable}}}`), index); - setIsVariablesPopoverOpen(false); - }} - > - {`{{${variable}}}`} - - )); + + const onSelectMessageVariable = (paramsProperty: string, variable: string) => { + editAction(paramsProperty, (message ?? '').concat(` {{${variable}}}`), index); + }; + return ( setIsVariablesPopoverOpen(true)} - iconType="indexOpen" - title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariableTitle', - { - defaultMessage: 'Add alert variable', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariablePopoverButton', - { - defaultMessage: 'Add alert variable', - } - )} - /> + + onSelectMessageVariable('message', variable) } - isOpen={isVariablesPopoverOpen} - closePopover={() => setIsVariablesPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + paramsProperty="message" + /> } > { .first() .prop('value') ).toStrictEqual('test message'); - expect(wrapper.find('[data-test-subj="webhookAddVariableButton"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="bodyAddVariableButton"]').length > 0).toBeTruthy(); }); test('params validation fails when body is not valid', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx index f611c3715e56..daa5a6caeabe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook.tsx @@ -22,9 +22,6 @@ import { EuiCodeEditor, EuiSwitch, EuiButtonEmpty, - EuiContextMenuItem, - EuiPopover, - EuiContextMenuPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { @@ -34,6 +31,7 @@ import { ActionParamsProps, } from '../../../types'; import { WebhookActionParams, WebhookActionConnector } from './types'; +import { AddMessageVariables } from '../add_message_variables'; const HTTP_VERBS = ['post', 'put']; @@ -467,20 +465,9 @@ const WebhookParamsFields: React.FunctionComponent { const { body } = actionParams; - const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState(false); - const messageVariablesItems = messageVariables?.map((variable: string, i: number) => ( - { - editAction('body', (body ?? '').concat(` {{${variable}}}`), index); - setIsVariablesPopoverOpen(false); - }} - > - {`{{${variable}}}`} - - )); + const onSelectMessageVariable = (paramsProperty: string, variable: string) => { + editAction(paramsProperty, (body ?? '').concat(` {{${variable}}}`), index); + }; return ( setIsVariablesPopoverOpen(true)} - iconType="indexOpen" - title={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariableTitle', - { - defaultMessage: 'Add variable', - } - )} - aria-label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.addVariablePopoverButton', - { - defaultMessage: 'Add variable', - } - )} - /> - } - isOpen={isVariablesPopoverOpen} - closePopover={() => setIsVariablesPopoverOpen(false)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - + onSelectMessageVariable('body', variable)} + paramsProperty="body" + /> } > Date: Thu, 9 Apr 2020 08:22:00 -0700 Subject: [PATCH 29/46] docs: fix rendering of bulleted list (#62855) --- docs/user/alerting/index.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index b4f7e6af3d61..c7cf1186a44b 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -163,7 +163,8 @@ If you are using an *on-premises* Elastic Stack deployment with <> * <> * <> From a0c247b9cc165638c3c01c87ce02c13ac2a4a698 Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Thu, 9 Apr 2020 11:42:10 -0400 Subject: [PATCH 30/46] [EPM] Change PACKAGES_SAVED_OBJECT_TYPE id (#62818) * changed PACKAGES_SAVED_OBJECT_TYPE id from packageName-version to packageName * change references to keys to package and version Co-authored-by: Elastic Machine --- .../server/routes/epm/handlers.ts | 4 +- .../server/services/datasource.ts | 10 ++-- .../services/epm/elasticsearch/ilm/install.ts | 12 +++-- .../elasticsearch/ingest_pipeline/install.ts | 23 +++++---- .../epm/elasticsearch/template/install.ts | 17 +++++-- .../epm/kibana/index_pattern/install.ts | 27 +++++++---- .../server/services/epm/packages/assets.ts | 2 +- .../server/services/epm/packages/get.ts | 40 +++++----------- .../services/epm/packages/get_objects.ts | 40 ---------------- .../server/services/epm/packages/index.ts | 1 - .../server/services/epm/packages/install.ts | 47 ++++++++++++------- .../server/services/epm/packages/remove.ts | 6 ++- .../server/services/epm/registry/index.ts | 25 +++++----- .../ingest_manager/server/services/setup.ts | 3 +- 14 files changed, 118 insertions(+), 139 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index 48f37a4d65ac..ad16e1dde456 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -102,7 +102,9 @@ export const getInfoHandler: RequestHandler { - const pkgInstall = await findInstalledPackageByName({ - savedObjectsClient: soClient, - pkgName, - }); + const pkgInstall = await getInstallation({ savedObjectsClient: soClient, pkgName }); if (pkgInstall) { const [pkgInfo, defaultOutputId] = await Promise.all([ getPackageInfo({ savedObjectsClient: soClient, - pkgkey: `${pkgInstall.name}-${pkgInstall.version}`, + pkgName: pkgInstall.name, + pkgVersion: pkgInstall.version, }), outputService.getDefaultOutputId(soClient), ]); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts index c56322239f27..60a85e367079 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ilm/install.ts @@ -7,9 +7,15 @@ import { CallESAsCurrentUser, ElasticsearchAssetType } from '../../../../types'; import * as Registry from '../../registry'; -export async function installILMPolicy(pkgkey: string, callCluster: CallESAsCurrentUser) { - const ilmPaths = await Registry.getArchiveInfo(pkgkey, (entry: Registry.ArchiveEntry) => - isILMPolicy(entry) +export async function installILMPolicy( + pkgName: string, + pkgVersion: string, + callCluster: CallESAsCurrentUser +) { + const ilmPaths = await Registry.getArchiveInfo( + pkgName, + pkgVersion, + (entry: Registry.ArchiveEntry) => isILMPolicy(entry) ); if (!ilmPaths.length) return; await Promise.all( diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts index 4b65e5554567..2bbb555ef739 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/install.ts @@ -30,11 +30,10 @@ export const installPipelines = async ( if (dataset.ingest_pipeline) { acc.push( installPipelinesForDataset({ - pkgkey: Registry.pkgToPkgKey(registryPackage), dataset, callCluster, - packageName: registryPackage.name, - packageVersion: registryPackage.version, + pkgName: registryPackage.name, + pkgVersion: registryPackage.version, }) ); } @@ -68,19 +67,19 @@ export function rewriteIngestPipeline( export async function installPipelinesForDataset({ callCluster, - pkgkey, + pkgName, + pkgVersion, dataset, - packageName, - packageVersion, }: { callCluster: CallESAsCurrentUser; - pkgkey: string; + pkgName: string; + pkgVersion: string; dataset: Dataset; - packageName: string; - packageVersion: string; }): Promise { - const pipelinePaths = await Registry.getArchiveInfo(pkgkey, (entry: Registry.ArchiveEntry) => - isDatasetPipeline(entry, dataset.path) + const pipelinePaths = await Registry.getArchiveInfo( + pkgName, + pkgVersion, + (entry: Registry.ArchiveEntry) => isDatasetPipeline(entry, dataset.path) ); let pipelines: any[] = []; const substitutions: RewriteSubstitution[] = []; @@ -90,7 +89,7 @@ export async function installPipelinesForDataset({ const nameForInstallation = getPipelineNameForInstallation({ pipelineName: name, dataset, - packageVersion, + packageVersion: pkgVersion, }); const content = Registry.getAsset(path).toString('utf-8'); pipelines.push({ diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index de4ba25590c9..560ddfc1f688 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -20,11 +20,12 @@ import * as Registry from '../../registry'; export const installTemplates = async ( registryPackage: RegistryPackage, callCluster: CallESAsCurrentUser, - pkgkey: string + pkgName: string, + pkgVersion: string ) => { // install any pre-built index template assets, // atm, this is only the base package's global template - installPreBuiltTemplates(pkgkey, callCluster); + installPreBuiltTemplates(pkgName, pkgVersion, callCluster); // build templates per dataset from yml files const datasets = registryPackage.datasets; @@ -45,9 +46,15 @@ export const installTemplates = async ( }; // this is temporary until we update the registry to use index templates v2 structure -const installPreBuiltTemplates = async (pkgkey: string, callCluster: CallESAsCurrentUser) => { - const templatePaths = await Registry.getArchiveInfo(pkgkey, (entry: Registry.ArchiveEntry) => - isTemplate(entry) +const installPreBuiltTemplates = async ( + pkgName: string, + pkgVersion: string, + callCluster: CallESAsCurrentUser +) => { + const templatePaths = await Registry.getArchiveInfo( + pkgName, + pkgVersion, + (entry: Registry.ArchiveEntry) => isTemplate(entry) ); templatePaths.forEach(async path => { const { file } = Registry.pathParts(path); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts index 0657fb7759b4..05e64c6565dc 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts @@ -68,25 +68,32 @@ export enum IndexPatternType { metrics = 'metrics', events = 'events', } - +// TODO: use a function overload and make pkgName and pkgVersion required for install/update +// and not for an update removal. or separate out the functions export async function installIndexPatterns( savedObjectsClient: SavedObjectsClientContract, - pkgkey?: string + pkgName?: string, + pkgVersion?: string ) { // get all user installed packages const installedPackages = await getPackageKeysByStatus( savedObjectsClient, InstallationStatus.installed ); - // add this package to the array if it doesn't already exist - // this should not happen because a user can't "reinstall" a package - // if it does because the install endpoint is called directly, the install continues - if (pkgkey && !installedPackages.includes(pkgkey)) { - installedPackages.push(pkgkey); + if (pkgName && pkgVersion) { + // add this package to the array if it doesn't already exist + const foundPkg = installedPackages.find(pkg => pkg.pkgName === pkgName); + // this may be removed if we add the packged to saved objects before installing index patterns + // otherwise this is a first time install + // TODO: handle update case when versions are different + if (!foundPkg) { + installedPackages.push({ pkgName, pkgVersion }); + } } - // get each package's registry info - const installedPackagesFetchInfoPromise = installedPackages.map(pkg => Registry.fetchInfo(pkg)); + const installedPackagesFetchInfoPromise = installedPackages.map(pkg => + Registry.fetchInfo(pkg.pkgName, pkg.pkgVersion) + ); const installedPackagesInfo = await Promise.all(installedPackagesFetchInfoPromise); // for each index pattern type, create an index pattern @@ -97,7 +104,7 @@ export async function installIndexPatterns( ]; indexPatternTypes.forEach(async indexPatternType => { // if this is an update because a package is being unisntalled (no pkgkey argument passed) and no other packages are installed, remove the index pattern - if (!pkgkey && installedPackages.length === 0) { + if (!pkgName && installedPackages.length === 0) { try { await savedObjectsClient.delete(INDEX_PATTERN_SAVED_OBJECT_TYPE, `${indexPatternType}-*`); } catch (err) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index d7a5c5569986..7026d9eae24c 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -58,7 +58,7 @@ export async function getAssetsData( ): Promise { // TODO: Needs to be called to fill the cache but should not be required const pkgkey = packageInfo.name + '-' + packageInfo.version; - if (!cacheHas(pkgkey)) await Registry.getArchiveInfo(pkgkey); + if (!cacheHas(pkgkey)) await Registry.getArchiveInfo(packageInfo.name, packageInfo.version); // Gather all asset data const assets = getAssets(packageInfo, filter, datasetName); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index e963ea138dfd..0e2c2a3d2607 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -41,7 +41,7 @@ export async function getPackages( .map(item => createInstallableFrom( item, - savedObjectsVisible.find(({ attributes }) => attributes.name === item.name) + savedObjectsVisible.find(({ id }) => id === item.name) ) ) .sort(sortByName); @@ -53,9 +53,9 @@ export async function getPackageKeysByStatus( status: InstallationStatus ) { const allPackages = await getPackages({ savedObjectsClient }); - return allPackages.reduce((acc, pkg) => { + return allPackages.reduce>((acc, pkg) => { if (pkg.status === status) { - acc.push(`${pkg.name}-${pkg.version}`); + acc.push({ pkgName: pkg.name, pkgVersion: pkg.version }); } return acc; }, []); @@ -63,13 +63,14 @@ export async function getPackageKeysByStatus( export async function getPackageInfo(options: { savedObjectsClient: SavedObjectsClientContract; - pkgkey: string; + pkgName: string; + pkgVersion: string; }): Promise { - const { savedObjectsClient, pkgkey } = options; + const { savedObjectsClient, pkgName, pkgVersion } = options; const [item, savedObject] = await Promise.all([ - Registry.fetchInfo(pkgkey), - getInstallationObject({ savedObjectsClient, pkgkey }), - Registry.getArchiveInfo(pkgkey), + Registry.fetchInfo(pkgName, pkgVersion), + getInstallationObject({ savedObjectsClient, pkgName }), + Registry.getArchiveInfo(pkgName, pkgVersion), ] as const); // adding `as const` due to regression in TS 3.7.2 // see https://github.com/microsoft/TypeScript/issues/34925#issuecomment-550021453 @@ -86,37 +87,22 @@ export async function getPackageInfo(options: { export async function getInstallationObject(options: { savedObjectsClient: SavedObjectsClientContract; - pkgkey: string; + pkgName: string; }) { - const { savedObjectsClient, pkgkey } = options; + const { savedObjectsClient, pkgName } = options; return savedObjectsClient - .get(PACKAGES_SAVED_OBJECT_TYPE, pkgkey) + .get(PACKAGES_SAVED_OBJECT_TYPE, pkgName) .catch(e => undefined); } export async function getInstallation(options: { savedObjectsClient: SavedObjectsClientContract; - pkgkey: string; + pkgName: string; }) { const savedObject = await getInstallationObject(options); return savedObject?.attributes; } -export async function findInstalledPackageByName(options: { - savedObjectsClient: SavedObjectsClientContract; - pkgName: string; -}): Promise { - const { savedObjectsClient, pkgName } = options; - - const res = await savedObjectsClient.find({ - type: PACKAGES_SAVED_OBJECT_TYPE, - search: pkgName, - searchFields: ['name'], - }); - if (res.saved_objects.length) return res.saved_objects[0].attributes; - return undefined; -} - function sortByName(a: { name: string }, b: { name: string }) { if (a.name > b.name) { return 1; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts index b924c045870f..b623295c5e06 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts @@ -11,46 +11,6 @@ import * as Registry from '../registry'; type ArchiveAsset = Pick; type SavedObjectToBe = Required & { type: AssetType }; -export async function getObjects( - pkgkey: string, - filter = (entry: Registry.ArchiveEntry): boolean => true -): Promise { - // Create a Map b/c some values, especially index-patterns, are referenced multiple times - const objects: Map = new Map(); - - // Get paths which match the given filter - const paths = await Registry.getArchiveInfo(pkgkey, filter); - - // Get all objects which matched filter. Add them to the Map - const rootObjects = await Promise.all(paths.map(getObject)); - rootObjects.forEach(obj => objects.set(obj.id, obj)); - - // Each of those objects might have `references` property like [{id, type, name}] - for (const object of rootObjects) { - // For each of those objects, if they have references - for (const reference of object.references) { - // Get the referenced objects. Call same function with a new filter - const referencedObjects = await getObjects(pkgkey, (entry: Registry.ArchiveEntry) => { - // Skip anything we've already stored - if (objects.has(reference.id)) return false; - - // Is the archive entry the reference we want? - const { type, file } = Registry.pathParts(entry.path); - const isType = type === reference.type; - const isJson = file === `${reference.id}.json`; - - return isType && isJson; - }); - - // Add referenced objects to the Map - referencedObjects.forEach(ro => objects.set(ro.id, ro)); - } - } - - // return the array of unique objects - return Array.from(objects.values()); -} - export async function getObject(key: string) { const buffer = Registry.getAsset(key); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts index 79259ce79ff4..d49e0e661440 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts @@ -21,7 +21,6 @@ export { getPackageInfo, getPackages, SearchParams, - findInstalledPackageByName, } from './get'; export { installKibanaAssets, installPackage, ensureInstalledPackage } from './install'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 82523e37509d..e250b4f17681 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -16,7 +16,7 @@ import { import { installIndexPatterns } from '../kibana/index_pattern/install'; import * as Registry from '../registry'; import { getObject } from './get_objects'; -import { getInstallation, findInstalledPackageByName } from './index'; +import { getInstallation } from './index'; import { installTemplates } from '../elasticsearch/template/install'; import { installPipelines } from '../elasticsearch/ingest_pipeline/install'; import { installILMPolicy } from '../elasticsearch/ilm/install'; @@ -63,7 +63,7 @@ export async function ensureInstalledPackage(options: { callCluster: CallESAsCurrentUser; }): Promise { const { savedObjectsClient, pkgName, callCluster } = options; - const installedPackage = await findInstalledPackageByName({ savedObjectsClient, pkgName }); + const installedPackage = await getInstallation({ savedObjectsClient, pkgName }); if (installedPackage) { return installedPackage; } @@ -74,7 +74,7 @@ export async function ensureInstalledPackage(options: { pkgName, callCluster, }); - return await findInstalledPackageByName({ savedObjectsClient, pkgName }); + return await getInstallation({ savedObjectsClient, pkgName }); } catch (err) { throw new Error(err.message); } @@ -86,22 +86,30 @@ export async function installPackage(options: { callCluster: CallESAsCurrentUser; }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; - const registryPackageInfo = await Registry.fetchInfo(pkgkey); - const { name: pkgName, version: pkgVersion, internal = false } = registryPackageInfo; + // TODO: change epm API to /packageName/version so we don't need to do this + const [pkgName, pkgVersion] = pkgkey.split('-'); + const registryPackageInfo = await Registry.fetchInfo(pkgName, pkgVersion); + const { internal = false } = registryPackageInfo; const installKibanaAssetsPromise = installKibanaAssets({ savedObjectsClient, - pkgkey, + pkgName, + pkgVersion, }); const installPipelinePromises = installPipelines(registryPackageInfo, callCluster); - const installTemplatePromises = installTemplates(registryPackageInfo, callCluster, pkgkey); + const installTemplatePromises = installTemplates( + registryPackageInfo, + callCluster, + pkgName, + pkgVersion + ); // index patterns and ilm policies are not currently associated with a particular package // so we do not save them in the package saved object state. at some point ILM policies can be installed/modified // per dataset and we should then save them - await installIndexPatterns(savedObjectsClient, pkgkey); + await installIndexPatterns(savedObjectsClient, pkgName, pkgVersion); // currenly only the base package has an ILM policy - await installILMPolicy(pkgkey, callCluster); + await installILMPolicy(pkgName, pkgVersion, callCluster); const res = await Promise.all([ installKibanaAssetsPromise, @@ -126,14 +134,15 @@ export async function installPackage(options: { // e.g. switch statement with cases for each enum key returning `never` for default case export async function installKibanaAssets(options: { savedObjectsClient: SavedObjectsClientContract; - pkgkey: string; + pkgName: string; + pkgVersion: string; }) { - const { savedObjectsClient, pkgkey } = options; + const { savedObjectsClient, pkgName, pkgVersion } = options; // Only install Kibana assets during package installation. const kibanaAssetTypes = Object.values(KibanaAssetType); const installationPromises = kibanaAssetTypes.map(async assetType => - installKibanaSavedObjects({ savedObjectsClient, pkgkey, assetType }) + installKibanaSavedObjects({ savedObjectsClient, pkgName, pkgVersion, assetType }) ); // installKibanaSavedObjects returns AssetReference[], so .map creates AssetReference[][] @@ -149,8 +158,8 @@ export async function saveInstallationReferences(options: { internal: boolean; toSave: AssetReference[]; }) { - const { savedObjectsClient, pkgkey, pkgName, pkgVersion, internal, toSave } = options; - const installation = await getInstallation({ savedObjectsClient, pkgkey }); + const { savedObjectsClient, pkgName, pkgVersion, internal, toSave } = options; + const installation = await getInstallation({ savedObjectsClient, pkgName }); const savedRefs = installation?.installed || []; const mergeRefsReducer = (current: AssetReference[], pending: AssetReference) => { const hasRef = current.find(c => c.id === pending.id && c.type === pending.type); @@ -162,7 +171,7 @@ export async function saveInstallationReferences(options: { await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, { installed: toInstall, name: pkgName, version: pkgVersion, internal }, - { id: pkgkey, overwrite: true } + { id: pkgName, overwrite: true } ); return toInstall; @@ -170,16 +179,18 @@ export async function saveInstallationReferences(options: { async function installKibanaSavedObjects({ savedObjectsClient, - pkgkey, + pkgName, + pkgVersion, assetType, }: { savedObjectsClient: SavedObjectsClientContract; - pkgkey: string; + pkgName: string; + pkgVersion: string; assetType: KibanaAssetType; }) { const isSameType = ({ path }: Registry.ArchiveEntry) => assetType === Registry.pathParts(path).type; - const paths = await Registry.getArchiveInfo(pkgkey, isSameType); + const paths = await Registry.getArchiveInfo(pkgName, pkgVersion, isSameType); const toBeSavedObjects = await Promise.all(paths.map(getObject)); if (toBeSavedObjects.length === 0) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 2e73160453c2..a30acb97b99c 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -17,12 +17,14 @@ export async function removeInstallation(options: { callCluster: CallESAsCurrentUser; }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; - const installation = await getInstallation({ savedObjectsClient, pkgkey }); + // TODO: the epm api should change to /name/version so we don't need to do this + const [pkgName] = pkgkey.split('-'); + const installation = await getInstallation({ savedObjectsClient, pkgName }); const installedObjects = installation?.installed || []; // Delete the manager saved object with references to the asset objects // could also update with [] or some other state - await savedObjectsClient.delete(PACKAGES_SAVED_OBJECT_TYPE, pkgkey); + await savedObjectsClient.delete(PACKAGES_SAVED_OBJECT_TYPE, pkgName); // recreate or delete index patterns when a package is uninstalled await installIndexPatterns(savedObjectsClient); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index 36a04b88bba2..a96afc5eb7fa 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -56,10 +56,9 @@ export async function fetchFindLatestPackage( } } -export async function fetchInfo(pkgkey: string): Promise { +export async function fetchInfo(pkgName: string, pkgVersion: string): Promise { const registryUrl = appContextService.getConfig()?.epm.registryUrl; - // change pkg-version to pkg/version - return fetchUrl(`${registryUrl}/package/${pkgkey.replace('-', '/')}`).then(JSON.parse); + return fetchUrl(`${registryUrl}/package/${pkgName}/${pkgVersion}`).then(JSON.parse); } export async function fetchFile(filePath: string): Promise { @@ -73,7 +72,8 @@ export async function fetchCategories(): Promise { } export async function getArchiveInfo( - pkgkey: string, + pkgName: string, + pkgVersion: string, filter = (entry: ArchiveEntry): boolean => true ): Promise { const paths: string[] = []; @@ -87,7 +87,7 @@ export async function getArchiveInfo( } }; - await extract(pkgkey, filter, onEntry); + await extract(pkgName, pkgVersion, filter, onEntry); return paths; } @@ -123,21 +123,22 @@ export function pathParts(path: string): AssetParts { } async function extract( - pkgkey: string, + pkgName: string, + pkgVersion: string, filter = (entry: ArchiveEntry): boolean => true, onEntry: (entry: ArchiveEntry) => void ) { - const archiveBuffer = await getOrFetchArchiveBuffer(pkgkey); + const archiveBuffer = await getOrFetchArchiveBuffer(pkgName, pkgVersion); return untarBuffer(archiveBuffer, filter, onEntry); } -async function getOrFetchArchiveBuffer(pkgkey: string): Promise { +async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { // assume .tar.gz for now. add support for .zip if/when we need it - const key = `${pkgkey}.tar.gz`; + const key = `${pkgName}-${pkgVersion}.tar.gz`; let buffer = cacheGet(key); if (!buffer) { - buffer = await fetchArchiveBuffer(pkgkey); + buffer = await fetchArchiveBuffer(pkgName, pkgVersion); cacheSet(key, buffer); } @@ -148,8 +149,8 @@ async function getOrFetchArchiveBuffer(pkgkey: string): Promise { } } -async function fetchArchiveBuffer(key: string): Promise { - const { download: archivePath } = await fetchInfo(key); +async function fetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { + const { download: archivePath } = await fetchInfo(pkgName, pkgVersion); const registryUrl = appContextService.getConfig()?.epm.registryUrl; return getResponseStream(`${registryUrl}${archivePath}`).then(streamToBuffer); } diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index 224355ced7cb..bbaf083fb839 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -120,7 +120,8 @@ async function addPackageToConfig( ) { const packageInfo = await getPackageInfo({ savedObjectsClient: soClient, - pkgkey: `${packageToInstall.name}-${packageToInstall.version}`, + pkgName: packageToInstall.name, + pkgVersion: packageToInstall.version, }); await datasourceService.create( soClient, From fddf6cbee5ca1e486f6bc2724d55b2143e93fe74 Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Thu, 9 Apr 2020 08:52:06 -0700 Subject: [PATCH 31/46] [APM] docs: add alerting examples for APM (#62864) --- docs/apm/apm-alerts.asciidoc | 97 +++++++++++++++++++++++++++++ docs/apm/images/apm-alert.png | Bin 0 -> 658181 bytes docs/apm/using-the-apm-ui.asciidoc | 3 + 3 files changed, 100 insertions(+) create mode 100644 docs/apm/apm-alerts.asciidoc create mode 100644 docs/apm/images/apm-alert.png diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc new file mode 100644 index 000000000000..b8552c007b13 --- /dev/null +++ b/docs/apm/apm-alerts.asciidoc @@ -0,0 +1,97 @@ +[role="xpack"] +[[apm-alerts]] +=== Create an alert + +beta::[] + +The APM app is integrated with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] feature. +It provides a set of built-in **actions** and APM specific threshold **alerts** for you to use, +and allows all alerts to be centrally managed from <>. + +[role="screenshot"] +image::apm/images/apm-alert.png[Create an alert in the APM app] + +There are two different types of threshold alerts: transaction duration, and error rate. +Below, we'll create one of each. + +[float] +[[apm-create-transaction-alert]] +=== Create a transaction duration alert + +This guide creates an alert for the `opbeans-java` service based on the following criteria: + +* Transaction type: `transaction.type:request` +* Average request is above `1500ms` for the last 5 minutes +* Check every 10 minutes, and repeat the alert every 30 minutes +* Send the alert via Slack + +From the APM app, navigate to the `opbeans-java` service and select +**Alerts** > **Create threshold alert** > **Transaction duration**. + +The name of your alert will automatically be set as `Transaction duration | opbeans-java`, +and the alert will be tagged with `apm` and `service.name:opbeans-java`. +Feel free to edit either of these defaults. + +Based on the alert criteria, define the following alert details: + +* **Check every** - `10 minutes` +* **Notify every** - `30 minutes` +* **TYPE** - `request` +* **WHEN** - `avg` +* **IS ABOVE** - `1500ms` +* **FOR THE LAST** - `5 minutes` + +Select an action type. +Multiple action types can be selected, but in this example we want to post to a slack channel. +Select **Slack** > **Create a connector**. +Enter a name for the connector, +and paste the webhook URL. +See Slack's webhook documentation if you need to create one. + +Select **Save**. The alert has been created and is now active! + +[float] +[[apm-create-error-alert]] +=== Create an error rate alert + +This guide creates an alert for the `opbeans-python` service based on the following criteria: + +* Error rate is above 25 for the last minute +* Check every 1 minute, and repeat the alert every 10 minutes +* Send the alert via email to the `opbeans-python` team + +From the APM app, navigate to the `opbeans-python` service and select +**Alerts** > **Create threshold alert** > **Error rate**. + +The name of your alert will automatically be set as `Error rate | opbeans-python`, +and the alert will be tagged with `apm` and `service.name:opbeans-python`. +Feel free to edit either of these defaults. + +Based on the alert criteria, define the following alert details: + +* **Check every** - `1 minute` +* **Notify every** - `10 minutes` +* **IS ABOVE** - `25 errors` +* **FOR THE LAST** - `1 minute` + +Select the **Email** action type and click **Create a connector**. +Fill out the required details: sender, host, port, etc., and click **save**. + +Select **Save**. The alert has been created and is now active! + +[float] +[[apm-alert-manage]] +=== Manage alerts and actions + +From the APM app, select **Alerts** > **View active alerts** to be taken to the Kibana alerts and actions management page. +From this page, you can create, edit, disable, mute, and delete alerts, and create, edit, and disable connectors. + +[float] +[[apm-alert-more-info]] +=== More information + +See {kibana-ref}/alerting-getting-started.html[alerting and actions] for more information. + +NOTE: If you are using an **on-premise** Elastic Stack deployment with security, +TLS must be configured for communication between Elasticsearch and Kibana. +More information is in the alerting {kibana-ref}/alerting-getting-started.html#alerting-setup-prerequisites[prerequisites]. \ No newline at end of file diff --git a/docs/apm/images/apm-alert.png b/docs/apm/images/apm-alert.png new file mode 100644 index 0000000000000000000000000000000000000000..4cee7214637f8e235db2f9ee1201c603cfdb8aa7 GIT binary patch literal 658181 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


#q|^Hy8wAq!HGKrX@3GsH68Pm$O( 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 z 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 Sfju@hR&lzL+{V0hctHXv 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

+ + + +

); }); @@ -170,3 +186,15 @@ export const HostDetailsFlyout = () => { ); }; + +const useHostLogsUrl = (hostId: string): { url: string; appId: string; appPath: string } => { + const { services } = useKibana(); + return useMemo(() => { + const appPath = `/stream?logFilter=(expression:'host.id:${hostId}',kind:kuery)`; + return { + url: `${services.application.getUrlForApp('logs')}${appPath}`, + appId: 'logs', + appPath, + }; + }, [hostId, services.application]); +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index f6dfae99c1b1..c3ff41268e3d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -6,40 +6,26 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { I18nProvider } from '@kbn/i18n/react'; -import { EuiThemeProvider } from '../../../../../../../legacy/common/eui_styled_components'; -import { appStoreFactory } from '../../store'; -import { RouteCapture } from '../route_capture'; -import { createMemoryHistory, MemoryHistory } from 'history'; -import { Router } from 'react-router-dom'; +import { fireEvent } from '@testing-library/react'; import { AppAction } from '../../types'; import { HostList } from './index'; -import { mockHostResultList } from '../../store/hosts/mock_host_result_list'; +import { + mockHostDetailsApiResult, + mockHostResultList, +} from '../../store/hosts/mock_host_result_list'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks'; +import { HostInfo } from '../../../../../common/types'; describe('when on the hosts page', () => { - let render: () => reactTestingLibrary.RenderResult; - let history: MemoryHistory; - let store: ReturnType; + let render: () => ReturnType; + let history: AppContextTestRender['history']; + let store: AppContextTestRender['store']; + let coreStart: AppContextTestRender['coreStart']; beforeEach(async () => { - history = createMemoryHistory(); - store = appStoreFactory(); - render = () => { - return reactTestingLibrary.render( - - - - - - - - - - - - ); - }; + const mockedContext = createAppRootMockRenderer(); + ({ history, store, coreStart } = mockedContext); + render = () => mockedContext.render(); }); it('should show a table', async () => { @@ -56,7 +42,7 @@ describe('when on the hosts page', () => { expect(e).not.toBeNull(); }); }); - describe('when data loads', () => { + describe('when list data loads', () => { beforeEach(() => { reactTestingLibrary.act(() => { const action: AppAction = { @@ -76,6 +62,16 @@ describe('when on the hosts page', () => { describe('when the user clicks the hostname in the table', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { + const hostDetailsApiResponse = mockHostDetailsApiResult(); + + coreStart.http.get.mockReturnValue(Promise.resolve(hostDetailsApiResponse)); + reactTestingLibrary.act(() => { + store.dispatch({ + type: 'serverReturnedHostDetails', + payload: hostDetailsApiResponse, + }); + }); + renderResult = render(); const detailsLink = await renderResult.findByTestId('hostnameCellLink'); if (detailsLink) { @@ -93,19 +89,71 @@ describe('when on the hosts page', () => { }); describe('when there is a selected host in the url', () => { + let hostDetails: HostInfo; beforeEach(() => { + const { + host_status, + metadata: { host, ...details }, + } = mockHostDetailsApiResult(); + hostDetails = { + host_status, + metadata: { + ...details, + host: { + ...host, + id: '1', + }, + }, + }; + + coreStart.http.get.mockReturnValue(Promise.resolve(hostDetails)); + coreStart.application.getUrlForApp.mockReturnValue('/app/logs'); + reactTestingLibrary.act(() => { history.push({ ...history.location, search: '?selected_host=1', }); }); + reactTestingLibrary.act(() => { + store.dispatch({ + type: 'serverReturnedHostDetails', + payload: hostDetails, + }); + }); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should show the flyout', () => { const renderResult = render(); return renderResult.findByTestId('hostDetailsFlyout').then(flyout => { expect(flyout).not.toBeNull(); }); }); + it('should include the link to logs', async () => { + const renderResult = render(); + const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); + expect(linkToLogs).not.toBeNull(); + expect(linkToLogs.textContent).toEqual('Endpoint Logs'); + expect(linkToLogs.getAttribute('href')).toEqual( + "/app/logs/stream?logFilter=(expression:'host.id:1',kind:kuery)" + ); + }); + describe('when link to logs is clicked', () => { + beforeEach(async () => { + const renderResult = render(); + const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); + reactTestingLibrary.act(() => { + fireEvent.click(linkToLogs); + }); + }); + + it('should navigate to logs without full page refresh', async () => { + // FIXME: this is not working :( + expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); + }); + }); }); }); From 0dd89e388d3a0d87e7ea07be74b706db234c1c26 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 9 Apr 2020 09:56:11 -0600 Subject: [PATCH 34/46] [Maps] create NOT EXISTS filter for tooltip property with no value (#62849) * [Maps] create NOT EXISTS filter for tooltip property with no value * review feedback --- .../tooltips/es_tooltip_property.test.ts | 105 ++++++++++++++++++ .../layers/tooltips/es_tooltip_property.ts | 19 +++- .../layers/tooltips/join_tooltip_property.ts | 4 +- .../layers/tooltips/tooltip_property.ts | 6 +- 4 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.test.ts diff --git a/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.test.ts b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.test.ts new file mode 100644 index 000000000000..2cc9e1513719 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; +import { ESTooltipProperty } from './es_tooltip_property'; +import { TooltipProperty } from './tooltip_property'; +import { AbstractField } from '../fields/field'; +import { FIELD_ORIGIN } from '../../../common/constants'; + +class MockField extends AbstractField {} + +const indexPatternField = { + name: 'machine.os', + type: 'string', + esTypes: ['text'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, +} as IFieldType; + +const featurePropertyField = new MockField({ + fieldName: 'machine.os', + origin: FIELD_ORIGIN.SOURCE, +}); + +const indexPattern = { + id: 'indexPatternId', + fields: { + getByName: (name: string): IFieldType | null => { + return name === 'machine.os' ? indexPatternField : null; + }, + }, + title: 'my index pattern', +} as IndexPattern; + +describe('getESFilters', () => { + test('Should return empty array when field does not exist in index pattern', async () => { + const notFoundFeaturePropertyField = new MockField({ + fieldName: 'field name that is not in index pattern', + origin: FIELD_ORIGIN.SOURCE, + }); + const esTooltipProperty = new ESTooltipProperty( + new TooltipProperty( + notFoundFeaturePropertyField.getName(), + await notFoundFeaturePropertyField.getLabel(), + 'my value' + ), + indexPattern, + notFoundFeaturePropertyField + ); + expect(await esTooltipProperty.getESFilters()).toEqual([]); + }); + + test('Should return phrase filter when field value is provided', async () => { + const esTooltipProperty = new ESTooltipProperty( + new TooltipProperty( + featurePropertyField.getName(), + await featurePropertyField.getLabel(), + 'my value' + ), + indexPattern, + featurePropertyField + ); + expect(await esTooltipProperty.getESFilters()).toEqual([ + { + meta: { + index: 'indexPatternId', + }, + query: { + match_phrase: { + ['machine.os']: 'my value', + }, + }, + }, + ]); + }); + + test('Should return NOT exists filter for null values', async () => { + const esTooltipProperty = new ESTooltipProperty( + new TooltipProperty( + featurePropertyField.getName(), + await featurePropertyField.getLabel(), + undefined + ), + indexPattern, + featurePropertyField + ); + expect(await esTooltipProperty.getESFilters()).toEqual([ + { + meta: { + index: 'indexPatternId', + negate: true, + }, + exists: { + field: 'machine.os', + }, + }, + ]); + }); +}); diff --git a/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts index 5c3500988192..d2fdcfaab476 100644 --- a/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts @@ -7,8 +7,12 @@ import _ from 'lodash'; import { ITooltipProperty } from './tooltip_property'; import { IField } from '../fields/field'; -import { esFilters, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; -import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + Filter, + IFieldType, + IndexPattern, +} from '../../../../../../src/plugins/data/public'; export class ESTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; @@ -64,12 +68,19 @@ export class ESTooltipProperty implements ITooltipProperty { ); } - async getESFilters(): Promise { + async getESFilters(): Promise { const indexPatternField = this._getIndexPatternField(); if (!indexPatternField) { return []; } - return [esFilters.buildPhraseFilter(indexPatternField, this.getRawValue(), this._indexPattern)]; + const value = this.getRawValue(); + if (value == null) { + const existsFilter = esFilters.buildExistsFilter(indexPatternField, this._indexPattern); + existsFilter.meta.negate = true; + return [existsFilter]; + } else { + return [esFilters.buildPhraseFilter(indexPatternField, value, this._indexPattern)]; + } } } diff --git a/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts index 4af236f6e9e3..cc95c12ef630 100644 --- a/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts @@ -6,7 +6,7 @@ import { ITooltipProperty } from './tooltip_property'; import { IJoin } from '../joins/join'; -import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { Filter } from '../../../../../../src/plugins/data/public'; export class JoinTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; @@ -37,7 +37,7 @@ export class JoinTooltipProperty implements ITooltipProperty { return this._tooltipProperty.getHtmlDisplayValue(); } - async getESFilters(): Promise { + async getESFilters(): Promise { const esFilters = []; if (this._tooltipProperty.isFilterable()) { esFilters.push(...(await this._tooltipProperty.getESFilters())); diff --git a/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts index 7d680dfe9cae..8da2ed795943 100644 --- a/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { Filter } from '../../../../../../src/plugins/data/public'; import { TooltipFeature } from '../../../../../plugins/maps/common/descriptor_types'; export interface ITooltipProperty { @@ -14,7 +14,7 @@ export interface ITooltipProperty { getHtmlDisplayValue(): string; getRawValue(): string | undefined; isFilterable(): boolean; - getESFilters(): Promise; + getESFilters(): Promise; } export interface LoadFeatureProps { @@ -70,7 +70,7 @@ export class TooltipProperty implements ITooltipProperty { return false; } - async getESFilters(): Promise { + async getESFilters(): Promise { return []; } } From dfea62187f0e1984a50c9cff0c367f31b8728083 Mon Sep 17 00:00:00 2001 From: Maryia Lapata Date: Thu, 9 Apr 2020 18:56:36 +0300 Subject: [PATCH 35/46] [NP] Inline buildPointSeriesData and buildHierarchicalData dependencies (#61575) * Move buildHierarchicalData to vislib * Move shortened version of buildPointSeriesData to Discover * Move buildPointSeriesData to vis_type_vislib * Convert unit tests to jest * Remove ui/agg_response * Convert point_series files to TS * Update TS in unit tests * Convert buildHierarchicalData to TS * Convert buildPointSeriesData to TS in Discover * Clean TS in Discover * Update TS for buildHierarchicalData * Update buildHierarchicalData unit tests * Clean up TS in point_series * Add unit tests fro response_handler.js * Simplify point_series for Discover * Return array for data * Add check for empty row * Simplify point_series for Discover * Return all points * Specify TS * Refactoring * Simplifying * improve types * Update _get_point.test.ts Co-authored-by: Elastic Machine Co-authored-by: Joe Reuter --- .../kibana/public/discover/kibana_services.ts | 2 - .../np_ready/angular/directives/histogram.tsx | 14 +- .../np_ready/angular/helpers/index.ts} | 0 .../np_ready/angular/helpers/point_series.ts | 111 +++++++ .../np_ready/angular/response_handler.js | 3 +- .../core_plugins/kibana/public/kibana.js | 1 - .../vis_type_vislib/public/legacy_imports.ts | 5 - .../vislib/__tests__/response_handlers.js | 137 --------- .../build_hierarchical_data.test.ts} | 108 ++++--- .../hierarchical/build_hierarchical_data.ts} | 50 +++- .../public/vislib/helpers/index.ts} | 13 +- .../helpers/point_series/_add_to_siri.test.ts | 84 ++++++ .../helpers/point_series/_add_to_siri.ts | 60 ++++ .../point_series/_fake_x_aspect.test.ts} | 15 +- .../helpers/point_series/_fake_x_aspect.ts} | 5 +- .../point_series/_get_aspects.test.ts} | 53 ++-- .../helpers/point_series/_get_aspects.ts} | 20 +- .../helpers/point_series/_get_point.test.ts | 104 +++++++ .../helpers/point_series/_get_point.ts} | 53 +++- .../helpers/point_series/_get_series.test.ts | 281 +++++++++++++++++ .../helpers/point_series/_get_series.ts | 88 ++++++ .../point_series/_init_x_axis.test.ts} | 91 +++--- .../helpers/point_series/_init_x_axis.ts} | 20 +- .../point_series/_init_y_axis.test.ts} | 19 +- .../helpers/point_series/_init_y_axis.ts} | 13 +- .../point_series/_ordered_date_axis.test.ts} | 20 +- .../point_series/_ordered_date_axis.ts} | 10 +- .../vislib/helpers/point_series/index.ts} | 10 +- .../point_series/point_series.test.ts} | 81 ++--- .../helpers/point_series/point_series.ts | 118 ++++++++ .../public/vislib/response_handler.js | 4 +- .../public/vislib/response_handler.test.ts | 130 ++++++++ .../vis_type_vislib/public/vislib/types.ts} | 40 +-- .../point_series/__tests__/_add_to_siri.js | 82 ----- .../point_series/__tests__/_get_point.js | 97 ------ .../point_series/__tests__/_get_series.js | 283 ------------------ .../agg_response/point_series/_get_series.js | 100 ------- .../agg_response/point_series/point_series.js | 42 --- .../dashboard_mode/public/dashboard_viewer.js | 1 - .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 41 files changed, 1330 insertions(+), 1042 deletions(-) rename src/legacy/{ui/public/agg_response/point_series/index.js => core_plugins/kibana/public/discover/np_ready/angular/helpers/index.ts} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts delete mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js rename src/legacy/{ui/public/agg_response/hierarchical/build_hierarchical_data.test.js => core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts} (80%) rename src/legacy/{ui/public/agg_response/hierarchical/build_hierarchical_data.js => core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts} (63%) rename src/legacy/{ui/public/agg_response/point_series/__tests__/point_series.js => core_plugins/vis_type_vislib/public/vislib/helpers/index.ts} (71%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts rename src/legacy/{ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts} (74%) rename src/legacy/{ui/public/agg_response/point_series/_fake_x_aspect.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts} (88%) rename src/legacy/{ui/public/agg_response/point_series/__tests__/_get_aspects.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts} (53%) rename src/legacy/{ui/public/agg_response/point_series/_get_aspects.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts} (72%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts rename src/legacy/{ui/public/agg_response/point_series/_get_point.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts} (69%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts rename src/legacy/{ui/public/agg_response/point_series/__tests__/_init_x_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts} (51%) rename src/legacy/{ui/public/agg_response/point_series/_init_x_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts} (73%) rename src/legacy/{ui/public/agg_response/point_series/__tests__/_init_y_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts} (81%) rename src/legacy/{ui/public/agg_response/point_series/_init_y_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts} (82%) rename src/legacy/{ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts} (76%) rename src/legacy/{ui/public/agg_response/point_series/_ordered_date_axis.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts} (72%) rename src/legacy/{ui/public/agg_response/index.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts} (69%) rename src/legacy/{ui/public/agg_response/point_series/__tests__/_main.js => core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts} (62%) create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts create mode 100644 src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts rename src/legacy/{ui/public/agg_response/point_series/_add_to_siri.js => core_plugins/vis_type_vislib/public/vislib/types.ts} (67%) delete mode 100644 src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js delete mode 100644 src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js delete mode 100644 src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js delete mode 100644 src/legacy/ui/public/agg_response/point_series/_get_series.js delete mode 100644 src/legacy/ui/public/agg_response/point_series/point_series.js diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 98679a8f24d1..0a81ca0222b0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -76,5 +76,3 @@ export { EsQuerySortValue, SortDirection, } from '../../../../../plugins/data/public'; -// @ts-ignore -export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx index f788347ac016..8c55622e4c60 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx @@ -46,9 +46,10 @@ import { IUiSettingsClient } from 'kibana/public'; import { EuiChartThemeType } from '@elastic/eui/dist/eui_charts_theme'; import { Subscription } from 'rxjs'; import { getServices } from '../../../kibana_services'; +import { Chart as IChart } from '../helpers/point_series'; export interface DiscoverHistogramProps { - chartData: any; + chartData: IChart; timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; } @@ -163,7 +164,7 @@ export class DiscoverHistogram extends Component { - const xAxisFormat = this.props.chartData.xAxisFormat.params.pattern; + const xAxisFormat = this.props.chartData.xAxisFormat.params!.pattern; return moment(val).format(xAxisFormat); }; @@ -208,18 +209,19 @@ export class DiscoverHistogram extends Component domainStart ? domainStart : data[0].x; + const domainMin = data[0]?.x > domainStart ? domainStart : data[0]?.x; const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue; const xDomain = { diff --git a/src/legacy/ui/public/agg_response/point_series/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/index.ts similarity index 100% rename from src/legacy/ui/public/agg_response/point_series/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts new file mode 100644 index 000000000000..02dd024b0981 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/helpers/point_series.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uniq } from 'lodash'; +import { Duration, Moment } from 'moment'; +import { Unit } from '@elastic/datemath'; + +import { SerializedFieldFormat } from '../../../../../../../../plugins/expressions/common/types'; + +export interface Column { + id: string; + name: string; +} + +export interface Row { + [key: string]: number | 'NaN'; +} + +export interface Table { + columns: Column[]; + rows: Row[]; +} + +interface HistogramParams { + date: true; + interval: Duration; + intervalESValue: number; + intervalESUnit: Unit; + format: string; + bounds: { + min: Moment; + max: Moment; + }; +} +export interface Dimension { + accessor: 0 | 1; + format: SerializedFieldFormat<{ pattern: string }>; +} + +export interface Dimensions { + x: Dimension & { params: HistogramParams }; + y: Dimension; +} + +interface Ordered { + date: true; + interval: Duration; + intervalESUnit: string; + intervalESValue: number; + min: Moment; + max: Moment; +} +export interface Chart { + values: Array<{ + x: number; + y: number; + }>; + xAxisOrderedValues: number[]; + xAxisFormat: Dimension['format']; + xAxisLabel: Column['name']; + yAxisLabel?: Column['name']; + ordered: Ordered; +} + +export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { + const { x, y } = dimensions; + const xAccessor = table.columns[x.accessor].id; + const yAccessor = table.columns[y.accessor].id; + const chart = {} as Chart; + + chart.xAxisOrderedValues = uniq(table.rows.map(r => r[xAccessor] as number)); + chart.xAxisFormat = x.format; + chart.xAxisLabel = table.columns[x.accessor].name; + + const { intervalESUnit, intervalESValue, interval, bounds } = x.params; + chart.ordered = { + date: true, + interval, + intervalESUnit, + intervalESValue, + min: bounds.min, + max: bounds.max, + }; + + chart.yAxisLabel = table.columns[y.accessor].name; + + chart.values = table.rows + .filter(row => row && row[yAccessor] !== 'NaN') + .map(row => ({ + x: row[xAccessor] as number, + y: row[yAccessor] as number, + })); + + return chart; +}; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js index 0c19c1084153..04ccb67ec7e2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/response_handler.js @@ -17,7 +17,8 @@ * under the License. */ -import { buildPointSeriesData, getServices } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; +import { buildPointSeriesData } from './helpers'; function tableResponseHandler(table, dimensions) { const converted = { tables: [] }; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index bceb3fa7eef8..0a026a5e0c31 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -46,7 +46,6 @@ import './discover/legacy'; import './visualize/legacy'; import './management'; import './dev_tools'; -import 'ui/agg_response'; import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public'; import 'leaflet'; import { localApplicationService } from './local_application_service'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index da16a38deba9..c04ffa506eb0 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -19,8 +19,3 @@ import { search } from '../../../../plugins/data/public'; export const { tabifyAggResponse, tabifyGetColumns } = search; - -// @ts-ignore -export { buildHierarchicalData } from 'ui/agg_response/hierarchical/build_hierarchical_data'; -// @ts-ignore -export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js deleted file mode 100644 index 3574fb232883..000000000000 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/response_handlers.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { aggResponseIndex } from 'ui/agg_response'; - -import { vislibSeriesResponseHandler } from '../response_handler'; - -/** - * TODO: Fix these tests if still needed - * - * All these tests were not being run in master or prodiced false positive results - * Fixing them would require changes to the response handler logic. - */ - -describe.skip('Basic Response Handler', function() { - beforeEach(ngMock.module('kibana')); - - it('returns empty object if conversion failed', () => { - const data = vislibSeriesResponseHandler({}); - expect(data).to.not.be.an('undefined'); - expect(data).to.equal({}); - }); - - it('returns empty object if no data was found', () => { - const data = vislibSeriesResponseHandler({ - columns: [{ id: '1', title: '1', aggConfig: {} }], - rows: [], - }); - expect(data).to.not.be.an('undefined'); - expect(data.rows).to.equal([]); - }); -}); - -describe.skip('renderbot#buildChartData', function() { - describe('for hierarchical vis', function() { - it('defers to hierarchical aggResponse converter', function() { - const football = {}; - const stub = sinon.stub(aggResponseIndex, 'hierarchical').returns(football); - expect(vislibSeriesResponseHandler(football)).to.be(football); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[1]).to.be(football); - }); - }); - - describe('for point plot', function() { - it('calls tabify to simplify the data into a table', function() { - const football = { tables: [], hits: { total: 1 } }; - const stub = sinon.stub(aggResponseIndex, 'tabify').returns(football); - expect(vislibSeriesResponseHandler(football)).to.eql({ rows: [], hits: 1 }); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[1]).to.be(football); - }); - - it('returns a single chart if the tabify response contains only a single table', function() { - const chart = { hits: 1, rows: [], columns: [] }; - const esResp = { hits: { total: 1 } }; - const tabbed = { tables: [{}] }; - - sinon.stub(aggResponseIndex, 'tabify').returns(tabbed); - expect(vislibSeriesResponseHandler(esResp)).to.eql(chart); - }); - - it('converts table groups into rows/columns wrappers for charts', function() { - const converter = sinon.stub().returns('chart'); - const esResp = { hits: { total: 1 } }; - const tables = [{}, {}, {}, {}]; - - sinon.stub(aggResponseIndex, 'tabify').returns({ - tables: [ - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [tables[0]], - }, - { - aggConfig: { params: { row: false } }, - tables: [tables[1]], - }, - ], - }, - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [tables[2]], - }, - { - aggConfig: { params: { row: false } }, - tables: [tables[3]], - }, - ], - }, - ], - }); - - const chartData = vislibSeriesResponseHandler(esResp); - - // verify tables were converted - expect(converter).to.have.property('callCount', 4); - expect(converter.args[0][1]).to.be(tables[0]); - expect(converter.args[1][1]).to.be(tables[1]); - expect(converter.args[2][1]).to.be(tables[2]); - expect(converter.args[3][1]).to.be(tables[3]); - - expect(chartData).to.have.property('rows'); - expect(chartData.rows).to.have.length(2); - chartData.rows.forEach(function(row) { - expect(row).to.have.property('columns'); - expect(row.columns).to.eql(['chart', 'chart']); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts similarity index 80% rename from src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts index 21a937bf1fb6..475555f3a15f 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts @@ -17,24 +17,26 @@ * under the License. */ -import { buildHierarchicalData } from './build_hierarchical_data'; +import { buildHierarchicalData, Dimensions, Dimension } from './build_hierarchical_data'; +import { Table, TableParent } from '../../types'; -function tableVisResponseHandler(table, dimensions) { - const converted = { +function tableVisResponseHandler(table: Table, dimensions: Dimensions) { + const converted: { + tables: Array; + } = { tables: [], }; const split = dimensions.splitColumn || dimensions.splitRow; if (split) { - converted.direction = dimensions.splitRow ? 'row' : 'column'; const splitColumnIndex = split[0].accessor; const splitColumn = table.columns[splitColumnIndex]; - const splitMap = {}; + const splitMap: { [key: string]: number } = {}; let splitIndex = 0; table.rows.forEach((row, rowIndex) => { - const splitValue = row[splitColumn.id]; + const splitValue = row[splitColumn.id] as string; if (!splitMap.hasOwnProperty(splitValue)) { splitMap[splitValue] = splitIndex++; @@ -46,8 +48,8 @@ function tableVisResponseHandler(table, dimensions) { column: splitColumnIndex, row: rowIndex, table, - tables: [], - }; + tables: [] as Table[], + } as any; tableGroup.tables.push({ $parent: tableGroup, @@ -59,34 +61,30 @@ function tableVisResponseHandler(table, dimensions) { } const tableIndex = splitMap[splitValue]; - converted.tables[tableIndex].tables[0].rows.push(row); + (converted.tables[tableIndex] as TableParent).tables![0].rows.push(row); }); } else { converted.tables.push({ columns: table.columns, rows: table.rows, - }); + } as Table); } return converted; } -jest.mock('ui/new_platform'); -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: jest.fn().mockReturnValue({ - get: jest.fn().mockReturnValue('KQL'), - }), -})); -jest.mock('ui/visualize/loader/pipeline_helpers/utilities', () => ({ - getFormat: jest.fn(() => ({ - convert: jest.fn(v => v), +jest.mock('../../../services', () => ({ + getFormatService: jest.fn(() => ({ + deserialize: () => ({ + convert: jest.fn(v => JSON.stringify(v)), + }), })), })); describe('buildHierarchicalData convertTable', () => { describe('metric only', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(() => { const tabifyResponse = { @@ -94,11 +92,11 @@ describe('buildHierarchicalData convertTable', () => { rows: [{ 'col-0-agg_1': 412032 }], }; dimensions = { - metric: { accessor: 0 }, + metric: { accessor: 0 } as Dimension, }; const tableGroup = tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the slices with one child to a consistent label', () => { @@ -118,8 +116,8 @@ describe('buildHierarchicalData convertTable', () => { }); describe('threeTermBuckets', () => { - let dimensions; - let tables; + let dimensions: Dimensions; + let tables: TableParent[]; beforeEach(async () => { const tabifyResponse = { @@ -231,60 +229,60 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - splitRow: [{ accessor: 0 }], - metric: { accessor: 5 }, - buckets: [{ accessor: 2 }, { accessor: 4 }], + splitRow: [{ accessor: 0 } as Dimension], + metric: { accessor: 5 } as Dimension, + buckets: [{ accessor: 2 }, { accessor: 4 }] as Dimension[], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - tables = tableGroup.tables; + tables = tableGroup.tables as TableParent[]; }); it('should set the correct hits attribute for each of the results', () => { tables.forEach(t => { - const results = buildHierarchicalData(t.tables[0], dimensions); + const results = buildHierarchicalData(t.tables![0], dimensions); expect(results).toHaveProperty('hits'); expect(results.hits).toBe(4); }); }); it('should set the correct names for each of the results', () => { - const results0 = buildHierarchicalData(tables[0].tables[0], dimensions); + const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); expect(results0).toHaveProperty('names'); expect(results0.names).toHaveLength(5); - const results1 = buildHierarchicalData(tables[1].tables[0], dimensions); + const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); expect(results1).toHaveProperty('names'); expect(results1.names).toHaveLength(5); - const results2 = buildHierarchicalData(tables[2].tables[0], dimensions); + const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); expect(results2).toHaveProperty('names'); expect(results2.names).toHaveLength(4); }); it('should set the parent of the first item in the split', () => { - const results0 = buildHierarchicalData(tables[0].tables[0], dimensions); + const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); expect(results0).toHaveProperty('slices'); expect(results0.slices).toHaveProperty('children'); expect(results0.slices.children).toHaveLength(2); - expect(results0.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'png'); + expect(results0.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'png'); - const results1 = buildHierarchicalData(tables[1].tables[0], dimensions); + const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); expect(results1).toHaveProperty('slices'); expect(results1.slices).toHaveProperty('children'); expect(results1.slices.children).toHaveLength(2); - expect(results1.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'css'); + expect(results1.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'css'); - const results2 = buildHierarchicalData(tables[2].tables[0], dimensions); + const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); expect(results2).toHaveProperty('slices'); expect(results2.slices).toHaveProperty('children'); expect(results2.slices.children).toHaveLength(2); - expect(results2.slices.children[0].rawData.table.$parent).toHaveProperty('key', 'html'); + expect(results2.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'html'); }); }); describe('oneHistogramBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -302,11 +300,11 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, - buckets: [{ accessor: 0, params: { field: 'bytes', interval: 8192 } }], + metric: { accessor: 1 } as Dimension, + buckets: [{ accessor: 0 } as Dimension], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { @@ -320,8 +318,8 @@ describe('buildHierarchicalData convertTable', () => { }); describe('oneRangeBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -335,11 +333,11 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, - buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } }], + metric: { accessor: 1 } as Dimension, + buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } } as Dimension], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { @@ -348,13 +346,13 @@ describe('buildHierarchicalData convertTable', () => { expect(results).toHaveProperty('slices'); expect(results.slices).toHaveProperty('children'); expect(results).toHaveProperty('names'); - // expect(results.names).toHaveLength(2); + expect(results.names).toHaveLength(2); }); }); describe('oneFilterBucket', () => { - let dimensions; - let table; + let dimensions: Dimensions; + let table: Table; beforeEach(async () => { const tabifyResponse = { @@ -368,15 +366,15 @@ describe('buildHierarchicalData convertTable', () => { ], }; dimensions = { - metric: { accessor: 1 }, + metric: { accessor: 1 } as Dimension, buckets: [ { accessor: 0, }, - ], + ] as Dimension[], }; const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0]; + table = tableGroup.tables[0] as Table; }); it('should set the hits attribute for the results', () => { diff --git a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts similarity index 63% rename from src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts index dcc27e956b3f..2c6d62ed084b 100644 --- a/src/legacy/ui/public/agg_response/hierarchical/build_hierarchical_data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts @@ -18,11 +18,41 @@ */ import { toArray } from 'lodash'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { SerializedFieldFormat } from '../../../../../../../plugins/expressions/common/types'; +import { getFormatService } from '../../../services'; +import { Table } from '../../types'; -export const buildHierarchicalData = (table, { metric, buckets = [] }) => { - let slices; - const names = {}; +export interface Dimension { + accessor: number; + format: { + id?: string; + params?: SerializedFieldFormat; + }; +} + +export interface Dimensions { + metric: Dimension; + buckets?: Dimension[]; + splitRow?: Dimension[]; + splitColumn?: Dimension[]; +} + +interface Slice { + name: string; + size: number; + parent?: Slice; + children?: []; + rawData?: { + table: Table; + row: number; + column: number; + value: string | number | object; + }; +} + +export const buildHierarchicalData = (table: Table, { metric, buckets = [] }: Dimensions) => { + let slices: Slice[]; + const names: { [key: string]: string } = {}; const metricColumn = table.columns[metric.accessor]; const metricFieldFormatter = metric.format; @@ -30,25 +60,25 @@ export const buildHierarchicalData = (table, { metric, buckets = [] }) => { slices = [ { name: metricColumn.name, - size: table.rows[0][metricColumn.id], + size: table.rows[0][metricColumn.id] as number, }, ]; names[metricColumn.name] = metricColumn.name; } else { slices = []; table.rows.forEach((row, rowIndex) => { - let parent; + let parent: Slice; let dataLevel = slices; buckets.forEach(bucket => { const bucketColumn = table.columns[bucket.accessor]; const bucketValueColumn = table.columns[bucket.accessor + 1]; - const bucketFormatter = getFormat(bucket.format); + const bucketFormatter = getFormatService().deserialize(bucket.format); const name = bucketFormatter.convert(row[bucketColumn.id]); - const size = row[bucketValueColumn.id]; + const size = row[bucketValueColumn.id] as number; names[name] = name; - let slice = dataLevel.find(slice => slice.name === name); + let slice = dataLevel.find(dataLevelSlice => dataLevelSlice.name === name); if (!slice) { slice = { name, @@ -66,7 +96,7 @@ export const buildHierarchicalData = (table, { metric, buckets = [] }) => { } parent = slice; - dataLevel = slice.children; + dataLevel = slice.children as []; }); }); } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts similarity index 71% rename from src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts index 9c3e1c8180eb..90924e79f602 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/index.ts @@ -17,14 +17,5 @@ * under the License. */ -describe('Point Series Agg Response', function() { - require('./_main'); - require('./_add_to_siri'); - require('./_fake_x_aspect'); - require('./_get_aspects'); - require('./_get_point'); - require('./_get_series'); - require('./_init_x_axis'); - require('./_init_y_axis'); - require('./_ordered_date_axis'); -}); +export { buildPointSeriesData } from './point_series'; +export { buildHierarchicalData } from './hierarchical/build_hierarchical_data'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts new file mode 100644 index 000000000000..e4fdd6bb71c0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.test.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { addToSiri, Serie } from './_add_to_siri'; +import { Point } from './_get_point'; +import { Dimension } from './point_series'; + +describe('addToSiri', function() { + it('creates a new series the first time it sees an id', function() { + const series = new Map(); + const point = {} as Point; + const id = 'id'; + addToSiri(series, point, id, id, { id }); + + const expectedSerie = series.get(id) as Serie; + expect(series.has(id)).toBe(true); + expect(expectedSerie).toEqual(expect.any(Object)); + expect(expectedSerie.label).toBe(id); + expect(expectedSerie.values).toHaveLength(1); + expect(expectedSerie.values[0]).toBe(point); + }); + + it('adds points to existing series if id has been seen', function() { + const series = new Map(); + const id = 'id'; + + const point = {} as Point; + addToSiri(series, point, id, id, { id }); + + const point2 = {} as Point; + addToSiri(series, point2, id, id, { id }); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(id); + expect(series.get(id).values).toHaveLength(2); + expect(series.get(id).values[0]).toBe(point); + expect(series.get(id).values[1]).toBe(point2); + }); + + it('allows overriding the series label', function() { + const series = new Map(); + const id = 'id'; + const label = 'label'; + const point = {} as Point; + addToSiri(series, point, id, label, { id }); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(label); + expect(series.get(id).values).toHaveLength(1); + expect(series.get(id).values[0]).toBe(point); + }); + + it('correctly sets id and rawId', function() { + const series = new Map(); + const id = 'id-id2'; + + const point = {} as Point; + addToSiri(series, point, id, undefined, {} as Dimension['format']); + + expect(series.has(id)).toBe(true); + expect(series.get(id)).toEqual(expect.any(Object)); + expect(series.get(id).label).toBe(id); + expect(series.get(id).rawId).toBe(id); + expect(series.get(id).id).toBe('id2'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts new file mode 100644 index 000000000000..5e5185d6c31a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_add_to_siri.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Point } from './_get_point'; +import { Dimension } from './point_series'; + +export interface Serie { + id: string; + rawId: string; + label: string; + count: number; + values: Point[]; + format: Dimension['format']; + zLabel?: string; + zFormat?: Dimension['format']; +} + +export function addToSiri( + series: Map, + point: Point, + id: string, + yLabel: string | undefined | null, + yFormat: Dimension['format'], + zFormat?: Dimension['format'], + zLabel?: string +) { + id = id == null ? '' : id + ''; + + if (series.has(id)) { + (series.get(id) as Serie).values.push(point); + return; + } + + series.set(id, { + id: id.split('-').pop() as string, + rawId: id, + label: yLabel == null ? id : yLabel, + count: 0, + values: [point], + format: yFormat, + zLabel, + zFormat, + }); +} diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts similarity index 74% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts index 6c246d7f5089..43d4c3d7ca7c 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_fake_x_aspect.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.test.ts @@ -16,20 +16,17 @@ * specific language governing permissions and limitations * under the License. */ - -import expect from '@kbn/expect'; -import { makeFakeXAspect } from '../_fake_x_aspect'; +import { makeFakeXAspect } from './_fake_x_aspect'; describe('makeFakeXAspect', function() { it('creates an object that looks like an aspect', function() { const aspect = makeFakeXAspect(); - expect(aspect) - .to.have.property('accessor', -1) - .and.have.property('title', 'All docs') - .and.have.property('format') - .and.have.property('params'); + expect(aspect).toHaveProperty('accessor', -1); + expect(aspect).toHaveProperty('title', 'All docs'); + expect(aspect).toHaveProperty('format'); + expect(aspect).toHaveProperty('params'); - expect(aspect.params).to.have.property('defaultValue', '_all'); + expect(aspect.params).toHaveProperty('defaultValue', '_all'); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts similarity index 88% rename from src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts index 254a42baeddb..1bffa4cceb5b 100644 --- a/src/legacy/ui/public/agg_response/point_series/_fake_x_aspect.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_fake_x_aspect.ts @@ -18,16 +18,17 @@ */ import { i18n } from '@kbn/i18n'; +import { Aspect } from './point_series'; export function makeFakeXAspect() { return { accessor: -1, - title: i18n.translate('common.ui.aggResponse.allDocsTitle', { + title: i18n.translate('visTypeVislib.aggResponse.allDocsTitle', { defaultMessage: 'All docs', }), params: { defaultValue: '_all', }, format: {}, - }; + } as Aspect; } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts similarity index 53% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts index fab5c2e290e7..450b283abbed 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_aspects.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.test.ts @@ -17,37 +17,37 @@ * under the License. */ -import expect from '@kbn/expect'; -import { getAspects } from '../_get_aspects'; +import { getAspects } from './_get_aspects'; +import { Dimension, Dimensions, Aspect } from './point_series'; +import { Table, Row } from '../../types'; describe('getAspects', function() { - let table; - let dimensions; + let table: Table; + let dimensions: Dimensions; - function validate(aspect, i) { - expect(aspect) - .to.be.an('object') - .and.have.property('accessor', i); + function validate(aspect: Aspect, i: string) { + expect(aspect).toEqual(expect.any(Object)); + expect(aspect).toHaveProperty('accessor', i); } - function init(group, x, y) { + function init(group: number, x: number | null, y: number) { table = { columns: [ - { id: '0', title: 'date' }, // date - { id: '1', title: 'date utc_time' }, // date - { id: '2', title: 'ext' }, // extension - { id: '3', title: 'geo.src' }, // extension - { id: '4', title: 'count' }, // count - { id: '5', title: 'avg bytes' }, // avg + { id: '0', name: 'date' }, // date + { id: '1', name: 'date utc_time' }, // date + { id: '2', name: 'ext' }, // extension + { id: '3', name: 'geo.src' }, // extension + { id: '4', name: 'count' }, // count + { id: '5', name: 'avg bytes' }, // avg ], - rows: [], - }; + rows: [] as Row[], + } as Table; dimensions = { - x: { accessor: x }, - y: { accessor: y }, - series: { accessor: group }, - }; + x: { accessor: x } as Dimension, + y: [{ accessor: y } as Dimension], + series: [{ accessor: group } as Dimension], + } as Dimensions; } it('produces an aspect object for each of the aspect types found in the columns', function() { @@ -55,8 +55,8 @@ describe('getAspects', function() { const aspects = getAspects(table, dimensions); validate(aspects.x[0], '0'); - validate(aspects.series[0], '1'); - validate(aspects.y[0], '2'); + validate(aspects.series![0], '1'); + validate(aspects.y![0], '2'); }); it('creates a fake x aspect if the column does not exist', function() { @@ -64,9 +64,8 @@ describe('getAspects', function() { const aspects = getAspects(table, dimensions); - expect(aspects.x[0]) - .to.be.an('object') - .and.have.property('accessor', -1) - .and.have.property('title'); + expect(aspects.x[0]).toEqual(expect.any(Object)); + expect(aspects.x[0]).toHaveProperty('accessor', -1); + expect(aspects.x[0]).toHaveProperty('title'); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_aspects.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts similarity index 72% rename from src/legacy/ui/public/agg_response/point_series/_get_aspects.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts index fe74d8566c0e..29134409ddd5 100644 --- a/src/legacy/ui/public/agg_response/point_series/_get_aspects.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_aspects.ts @@ -18,20 +18,22 @@ */ import { makeFakeXAspect } from './_fake_x_aspect'; +import { Dimensions, Aspects } from './point_series'; +import { Table } from '../../types'; /** * Identify and group the columns based on the aspect of the pointSeries * they represent. * - * @param {array} columns - the list of columns * @return {object} - an object with a key for each aspect (see map). The values - * may be undefined, a single aspect, or an array of aspects. + * may be undefined or an array of aspects. */ -export function getAspects(table, dimensions) { - const aspects = {}; - Object.keys(dimensions).forEach(name => { - const dimension = Array.isArray(dimensions[name]) ? dimensions[name] : [dimensions[name]]; - dimension.forEach(d => { +export function getAspects(table: Table, dimensions: Dimensions) { + const aspects: Partial = {}; + (Object.keys(dimensions) as Array).forEach(name => { + const dimension = dimensions[name]; + const dimensionList = Array.isArray(dimension) ? dimension : [dimension]; + dimensionList.forEach(d => { if (!d) { return; } @@ -42,7 +44,7 @@ export function getAspects(table, dimensions) { if (!aspects[name]) { aspects[name] = []; } - aspects[name].push({ + aspects[name]!.push({ accessor: column.id, column: d.accessor, title: column.name, @@ -56,5 +58,5 @@ export function getAspects(table, dimensions) { aspects.x = [makeFakeXAspect()]; } - return aspects; + return aspects as Aspects; } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts new file mode 100644 index 000000000000..0c79c5b263ce --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.test.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IFieldFormatsRegistry } from '../../../../../../../plugins/data/common'; +import { getPoint } from './_get_point'; +import { setFormatService } from '../../../services'; +import { Aspect } from './point_series'; +import { Table, Row, Column } from '../../types'; + +describe('getPoint', function() { + let deserialize: IFieldFormatsRegistry['deserialize']; + + beforeAll(() => { + deserialize = jest.fn(() => ({ + convert: jest.fn(v => v), + })) as any; + + setFormatService({ + deserialize, + } as any); + }); + + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 4, '1': 'NaN', '2': 6 }, + ], + } as Table; + + describe('Without series aspect', function() { + let seriesAspect: undefined; + let xAspect: Aspect; + let yAspect: Aspect; + + beforeEach(function() { + xAspect = { accessor: '0' } as Aspect; + yAspect = { accessor: '1', title: 'Y' } as Aspect; + }); + + it('properly unwraps values', function() { + const row = table.rows[0]; + const zAspect = { accessor: '2' } as Aspect; + const point = getPoint(table, xAspect, seriesAspect, row, 0, yAspect, zAspect); + + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', 2); + expect(point).toHaveProperty('z', 3); + expect(point).toHaveProperty('series', yAspect.title); + }); + + it('ignores points with a y value of NaN', function() { + const row = table.rows[1]; + const point = getPoint(table, xAspect, seriesAspect, row, 1, yAspect); + expect(point).toBe(void 0); + }); + }); + + describe('With series aspect', function() { + let row: Row; + let xAspect: Aspect; + let yAspect: Aspect; + + beforeEach(function() { + row = table.rows[0]; + xAspect = { accessor: '0' } as Aspect; + yAspect = { accessor: '2' } as Aspect; + }); + + it('properly unwraps values', function() { + const seriesAspect = [{ accessor: '1' } as Aspect]; + const point = getPoint(table, xAspect, seriesAspect, row, 0, yAspect); + + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('series', '2'); + expect(point).toHaveProperty('y', 3); + }); + + it('should call deserialize', function() { + const seriesAspect = [ + { accessor: '1', format: { id: 'number', params: { pattern: '$' } } } as Aspect, + ]; + getPoint(table, xAspect, seriesAspect, row, 0, yAspect); + + expect(deserialize).toHaveBeenCalledWith(seriesAspect[0].format); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_point.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts similarity index 69% rename from src/legacy/ui/public/agg_response/point_series/_get_point.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts index 11e639f3f54a..3fc13eb0c04b 100644 --- a/src/legacy/ui/public/agg_response/point_series/_get_point.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_point.ts @@ -17,19 +17,55 @@ * under the License. */ -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; +import { getFormatService } from '../../../services'; +import { Aspect } from './point_series'; +import { Table, Row } from '../../types'; -export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { +type RowValue = number | string | object | 'NaN'; +interface Raw { + table: Table; + column: number | undefined; + row: number | undefined; + value?: RowValue; +} +export interface Point { + x: RowValue | '_all'; + y: RowValue; + z?: RowValue; + extraMetrics: []; + seriesRaw?: Raw; + xRaw: Raw; + yRaw: Raw; + zRaw?: Raw; + tableRaw?: { + table: Table; + column: number; + row: number; + value: number; + title: string; + }; + parent: Aspect | null; + series?: string; + seriesId?: string; +} +export function getPoint( + table: Table, + x: Aspect, + series: Aspect[] | undefined, + row: Row, + rowIndex: number, + y: Aspect, + z?: Aspect +): Point | undefined { const xRow = x.accessor === -1 ? '_all' : row[x.accessor]; const yRow = row[y.accessor]; const zRow = z && row[z.accessor]; - const point = { + const point: Point = { x: xRow, y: yRow, z: zRow, extraMetrics: [], - yScale: yScale, seriesRaw: series && { table, column: series[0].column, @@ -71,10 +107,9 @@ export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { } if (series) { - const seriesArray = series.length ? series : [series]; - point.series = seriesArray + point.series = series .map(s => { - const fieldFormatter = getFormat(s.format); + const fieldFormatter = getFormatService().deserialize(s.format); return fieldFormatter.convert(row[s.accessor]); }) .join(' - '); @@ -84,9 +119,5 @@ export function getPoint(table, x, series, yScale, row, rowIndex, y, z) { point.series = y.title; } - if (yScale) { - point.y *= yScale; - } - return point; } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts new file mode 100644 index 000000000000..6b94b9de8e15 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.test.ts @@ -0,0 +1,281 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getSeries } from './_get_series'; +import { setFormatService } from '../../../services'; +import { Chart, Aspect } from './point_series'; +import { Table, Column } from '../../types'; +import { Serie } from './_add_to_siri'; +import { Point } from './_get_point'; + +describe('getSeries', function() { + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); + + it('produces a single series with points for each row', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [{ accessor: '1', title: 'y' }], + z: [{ accessor: '2' }], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(1); + + const siri = series[0]; + + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', chart.aspects.y[0].title); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(5); + + siri.values.forEach(point => { + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', 2); + expect(point).toHaveProperty('z', 3); + }); + }); + + it('adds the seriesId to each point', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + series[0].values.forEach(point => { + expect(point).toHaveProperty('seriesId', '1'); + }); + + series[1].values.forEach(point => { + expect(point).toHaveProperty('seriesId', '2'); + }); + }); + + it('produces multiple series if there are multiple y aspects', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(2); + + series.forEach(function(siri: Serie, i: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', '' + i); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(5); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('x', 1); + expect(point).toHaveProperty('y', i + 2); + }); + }); + }); + + it('produces multiple series if there is a series aspect', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [{ accessor: '1', title: '0' }], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(2); + + series.forEach(function(siri: Serie, i: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', '' + i); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(3); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('y', 2); + }); + }); + }); + + it('produces multiple series if there is a series aspect and multiple y aspects', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + { '0': 0, '1': 3, '2': 4 }, + { '0': 1, '1': 3, '2': 4 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + + expect(series).toEqual(expect.any(Array)); + expect(series).toHaveLength(4); // two series * two metrics + + checkSiri(series[0], '0: 0', 3); + checkSiri(series[1], '0: 1', 4); + checkSiri(series[2], '1: 0', 3); + checkSiri(series[3], '1: 1', 4); + + function checkSiri(siri: Serie, label: string, y: number) { + expect(siri).toEqual(expect.any(Object)); + expect(siri).toHaveProperty('label', label); + expect(siri).toHaveProperty('values'); + + expect(siri.values).toEqual(expect.any(Array)); + expect(siri.values).toHaveLength(3); + + siri.values.forEach(function(point: Point) { + expect(point).toHaveProperty('y', y); + }); + } + }); + + it('produces a series list in the same order as its corresponding metric column', function() { + const table = { + columns: [{ id: '0' }, { id: '1' }, { id: '3' }] as Column[], + rows: [ + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + { '0': 1, '1': 2, '2': 3 }, + { '0': 0, '1': 2, '2': 3 }, + ], + } as Table; + + const chart = { + aspects: { + x: [{ accessor: -1 } as Aspect], + series: [{ accessor: '0' }], + y: [ + { accessor: '1', title: '0' }, + { accessor: '2', title: '1' }, + ], + }, + } as Chart; + + const series = getSeries(table, chart); + expect(series[0]).toHaveProperty('label', '0: 0'); + expect(series[1]).toHaveProperty('label', '0: 1'); + expect(series[2]).toHaveProperty('label', '1: 0'); + expect(series[3]).toHaveProperty('label', '1: 1'); + + // switch the order of the y columns + chart.aspects.y = chart.aspects.y.reverse(); + chart.aspects.y.forEach(function(y: any, i) { + y.i = i; + }); + + const series2 = getSeries(table, chart); + expect(series2[0]).toHaveProperty('label', '0: 1'); + expect(series2[1]).toHaveProperty('label', '0: 0'); + expect(series2[2]).toHaveProperty('label', '1: 1'); + expect(series2[3]).toHaveProperty('label', '1: 0'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts new file mode 100644 index 000000000000..edde5b69af02 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_get_series.ts @@ -0,0 +1,88 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { partial } from 'lodash'; +import { getPoint } from './_get_point'; +import { addToSiri, Serie } from './_add_to_siri'; +import { Chart } from './point_series'; +import { Table } from '../../types'; + +export function getSeries(table: Table, chart: Chart) { + const aspects = chart.aspects; + const xAspect = aspects.x[0]; + const yAspect = aspects.y[0]; + const zAspect = aspects.z && aspects.z[0]; + const multiY = Array.isArray(aspects.y) && aspects.y.length > 1; + + const partGetPoint = partial(getPoint, table, xAspect, aspects.series); + + const seriesMap = new Map(); + + table.rows.forEach((row, rowIndex) => { + if (!multiY) { + const point = partGetPoint(row, rowIndex, yAspect, zAspect); + if (point) { + const id = `${point.series}-${yAspect.accessor}`; + point.seriesId = id; + addToSiri( + seriesMap, + point, + id, + point.series, + yAspect.format, + zAspect && zAspect.format, + zAspect && zAspect.title + ); + } + return; + } + + aspects.y.forEach(function(y) { + const point = partGetPoint(row, rowIndex, y, zAspect); + if (!point) { + return; + } + + // use the point's y-axis as it's series by default, + // but augment that with series aspect if it's actually + // available + let seriesId = y.accessor; + let seriesLabel = y.title; + + if (aspects.series) { + const prefix = point.series ? point.series + ': ' : ''; + seriesId = prefix + seriesId; + seriesLabel = prefix + seriesLabel; + } + + point.seriesId = seriesId; + addToSiri( + seriesMap, + point, + seriesId as string, + seriesLabel, + y.format, + zAspect && zAspect.format, + zAspect && zAspect.title + ); + }); + }); + + return [...seriesMap.values()]; +} diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts similarity index 51% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts index a8512edee658..d3049d767540 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.test.ts @@ -17,14 +17,22 @@ * under the License. */ -import expect from '@kbn/expect'; import moment from 'moment'; -import { initXAxis } from '../_init_x_axis'; -import { makeFakeXAspect } from '../_fake_x_aspect'; +import { initXAxis } from './_init_x_axis'; +import { makeFakeXAspect } from './_fake_x_aspect'; +import { + Aspects, + Chart, + DateHistogramOrdered, + DateHistogramParams, + HistogramOrdered, + HistogramParams, +} from './point_series'; +import { Table, Column } from '../../types'; describe('initXAxis', function() { - let chart; - let table; + let chart: Chart; + let table: Table; beforeEach(function() { chart = { @@ -32,50 +40,48 @@ describe('initXAxis', function() { x: [ { ...makeFakeXAspect(), - accessor: 0, + accessor: '0', title: 'label', }, ], - }, - }; + } as Aspects, + } as Chart; table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [{ '0': 'hello' }, { '0': 'world' }, { '0': 'foo' }, { '0': 'bar' }, { '0': 'baz' }], }; }); it('sets the xAxisFormatter if the agg is not ordered', function() { initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); }); it('makes the chart ordered if the agg is ordered', function() { - chart.aspects.x[0].params.interval = 10; + (chart.aspects.x[0].params as HistogramParams).interval = 10; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); }); describe('xAxisOrderedValues', function() { it('sets the xAxisOrderedValues property', function() { initXAxis(chart, table); - expect(chart).to.have.property('xAxisOrderedValues'); + expect(chart).toHaveProperty('xAxisOrderedValues'); }); it('returns a list of values, preserving the table order', function() { initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['hello', 'world', 'foo', 'bar', 'baz']); + expect(chart.xAxisOrderedValues).toEqual(['hello', 'world', 'foo', 'bar', 'baz']); }); it('only returns unique values', function() { table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [ { '0': 'hello' }, { '0': 'world' }, @@ -88,45 +94,46 @@ describe('initXAxis', function() { ], }; initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['hello', 'world', 'foo', 'bar', 'baz']); + expect(chart.xAxisOrderedValues).toEqual(['hello', 'world', 'foo', 'bar', 'baz']); }); it('returns the defaultValue if using fake x aspect', function() { chart = { aspects: { x: [makeFakeXAspect()], - }, - }; + } as Aspects, + } as Chart; initXAxis(chart, table); - expect(chart.xAxisOrderedValues).to.eql(['_all']); + expect(chart.xAxisOrderedValues).toEqual(['_all']); }); }); it('reads the date interval param from the x agg', function() { - chart.aspects.x[0].params.interval = 'P1D'; - chart.aspects.x[0].params.intervalESValue = 1; - chart.aspects.x[0].params.intervalESUnit = 'd'; - chart.aspects.x[0].params.date = true; + const dateHistogramParams = chart.aspects.x[0].params as DateHistogramParams; + dateHistogramParams.interval = 'P1D'; + dateHistogramParams.intervalESValue = 1; + dateHistogramParams.intervalESUnit = 'd'; + dateHistogramParams.date = true; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); - expect(moment.isDuration(chart.ordered.interval)).to.be(true); - expect(chart.ordered.interval.toISOString()).to.eql('P1D'); - expect(chart.ordered.intervalESValue).to.be(1); - expect(chart.ordered.intervalESUnit).to.be('d'); + expect(chart.ordered).toEqual(expect.any(Object)); + const { intervalESUnit, intervalESValue, interval } = chart.ordered as DateHistogramOrdered; + expect(moment.isDuration(interval)).toBe(true); + expect(interval.toISOString()).toEqual('P1D'); + expect(intervalESValue).toBe(1); + expect(intervalESUnit).toBe('d'); }); it('reads the numeric interval param from the x agg', function() { - chart.aspects.x[0].params.interval = 0.5; + (chart.aspects.x[0].params as HistogramParams).interval = 0.5; initXAxis(chart, table); - expect(chart) - .to.have.property('xAxisLabel', 'label') - .and.have.property('xAxisFormat', chart.aspects.x[0].format) - .and.have.property('ordered'); + expect(chart).toHaveProperty('xAxisLabel', 'label'); + expect(chart).toHaveProperty('xAxisFormat', chart.aspects.x[0].format); + expect(chart).toHaveProperty('ordered'); - expect(chart.ordered.interval).to.eql(0.5); + expect((chart.ordered as HistogramOrdered).interval).toEqual(0.5); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts similarity index 73% rename from src/legacy/ui/public/agg_response/point_series/_init_x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts index 4a81486783b0..9d16c4857be0 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts @@ -19,27 +19,31 @@ import { uniq } from 'lodash'; import moment from 'moment'; +import { Chart } from './point_series'; +import { Table } from '../../types'; -export function initXAxis(chart, table) { +export function initXAxis(chart: Chart, table: Table) { const { format, title, params, accessor } = chart.aspects.x[0]; chart.xAxisOrderedValues = - accessor === -1 ? [params.defaultValue] : uniq(table.rows.map(r => r[accessor])); + accessor === -1 && 'defaultValue' in params + ? [params.defaultValue] + : uniq(table.rows.map(r => r[accessor])); chart.xAxisFormat = format; chart.xAxisLabel = title; - const { interval, date } = params; - if (interval) { - if (date) { + if ('interval' in params) { + const { interval } = params; + if ('date' in params) { const { intervalESUnit, intervalESValue } = params; chart.ordered = { interval: moment.duration(interval), - intervalESUnit: intervalESUnit, - intervalESValue: intervalESValue, + intervalESUnit, + intervalESValue, }; } else { chart.ordered = { - interval, + interval: params.interval, }; } } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts similarity index 81% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts index 78cd5334e6c8..df84d69c9f84 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.test.ts @@ -18,8 +18,8 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; -import { initYAxis } from '../_init_y_axis'; +import { initYAxis } from './_init_y_axis'; +import { Chart } from './point_series'; describe('initYAxis', function() { const baseChart = { @@ -34,7 +34,7 @@ describe('initYAxis', function() { }, ], }, - }; + } as Chart; describe('with a single y aspect', function() { const singleYBaseChart = _.cloneDeep(baseChart); @@ -43,13 +43,13 @@ describe('initYAxis', function() { it('sets the yAxisFormatter the the field formats convert fn', function() { const chart = _.cloneDeep(singleYBaseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisFormat'); + expect(chart).toHaveProperty('yAxisFormat'); }); it('sets the yAxisLabel', function() { const chart = _.cloneDeep(singleYBaseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisLabel', 'y1'); + expect(chart).toHaveProperty('yAxisLabel', 'y1'); }); }); @@ -58,16 +58,15 @@ describe('initYAxis', function() { const chart = _.cloneDeep(baseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisFormat'); - expect(chart.yAxisFormat) - .to.be(chart.aspects.y[0].format) - .and.not.be(chart.aspects.y[1].format); + expect(chart).toHaveProperty('yAxisFormat'); + expect(chart.yAxisFormat).toBe(chart.aspects.y[0].format); + expect(chart.yAxisFormat).not.toBe(chart.aspects.y[1].format); }); it('does not set the yAxisLabel, it does not make sense to put multiple labels on the same axis', function() { const chart = _.cloneDeep(baseChart); initYAxis(chart); - expect(chart).to.have.property('yAxisLabel', ''); + expect(chart).toHaveProperty('yAxisLabel', ''); }); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_init_y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts similarity index 82% rename from src/legacy/ui/public/agg_response/point_series/_init_y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts index 42f5e79a6317..43ba0557949a 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_y_axis.ts @@ -17,7 +17,9 @@ * under the License. */ -export function initYAxis(chart) { +import { Chart } from './point_series'; + +export function initYAxis(chart: Chart) { const y = chart.aspects.y; if (Array.isArray(y)) { @@ -28,12 +30,7 @@ export function initYAxis(chart) { const z = chart.aspects.series; if (z) { - if (Array.isArray(z)) { - chart.zAxisFormat = z[0].format; - chart.zAxisLabel = ''; - } else { - chart.zAxisFormat = z.format; - chart.zAxisLabel = z.title; - } + chart.zAxisFormat = z[0].format; + chart.zAxisLabel = ''; } } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts similarity index 76% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts index 2e08be16278d..25e466f21c3e 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_ordered_date_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.test.ts @@ -19,8 +19,8 @@ import moment from 'moment'; import _ from 'lodash'; -import expect from '@kbn/expect'; -import { orderedDateAxis } from '../_ordered_date_axis'; +import { orderedDateAxis } from './_ordered_date_axis'; +import { DateHistogramParams, OrderedChart } from './point_series'; describe('orderedDateAxis', function() { const baseArgs = { @@ -46,7 +46,7 @@ describe('orderedDateAxis', function() { }, ], }, - }, + } as OrderedChart, }; describe('ordered object', function() { @@ -54,24 +54,24 @@ describe('orderedDateAxis', function() { const args = _.cloneDeep(baseArgs); orderedDateAxis(args.chart); - expect(args.chart).to.have.property('ordered'); + expect(args.chart).toHaveProperty('ordered'); - expect(args.chart.ordered).to.have.property('date', true); + expect(args.chart.ordered).toHaveProperty('date', true); }); it('sets the min/max when the buckets are bounded', function() { const args = _.cloneDeep(baseArgs); orderedDateAxis(args.chart); - expect(args.chart.ordered).to.have.property('min'); - expect(args.chart.ordered).to.have.property('max'); + expect(args.chart.ordered).toHaveProperty('min'); + expect(args.chart.ordered).toHaveProperty('max'); }); it('does not set the min/max when the buckets are unbounded', function() { const args = _.cloneDeep(baseArgs); - args.chart.aspects.x[0].params.bounds = null; + (args.chart.aspects.x[0].params as DateHistogramParams).bounds = undefined; orderedDateAxis(args.chart); - expect(args.chart.ordered).to.not.have.property('min'); - expect(args.chart.ordered).to.not.have.property('max'); + expect(args.chart.ordered).not.toHaveProperty('min'); + expect(args.chart.ordered).not.toHaveProperty('max'); }); }); }); diff --git a/src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts similarity index 72% rename from src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts index a1dd50dc6c71..193b10a56356 100644 --- a/src/legacy/ui/public/agg_response/point_series/_ordered_date_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/_ordered_date_axis.ts @@ -17,17 +17,17 @@ * under the License. */ -// import moment from 'moment'; +import { OrderedChart } from './point_series'; -export function orderedDateAxis(chart) { +export function orderedDateAxis(chart: OrderedChart) { const x = chart.aspects.x[0]; - const { bounds } = x.params; + const bounds = 'bounds' in x.params ? x.params.bounds : undefined; chart.ordered.date = true; if (bounds) { - chart.ordered.min = isNaN(bounds.min) ? Date.parse(bounds.min) : bounds.min; - chart.ordered.max = isNaN(bounds.max) ? Date.parse(bounds.max) : bounds.max; + chart.ordered.min = typeof bounds.min === 'string' ? Date.parse(bounds.min) : bounds.min; + chart.ordered.max = typeof bounds.max === 'string' ? Date.parse(bounds.max) : bounds.max; } else { chart.ordered.endzones = false; } diff --git a/src/legacy/ui/public/agg_response/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts similarity index 69% rename from src/legacy/ui/public/agg_response/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts index 982c1c25a810..9bfba4de966b 100644 --- a/src/legacy/ui/public/agg_response/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/index.ts @@ -17,12 +17,4 @@ * under the License. */ -import { buildHierarchicalData } from './hierarchical/build_hierarchical_data'; -import { buildPointSeriesData } from './point_series/point_series'; -import { search } from '../../../../plugins/data/public'; - -export const aggResponseIndex = { - hierarchical: buildHierarchicalData, - pointSeries: buildPointSeriesData, - tabify: search.tabifyAggResponse, -}; +export { buildPointSeriesData } from './point_series'; diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_main.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts similarity index 62% rename from src/legacy/ui/public/agg_response/point_series/__tests__/_main.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts index a4c23cb53748..3725bf06660e 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_main.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.test.ts @@ -18,17 +18,25 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; -import { buildPointSeriesData } from '../point_series'; +import { buildPointSeriesData, Dimensions } from './point_series'; +import { Table, Column } from '../../types'; +import { setFormatService } from '../../../services'; +import { Serie } from './_add_to_siri'; describe('pointSeriesChartDataFromTable', function() { - this.slow(1000); + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); it('handles a table with just a count', function() { const table = { - columns: [{ id: '0' }], + columns: [{ id: '0' } as Column], rows: [{ '0': 100 }], - }; + } as Table; const chartData = buildPointSeriesData(table, { y: [ { @@ -36,16 +44,15 @@ describe('pointSeriesChartDataFromTable', function() { params: {}, }, ], - }); + } as Dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(1); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(1); const series = chartData.series[0]; - expect(series.values).to.have.length(1); - expect(series.values[0]) - .to.have.property('x', '_all') - .and.have.property('y', 100); + expect(series.values).toHaveLength(1); + expect(series.values[0]).toHaveProperty('x', '_all'); + expect(series.values[0]).toHaveProperty('y', 100); }); it('handles a table with x and y column', function() { @@ -59,21 +66,21 @@ describe('pointSeriesChartDataFromTable', function() { { '0': 2, '1': 200 }, { '0': 3, '1': 200 }, ], - }; + } as Table; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, y: [{ accessor: 1, params: {} }], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(1); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(1); const series = chartData.series[0]; - expect(series).to.have.property('label', 'Count'); - expect(series.values).to.have.length(3); + expect(series).toHaveProperty('label', 'Count'); + expect(series.values).toHaveLength(3); }); it('handles a table with an x and two y aspects', function() { @@ -84,23 +91,23 @@ describe('pointSeriesChartDataFromTable', function() { { '0': 2, '1': 200, '2': 300 }, { '0': 3, '1': 200, '2': 300 }, ], - }; + } as Table; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, y: [ { accessor: 1, params: {} }, { accessor: 2, params: {} }, ], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); - expect(chartData.series).to.have.length(2); - chartData.series.forEach(function(siri, i) { - expect(siri).to.have.property('label', `Count-${i}`); - expect(siri.values).to.have.length(3); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); + expect(chartData.series).toHaveLength(2); + chartData.series.forEach(function(siri: Serie, i: number) { + expect(siri).toHaveProperty('label', `Count-${i}`); + expect(siri.values).toHaveLength(3); }); }); @@ -121,21 +128,21 @@ describe('pointSeriesChartDataFromTable', function() { }; const dimensions = { - x: [{ accessor: 0, params: {} }], + x: { accessor: 0, params: {} }, series: [{ accessor: 1, params: {} }], y: [ { accessor: 2, params: {} }, { accessor: 3, params: {} }, ], - }; + } as Dimensions; const chartData = buildPointSeriesData(table, dimensions); - expect(chartData).to.be.an('object'); - expect(chartData.series).to.be.an('array'); + expect(chartData).toEqual(expect.any(Object)); + expect(chartData.series).toEqual(expect.any(Array)); // one series for each extension, and then one for each metric inside - expect(chartData.series).to.have.length(4); - chartData.series.forEach(function(siri) { - expect(siri.values).to.have.length(2); + expect(chartData.series).toHaveLength(4); + chartData.series.forEach(function(siri: Serie) { + expect(siri.values).toHaveLength(2); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts new file mode 100644 index 000000000000..a1681e0d71bd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts @@ -0,0 +1,118 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Duration } from 'moment'; +import { getSeries } from './_get_series'; +import { getAspects } from './_get_aspects'; +import { initYAxis } from './_init_y_axis'; +import { initXAxis } from './_init_x_axis'; +import { orderedDateAxis } from './_ordered_date_axis'; +import { Serie } from './_add_to_siri'; +import { Column, Table } from '../../types'; + +export interface DateHistogramParams { + date: boolean; + interval: string; + intervalESValue: number; + intervalESUnit: string; + format: string; + bounds?: { + min: string | number; + max: string | number; + }; +} +export interface HistogramParams { + interval: number; +} +export interface FakeParams { + defaultValue: string; +} +export interface Dimension { + accessor: number; + format: { + id?: string; + params?: { pattern?: string; [key: string]: any }; + }; + params: DateHistogramParams | HistogramParams | FakeParams | {}; +} + +export interface Dimensions { + x: Dimension | null; + y: Dimension[]; + z?: Dimension[]; + series?: Dimension | Dimension[]; +} +export interface Aspect { + accessor: Column['id']; + column?: Dimension['accessor']; + title: Column['name']; + format: Dimension['format']; + params: Dimension['params']; +} +export type Aspects = { x: Aspect[]; y: Aspect[] } & { [key in keyof Dimensions]?: Aspect[] }; + +export interface DateHistogramOrdered { + interval: Duration; + intervalESUnit: DateHistogramParams['intervalESUnit']; + intervalESValue: DateHistogramParams['intervalESValue']; +} +export interface HistogramOrdered { + interval: HistogramParams['interval']; +} + +type Ordered = (DateHistogramOrdered | HistogramOrdered) & { + date?: boolean; + min?: number; + max?: number; + endzones?: boolean; +}; + +export interface Chart { + aspects: Aspects; + series: Serie[]; + xAxisOrderedValues?: Array; + xAxisFormat?: Dimension['format']; + xAxisLabel?: Column['name']; + yAxisFormat?: Dimension['format']; + yAxisLabel?: Column['name']; + zAxisFormat?: Dimension['format']; + zAxisLabel?: Column['name']; + ordered?: Ordered; +} + +export type OrderedChart = Chart & { ordered: Ordered }; + +export const buildPointSeriesData = (table: Table, dimensions: Dimensions) => { + const chart = { + aspects: getAspects(table, dimensions), + } as Chart; + + initXAxis(chart, table); + initYAxis(chart); + + if ('date' in chart.aspects.x[0].params) { + // initXAxis will turn `chart` into an `OrderedChart if it is a date axis` + orderedDateAxis(chart as OrderedChart); + } + + chart.series = getSeries(table, chart); + + delete chart.aspects; + return chart; +}; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js index 9ba86c5181a4..b5f80303b1d7 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.js @@ -17,8 +17,8 @@ * under the License. */ -import { buildHierarchicalData, buildPointSeriesData } from '../legacy_imports'; import { getFormatService } from '../services'; +import { buildHierarchicalData, buildPointSeriesData } from './helpers'; function tableResponseHandler(table, dimensions) { const converted = { tables: [] }; @@ -72,7 +72,7 @@ function tableResponseHandler(table, dimensions) { function convertTableGroup(tableGroup, convertTable) { const tables = tableGroup.tables; - if (!tables.length) return; + if (!tables || !tables.length) return; const firstChild = tables[0]; if (firstChild.columns) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts new file mode 100644 index 000000000000..4a8bebc49323 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/response_handler.test.ts @@ -0,0 +1,130 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { setFormatService } from '../services'; + +jest.mock('./helpers', () => ({ + buildHierarchicalData: jest.fn(() => ({})), + buildPointSeriesData: jest.fn(() => ({})), +})); + +// @ts-ignore +import { vislibSeriesResponseHandler, vislibSlicesResponseHandler } from './response_handler'; +import { buildHierarchicalData, buildPointSeriesData } from './helpers'; +import { Table } from './types'; + +describe('response_handler', () => { + describe('vislibSlicesResponseHandler', () => { + test('should not call buildHierarchicalData when no columns', () => { + vislibSlicesResponseHandler({ rows: [] }, {}); + expect(buildHierarchicalData).not.toHaveBeenCalled(); + }); + + test('should call buildHierarchicalData', () => { + const response = { + rows: [{ 'col-0-1': 1 }], + columns: [{ id: 'col-0-1', name: 'Count' }], + }; + const dimensions = { metric: { accessor: 0 } }; + vislibSlicesResponseHandler(response, dimensions); + + expect(buildHierarchicalData).toHaveBeenCalledWith( + { columns: [...response.columns], rows: [...response.rows] }, + dimensions + ); + }); + }); + + describe('vislibSeriesResponseHandler', () => { + let resp: Table; + let expected: any; + + beforeAll(() => { + setFormatService({ + deserialize: () => ({ + convert: jest.fn(v => v), + }), + } as any); + }); + + beforeAll(() => { + resp = { + rows: [ + { 'col-0-3': 158599872, 'col-1-1': 1 }, + { 'col-0-3': 158599893, 'col-1-1': 2 }, + { 'col-0-3': 158599908, 'col-1-1': 1 }, + ], + columns: [ + { id: 'col-0-3', name: 'timestamp per 30 seconds' }, + { id: 'col-1-1', name: 'Count' }, + ], + } as Table; + + const colId = resp.columns[0].id; + expected = [ + { label: `${resp.rows[0][colId]}: ${resp.columns[0].name}` }, + { label: `${resp.rows[1][colId]}: ${resp.columns[0].name}` }, + { label: `${resp.rows[2][colId]}: ${resp.columns[0].name}` }, + ]; + }); + + test('should not call buildPointSeriesData when no columns', () => { + vislibSeriesResponseHandler({ rows: [] }, {}); + expect(buildPointSeriesData).not.toHaveBeenCalled(); + }); + + test('should call buildPointSeriesData', () => { + const response = { + rows: [{ 'col-0-1': 1 }], + columns: [{ id: 'col-0-1', name: 'Count' }], + }; + const dimensions = { x: null, y: { accessor: 0 } }; + vislibSeriesResponseHandler(response, dimensions); + + expect(buildPointSeriesData).toHaveBeenCalledWith( + { columns: [...response.columns], rows: [...response.rows] }, + dimensions + ); + }); + + test('should split columns', () => { + const dimensions = { + x: null, + y: [{ accessor: 1 }], + splitColumn: [{ accessor: 0 }], + }; + + const convertedResp = vislibSlicesResponseHandler(resp, dimensions); + expect(convertedResp.columns).toHaveLength(resp.rows.length); + expect(convertedResp.columns).toEqual(expected); + }); + + test('should split rows', () => { + const dimensions = { + x: null, + y: [{ accessor: 1 }], + splitRow: [{ accessor: 0 }], + }; + + const convertedResp = vislibSlicesResponseHandler(resp, dimensions); + expect(convertedResp.rows).toHaveLength(resp.rows.length); + expect(convertedResp.rows).toEqual(expected); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_response/point_series/_add_to_siri.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts similarity index 67% rename from src/legacy/ui/public/agg_response/point_series/_add_to_siri.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts index 9a0fcbc7b267..ad59603663b8 100644 --- a/src/legacy/ui/public/agg_response/point_series/_add_to_siri.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/types.ts @@ -17,22 +17,26 @@ * under the License. */ -export function addToSiri(series, point, id, yLabel, yFormat, zFormat, zLabel) { - id = id == null ? '' : id + ''; - - if (series.has(id)) { - series.get(id).values.push(point); - return; - } - - series.set(id, { - id: id.split('-').pop(), - rawId: id, - label: yLabel == null ? id : yLabel, - count: 0, - values: [point], - format: yFormat, - zLabel, - zFormat, - }); +export interface Column { + // -1 value can be in a fake X aspect + id: string | -1; + name: string; +} + +export interface Row { + [key: string]: number | string | object; +} + +export interface TableParent { + table: Table; + tables?: Table[]; + column: number; + row: number; + key: number; + name: string; +} +export interface Table { + columns: Column[]; + rows: Row[]; + $parent?: TableParent; } diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js deleted file mode 100644 index 43a10ebbfb12..000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_add_to_siri.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { addToSiri } from '../_add_to_siri'; - -describe('addToSiri', function() { - it('creates a new series the first time it sees an id', function() { - const series = new Map(); - const point = {}; - const id = 'id'; - addToSiri(series, point, id, id, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).values).to.have.length(1); - expect(series.get(id).values[0]).to.be(point); - }); - - it('adds points to existing series if id has been seen', function() { - const series = new Map(); - const id = 'id'; - - const point = {}; - addToSiri(series, point, id, id, { id: id }); - - const point2 = {}; - addToSiri(series, point2, id, id, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).values).to.have.length(2); - expect(series.get(id).values[0]).to.be(point); - expect(series.get(id).values[1]).to.be(point2); - }); - - it('allows overriding the series label', function() { - const series = new Map(); - const id = 'id'; - const label = 'label'; - const point = {}; - addToSiri(series, point, id, label, { id: id }); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(label); - expect(series.get(id).values).to.have.length(1); - expect(series.get(id).values[0]).to.be(point); - }); - - it('correctly sets id and rawId', function() { - const series = new Map(); - const id = 'id-id2'; - - const point = {}; - addToSiri(series, point, id); - - expect(series.has(id)).to.be(true); - expect(series.get(id)).to.be.an('object'); - expect(series.get(id).label).to.be(id); - expect(series.get(id).rawId).to.be(id); - expect(series.get(id).id).to.be('id2'); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js deleted file mode 100644 index 0eb2c608d6d6..000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_point.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { getPoint } from '../_get_point'; - -describe('getPoint', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 4, '1': 'NaN', '2': 6 }, - ], - }; - - describe('Without series aspect', function() { - let seriesAspect; - let xAspect; - let yAspect; - let yScale; - - beforeEach(function() { - seriesAspect = null; - xAspect = { accessor: 0 }; - yAspect = { accessor: 1, title: 'Y' }; - yScale = 5; - }); - - it('properly unwraps and scales values', function() { - const row = table.rows[0]; - const zAspect = { accessor: 2 }; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect, zAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('y', 10) - .and.have.property('z', 3) - .and.have.property('series', yAspect.title); - }); - - it('ignores points with a y value of NaN', function() { - const row = table.rows[1]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 1, yAspect); - expect(point).to.be(void 0); - }); - }); - - describe('With series aspect', function() { - let row; - let xAspect; - let yAspect; - let yScale; - - beforeEach(function() { - row = table.rows[0]; - xAspect = { accessor: 0 }; - yAspect = { accessor: 2 }; - yScale = null; - }); - - it('properly unwraps and scales values', function() { - const seriesAspect = [{ accessor: 1 }]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('series', '2') - .and.have.property('y', 3); - }); - - it('properly formats series values', function() { - const seriesAspect = [{ accessor: 1, format: { id: 'number', params: { pattern: '$' } } }]; - const point = getPoint(table, xAspect, seriesAspect, yScale, row, 0, yAspect); - - expect(point) - .to.have.property('x', 1) - .and.have.property('series', '$2') - .and.have.property('y', 3); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js deleted file mode 100644 index 172799497638..000000000000 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_get_series.js +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { getSeries } from '../_get_series'; - -describe('getSeries', function() { - it('produces a single series with points for each row', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [{ accessor: 1, title: 'y' }], - z: { accessor: 2 }, - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(1); - - const siri = series[0]; - expect(siri) - .to.be.an('object') - .and.have.property('label', chart.aspects.y.title) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(5); - - siri.values.forEach(function(point) { - expect(point) - .to.have.property('x', 1) - .and.property('y', 2) - .and.property('z', 3); - }); - }); - - it('adds the seriesId to each point', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - series[0].values.forEach(function(point) { - expect(point).to.have.property('seriesId', 1); - }); - - series[1].values.forEach(function(point) { - expect(point).to.have.property('seriesId', 2); - }); - }); - - it('produces multiple series if there are multiple y aspects', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: 0 }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(2); - - series.forEach(function(siri, i) { - expect(siri) - .to.be.an('object') - .and.have.property('label', '' + i) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(5); - - siri.values.forEach(function(point) { - expect(point) - .to.have.property('x', 1) - .and.property('y', i + 2); - }); - }); - }); - - it('produces multiple series if there is a series aspect', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [{ accessor: 1, title: '0' }], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(2); - - series.forEach(function(siri, i) { - expect(siri) - .to.be.an('object') - .and.have.property('label', '' + i) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(3); - - siri.values.forEach(function(point) { - expect(point).to.have.property('y', 2); - }); - }); - }); - - it('produces multiple series if there is a series aspect and multiple y aspects', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - { '0': 0, '1': 3, '2': 4 }, - { '0': 1, '1': 3, '2': 4 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - - expect(series) - .to.be.an('array') - .and.to.have.length(4); // two series * two metrics - - checkSiri(series[0], '0: 0', 3); - checkSiri(series[1], '0: 1', 4); - checkSiri(series[2], '1: 0', 3); - checkSiri(series[3], '1: 1', 4); - - function checkSiri(siri, label, y) { - expect(siri) - .to.be.an('object') - .and.have.property('label', label) - .and.have.property('values'); - - expect(siri.values) - .to.be.an('array') - .and.have.length(3); - - siri.values.forEach(function(point) { - expect(point).to.have.property('y', y); - }); - } - }); - - it('produces a series list in the same order as its corresponding metric column', function() { - const table = { - columns: [{ id: '0' }, { id: '1' }, { id: '3' }], - rows: [ - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - { '0': 1, '1': 2, '2': 3 }, - { '0': 0, '1': 2, '2': 3 }, - ], - }; - - const chart = { - aspects: { - x: [{ accessor: -1 }], - series: [{ accessor: 0, fieldFormatter: _.identity }], - y: [ - { accessor: 1, title: '0' }, - { accessor: 2, title: '1' }, - ], - }, - }; - - const series = getSeries(table, chart); - expect(series[0]).to.have.property('label', '0: 0'); - expect(series[1]).to.have.property('label', '0: 1'); - expect(series[2]).to.have.property('label', '1: 0'); - expect(series[3]).to.have.property('label', '1: 1'); - - // switch the order of the y columns - chart.aspects.y = chart.aspects.y.reverse(); - chart.aspects.y.forEach(function(y, i) { - y.i = i; - }); - - const series2 = getSeries(table, chart); - expect(series2[0]).to.have.property('label', '0: 1'); - expect(series2[1]).to.have.property('label', '0: 0'); - expect(series2[2]).to.have.property('label', '1: 1'); - expect(series2[3]).to.have.property('label', '1: 0'); - }); -}); diff --git a/src/legacy/ui/public/agg_response/point_series/_get_series.js b/src/legacy/ui/public/agg_response/point_series/_get_series.js deleted file mode 100644 index 73c1735191ab..000000000000 --- a/src/legacy/ui/public/agg_response/point_series/_get_series.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { getPoint } from './_get_point'; -import { addToSiri } from './_add_to_siri'; - -export function getSeries(table, chart) { - const aspects = chart.aspects; - const xAspect = aspects.x[0]; - const yAspect = aspects.y[0]; - const zAspect = aspects.z && aspects.z.length ? aspects.z[0] : aspects.z; - const multiY = Array.isArray(aspects.y) && aspects.y.length > 1; - const yScale = chart.yScale; - - const partGetPoint = _.partial(getPoint, table, xAspect, aspects.series, yScale); - - let series = _(table.rows) - .transform(function(series, row, rowIndex) { - if (!multiY) { - const point = partGetPoint(row, rowIndex, yAspect, zAspect); - if (point) { - const id = `${point.series}-${yAspect.accessor}`; - point.seriesId = id; - addToSiri( - series, - point, - id, - point.series, - yAspect.format, - zAspect && zAspect.format, - zAspect && zAspect.title - ); - } - return; - } - - aspects.y.forEach(function(y) { - const point = partGetPoint(row, rowIndex, y, zAspect); - if (!point) return; - - // use the point's y-axis as it's series by default, - // but augment that with series aspect if it's actually - // available - let seriesId = y.accessor; - let seriesLabel = y.title; - - if (aspects.series) { - const prefix = point.series ? point.series + ': ' : ''; - seriesId = prefix + seriesId; - seriesLabel = prefix + seriesLabel; - } - - point.seriesId = seriesId; - addToSiri( - series, - point, - seriesId, - seriesLabel, - y.format, - zAspect && zAspect.format, - zAspect && zAspect.title - ); - }); - }, new Map()) - .thru(series => [...series.values()]) - .value(); - - if (multiY) { - series = _.sortBy(series, function(siri) { - const firstVal = siri.values[0]; - let y; - - if (firstVal) { - y = _.find(aspects.y, function(y) { - return y.accessor === firstVal.accessor; - }); - } - - return y ? y.i : series.length; - }); - } - return series; -} diff --git a/src/legacy/ui/public/agg_response/point_series/point_series.js b/src/legacy/ui/public/agg_response/point_series/point_series.js deleted file mode 100644 index 8489f7bc2ca4..000000000000 --- a/src/legacy/ui/public/agg_response/point_series/point_series.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { getSeries } from './_get_series'; -import { getAspects } from './_get_aspects'; -import { initYAxis } from './_init_y_axis'; -import { initXAxis } from './_init_x_axis'; -import { orderedDateAxis } from './_ordered_date_axis'; - -export const buildPointSeriesData = (table, dimensions) => { - const chart = { - aspects: getAspects(table, dimensions), - }; - - initXAxis(chart, table); - initYAxis(chart); - - if (chart.aspects.x[0].params.date) { - orderedDateAxis(chart); - } - - chart.series = getSeries(table, chart); - - delete chart.aspects; - return chart; -}; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 905c88a6d18a..532c49803e7b 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -27,7 +27,6 @@ import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; -import 'ui/agg_response'; import 'leaflet'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d357e40c0293..705a4577cbd0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -130,7 +130,6 @@ "charts.colormaps.greysText": "グレー", "charts.colormaps.redsText": "赤", "charts.colormaps.yellowToRedText": "黄色から赤", - "common.ui.aggResponse.allDocsTitle": "すべてのドキュメント", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー", "common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "申し訳ございませんが、この問題が解決されるまで Kibana で何も保存することができません。", @@ -3809,6 +3808,7 @@ "visTypeVega.visualization.renderErrorTitle": "Vega エラー", "visTypeVega.visualization.unableToFindDefaultIndexErrorMessage": "デフォルトのインデックスが見つかりません", "visTypeVega.visualization.unableToRenderWithoutDataWarningMessage": "データなしにはレンダリングできません", + "visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント", "visTypeVislib.area.areaDescription": "折れ線グラフの下の数量を強調します。", "visTypeVislib.area.areaTitle": "エリア", "visTypeVislib.area.countText": "カウント", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 839c89f3b1ca..50b807a4934e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -130,7 +130,6 @@ "charts.colormaps.greysText": "灰色", "charts.colormaps.redsText": "红色", "charts.colormaps.yellowToRedText": "黄到红", - "common.ui.aggResponse.allDocsTitle": "所有文档", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "错误", "common.ui.errorAutoCreateIndex.errorDescription": "似乎 Elasticsearch 集群的 {autoCreateIndexActionConfig} 设置使 Kibana 无法自动创建用于存储已保存对象的索引。Kibana 将使用此 Elasticsearch 功能,因为这是确保已保存对象索引使用正确映射/架构的最好方式,而且其允许 Kibana 较少地轮询 Elasticsearch。", "common.ui.errorAutoCreateIndex.errorDisclaimer": "但是,只有解决了此问题后,您才能在 Kibana 保存内容。", @@ -3810,6 +3809,7 @@ "visTypeVega.visualization.renderErrorTitle": "Vega 错误", "visTypeVega.visualization.unableToFindDefaultIndexErrorMessage": "找不到默认索引", "visTypeVega.visualization.unableToRenderWithoutDataWarningMessage": "没有数据时无法渲染", + "visTypeVislib.aggResponse.allDocsTitle": "所有文档", "visTypeVislib.area.areaDescription": "突出折线图下方的数量", "visTypeVislib.area.areaTitle": "面积图", "visTypeVislib.area.countText": "计数", From b73fe279d6609162530619b4582f7f1da35a88c6 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 9 Apr 2020 09:44:33 -0700 Subject: [PATCH 36/46] [EPM] Update UI copy to use `integration` (#63077) * Update epm copy to say Integrations * Update copy in create data source flow * Update copy in data sources table * Fix missed copies * Remove unused translation keys (they were renamed & strings were changed) --- .../ingest_manager/layouts/default.tsx | 4 ++-- .../components/layout.tsx | 2 +- .../components/navigation.tsx | 2 +- .../create_datasource_page/index.tsx | 2 +- .../step_select_package.tsx | 8 ++++---- .../datasources/datasources_table.tsx | 2 +- .../epm/components/package_list_grid.tsx | 2 +- .../sections/epm/screens/detail/header.tsx | 8 +++++++- .../sections/epm/screens/home/header.tsx | 4 ++-- .../sections/epm/screens/home/index.tsx | 18 +++++++++--------- .../translations/translations/ja-JP.json | 5 ----- .../translations/translations/zh-CN.json | 5 ----- 12 files changed, 29 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx index 8ec2d2ec03b3..26f2c85a291a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -55,8 +55,8 @@ export const DefaultLayout: React.FunctionComponent = ({ section, childre disabled={!epm?.enabled} > diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx index 8bb7b2553c1b..dd242f366e8c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/layout.tsx @@ -88,7 +88,7 @@ export const CreateDatasourcePageLayout: React.FunctionComponent<{ diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx index 099a7a83caa1..7dae981e65c3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/navigation.tsx @@ -27,7 +27,7 @@ export const CreateDatasourceStepsNavigation: React.FunctionComponent<{ from === 'config' ? { title: i18n.translate('xpack.ingestManager.createDatasource.stepSelectPackageLabel', { - defaultMessage: 'Select package', + defaultMessage: 'Select integration', }), isSelected: currentStep === 'selectPackage', isComplete: diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx index 7815ab9cd1d6..461bb750ca6f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx @@ -221,7 +221,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { {from === 'config' ? ( ) : ( } error={packagesError} @@ -114,7 +114,7 @@ export const StepSelectPackage: React.FunctionComponent<{

@@ -149,7 +149,7 @@ export const StepSelectPackage: React.FunctionComponent<{ placeholder: i18n.translate( 'xpack.ingestManager.createDatasource.stepSelectPackage.filterPackagesInputPlaceholder', { - defaultMessage: 'Search for packages', + defaultMessage: 'Search for integrations', } ), }} @@ -179,7 +179,7 @@ export const StepSelectPackage: React.FunctionComponent<{ title={ } error={selectedPkgError} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx index 87155afdc21b..1eee9f6b0c34 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/components/datasources/datasources_table.tsx @@ -138,7 +138,7 @@ export const DatasourcesTable: React.FunctionComponent = ({ name: i18n.translate( 'xpack.ingestManager.configDetails.datasourcesTable.packageNameColumnTitle', { - defaultMessage: 'Package', + defaultMessage: 'Integration', } ), render(packageTitle: string, datasource: InMemoryDatasource) { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx index 2ca49298decf..818b365d5be1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx @@ -62,7 +62,7 @@ export function PackageListGrid({ query={searchTerm} box={{ placeholder: i18n.translate('xpack.ingestManager.epmList.searchPackagesPlaceholder', { - defaultMessage: 'Search for a package', + defaultMessage: 'Search for integrations', }), incremental: true, }} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx index a7204dd72260..d83910f29f1a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx @@ -5,6 +5,7 @@ */ import React, { Fragment } from 'react'; import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType, EuiButton } from '@elastic/eui'; import { PackageInfo } from '../../../../types'; @@ -41,7 +42,12 @@ export function Header(props: HeaderProps) { return ( - + {iconType ? ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx index 4230775c04e0..4d6c02eeef8b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx @@ -17,7 +17,7 @@ export const HeroCopy = memo(() => {

@@ -27,7 +27,7 @@ export const HeroCopy = memo(() => {

diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx index 5f215b778825..bf785147502b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx @@ -35,16 +35,16 @@ export function EPMHomePage() { ([ { id: 'all_packages', - name: i18n.translate('xpack.ingestManager.epmList.allPackagesTabText', { - defaultMessage: 'All packages', + name: i18n.translate('xpack.ingestManager.epmList.allTabText', { + defaultMessage: 'All integrations', }), href: ALL_PACKAGES_URI, isSelected: tabId !== 'installed', }, { id: 'installed_packages', - name: i18n.translate('xpack.ingestManager.epmList.installedPackagesTabText', { - defaultMessage: 'Installed packages', + name: i18n.translate('xpack.ingestManager.epmList.installedTabText', { + defaultMessage: 'Installed integrations', }), href: INSTALLED_PACKAGES_URI, isSelected: tabId === 'installed', @@ -72,14 +72,14 @@ function InstalledPackages() { ? allPackages.response.filter(pkg => pkg.status === 'installed') : []; - const title = i18n.translate('xpack.ingestManager.epmList.installedPackagesTitle', { - defaultMessage: 'Installed packages', + const title = i18n.translate('xpack.ingestManager.epmList.installedTitle', { + defaultMessage: 'Installed integrations', }); const categories = [ { id: '', - title: i18n.translate('xpack.ingestManager.epmList.allPackagesFilterLinkText', { + title: i18n.translate('xpack.ingestManager.epmList.allFilterLinkText', { defaultMessage: 'All', }), count: packages.length, @@ -120,8 +120,8 @@ function AvailablePackages() { const packages = categoryPackagesRes && categoryPackagesRes.response ? categoryPackagesRes.response : []; - const title = i18n.translate('xpack.ingestManager.epmList.allPackagesTitle', { - defaultMessage: 'All packages', + const title = i18n.translate('xpack.ingestManager.epmList.allTitle', { + defaultMessage: 'All integrations', }); const categories = [ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 705a4577cbd0..687834a683c4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8389,7 +8389,6 @@ "xpack.ingestManager.appNavigation.configurationsLinkText": "構成", "xpack.ingestManager.appNavigation.fleetLinkText": "フリート", "xpack.ingestManager.appNavigation.overviewLinkText": "概要", - "xpack.ingestManager.appNavigation.packagesLinkText": "パッケージ", "xpack.ingestManager.appTitle": "Ingest Manager", "xpack.ingestManager.configDetails.addDatasourceButtonText": "データソースを作成", "xpack.ingestManager.configDetails.configDetailsTitle": "構成「{id}」", @@ -8515,10 +8514,6 @@ "xpack.ingestManager.epm.pageSubtitle": "人気のアプリやサービスのパッケージを参照する", "xpack.ingestManager.epm.pageTitle": "Elastic Package Manager", "xpack.ingestManager.epmList.allPackagesFilterLinkText": "すべて", - "xpack.ingestManager.epmList.allPackagesTabText": "すべてのパッケージ", - "xpack.ingestManager.epmList.allPackagesTitle": "すべてのパッケージ", - "xpack.ingestManager.epmList.installedPackagesTabText": "パッケージをインストールしました", - "xpack.ingestManager.epmList.installedPackagesTitle": "パッケージをインストールしました", "xpack.ingestManager.epmList.noPackagesFoundPlaceholder": "パッケージが見つかりません", "xpack.ingestManager.epmList.searchPackagesPlaceholder": "パッケージを検索", "xpack.ingestManager.epmList.updatesAvailableFilterLinkText": "更新が可能です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 50b807a4934e..58905787da8d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8392,7 +8392,6 @@ "xpack.ingestManager.appNavigation.configurationsLinkText": "配置", "xpack.ingestManager.appNavigation.fleetLinkText": "Fleet", "xpack.ingestManager.appNavigation.overviewLinkText": "概览", - "xpack.ingestManager.appNavigation.packagesLinkText": "软件包", "xpack.ingestManager.appTitle": "Ingest Manager", "xpack.ingestManager.configDetails.addDatasourceButtonText": "创建数据源", "xpack.ingestManager.configDetails.configDetailsTitle": "配置“{id}”", @@ -8518,10 +8517,6 @@ "xpack.ingestManager.epm.pageSubtitle": "浏览热门应用和服务的软件。", "xpack.ingestManager.epm.pageTitle": "Elastic Package Manager", "xpack.ingestManager.epmList.allPackagesFilterLinkText": "全部", - "xpack.ingestManager.epmList.allPackagesTabText": "所有软件包", - "xpack.ingestManager.epmList.allPackagesTitle": "所有软件包", - "xpack.ingestManager.epmList.installedPackagesTabText": "已安装软件包", - "xpack.ingestManager.epmList.installedPackagesTitle": "已安装软件包", "xpack.ingestManager.epmList.noPackagesFoundPlaceholder": "未找到任何软件包", "xpack.ingestManager.epmList.searchPackagesPlaceholder": "搜索软件包", "xpack.ingestManager.epmList.updatesAvailableFilterLinkText": "有可用更新", From 59c044ff00d88d63c7bf30685a6f1371c7164f8c Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Thu, 9 Apr 2020 12:42:30 -0500 Subject: [PATCH 37/46] [DOCS] Removed references to right (#62508) --- docs/apm/spans.asciidoc | 2 +- docs/apm/transactions.asciidoc | 1 + docs/canvas/canvas-share-workpad.asciidoc | 4 ++-- docs/canvas/canvas-tutorial.asciidoc | 6 +++--- docs/canvas/canvas-workpad.asciidoc | 2 +- .../alerting/alert-management.asciidoc | 14 ++++++------- .../alerting/connector-management.asciidoc | 8 ++++---- docs/management/index-patterns.asciidoc | 1 - docs/management/managing-fields.asciidoc | 2 +- .../create_and_manage_rollups.asciidoc | 4 ++-- docs/maps/geojson-upload.asciidoc | 3 +-- .../indexing-geojson-data-tutorial.asciidoc | 4 ++-- docs/maps/maps-getting-started.asciidoc | 5 ++--- docs/maps/search.asciidoc | 2 +- docs/user/alerting/defining-alerts.asciidoc | 20 +++++++++---------- docs/user/dashboard.asciidoc | 2 +- docs/user/discover.asciidoc | 2 +- docs/user/graph/getting-started.asciidoc | 4 ++-- docs/user/monitoring/beats-details.asciidoc | 4 ++-- docs/visualize/lens.asciidoc | 12 +++++------ 20 files changed, 50 insertions(+), 52 deletions(-) diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc index b09de576f2d4..ef21e1c5333e 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -34,4 +34,4 @@ which indicates the next transaction in the trace. These transactions can be expanded and viewed in detail by clicking on them. After exploring these traces, -you can return to the full trace by clicking *View full trace* in the upper right hand corner of the page. +you can return to the full trace by clicking *View full trace*. diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 5c92afa55109..1eb037009eff 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -105,6 +105,7 @@ image::apm/images/apm-transaction-duration-dist.png[Example view of transactions This graph shows a typical distribution, and indicates most of our requests were served quickly - awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. + When you select one of these buckets, you're presented with up to ten trace samples. Each sample has a span timeline waterfall that shows what a typical request in that bucket was doing. diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index ee29926914ad..5cae3fcc7b53 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -76,7 +76,7 @@ After you've added the workpad to your website, you can change the autoplay and To change the autoplay settings: -. In the lower right corner of the shareable workpad, click the settings icon. +. Click the settings icon. . Click *Auto Play*, then change the settings. + @@ -85,7 +85,7 @@ image::images/canvas_share_autoplay_480.gif[Autoplay settings] To change the toolbar settings: -. In the lower right corner, click the settings icon. +. Click the settings icon. . Click *Toolbar*, then change the settings. + diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index b6d684bdf5dd..a38ab4a69598 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -18,7 +18,7 @@ Your first step to working with Canvas is to create a workpad. . Click *Create workpad*. -. To add a *Name* for your workpad, use the editor on the right. For example, `My Canvas Workpad`. +. To add a *Name* for your workpad, use the editor. For example, `My Canvas Workpad`. [float] === Customize your workpad with images @@ -29,7 +29,7 @@ To customize your workpad to look the way you want, add your own images. + The default Elastic logo image appears on your page. -. To replace the Elastic logo with your own image, select the image, then use the editor on the right. +. To replace the Elastic logo with your own image, select the image, then use the editor. . To move the image, click and drag it to your preferred location. @@ -73,7 +73,7 @@ You'll notice that the error is gone, but the number could use some formatting. . To format the number, use the Canvas expression language. -.. In the lower right corner, click *Expression editor*. +.. Click *Expression editor*. + You're now looking at the raw data syntax that Canvas uses to display the element. diff --git a/docs/canvas/canvas-workpad.asciidoc b/docs/canvas/canvas-workpad.asciidoc index c5c163441439..42eedf55c404 100644 --- a/docs/canvas/canvas-workpad.asciidoc +++ b/docs/canvas/canvas-workpad.asciidoc @@ -124,7 +124,7 @@ Organize your ideas onto separate pages by adding more pages. . Click *Page 1*, then click *+*. -. On the *Page* editor panel on the right, select the page transition from the *Transition* dropdown. +. On the *Page* editor panel, select the page transition from the *Transition* dropdown. + [role="screenshot"] image::images/canvas-add-pages.gif[Add pages] diff --git a/docs/management/alerting/alert-management.asciidoc b/docs/management/alerting/alert-management.asciidoc index caf260937b7b..73cf40c4d7c4 100644 --- a/docs/management/alerting/alert-management.asciidoc +++ b/docs/management/alerting/alert-management.asciidoc @@ -18,9 +18,9 @@ For more information on alerting concepts and the types of alerts and actions av [float] ==== Finding alerts -The *Alerts* tab lists all alerts in the current space, including summary information about their execution frequency, tags, and type. +The *Alerts* tab lists all alerts in the current space, including summary information about their execution frequency, tags, and type. -The *search bar* can be used to quickly find alerts by name or tag. +The *search bar* can be used to quickly find alerts by name or tag. [role="screenshot"] image::images/alerts-filter-by-search.png[Filtering the alerts list using the search bar] @@ -30,7 +30,7 @@ The *type* dropdown lets you filter to a subset of alert types. [role="screenshot"] image::images/alerts-filter-by-type.png[Filtering the alerts list by types of alert] -The *Action type* dropdown lets you filter by the type of action used in the alert. +The *Action type* dropdown lets you filter by the type of action used in the alert. [role="screenshot"] image::images/alerts-filter-by-action-type.png[Filtering the alert list by type of action] @@ -39,16 +39,16 @@ image::images/alerts-filter-by-action-type.png[Filtering the alert list by type [[create-edit-alerts]] ==== Creating and editing alerts -Many alerts must be created within the context of a {kib} app like <>, <>, or <>, but others are generic. Generic alert types can be created in the *Alerts* management UI by clicking the *Create* button. This will launch a flyout that guides you through selecting an alert type and configuring it's properties. Refer to <> for details on what types of alerts are available and how to configure them. +Many alerts must be created within the context of a {kib} app like <>, <>, or <>, but others are generic. Generic alert types can be created in the *Alerts* management UI by clicking the *Create* button. This will launch a flyout that guides you through selecting an alert type and configuring it's properties. Refer to <> for details on what types of alerts are available and how to configure them. -After an alert is created, you can re-open the flyout and change an alerts properties by clicking the *Edit* button shown on each row of the alert listing. +After an alert is created, you can re-open the flyout and change an alerts properties by clicking the *Edit* button shown on each row of the alert listing. [float] [[controlling-alerts]] ==== Controlling alerts -The alert listing allows you to quickly mute/unmute, disable/enable, and delete individual alerts by clicking the action button at the right of each row. +The alert listing allows you to quickly mute/unmute, disable/enable, and delete individual alerts by clicking the action button. [role="screenshot"] image:management/alerting/images/individual-mute-disable.png[The actions button allows an individual alert to be muted, disabled, or deleted] @@ -56,4 +56,4 @@ image:management/alerting/images/individual-mute-disable.png[The actions button These operations can also be performed in bulk by multi-selecting alerts and clicking the *Manage alerts* button: [role="screenshot"] -image:management/alerting/images/bulk-mute-disable.png[The Manage alerts button lets you mute/unmute, enable/disable, and delete in bulk] \ No newline at end of file +image:management/alerting/images/bulk-mute-disable.png[The Manage alerts button lets you mute/unmute, enable/disable, and delete in bulk] diff --git a/docs/management/alerting/connector-management.asciidoc b/docs/management/alerting/connector-management.asciidoc index 1002a372f946..46e106e6e964 100644 --- a/docs/management/alerting/connector-management.asciidoc +++ b/docs/management/alerting/connector-management.asciidoc @@ -15,7 +15,7 @@ image::images/connector-listing.png[Example connector listing in the Alerts and [float] ==== Connector list -The *Connectors* tab lists all connectors in the current space. The *search bar* can be used to find specific connectors by name and/or type. +The *Connectors* tab lists all connectors in the current space. The *search bar* can be used to find specific connectors by name and/or type. [role="screenshot"] image::images/connector-filter-by-search.png[Filtering the connector list using the search bar] @@ -26,12 +26,12 @@ The *type* dropdown also lets you filter to a subset of action types. [role="screenshot"] image::images/connector-filter-by-type.png[Filtering the connector list by types of actions] -The *Actions* column indicates the number of actions that reference the connector. This count helps you confirm a connector is unused before you delete it, and tells you how many actions will be affected when a connector is modified. +The *Actions* column indicates the number of actions that reference the connector. This count helps you confirm a connector is unused before you delete it, and tells you how many actions will be affected when a connector is modified. [role="screenshot"] image::images/connector-action-count.png[Filtering the connector list by types of actions] -You can delete individual connectors using the trash icon on the right of each row. Connectors can also be deleted in bulk by multi-selecting them and clicking the *Delete* button to the left of the search box. +You can delete individual connectors using the trash icon. Connectors can also be deleted in bulk by multi-selecting them and clicking the *Delete* button to the left of the search box. [role="screenshot"] image::images/connector-delete.png[Deleting connectors individually or in bulk] @@ -44,4 +44,4 @@ When this happens the action will fail to execute, and appear as errors in the { ==== Creating a new connector -New connectors can be created by clicking the *Create connector* button, which will guide you to select the type of connector and configure it's properties. Refer to <> for the types of connectors available and how to configure them. Once you create a connector it will be made available to you anytime you set up an action in the current space. \ No newline at end of file +New connectors can be created by clicking the *Create connector* button, which will guide you to select the type of connector and configure it's properties. Refer to <> for the types of connectors available and how to configure them. Once you create a connector it will be made available to you anytime you set up an action in the current space. diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 45f8bd13a5c5..bb16faab7fe5 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -38,7 +38,6 @@ image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollu Just start typing in the *Index pattern* field, and {kib} looks for the names of {es} indices that match your input. Make sure that the name of the index pattern is unique. -To include system indices in your search, toggle the switch in the upper right. [role="screenshot"] image:management/index-patterns/images/create-index-pattern.png["Create index pattern"] diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index 1a1bcec10ab5..9682d918aabe 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -25,7 +25,7 @@ the *Index patterns* overview. [role="screenshot"] image::management/index-patterns/images/new-index-pattern.png["Index files and data types"] -Use the icons in the upper right to perform the following actions: +Use the icons to perform the following actions: * [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users aware of which index pattern is the default. The first pattern diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index 6a56970687fd..da2e190847fd 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -42,8 +42,8 @@ image::images/management_create_rollup_job.png[][Wizard that walks you through c === Start, stop, and delete rollup jobs Once you’ve saved a rollup job, you’ll see it the *Rollup Jobs* overview page, -where you can drill down for further investigation. The *Manage* menu in -the lower right enables you to start, stop, and delete the rollup job. +where you can drill down for further investigation. The *Manage* menu enables +you to start, stop, and delete the rollup job. You must first stop a rollup job before deleting it. [role="screenshot"] diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index ad20264f5613..7e2cdddfd30e 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -37,7 +37,6 @@ the Elasticsearch responses are shown on the *Layer add panel* and the indexed d appears on the map. The geospatial data on the map should be identical to the locally-previewed data, but now it's indexed data from Elasticsearch. -. To continue adding data to the map, click *Add layer* in the lower -right-hand corner. +. To continue adding data to the map, click *Add layer*. . In *Layer settings*, adjust any settings or <> as needed. . Click *Save & close*. diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index a94e5757d5df..bf846a2b80e0 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -55,14 +55,14 @@ auto-populate *Index type* with either {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape] and *Index name* with ``. -. Click *Import file* in the lower right. +. Click *Import file*. + You'll see activity as the GeoJSON Upload utility creates a new index and index pattern for the data set. When the process is complete, you should receive messages that the creation of the new index and index pattern were successful. -. Click *Add layer* in the bottom right. +. Click *Add layer*. . In *Layer settings*, adjust settings and <> as needed. . Click *Save & close*. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index b13eeebe56fd..6495b8a057cf 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -80,7 +80,7 @@ To symbolize countries by web traffic, you'll need to augment the world country To do this, you'll create a <> to link the vector source *World Countries* to the {es} index `kibana_sample_data_logs` on the shared key iso2 = geo.src. -. Click plus image:maps/images/gs_plus_icon.png[] to the right of *Term Joins* label. +. Click plus image:maps/images/gs_plus_icon.png[] next to the *Term Joins* label. . Click *Join --select--* . Set *Left field* to *ISO 3166-1 alpha-2 code*. . Set *Right source* to *kibana_sample_data_logs*. @@ -238,7 +238,7 @@ The *machine.os.keyword: osx* filter appears in the dashboard query bar. + . Click the *x* to remove the *machine.os.keyword: osx* filter. . In the map, click in the United States vector. -. Click plus image:maps/images/gs_plus_icon.png[] to the right of *iso2* row in the tooltip. +. Click plus image:maps/images/gs_plus_icon.png[] next to the *iso2* row in the tooltip. + Both the visualizations and the map are filtered to only show documents where *geo.src* is *US*. The *geo.src: US* filter appears in the dashboard query bar. @@ -247,4 +247,3 @@ Your dashboard should look like this: + [role="screenshot"] image::maps/images/gs_dashboard_with_terms_filter.png[] - diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 8a93352798d2..a461ab6fbb3a 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -4,7 +4,7 @@ **Elastic Maps** embeds the search bar for real-time search. Only layers requesting data from {es} are filtered when you submit a search request. -Layers narrowed by the search context contain the filter icon image:maps/images/filter_icon.png[] to the right of layer name in the legend. +Layers narrowed by the search context contain the filter icon image:maps/images/filter_icon.png[] next to the layer name in the legend. You can create a layer that requests data from {es} from the following: diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 89c4c88708d5..f05afac34e59 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -2,7 +2,7 @@ [[defining-alerts]] == Defining alerts -{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. +{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. [float] === Alert flyout @@ -25,20 +25,20 @@ All alert share the following four properties in common: image::images/alert-flyout-general-details.png[All alerts have name, tags, check every, and re-notify every properties in common] Name:: The name of the alert. While this name does not have to be unique, the name can be referenced in actions and also appears in the searchable alert listing in the management UI. A distinctive name can help identify and find an alert. -Tags:: A list of tag names that can be applied to an alert. Tags can help you organize and find alerts, because tags appear in the alert listing in the management UI which is searchable by tag. +Tags:: A list of tag names that can be applied to an alert. Tags can help you organize and find alerts, because tags appear in the alert listing in the management UI which is searchable by tag. Check every:: This value determines how frequently the alert conditions below are checked. Note that the timing of background alert checks are not guaranteed, particularly for intervals of less than 10 seconds. See <> for more information. -Re-notify every:: This value limits how often actions are repeated when an alert instance remains active across alert checks. See <> for more information. +Re-notify every:: This value limits how often actions are repeated when an alert instance remains active across alert checks. See <> for more information. [float] [[defining-alerts-type-conditions]] === Alert type and conditions -Depending upon the {kib} app and context, you may be prompted to choose the type of alert you wish to create. Some apps will pre-select the type of alert for you. +Depending upon the {kib} app and context, you may be prompted to choose the type of alert you wish to create. Some apps will pre-select the type of alert for you. [role="screenshot"] image::images/alert-flyout-alert-type-selection.png[Choosing the type of alert to create] -Each alert type provides its own way of defining the conditions to detect, but an expression formed by a series of clauses is a common pattern. Each clause has a UI control that allows you to define the clause. For example, in an index threshold alert the `WHEN` clause allows you to select an aggregation operation to apply to a numeric field. +Each alert type provides its own way of defining the conditions to detect, but an expression formed by a series of clauses is a common pattern. Each clause has a UI control that allows you to define the clause. For example, in an index threshold alert the `WHEN` clause allows you to select an aggregation operation to apply to a numeric field. [role="screenshot"] image::images/alert-flyout-alert-conditions.png[UI for defining alert conditions on an index threshold alert] @@ -52,19 +52,19 @@ To add an action to an alert, you first select the type of action: [role="screenshot"] image::images/alert-flyout-action-type-selection.png[UI for selecting an action type] -Each action must specify a <> instance. If no connectors exist for that action type, click "Add new" to create one. +Each action must specify a <> instance. If no connectors exist for that action type, click "Add new" to create one. -Each action type exposes different properties. For example an email action allows you to set the recipients, the subject, and a message body in markdown format. See <> for details on the types of actions provided by {kib} and their properties. +Each action type exposes different properties. For example an email action allows you to set the recipients, the subject, and a message body in markdown format. See <> for details on the types of actions provided by {kib} and their properties. [role="screenshot"] image::images/alert-flyout-action-details.png[UI for defining an email action] -Using the https://mustache.github.io/[Mustache] template syntax `{{variable name}}`, you can pass alert values at the time a condition is detected to an action. Available variables differ by alert type, and a list can be accessed using the "add variable" button at the right of the text box. +Using the https://mustache.github.io/[Mustache] template syntax `{{variable name}}`, you can pass alert values at the time a condition is detected to an action. Available variables differ by alert type, and a list can be accessed using the "add variable" button. [role="screenshot"] image::images/alert-flyout-action-variables.png[Passing alert values to an action] -You can attach more than one action. Clicking the "Add action" button will prompt you to select another alert type and repeat the above steps again. +You can attach more than one action. Clicking the "Add action" button will prompt you to select another alert type and repeat the above steps again. [role="screenshot"] image::images/alert-flyout-add-action.png[You can add multiple actions on an alert] @@ -77,4 +77,4 @@ Actions are not required on alerts. In some cases you may want to run an alert w [float] === Managing alerts -To modify an alert after it was created, including muting or disabling it, use the <>. \ No newline at end of file +To modify an alert after it was created, including muting or disabling it, use the <>. diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index a17e46c5b354..ab529a533d5e 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -90,7 +90,7 @@ In *Edit* mode, you can move, resize, customize, and delete panels to suit your * To move a panel, click and hold the panel header and drag to the new location. [[resizing-containers]] -* To resize a panel, click the resize control on the lower right and drag +* To resize a panel, click the resize control and drag to the new dimensions. * To toggle the use of margins and panel titles, use the *Options* menu. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 4222ba40debb..2547b38a2261 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -33,7 +33,7 @@ which has a pre-built index pattern. By default, *Discover* shows data for the last 15 minutes. If you have a time-based index, and no data displays, -you might need to increase the time range. Using the <> in the upper right, +you might need to increase the time range. Using the <>, you can specify a common or recently-used time range, a relative time from now, or an absolute time range. diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 1749678ace9e..a155017f1bb2 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -38,7 +38,7 @@ image::user/graph/images/graph-url-connections.png["URL connections"] [role="screenshot"] image::user/graph/images/graph-link-summary.png["Link summary"] -. Use the control bar on the right to explore +. Use the control bar to explore additional connections: + * To display additional vertices that connect to your graph, click the expand icon @@ -70,7 +70,7 @@ select *Edit settings*. To change the color and label of selected vertices, click the style icon image:user/graph/images/graph-style-button.png[Style] -in the control bar on the right. +in the control bar. [float] diff --git a/docs/user/monitoring/beats-details.asciidoc b/docs/user/monitoring/beats-details.asciidoc index 0b2be4dd9e3d..f4ecb2a74d91 100644 --- a/docs/user/monitoring/beats-details.asciidoc +++ b/docs/user/monitoring/beats-details.asciidoc @@ -14,8 +14,8 @@ image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="imag To view an overview of the Beats data in the cluster, click *Overview*. The overview page has a section for activity in the last day, which is a real-time sample of data. The summary bar and charts follow the typical paradigm -of data in the Monitoring UI, which is bound to the span of the time filter in -the top right corner of the page. This overview page can therefore show +of data in the Monitoring UI, which is bound to the span of the time filter. +This overview page can therefore show up-to-date or historical information. To view a listing of the individual Beat instances in the cluster, click *Beats*. diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index 35570ea7ca1d..b181763c0d0d 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -38,7 +38,7 @@ you'll see two places highlighted in green: * The visualization builder pane -* The *X-axis* or *Y-axis* fields in the right column +* The *X-axis* or *Y-axis* fields You can incorporate many fields into your visualization, and Lens uses heuristics to decide how to apply each one to the visualization. @@ -89,8 +89,8 @@ You can switch between suggestions without losing your previous state: [role="screenshot"] image::images/lens_suggestions.gif[] -If you want to switch to a chart type that is not suggested, click the chart type in the -top right, then select a chart type. When there is an exclamation point (!) +If you want to switch to a chart type that is not suggested, click the chart type, +then select a chart type. When there is an exclamation point (!) next to a chart type, Lens is unable to transfer your current data, but still allows you to make the change. @@ -106,7 +106,7 @@ If there is a match, Lens displays the new data. All fields that do not match th . Change the data field options, such as the aggregation or label. -.. Click *Drop a field here* or the field name in the right column. +.. Click *Drop a field here* or the field name in the column. .. Change the options that appear depending on the type of field. @@ -168,7 +168,7 @@ image::images/lens_tutorial_2.png[Lens tutorial] Customize your visualization to look exactly how you want. -. In the right column, click *Average of taxful_total_price*. +. Click *Average of taxful_total_price*. .. Change the *Label* to `Sales`, or a name that you prefer for the data. @@ -180,7 +180,7 @@ six available categories. . Look at the suggestions. None of them show an area chart, but for sales data, a stacked area chart might make sense. To switch the chart type: -.. Click *Stacked bar chart* in the right column. +.. Click *Stacked bar chart* in the column. .. Click *Stacked area*. + From bc3f38288337220fba91c32de3ed5a14e9c219cc Mon Sep 17 00:00:00 2001 From: spalger Date: Thu, 9 Apr 2020 10:47:42 -0700 Subject: [PATCH 38/46] skip flaky suite (#62927) --- x-pack/test/functional/apps/canvas/custom_elements.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/canvas/custom_elements.ts b/x-pack/test/functional/apps/canvas/custom_elements.ts index 6c3455684096..de3976509be1 100644 --- a/x-pack/test/functional/apps/canvas/custom_elements.ts +++ b/x-pack/test/functional/apps/canvas/custom_elements.ts @@ -19,7 +19,8 @@ export default function canvasCustomElementTest({ const PageObjects = getPageObjects(['canvas', 'common']); const find = getService('find'); - describe('custom elements', function() { + // FLAKY: https://github.com/elastic/kibana/issues/62927 + describe.skip('custom elements', function() { this.tags('skipFirefox'); before(async () => { From 2574d0f8055981e4a44416dcfea2a9b9b64c5d9b Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Thu, 9 Apr 2020 11:01:25 -0700 Subject: [PATCH 39/46] Adds a new config flag to encode with BOM for our CSVs (#63006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds a new config flag to encode with BOM for our CSVs * Push out bom-chars to it's own constant * Getting those snapshots back into shape 💪 Co-authored-by: Elastic Machine --- .../__snapshots__/index.test.js.snap | 4 ++ .../plugins/reporting/common/constants.ts | 1 + x-pack/legacy/plugins/reporting/config.ts | 1 + .../csv/server/execute_job.test.js | 45 +++++++++++++++++++ .../export_types/csv/server/execute_job.ts | 6 ++- .../plugins/reporting/server/config/index.ts | 1 + 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap index 757677f1d4f8..3ae3079da136 100644 --- a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap +++ b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap @@ -66,6 +66,7 @@ Object { "duration": "30s", "size": 500, }, + "useByteOrderMarkEncoding": false, }, "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -162,6 +163,7 @@ Object { "duration": "30s", "size": 500, }, + "useByteOrderMarkEncoding": false, }, "enabled": true, "index": ".reporting", @@ -257,6 +259,7 @@ Object { "duration": "30s", "size": 500, }, + "useByteOrderMarkEncoding": false, }, "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -353,6 +356,7 @@ Object { "duration": "30s", "size": 500, }, + "useByteOrderMarkEncoding": false, }, "enabled": true, "index": ".reporting", diff --git a/x-pack/legacy/plugins/reporting/common/constants.ts b/x-pack/legacy/plugins/reporting/common/constants.ts index 8f7a06ba9f8e..e3d6a4274e7d 100644 --- a/x-pack/legacy/plugins/reporting/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/common/constants.ts @@ -19,6 +19,7 @@ export const API_GENERATE_IMMEDIATE = `${API_BASE_URL_V1}/generate/immediate/csv export const CONTENT_TYPE_CSV = 'text/csv'; export const CSV_REPORTING_ACTION = 'downloadCsvReport'; +export const CSV_BOM_CHARS = '\ufeff'; export const WHITELISTED_JOB_CONTENT_TYPES = [ 'application/json', diff --git a/x-pack/legacy/plugins/reporting/config.ts b/x-pack/legacy/plugins/reporting/config.ts index 211fa70301bb..5eceb84c83e4 100644 --- a/x-pack/legacy/plugins/reporting/config.ts +++ b/x-pack/legacy/plugins/reporting/config.ts @@ -135,6 +135,7 @@ export async function config(Joi: any) { .default(), }).default(), csv: Joi.object({ + useByteOrderMarkEncoding: Joi.boolean().default(false), checkForFormulas: Joi.boolean().default(true), enablePanelActionDownload: Joi.boolean().default(true), maxSizeBytes: Joi.number() diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js index 93dbe598b367..4870e1e35cda 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.test.js @@ -13,6 +13,7 @@ import { createMockReportingCore } from '../../../test_helpers'; import { LevelLogger } from '../../../server/lib/level_logger'; import { setFieldFormats } from '../../../server/services'; import { executeJobFactory } from './execute_job'; +import { CSV_BOM_CHARS } from '../../../common/constants'; const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); @@ -374,6 +375,50 @@ describe('CSV Execute Job', function() { }); }); + describe('Byte order mark encoding', () => { + it('encodes CSVs with BOM', async () => { + configGetStub.withArgs('csv', 'useByteOrderMarkEncoding').returns(true); + callAsCurrentUserStub.onFirstCall().returns({ + hits: { + hits: [{ _source: { one: 'one', two: 'bar' } }], + }, + _scroll_id: 'scrollId', + }); + + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const jobParams = { + headers: encryptedHeaders, + fields: ['one', 'two'], + conflictedTypesFields: [], + searchRequest: { index: null, body: null }, + }; + const { content } = await executeJob('job123', jobParams, cancellationToken); + + expect(content).toEqual(`${CSV_BOM_CHARS}one,two\none,bar\n`); + }); + + it('encodes CSVs without BOM', async () => { + configGetStub.withArgs('csv', 'useByteOrderMarkEncoding').returns(false); + callAsCurrentUserStub.onFirstCall().returns({ + hits: { + hits: [{ _source: { one: 'one', two: 'bar' } }], + }, + _scroll_id: 'scrollId', + }); + + const executeJob = await executeJobFactory(mockReportingPlugin, mockLogger); + const jobParams = { + headers: encryptedHeaders, + fields: ['one', 'two'], + conflictedTypesFields: [], + searchRequest: { index: null, body: null }, + }; + const { content } = await executeJob('job123', jobParams, cancellationToken); + + expect(content).toEqual('one,two\none,bar\n'); + }); + }); + describe('Elasticsearch call errors', function() { it('should reject Promise if search call errors out', async function() { callAsCurrentUserStub.rejects(new Error()); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index 3a282eb0b297..376a398da274 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import Hapi from 'hapi'; import { IUiSettingsClient, KibanaRequest } from '../../../../../../../src/core/server'; -import { CSV_JOB_TYPE } from '../../../common/constants'; +import { CSV_JOB_TYPE, CSV_BOM_CHARS } from '../../../common/constants'; import { ReportingCore } from '../../../server/core'; import { cryptoFactory } from '../../../server/lib'; import { getFieldFormats } from '../../../server/services'; @@ -121,6 +121,8 @@ export const executeJobFactory: ExecuteJobFactory Date: Thu, 9 Apr 2020 21:26:13 +0300 Subject: [PATCH 40/46] [SIEM][CASE] Test configuration API and hooks (#62803) * Test API * Test useConnectors * Test useConfigure * Fixes --- .../case/configure/__mocks__/api.ts | 31 ++ .../containers/case/configure/api.test.ts | 115 +++++++ .../public/containers/case/configure/mock.ts | 99 ++++++ .../case/configure/use_configure.test.tsx | 299 ++++++++++++++++++ .../case/configure/use_configure.tsx | 2 +- .../case/configure/use_connectors.test.tsx | 95 ++++++ 6 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/configure/__mocks__/api.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/configure/api.test.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/configure/mock.ts create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/__mocks__/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/__mocks__/api.ts new file mode 100644 index 000000000000..03f7d241e5df --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/__mocks__/api.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CasesConfigurePatch, + CasesConfigureRequest, + Connector, +} from '../../../../../../../../plugins/case/common/api'; + +import { ApiProps } from '../../types'; +import { CaseConfigure } from '../types'; +import { connectorsMock, caseConfigurationCamelCaseResponseMock } from '../mock'; + +export const fetchConnectors = async ({ signal }: ApiProps): Promise => + Promise.resolve(connectorsMock); + +export const getCaseConfigure = async ({ signal }: ApiProps): Promise => + Promise.resolve(caseConfigurationCamelCaseResponseMock); + +export const postCaseConfigure = async ( + caseConfiguration: CasesConfigureRequest, + signal: AbortSignal +): Promise => Promise.resolve(caseConfigurationCamelCaseResponseMock); + +export const patchCaseConfigure = async ( + caseConfiguration: CasesConfigurePatch, + signal: AbortSignal +): Promise => Promise.resolve(caseConfigurationCamelCaseResponseMock); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.test.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.test.ts new file mode 100644 index 000000000000..ef0e51fb1c24 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.test.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaServices } from '../../../lib/kibana'; +import { fetchConnectors, getCaseConfigure, postCaseConfigure, patchCaseConfigure } from './api'; +import { + connectorsMock, + caseConfigurationMock, + caseConfigurationResposeMock, + caseConfigurationCamelCaseResponseMock, +} from './mock'; + +const abortCtrl = new AbortController(); +const mockKibanaServices = KibanaServices.get as jest.Mock; +jest.mock('../../../lib/kibana'); + +const fetchMock = jest.fn(); +mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); + +describe('Case Configuration API', () => { + describe('fetch connectors', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(connectorsMock); + }); + + test('check url, method, signal', async () => { + await fetchConnectors({ signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/cases/configure/connectors/_find', { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await fetchConnectors({ signal: abortCtrl.signal }); + expect(resp).toEqual(connectorsMock); + }); + }); + + describe('fetch configuration', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(caseConfigurationResposeMock); + }); + + test('check url, method, signal', async () => { + await getCaseConfigure({ signal: abortCtrl.signal }); + expect(fetchMock).toHaveBeenCalledWith('/api/cases/configure', { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await getCaseConfigure({ signal: abortCtrl.signal }); + expect(resp).toEqual(caseConfigurationCamelCaseResponseMock); + }); + + test('return null on empty response', async () => { + fetchMock.mockResolvedValue({}); + const resp = await getCaseConfigure({ signal: abortCtrl.signal }); + expect(resp).toBe(null); + }); + }); + + describe('create configuration', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(caseConfigurationResposeMock); + }); + + test('check url, body, method, signal', async () => { + await postCaseConfigure(caseConfigurationMock, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith('/api/cases/configure', { + body: + '{"connector_id":"123","connector_name":"My Connector","closure_type":"close-by-user"}', + method: 'POST', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await postCaseConfigure(caseConfigurationMock, abortCtrl.signal); + expect(resp).toEqual(caseConfigurationCamelCaseResponseMock); + }); + }); + + describe('update configuration', () => { + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue(caseConfigurationResposeMock); + }); + + test('check url, body, method, signal', async () => { + await patchCaseConfigure({ connector_id: '456', version: 'WzHJ12' }, abortCtrl.signal); + expect(fetchMock).toHaveBeenCalledWith('/api/cases/configure', { + body: '{"connector_id":"456","version":"WzHJ12"}', + method: 'PATCH', + signal: abortCtrl.signal, + }); + }); + + test('happy path', async () => { + const resp = await patchCaseConfigure( + { connector_id: '456', version: 'WzHJ12' }, + abortCtrl.signal + ); + expect(resp).toEqual(caseConfigurationCamelCaseResponseMock); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/mock.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/mock.ts new file mode 100644 index 000000000000..d2491b39fdf5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/mock.ts @@ -0,0 +1,99 @@ +/* + * 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 { + Connector, + CasesConfigureResponse, + CasesConfigureRequest, +} from '../../../../../../../plugins/case/common/api'; +import { CaseConfigure } from './types'; + +export const connectorsMock: Connector[] = [ + { + id: '123', + actionTypeId: '.servicenow', + name: 'My Connector', + config: { + apiUrl: 'https://instance1.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + isPreconfigured: true, + }, + { + id: '456', + actionTypeId: '.servicenow', + name: 'My Connector 2', + config: { + apiUrl: 'https://instance2.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + isPreconfigured: true, + }, +]; + +export const caseConfigurationResposeMock: CasesConfigureResponse = { + created_at: '2020-04-06T13:03:18.657Z', + created_by: { username: 'elastic', full_name: 'Elastic', email: 'elastic@elastic.co' }, + connector_id: '123', + connector_name: 'My Connector', + closure_type: 'close-by-user', + updated_at: '2020-04-06T14:03:18.657Z', + updated_by: { username: 'elastic', full_name: 'Elastic', email: 'elastic@elastic.co' }, + version: 'WzHJ12', +}; + +export const caseConfigurationMock: CasesConfigureRequest = { + connector_id: '123', + connector_name: 'My Connector', + closure_type: 'close-by-user', +}; + +export const caseConfigurationCamelCaseResponseMock: CaseConfigure = { + createdAt: '2020-04-06T13:03:18.657Z', + createdBy: { username: 'elastic', fullName: 'Elastic', email: 'elastic@elastic.co' }, + connectorId: '123', + connectorName: 'My Connector', + closureType: 'close-by-user', + updatedAt: '2020-04-06T14:03:18.657Z', + updatedBy: { username: 'elastic', fullName: 'Elastic', email: 'elastic@elastic.co' }, + version: 'WzHJ12', +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.test.tsx new file mode 100644 index 000000000000..3ee16e19eaf9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.test.tsx @@ -0,0 +1,299 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useCaseConfigure, ReturnUseCaseConfigure, PersistCaseConfigure } from './use_configure'; +import { caseConfigurationCamelCaseResponseMock } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +const configuration: PersistCaseConfigure = { + connectorId: '456', + connectorName: 'My Connector 2', + closureType: 'close-by-pushing', +}; + +describe('useConfigure', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + const args = { + setConnector: jest.fn(), + setClosureType: jest.fn(), + setCurrentConfiguration: jest.fn(), + }; + + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: true, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('fetch case configuration', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('fetch case configuration - setConnector', async () => { + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(args.setConnector).toHaveBeenCalledWith('123', 'My Connector'); + }); + }); + + test('fetch case configuration - setClosureType', async () => { + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(args.setClosureType).toHaveBeenCalledWith('close-by-user'); + }); + }); + + test('fetch case configuration - setCurrentConfiguration', async () => { + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(args.setCurrentConfiguration).toHaveBeenCalledWith({ + connectorId: '123', + closureType: 'close-by-user', + }); + }); + }); + + test('fetch case configuration - only setConnector', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure({ setConnector: jest.fn() }) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('refetch case configuration', async () => { + const spyOnGetCaseConfigure = jest.spyOn(api, 'getCaseConfigure'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchCaseConfigure(); + expect(spyOnGetCaseConfigure).toHaveBeenCalledTimes(2); + }); + }); + + test('set isLoading to true when fetching case configuration', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchCaseConfigure(); + + expect(result.current.loading).toBe(true); + }); + }); + + test('persist case configuration', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + result.current.persistCaseConfigure(configuration); + + expect(result.current).toEqual({ + loading: false, + persistLoading: true, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('save case configuration - postCaseConfigure', async () => { + // When there is no version, a configuration is created. Otherwise is updated. + const spyOnGetCaseConfigure = jest.spyOn(api, 'getCaseConfigure'); + spyOnGetCaseConfigure.mockImplementation(() => + Promise.resolve({ + ...caseConfigurationCamelCaseResponseMock, + version: '', + }) + ); + + const spyOnPostCaseConfigure = jest.spyOn(api, 'postCaseConfigure'); + spyOnPostCaseConfigure.mockImplementation(() => + Promise.resolve({ + ...caseConfigurationCamelCaseResponseMock, + ...configuration, + }) + ); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + result.current.persistCaseConfigure(configuration); + + await waitForNextUpdate(); + + expect(args.setConnector).toHaveBeenNthCalledWith(2, '456'); + expect(args.setClosureType).toHaveBeenNthCalledWith(2, 'close-by-pushing'); + expect(args.setCurrentConfiguration).toHaveBeenNthCalledWith(2, { + connectorId: '456', + closureType: 'close-by-pushing', + }); + }); + }); + + test('save case configuration - patchCaseConfigure', async () => { + const spyOnPatchCaseConfigure = jest.spyOn(api, 'patchCaseConfigure'); + spyOnPatchCaseConfigure.mockImplementation(() => + Promise.resolve({ + ...caseConfigurationCamelCaseResponseMock, + ...configuration, + }) + ); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + result.current.persistCaseConfigure(configuration); + + await waitForNextUpdate(); + + expect(args.setConnector).toHaveBeenNthCalledWith(2, '456'); + expect(args.setClosureType).toHaveBeenNthCalledWith(2, 'close-by-pushing'); + expect(args.setCurrentConfiguration).toHaveBeenNthCalledWith(2, { + connectorId: '456', + closureType: 'close-by-pushing', + }); + }); + }); + + test('save case configuration - only setConnector', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure({ setConnector: jest.fn() }) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + result.current.persistCaseConfigure(configuration); + + await waitForNextUpdate(); + + expect(result.current).toEqual({ + loading: false, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('unhappy path - fetch case configuration', async () => { + const spyOnGetCaseConfigure = jest.spyOn(api, 'getCaseConfigure'); + spyOnGetCaseConfigure.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + loading: false, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); + + test('unhappy path - persist case configuration', async () => { + const spyOnPostCaseConfigure = jest.spyOn(api, 'postCaseConfigure'); + spyOnPostCaseConfigure.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useCaseConfigure(args) + ); + + await waitForNextUpdate(); + await waitForNextUpdate(); + + result.current.persistCaseConfigure(configuration); + + await waitForNextUpdate(); + + expect(result.current).toEqual({ + loading: false, + persistLoading: false, + refetchCaseConfigure: result.current.refetchCaseConfigure, + persistCaseConfigure: result.current.persistCaseConfigure, + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx index 19d80bba1e0f..7f57149d4e56 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx @@ -12,7 +12,7 @@ import * as i18n from './translations'; import { ClosureType } from './types'; import { CurrentConfiguration } from '../../../pages/case/components/configure_cases/reducer'; -interface PersistCaseConfigure { +export interface PersistCaseConfigure { connectorId: string; connectorName: string; closureType: ClosureType; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.test.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.test.tsx new file mode 100644 index 000000000000..0d6b6acfd906 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.test.tsx @@ -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 { renderHook, act } from '@testing-library/react-hooks'; +import { useConnectors, ReturnConnectors } from './use_connectors'; +import { connectorsMock } from './mock'; +import * as api from './api'; + +jest.mock('./api'); + +describe('useConnectors', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + test('init', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useConnectors() + ); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: true, + connectors: [], + refetchConnectors: result.current.refetchConnectors, + }); + }); + }); + + test('fetch connectors', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useConnectors() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + loading: false, + connectors: connectorsMock, + refetchConnectors: result.current.refetchConnectors, + }); + }); + }); + + test('refetch connectors', async () => { + const spyOnfetchConnectors = jest.spyOn(api, 'fetchConnectors'); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useConnectors() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchConnectors(); + expect(spyOnfetchConnectors).toHaveBeenCalledTimes(2); + }); + }); + + test('set isLoading to true when refetching connectors', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useConnectors() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + result.current.refetchConnectors(); + + expect(result.current.loading).toBe(true); + }); + }); + + test('unhappy path', async () => { + const spyOnfetchConnectors = jest.spyOn(api, 'fetchConnectors'); + spyOnfetchConnectors.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + useConnectors() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(result.current).toEqual({ + loading: false, + connectors: [], + refetchConnectors: result.current.refetchConnectors, + }); + }); + }); +}); From 9fd63a7daad6f3f13585d2d6397402654c278914 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:31:19 -0700 Subject: [PATCH 41/46] [DOCS] Adds docs for Painless Lab (#62997) * [DOCS] Adds docs for Painless Lab * [DOCS] Incorporated review comments --- .../painlesslab/images/painless-lab.png | Bin 0 -> 220876 bytes docs/dev-tools/painlesslab/index.asciidoc | 17 +++++++++++ docs/user/dev-tools.asciidoc | 28 ++++++++++++++---- 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 docs/dev-tools/painlesslab/images/painless-lab.png create mode 100644 docs/dev-tools/painlesslab/index.asciidoc diff --git a/docs/dev-tools/painlesslab/images/painless-lab.png b/docs/dev-tools/painlesslab/images/painless-lab.png new file mode 100644 index 0000000000000000000000000000000000000000..f65257852792e03eae3d2e111da11d81f6df630f GIT binary patch literal 220876 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%0DJ;r1F_I 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

}xIV{+K-l~dNlQoghqoZX| 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, +process {ref}/docs-reindex.html[reindexed data], define complex +<>, +and work with data in other contexts. + +To get started, go to *Dev Tools > Painless Lab*. + +image::dev-tools/painlesslab/images/painless-lab.png[Painless Lab] diff --git a/docs/user/dev-tools.asciidoc b/docs/user/dev-tools.asciidoc index 77a781fd069e..0ee7fbc741e0 100644 --- a/docs/user/dev-tools.asciidoc +++ b/docs/user/dev-tools.asciidoc @@ -4,13 +4,29 @@ [partintro] -- -The *Dev Tools* page contains development tools that you can use to interact -with your data in Kibana. +*Dev Tools* contains tools that you can use to interact +with your data. -* <> -* <> -* <> +[cols="2"] +|=== +a| <> + +| Interact with the REST API of Elasticsearch, including sending requests +and viewing API documentation. + +a| <> + +| Inspect and analyze your search queries. + +a| <> + +| Build and debug grok patterns before you use them in your data processing pipelines. + +a| <> + +| beta:[] Test and debug Painless scripts in real-time. +|=== -- @@ -19,3 +35,5 @@ include::{kib-repo-dir}/dev-tools/console/console.asciidoc[] include::{kib-repo-dir}/dev-tools/searchprofiler/index.asciidoc[] include::{kib-repo-dir}/dev-tools/grokdebugger/index.asciidoc[] + +include::{kib-repo-dir}/dev-tools/painlesslab/index.asciidoc[] From 087df824523d007409c68e7d3ba32d824cce07ee Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 9 Apr 2020 11:48:56 -0700 Subject: [PATCH 42/46] [TSVB] Add Positive Rate to Aggregations (#59843) * [TSVB] Add Rate to Aggregations * Fixing i18n labels * Changing from rate to growth_rate; adding message to aggregation form; * Change units to scale; change free text to combobox * Fixing placeholder * Fixing i18n label * Changing from Growth Rate to Positive Rate Co-authored-by: Elastic Machine --- .../public/components/aggs/agg_select.js | 6 + .../public/components/aggs/positive_rate.js | 191 ++++++++++++++++++ .../public/components/lib/agg_to_component.js | 2 + .../vis_type_timeseries/common/agg_lookup.js | 3 + .../common/calculate_label.js | 6 + .../request_processors/series/index.js | 2 + .../series/positive_rate.js | 68 +++++++ .../series/positive_rate.test.js | 95 +++++++++ .../request_processors/table/index.js | 2 + .../request_processors/table/positive_rate.js | 35 ++++ 10 files changed, 410 insertions(+) create mode 100644 src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js index f93dee14d0ee..8607ff184dfa 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/agg_select.js @@ -49,6 +49,12 @@ const metricAggs = [ }), value: 'filter_ratio', }, + { + label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel', { + defaultMessage: 'Positive Rate', + }), + value: 'positive_rate', + }, { label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.maxLabel', { defaultMessage: 'Max', diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js new file mode 100644 index 000000000000..39558fa3a922 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/positive_rate.js @@ -0,0 +1,191 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import PropTypes from 'prop-types'; +import React from 'react'; +import { AggSelect } from './agg_select'; +import { FieldSelect } from './field_select'; +import { AggRow } from './agg_row'; +import { createChangeHandler } from '../lib/create_change_handler'; +import { createSelectHandler } from '../lib/create_select_handler'; +import { + htmlIdGenerator, + EuiFlexGroup, + EuiFlexItem, + EuiFormLabel, + EuiFormRow, + EuiSpacer, + EuiText, + EuiLink, + EuiComboBox, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; + +const UNIT_OPTIONS = [ + { + label: i18n.translate('visTypeTimeseries.units.auto', { defaultMessage: 'auto' }), + value: '', + }, + { + label: i18n.translate('visTypeTimeseries.units.perMillisecond', { + defaultMessage: 'per millisecond', + }), + value: '1ms', + }, + { + label: i18n.translate('visTypeTimeseries.units.perSecond', { defaultMessage: 'per second' }), + value: '1s', + }, + { + label: i18n.translate('visTypeTimeseries.units.perMinute', { defaultMessage: 'per minute' }), + value: '1m', + }, + { + label: i18n.translate('visTypeTimeseries.units.perHour', { defaultMessage: 'per hour' }), + value: '1h', + }, + { + label: i18n.translate('visTypeTimeseries.units.perDay', { defaultMessage: 'per day' }), + value: '1d', + }, +]; + +export const PositiveRateAgg = props => { + const defaults = { unit: '' }; + const model = { ...defaults, ...props.model }; + + const handleChange = createChangeHandler(props.onChange, model); + const handleSelectChange = createSelectHandler(handleChange); + + const htmlId = htmlIdGenerator(); + const indexPattern = + (props.series.override_index_pattern && props.series.series_index_pattern) || + props.panel.index_pattern; + + const selectedUnitOptions = UNIT_OPTIONS.filter(o => o.value === model.unit); + + return ( + + + + + + + + + + + + } + fullWidth + > + + + + + + } + fullWidth + > + + + + + + +

+ + + + ), + }} + /> +

+ + + ); +}; + +PositiveRateAgg.propTypes = { + disableDelete: PropTypes.bool, + fields: PropTypes.object, + model: PropTypes.object, + onAdd: PropTypes.func, + onChange: PropTypes.func, + onDelete: PropTypes.func, + panel: PropTypes.object, + series: PropTypes.object, + siblings: PropTypes.array, +}; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js index ca40d60f2084..a53192afafdc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/agg_to_component.js @@ -33,6 +33,7 @@ import { PercentileRankAgg } from '../aggs/percentile_rank'; import { Static } from '../aggs/static'; import { MathAgg } from '../aggs/math'; import { TopHitAgg } from '../aggs/top_hit'; +import { PositiveRateAgg } from '../aggs/positive_rate'; export const aggToComponent = { count: StandardAgg, @@ -65,4 +66,5 @@ export const aggToComponent = { static: Static, math: MathAgg, top_hit: TopHitAgg, + positive_rate: PositiveRateAgg, }; diff --git a/src/plugins/vis_type_timeseries/common/agg_lookup.js b/src/plugins/vis_type_timeseries/common/agg_lookup.js index 4dfdc83dcfab..432da03e3d45 100644 --- a/src/plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/plugins/vis_type_timeseries/common/agg_lookup.js @@ -97,6 +97,9 @@ export const lookup = { defaultMessage: 'Static Value', }), top_hit: i18n.translate('visTypeTimeseries.aggLookup.topHitLabel', { defaultMessage: 'Top Hit' }), + positive_rate: i18n.translate('visTypeTimeseries.aggLookup.positiveRateLabel', { + defaultMessage: 'Positive Rate', + }), }; const pipeline = [ diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.js b/src/plugins/vis_type_timeseries/common/calculate_label.js index 756d6e57a83e..71aa0aed7dc1 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.js +++ b/src/plugins/vis_type_timeseries/common/calculate_label.js @@ -70,6 +70,12 @@ export function calculateLabel(metric, metrics) { defaultMessage: 'Filter Ratio', }); } + if (metric.type === 'positive_rate') { + return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', { + defaultMessage: 'Positive Rate of {field}', + values: { field: metric.field }, + }); + } if (metric.type === 'static') { return i18n.translate('visTypeTimeseries.calculateLabel.staticValueLabel', { defaultMessage: 'Static Value of {metricValue}', diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/index.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/index.js index 4b0b8f33716a..c727a3131f5d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/index.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/index.js @@ -26,6 +26,7 @@ import { dateHistogram } from './date_histogram'; import { metricBuckets } from './metric_buckets'; import { siblingBuckets } from './sibling_buckets'; import { ratios as filterRatios } from './filter_ratios'; +import { positiveRate } from './positive_rate'; import { normalizeQuery } from './normalize_query'; export const processors = [ @@ -38,5 +39,6 @@ export const processors = [ metricBuckets, siblingBuckets, filterRatios, + positiveRate, normalizeQuery, ]; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js new file mode 100644 index 000000000000..1ff548cc19e0 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; +import { bucketTransform } from '../../helpers/bucket_transform'; +import { set } from 'lodash'; + +export const filter = metric => metric.type === 'positive_rate'; + +export const createPositiveRate = (doc, intervalString, aggRoot) => metric => { + const maxFn = bucketTransform.max; + const derivativeFn = bucketTransform.derivative; + const positiveOnlyFn = bucketTransform.positive_only; + + const maxMetric = { id: `${metric.id}-positive-rate-max`, type: 'max', field: metric.field }; + const derivativeMetric = { + id: `${metric.id}-positive-rate-derivative`, + type: 'derivative', + field: `${metric.id}-positive-rate-max`, + unit: metric.unit, + }; + const positiveOnlyMetric = { + id: metric.id, + type: 'positive_only', + field: `${metric.id}-positive-rate-derivative`, + }; + + const fakeSeriesMetrics = [maxMetric, derivativeMetric, positiveOnlyMetric]; + + const maxBucket = maxFn(maxMetric, fakeSeriesMetrics, intervalString); + const derivativeBucket = derivativeFn(derivativeMetric, fakeSeriesMetrics, intervalString); + const positiveOnlyBucket = positiveOnlyFn(positiveOnlyMetric, fakeSeriesMetrics, intervalString); + + set(doc, `${aggRoot}.timeseries.aggs.${metric.id}-positive-rate-max`, maxBucket); + set(doc, `${aggRoot}.timeseries.aggs.${metric.id}-positive-rate-derivative`, derivativeBucket); + set(doc, `${aggRoot}.timeseries.aggs.${metric.id}`, positiveOnlyBucket); +}; + +export function positiveRate(req, panel, series, esQueryConfig, indexPatternObject, capabilities) { + return next => doc => { + const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject); + const { intervalString } = getBucketSize(req, interval, capabilities); + if (series.metrics.some(filter)) { + series.metrics + .filter(filter) + .forEach(createPositiveRate(doc, intervalString, `aggs.${series.id}.aggs`)); + return next(doc); + } + return next(doc); + }; +} diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js new file mode 100644 index 000000000000..946884c05c72 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { positiveRate } from './positive_rate'; +describe('positiveRate(req, panel, series)', () => { + let panel; + let series; + let req; + beforeEach(() => { + panel = { + time_field: 'timestamp', + }; + series = { + id: 'test', + split_mode: 'terms', + terms_size: 10, + terms_field: 'host', + metrics: [ + { + id: 'metric-1', + type: 'positive_rate', + field: 'system.network.out.bytes', + unit: '1s', + }, + ], + }; + req = { + payload: { + timerange: { + min: '2017-01-01T00:00:00Z', + max: '2017-01-01T01:00:00Z', + }, + }, + }; + }); + + test('calls next when finished', () => { + const next = jest.fn(); + positiveRate(req, panel, series)(next)({}); + expect(next.mock.calls.length).toEqual(1); + }); + + test('returns positive rate aggs', () => { + const next = doc => doc; + const doc = positiveRate(req, panel, series)(next)({}); + expect(doc).toEqual({ + aggs: { + test: { + aggs: { + timeseries: { + aggs: { + 'metric-1-positive-rate-max': { + max: { field: 'system.network.out.bytes' }, + }, + 'metric-1-positive-rate-derivative': { + derivative: { + buckets_path: 'metric-1-positive-rate-max', + gap_policy: 'skip', + unit: '1s', + }, + }, + 'metric-1': { + bucket_script: { + buckets_path: { value: 'metric-1-positive-rate-derivative[normalized_value]' }, + script: { + source: 'params.value > 0.0 ? params.value : 0.0', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, + }, + }, + }, + }, + }, + }); + }); +}); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/index.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/index.js index a62533ae7a37..5864d2538005 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/index.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/index.js @@ -26,6 +26,7 @@ import { metricBuckets } from './metric_buckets'; import { siblingBuckets } from './sibling_buckets'; import { ratios as filterRatios } from './filter_ratios'; import { normalizeQuery } from './normalize_query'; +import { positiveRate } from './positive_rate'; export const processors = [ query, @@ -36,5 +37,6 @@ export const processors = [ metricBuckets, siblingBuckets, filterRatios, + positiveRate, normalizeQuery, ]; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js new file mode 100644 index 000000000000..da4b834822d7 --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; +import { calculateAggRoot } from './calculate_agg_root'; +import { createPositiveRate, filter } from '../series/positive_rate'; + +export function positiveRate(req, panel, esQueryConfig, indexPatternObject) { + return next => doc => { + const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject); + const { intervalString } = getBucketSize(req, interval); + panel.series.forEach(column => { + const aggRoot = calculateAggRoot(doc, column); + column.metrics.filter(filter).forEach(createPositiveRate(doc, intervalString, aggRoot)); + }); + return next(doc); + }; +} From b8738b0eebf7a7ad708b5f498dcc51f6a7fb2c87 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 9 Apr 2020 15:05:09 -0400 Subject: [PATCH 43/46] Ensure that discover data exists for home/_navigation test so that the test suite can run in isolation (#62516) --- test/functional/apps/home/_navigation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index efc0dad39446..2c927e9a2f4c 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -52,6 +52,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { describe('Kibana browser back navigation should work', function describeIndexTests() { before(async () => { + await esArchiver.loadIfNeeded('discover'); await esArchiver.loadIfNeeded('logstash_functional'); if (browser.isInternetExplorer) { await kibanaServer.uiSettings.replace({ 'state:storeInSessionStorage': false }); From e61680571acce5b6bb8be8d9f6e574864c49f290 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Thu, 9 Apr 2020 15:05:21 -0400 Subject: [PATCH 44/46] Ensure alerting action exists in connectors test setup (#62511) --- .../apps/triggers_actions_ui/connectors.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index c2013ba3502e..b5bcd33c3b9a 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -13,12 +13,20 @@ function generateUniqueKey() { } export default ({ getPageObjects, getService }: FtrProviderContext) => { + const alerting = getService('alerting'); const testSubjects = getService('testSubjects'); const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const find = getService('find'); describe('Connectors', function() { before(async () => { + await alerting.actions.createAction({ + name: `server-log-${Date.now()}`, + actionTypeId: '.server-log', + config: {}, + secrets: {}, + }); + await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('connectorsTab'); }); From 783e3c17a9ed32d6a5aff39a83bdfa38aa152bf1 Mon Sep 17 00:00:00 2001 From: Tim Schnell Date: Thu, 9 Apr 2020 14:32:24 -0500 Subject: [PATCH 45/46] ignore some things for code coverage (#62701) Co-authored-by: Elastic Machine --- x-pack/legacy/plugins/canvas/scripts/jest.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/canvas/scripts/jest.js b/x-pack/legacy/plugins/canvas/scripts/jest.js index cce1b8d35584..133f775c7192 100644 --- a/x-pack/legacy/plugins/canvas/scripts/jest.js +++ b/x-pack/legacy/plugins/canvas/scripts/jest.js @@ -36,6 +36,14 @@ run( `!${path}/**/__tests__/**/*`, '--collectCoverageFrom', // Ignore coverage on example files `!${path}/**/__examples__/**/*`, + '--collectCoverageFrom', // Ignore flot files + `!${path}/**/flot-charts/**`, + '--collectCoverageFrom', // Ignore coverage files + `!${path}/**/coverage/**`, + '--collectCoverageFrom', // Ignore scripts + `!${path}/**/scripts/**`, + '--collectCoverageFrom', // Ignore mock files + `!${path}/**/mocks/**`, '--collectCoverageFrom', // Include JS files `${path}/**/*.js`, '--collectCoverageFrom', // Include TS/X files @@ -76,7 +84,7 @@ run( --all Runs all tests and snapshots. Slower. --storybook Runs Storybook Snapshot tests only. --update Updates Storybook Snapshot tests. - --path Runs any tests at a given path. + --path Runs any tests at a given path. --coverage Collect coverage statistics. `, }, From 834306458ac5178cbe26a0f962c764a0451568e2 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 9 Apr 2020 13:52:02 -0600 Subject: [PATCH 46/46] Fixes a needed import that was coming from APM but now isolates things better for optimization import builds ## Summary Adds a single line to work with ts config optimizers. What is happening is that this is relying on an import from here: ``` x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx ``` when really it should be isolated and imported within this file. Testing is just to go to siem and run these commands: ```ts cd /projects/kibana node x-pack/legacy/plugins/siem/scripts/optimize_tsconfig.js node scripts/type_check.js --project x-pack/tsconfig.json ``` Ensure you don't see any errors. --- .../public/application/components/health_check.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index 5156a6146f3a..9c51139993b3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -10,6 +10,7 @@ import { HealthCheck } from './health_check'; import { act } from 'react-dom/test-utils'; import { httpServiceMock } from '../../../../../../src/core/public/mocks'; +import '@testing-library/jest-dom/extend-expect'; const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' };

kCbAFrnrvi1y$?L2<5~xtW^1 zQ6b)p@`naH;H&fqaL%-Qp27`tO;^2`(vh5ZorS5p69?mgNB{3Vp}ZR z*n9{XBbycrgf`#c4}d|;k#%5oT7j+7v3Q`ARQTn0t%>F<($ZH8eB{3OS7bB(?OgXr zoI(0j;(D^%GAQpdE=9wOQ(rKn@0{h{p>yjeuaYm`5s8VuP_zB?ffT{uckbDuVlUas zj)w%rpollU#YUF-ifor>!#j;$Y2R}1k^T=&;is_+PXXzNaQD_v9&G~-@MzvU+v7>k z=xmcS&0ClLRKfknBSf9KpY`27>y8e=WDNCeIs9De)64E&fZqbGU)sxq?$ZI@TOY&j z^wpVVdkUj{JMM0{Rmbr16Izp%#g)vP>)0us%3FN450CRy+iMkj)z$e{U!J8%pZgXD z{w^hbCG>z#pu!9|EO@wt?ZI~s8V@XS>-tw}h7g|&Cg+c+x2~7B$}V2KXl);TWZTpgTQP_IP+0R5aO8HBhv*V5~N)%A>&}ARYbV1tGjuKr$pn^HlIsc z8>M3+vI$htt+ntU7=8fIh`zUj8A5d*zRKTgeW5CZ`RvuKn&5#`2V7vIba3Dc8WM zlV>*N^Z3<&>W&x#9>|f8?VS|uYl+abz;%7eXY1U=vYm#OLn1h?SgNy-(bv?8=oU1~ zoBU5mV2mN^DoNqS4w6H^6T3=PtIPkVj#ec=YvVs2$V>~JETYD!+Eu+7 zgd^v#Y-j-@y^Z%LbpRoT-Pye%6<@vrL#X^N+rU=;*kJo~(fT!86UySL?V%E|gqecX zhvFEnCr!V?bdFf4}*Oh;4XnZWe|t8}BhS8~%0D@aYx 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 Q1@I#&CMQ}btn2gt0O?oeDF6Tf literal 0 HcmV?d00001 diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc index b1b7ed730798..904718999069 100644 --- a/docs/apm/using-the-apm-ui.asciidoc +++ b/docs/apm/using-the-apm-ui.asciidoc @@ -15,6 +15,7 @@ APM is available via the navigation sidebar in {Kib}. * <> * <> * <> +* <> * <> * <> * <> @@ -37,6 +38,8 @@ include::errors.asciidoc[] include::metrics.asciidoc[] +include::apm-alerts.asciidoc[] + include::agent-configuration.asciidoc[] include::custom-links.asciidoc[] From 13fe738b2a59c5117e15aca4af1c155e00129a37 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Thu, 9 Apr 2020 08:55:29 -0700 Subject: [PATCH 32/46] [UI COPY] Fixes typo in max_shingle_size for search_as_you_type (#63071) --- .../field_parameters/max_shingle_size_parameter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx index bc1917b2da96..cec97fb925ee 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/max_shingle_size_parameter.tsx @@ -23,7 +23,7 @@ export const MaxShingleSizeParameter = ({ defaultToggleValue }: Props) => ( })} description={i18n.translate('xpack.idxMgmt.mappingsEditor.maxShingleSizeFieldDescription', { defaultMessage: - 'The default is three shingle subfields. More subfields enables more specific queries, but increases index size.', + 'The default is three shingle subfields. More subfields enable more specific queries, but increase index size.', })} defaultToggleValue={defaultToggleValue} > From 5e1c0be501b672174b2553dbca74d4eba16295f6 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:56:03 -0400 Subject: [PATCH 33/46] [Endpoint] Add link to Logs UI to the Host Details view (#62852) * Add LinktoApp to host details for logs * initial setup for testing link on details * Export interface AppContextTestRender for reference in tests * Refactor hosts tests to use AppContextTestRender * Render full details and validate link to logs * one more test to ensure we navigate to app (not full page refresh) * Fixes post master merge --- .../endpoint/components/link_to_app.tsx | 2 +- .../endpoint/mocks/app_context_render.tsx | 2 +- .../store/hosts/mock_host_result_list.ts | 13 ++- .../endpoint/view/hosts/details.tsx | 28 +++++ .../endpoint/view/hosts/index.test.tsx | 106 +++++++++++++----- 5 files changed, 119 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx index b110d32442c2..858dac864b58 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx @@ -12,7 +12,7 @@ import { useNavigateToAppEventHandler } from '../hooks/use_navigate_to_app_event export type LinkToAppProps = EuiLinkProps & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ appId: string; - /** Any app specic path (route) */ + /** Any app specific path (route) */ appPath?: string; appState?: any; onClick?: MouseEventHandler; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx index af34205e2310..7cb1031ef9a0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx @@ -18,7 +18,7 @@ type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResul /** * Mocked app root context renderer */ -interface AppContextTestRender { +export interface AppContextTestRender { store: ReturnType; history: ReturnType; coreStart: ReturnType; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts index d4c2602e3438..20aa973ffc93 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList, HostStatus } from '../../../../../common/types'; +import { HostInfo, HostResultList, HostStatus } from '../../../../../common/types'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; export const mockHostResultList: (options?: { @@ -40,3 +40,14 @@ export const mockHostResultList: (options?: { }; return mock; }; + +/** + * returns a mocked API response for retrieving a single host metadata + */ +export const mockHostDetailsApiResult = (): HostInfo => { + const generator = new EndpointDocGenerator('seed'); + return { + metadata: generator.generateHostMetadata(), + host_status: HostStatus.ERROR, + }; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 37080e856835..90829f7ad4cb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -28,6 +28,7 @@ import { useHostListSelector } from './hooks'; import { urlFromQueryParams } from './url_from_query_params'; import { FormattedDateAndTime } from '../formatted_date_time'; import { uiQueryParams, detailsData, detailsError } from './../../store/hosts/selectors'; +import { LinkToApp } from '../../components/link_to_app'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -37,6 +38,7 @@ const HostIds = styled(EuiListGroupItem)` `; const HostDetails = memo(({ details }: { details: HostMetadata }) => { + const { appId, appPath, url } = useHostLogsUrl(details.host.id); const detailsResultsUpper = useMemo(() => { return [ { @@ -113,6 +115,20 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => { listItems={detailsResultsLower} data-test-subj="hostDetailsLowerList" /> + +