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 8bb3fb20cea8b7..ba3cd1280f34bb 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 55365c3d4c2ba1..d84e46d08a2879 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 e44e20751b315e..e025248fad52e6 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 d399f6136690b7..3de0a99ccbbd91 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 fb3ff9ceb09265..99dd7b63383fbf 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 31c77833cc0e87..7f78d327d01225 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 ab039be8e7c22d..b897c03e89f821 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 f58eaacb7be673..20bbbec8919d63 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 a941494072ae3d..309ddca3784c29 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 21bedc204f48b7..e4b656ae8160d7 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 b648004760d7c7..b3e5a8c518682e 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 d52d89eebe2f13..4b4bb1282d9395 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 d2305d643e4d20..1f1392b02baffa 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 e5cbffc3e2d943..b4e2c3095f14a7 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 63aba0dde2af81..bf7711055e97ba 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 023e6ebb7125ce..badea68eec19f9 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 e6421142f66666..98679a8f24d16a 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 5af11bc7f0b039..b303ccd5aeed20 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 08efed733cafe5..b4b491ac7a5552 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 fef46282eb8ddd..272bed3e91a08c 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 30c62d778933bf..1db35c406eb132 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 64a9aaaf3b7a68..b2f3e5b2241e61 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 5d9ed5c8df91c5..f56d7682efc6f9 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 da50e227d84d20..0d6b1b5e8de589 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 ad4aa97d8ea7a0..1093153edbbf7e 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 3a28759d82b71d..37e2144800ea11 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 56facc37fc6661..ddd84b05443455 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 00000000000000..5a3db3b3440901
--- /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 944b800c66a284..6db6248f4c68fa 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 00000000000000..0add1bee84ae02
--- /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 b4007b30cf8cab..21bba92ada4c16 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 939d372b9997f7..aefbd66e50fcf4 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 9b34880cf4fe3a..ee99ea36726727 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 e1689e38dbfe05..9ad9f14ac5659d 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 00000000000000..6fa39d82d10215
--- /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 1876e688c989ac..2f139050e994ab 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 2b2fc004a84c62..f532c2c8aa2193 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 c590cf8f34ee07..3eaa13572a826e 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 | 6 +-
src/core/server/legacy/legacy_service.test.ts | 2 +
src/core/server/legacy/legacy_service.ts | 3 +
src/core/server/mocks.ts | 9 +-
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 | 18 +-
.../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, 2006 insertions(+), 24 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 29fdc37a811767..c10b460da8b4f7 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 00000000000000..f5ea627a9f008a
--- /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 00000000000000..b41e7020c38e95
--- /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 00000000000000..3fde86a18c58bd
--- /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 00000000000000..d554c6f70d7208
--- /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 00000000000000..f8a45fe9a5a9ce
--- /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 00000000000000..2398410fa4b840
--- /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 00000000000000..7374ccd9e7fa87
--- /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 793684c1b37961..accab9bf0cb360 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 00000000000000..8e7298d28801c9
--- /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 00000000000000..82a4800a3b4b63
--- /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 00000000000000..347f2d3474b114
--- /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 00000000000000..6fcfacc3bc9085
--- /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 00000000000000..415a7825ee2bfe
--- /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 00000000000000..6c017e9fc800ce
--- /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 00000000000000..3a0b23d18632f1
--- /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 00000000000000..6a29623b2f122d
--- /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 00000000000000..fa369aa0bdfbb4
--- /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 00000000000000..5ef8c1251a6024
--- /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 00000000000000..551c10c9bff820
--- /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 00000000000000..d35fc951c57ffb
--- /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 00000000000000..a48994daa5a4e4
--- /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 00000000000000..db90afd6f74a65
--- /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 00000000000000..5f995ff5e13e3b
--- /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 00000000000000..a66cec78c736b8
--- /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 00000000000000..6662e68b44d364
--- /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 00000000000000..0551a217520ad7
--- /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 259d725b3bf0d9..e756eb9b729050 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 389d98a0818c8e..da8846f6dddbb9 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 b92a6edf778ed0..684f6e15caff98 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 cfd72a6fd5e47b..2e45f710c4dcfd 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 00000000000000..dd5fb04bfd1c63
--- /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 00000000000000..1eaa338af12399
--- /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 ef8edecfd26ec6..3d38935e9fbf0f 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 3e760ec0efabd6..7bd6331978d1de 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 56ce16a951aa26..a298f80f96d8f0 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 825deea99bc230..ede0d3dc9fcc71 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;
+ metrics: InternalMetricsServiceSetup;
savedObjects: InternalSavedObjectsServiceSetup;
+ status: InternalStatusServiceSetup;
+ uiSettings: InternalUiSettingsServiceSetup;
uuid: UuidServiceSetup;
- metrics: InternalMetricsServiceSetup;
}
/**
diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts
index c6860086e77842..0cf2ebe55ea10d 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 bb5f6d5617aaea..f77230301ce023 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 31bf17da041afb..faf73044cac4d1 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(),
+ metrics: metricsServiceMock.createInternalSetupContract(),
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
+ status: statusServiceMock.createInternalSetupContract(),
uuid: uuidServiceMock.createSetupContract(),
- metrics: metricsServiceMock.createInternalSetupContract(),
+ uiSettings: uiSettingsServiceMock.createSetupContract(),
};
return setupDeps;
}
diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts
index 32662f07a86f02..61d97aea97459c 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 b50e47b9eab738..fe4795cad11a5a 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 4fbadf90f4b600..466d399f653cd6 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 ddd82edd934484..5ba2d0afc692e8 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 dc966f0797822c..8ddaed3707eb0c 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 25772c4c9b0b16..df4751521ac533 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 2ee656721abd0b..257b32c1e4c237 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 fd82bf282266ef..336eeff99f47b6 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 bc29061b380b88..dafd6c53411966 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 9fe32b14e64507..7ba4613c857d7b 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 aa440c6454569a..62027928c0bb5d 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 00000000000000..8efea1e2c00c65
--- /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 00000000000000..66a6e2baa17a72
--- /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 962965a08f8b2a..f14e9d9efb5e3d 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 e4e2b8d7adbb77..f3e3b7736d8d30 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 53d1b742a64944..5d535c98457249 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 a4b5a9d81df203..24c41d511180a1 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 222be572b75e49..07ea431dd3a0df 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,
+ metrics: metricsSetup,
savedObjects: savedObjectsSetup,
+ status: statusSetup,
+ uiSettings: uiSettingsSetup,
uuid: uuidSetup,
- metrics: metricsSetup,
};
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 00000000000000..7516e82ee784de
--- /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 00000000000000..748a54f0bf8bba
--- /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 00000000000000..c39115d55a6827
--- /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 00000000000000..d550c2f06750bf
--- /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 00000000000000..6d92a266369b90
--- /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 00000000000000..b6697d82219519
--- /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 00000000000000..765fa8771f3758
--- /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 00000000000000..84a7356c66bbf7
--- /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 470b1c2d135b70..f7e6fbcd0c131e 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 | 41 +++++++--------
.../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, 183 insertions(+), 296 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 3b0b40b40792e8..19d361aed93445 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 9df010223542be..a4fffc6eec26da 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 25bd36829b6d03..40471b95d774c1 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 bbb6bf26e5b311..648bf7f8f97388 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 e0c43105cb320f..b23b1e3ad9051e 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 80582cc1fbd921..a58bf10c9dab8b 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 50c5a58d35db3b..47cb773258cb46 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 5a8460fcb51bae..40583af7177fee 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 3848c425e2d495..3b1b7a3b52a5b8 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 6d302ac5a74f35..594430ca01f4ce 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 310797a7f3a0cc..a8376c0e84bf9b 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 a5e34f8955fe30..7b9c17640a0f3e 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 97c0d5b89d657b..5d14c4609b918c 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 4962c948f842f2..00000000000000
--- 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 77d33a7bce3b6c..00000000000000
--- 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 bc3737524e1259..00000000000000
--- 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 96d2c74398a0e8..00000000000000
--- 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 f70ef069dd134e..0779d6472671cb 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 b4b5099081759e..cdd7e1a9949127 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 00000000000000..d5397a11184aab
--- /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 bae0f1d3e23cdb..da482c0c51f0ac 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 ae0be98de63f37..bc97f46c302e32 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 2a8ef10c817cc9..93bb0ead1df4af 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';
+import {
+ IndexPatternManagementService,
+ IndexPatternManagementServiceSetup,
+ IndexPatternManagementServiceStart,
+} from './service';
-export interface ManagementPluginSetupDependencies {
- home: HomePublicPluginSetup;
-}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface IndexPatternManagementSetupDependencies {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
-interface ManagementPluginStartDependencies {}
+export interface IndexPatternManagementStartDependencies {}
-export interface ManagementSetup {
- indexPattern: IndexPatternManagementSetup;
-}
+export type IndexPatternManagementSetup = IndexPatternManagementServiceSetup;
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface ManagementStart {}
+export type IndexPatternManagementStart = IndexPatternManagementServiceStart;
-export class ManagementPlugin
+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 5714fa33389624..29ab0ebfc3d5f7 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 e7fa13409ab046..32b3e7ee7a133b 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 2b6f008dd928a1..4780fa00ed468d 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();
+ }
+
+ public setup({ httpClient }: SetupDependencies) {
+ const creationManagerSetup = this.indexPatternCreationManager.setup(httpClient);
+ creationManagerSetup.addCreationConfig(IndexPatternCreationConfig);
+ this.indexPatternListConfig.setup().addListConfig(IndexPatternListConfig);
- 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,
- });
+ return {
+ creation: creationManagerSetup,
+ list: this.indexPatternListConfig.setup(),
+ };
+ }
+ public start() {
return {
- creation,
- list,
+ 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 dd4d77a6811717..87c246e8913e53 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 73ca33ae914a9b..3a2910a222cd7c 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 3781d59d8c0f36..3df8bd7c187d5e 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 f0eb21a219442f..f4de2a30981276 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 fbf2612b83aa82..809a76d1868b22 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 ec530e63408f45..83945110c2c760 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 c58975419e20f1..5782e88c3448bf 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 7809b511adda49..99b4e184c071a6 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 4d92505c4aebbc..fee41fc7e36d32 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 b22cec0af5ea83..4f6ff55dbfbb2d 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 96cbac4b48065a..ea74fc92b95ead 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 fb2616619c644b..65a0cabfeb7166 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 96e642b0f45d8b..44c9e9993bf102 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 d07026c0883b8d..00ac5b77d00f3b 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 7e64ff6301fafc..f6d84431bef7fb 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 53b7e7062ee2d4..2f9d9f9bfb178f 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 356739af1ff9a8..0e8763cb2d4c09 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 74b08f48df12de..6b0c68ccb7d3fa 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 3bf5b2bb4c0f02..43e8f5a2c060db 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 bcb99eed88ac04..846a807f9240d3 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 e5f18e1449d1b2..3bfca756689115 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 23d0f3317a6673..7815ab9cd1d6ed 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 44e5bfa41cb9bf..d99f0712db3c36 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 00000000000000..a45fabeb5ed6a3
--- /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 00000000000000..518e2bfc1af07a
--- /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 b45beef4a8b5e4..105d6c66a57047 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 0aa08602e4d4de..5ebd1300baf65d 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 333a9b049fa853..32615278b67d76 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 4c5db5321c2823..bd0fec3a5c116e 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 0cc1ad6326671d..7a858deff41d37 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 66f17ab579ec39..f4b91d154cbb88 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 00000000000000..ed68b41e8c89ab
--- /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 5017947e95d03b..13d2365c07191b 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 157918df874c82..8b57ecd3c82350 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 46d81f1737a55f..aa6e364d0a09d0 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 e571a1a7e55512..b0724488cb5db8 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 3bf5b865aa7ba5..fc0b5bbb787c81 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 00000000000000..882fb6fea36861
--- /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 77ab69c715573c..3f04b2d26a0139 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 | 58 +++++-
.../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, 662 insertions(+), 562 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 9d4ea69a636090..2574e73dd4f9ae 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 172a1062263453..fdca6c0a9b503c 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 9efe020bc10c4b..42c83a7c04deba 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,37 @@ describe('healthRoute', () => {
`);
});
+ it('evaluates whether Encrypted Saved Objects is using an ephemeral encryption key', async () => {
+ const router: RouterMock = mockRouter.create();
+
+ const licenseState = mockLicenseState();
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
+ encryptedSavedObjects.usingEphemeralEncryptionKey = true;
+ 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": 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();
- 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();
@@ -73,6 +104,7 @@ describe('healthRoute', () => {
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 29c2f3c5730f4c..fa2358a1f181ce 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 00ac5b77d00f3b..e63e1c8ad2c916 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 f6d84431bef7fb..cc75ceb988d972 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 85699cfbd750f0..00000000000000
--- 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 f7a80202dff89a..00000000000000
--- 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 00000000000000..c4d12221e3a01e
--- /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 00000000000000..5156a6146f3a12
--- /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 00000000000000..c967cf5de0771c
--- /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 00000000000000..fe001ce294ef4f
--- /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 00000000000000..0e956ea56faa9c
--- /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 00000000000000..df593d587de3fb
--- /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 28bc02ec3392f2..00000000000000
--- 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 9874a3a0697d2c..00000000000000
--- 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 7c8d798984bf27..4d0a9980f2231f 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 3d65b8a799b1b1..70ad1cae6c1d14 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 81693e1d2d9d14..47e058f4739463 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 e025248fad52e6..0620ced6365a96 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 6fcfb463c4c778..916ba368e07320 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 3f27a7860bafa0..4255eca83be473 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 afd3299f0c2bbc..5d59180ff572b0 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 bc435179762a28..49840d2157af76 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 bb050076b9f9a9..2e10c743785494 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 205a303bcf47b0..afa0cb51cd1083 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=""
>
+
+
+ Type
+
+
+
List should render with data 1`] = `
className=""
>
+
+
+ Type
+
+
+
List should render with data 1`] = `
className=""
>
+
+
+ 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 b26833c02fe22c..250b9a5d188d0e 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 5de82a9ee8788e..54dd4704edfc0a 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 bc1b346f50da78..d5c3f91eb92470 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 b9ac9d5431700d..982ad558dc91d5 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 8ea6df5a9898af..5221d737866f48 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 662fb8fb8ef68e..22f1b3beffa35d 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 6955e57d099be3..14b2e1ae9e366c 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 073251b68f414c..3878f5dae88898 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 3a1697f1c8afc6..ccd7576255d83d 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 ced81098c9f8e5..e4ad53de742d61 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 128a7965cd7dc5..32a8737adc7c9c 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 4c077dd9fc1fb0..e1f7526438c31a 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 3197d21c0e95a7..ac0de406aceb29 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 d0e36515946a89..5377e9039785e8 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 6038ad2095323a..9a329b78b8f129 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 43e970702ba72e..29ae5056a3ae8f 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 9916972f418433..36e15780f5cb3f 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 21dd2a4429cca5..0444c757a9b316 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 97cfc1d2d9ea79..991690d901d8ae 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 864281da5bafd2..91489334940bd1 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 61b544db5a18a4..dad35f6cb1f96e 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 adc87150f89a75..d79c61f6200e3d 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 a15005110c56b1..2a2c84838ed93c 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 4c8d0f51f251bd..a60f1d41779782 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 bebf4f350483b6..91effb4741b8b0 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 bcbe460fb6a66c..6d4bacb9cc243c 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 d7655a15499eb7..5c4889ec5fd683 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 38b1097a845f8b..b1bed5d7161556 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 e6ee1e6a297643..00000000000000
--- 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 ca299db6ace50e..72f4cbcbe68e80 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 0e70e05f4de78c..99326768ed33b9 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 bb66a5ee1342f7..994a54048b71a3 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 d4469351de5442..040e32aa0d360d 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 355041d9efbdb5..ba8938f116fc6b 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 414270ffcdd5c8..81600b0b8dd9b6 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 ff81730bc4a72b..3a964cb91fbdb7 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 3d6f443ce60d61..03fb5832fdf42e 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 faac4a547fc176..0357f906f8035a 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 56f061cdfa3ca9..45365b446cbf0e 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 6dd8823b57e4de..fc33d0e15e43fe 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 d3fa98fd73d3a3..035f1b10ff8b2c 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 2b77b6f4592a87..2b1159be890033 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 d2ef2c67f0004e..38a819e285d671 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 e6fd259fabc926..57f3aa85ad9443 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 00000000000000..af3c15167295cb
--- /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 c14f02e7decdfd..b8d6586652d6b1 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 459de66d057d43..9d976784329ccf 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 18af94c919247b..f3192ba3da81f8 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 36181b66786d5b..af33ec2301edc6 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 | 87 +++++-----
.../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, 685 insertions(+), 273 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 00000000000000..5c5aa348eecdf3
--- /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 6964c070097c57..fc0dab94a0f656 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 8e1dbb6e2671dc..5f2fc809a55901 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 00000000000000..52d25dec01dfdc
--- /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 180ff13cdddc0d..a3a99a0ded523c 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 7261b2ba033725..705be68a141e70 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 5d14c4609b918c..0d16e0ae35dd66 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 105c2792183758..da2221bb54203d 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 8243aa69ac082a..dc6d2643145ffc 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;
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '3',
+ }),
},
},
- hydrateIndexPattern,
- save,
},
},
- ];
+ ] 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' } }];
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '1',
+ filter: [{ meta: { index: 'filterIndex' } }],
+ }),
},
- setField: jest.fn(),
},
- hydrateIndexPattern,
- save,
},
},
{
obj: {
- searchSource: {
- getOwnField: (field: string) => {
- return field === 'index' ? '3' : undefined;
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '3',
+ }),
},
},
- hydrateIndexPattern,
- save,
},
},
- ];
+ ] 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 902de654f5f85a..d9473367f7502e 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 7c9ab32ab2f72c..a710d3e318749b 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 201b21f932988a..e7f431a178ea06 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 8cf015d5dff5ce..400f31e73ffa10 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 c98fa612dc7afa..322d734d9f39f4 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 2a1e64fa88a02d..09357072a13a63 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 efafea44167d46..06a46065baa847 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 15067077afc43b..2ebe377b3b32fe 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 1687d749f46e28..cce973d632f417 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 b70e889066a453..cb1c625a729599 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 42f31ef450d28c..61246821848213 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 00000000000000..d49ce5a0d11f8d
--- /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 00000000000000..35b7ac4eb9762b
--- /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 10f1b2bc332e11..0e9f530d0968ab 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 700bea741bd6ae..1ef7c1187a9e0b 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 fcd116a3f41211..6bad093d31402b 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 0c3321f03dabc5..c70db7bb82ef7f 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 03cbfa9f8ed84a..ba6e44f47b75eb 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 0f5773c00283e3..7927238e120663 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 2e965eaf1989b3..9776887b6d741f 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 b9043890e2775f..e8faef4e9e0402 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 b55538e4073ba4..84275cf35befb6 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 cdb191f9e7df83..00000000000000
--- 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 8a020ca03aea31..78f9eeb8b5fb17 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 08389e9e3c97fc..60c66f84080b21 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 99088df84ec36a..3184038040952e 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 216defcee90161..8fcb84b19a9be8 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 bc96e08f4b9da4..c99c7a4c2caa19 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 c4668fa4b0c792..618c61dff176a7 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 252d602e8f5649..bc636c0b200f84 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 f5f9e98fe659c1..feff17b8131128 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 fb38f3e7d4c496..d068e824a3e0a8 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 @@ {{screenTitle}}
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 00000000000000..87a2da7e44a5e2
--- /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 582c979a194f49..50f140b99aa1ab 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 00000000000000..9493408a30040d
--- /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 00000000000000..00961119239511
--- /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 78482198b1a5d4..569c9a6c56c5af 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 f7625d9eec0909..a5905227f49b8d 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 954deb8b98d3e1..bc73387831c9d4 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 1e6c2c4d289aab..35c566548f158b 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]}
>
-
+