diff --git a/.eslintrc.js b/.eslintrc.js
index 22d8e67ea702d..a7b712dabb6c2 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -55,6 +55,201 @@ module.exports = {
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
overrides: [
+ /**
+ * Temporarily disable some react rules for specific plugins, remove in separate PRs
+ */
+ {
+ files: ['packages/kbn-ui-framework/**/*.{js,ts,tsx}'],
+ rules: {
+ 'jsx-a11y/no-onchange': 'off',
+ },
+ },
+ {
+ files: ['src/core/public/application/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react/no-danger': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/console/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/data/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/expressions/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/kbn_vislib_vis_types/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/kibana/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/rules-of-hooks': 'off',
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/tile_map/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/vis_type_markdown/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/vis_type_metric/**/*.{js,ts,tsx}'],
+ rules: {
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/vis_type_table/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/core_plugins/vis_type_vega/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/plugins/es_ui_shared/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/plugins/eui_utils/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/plugins/kibana_react/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/rules-of-hooks': 'off',
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['src/plugins/kibana_utils/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/canvas/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/cross_cluster_replication/**/*.{js,ts,tsx}'],
+ rules: {
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/graph/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/index_management/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/infra/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/lens/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/ml/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/monitoring/**/*.{js,ts,tsx}'],
+ rules: {
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/siem/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/transform/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/uptime/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/exhaustive-deps': 'off',
+ 'react-hooks/rules-of-hooks': 'off',
+ },
+ },
+ {
+ files: ['x-pack/legacy/plugins/watcher/**/*.{js,ts,tsx}'],
+ rules: {
+ 'react-hooks/rules-of-hooks': 'off',
+ 'react-hooks/exhaustive-deps': 'off',
+ },
+ },
+
/**
* Prettier
*/
diff --git a/README.md b/README.md
index 2d33cd218b3da..03ce6a6525873 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@ _Note: The version numbers below are only examples, meant to illustrate the rela
## Questions? Problems? Suggestions?
-- If you've found a bug or want to request a feature, please create a [GitHub Issue](https://github.com/elastic/kibana/issues/new).
-Please check to make sure someone else hasn't already created an issue for the same topic.
+- If you've found a bug or want to request a feature, please create a [GitHub Issue](https://github.com/elastic/kibana/issues/new/choose).
+ Please check to make sure someone else hasn't already created an issue for the same topic.
- Need help using Kibana? Ask away on our [Kibana Discuss Forum](https://discuss.elastic.co/c/kibana) and a fellow community member or
Elastic engineer will be glad to help you out.
diff --git a/docs/infrastructure/getting-started.asciidoc b/docs/infrastructure/getting-started.asciidoc
index 1c5645f5a6e4e..7122ad5c19f75 100644
--- a/docs/infrastructure/getting-started.asciidoc
+++ b/docs/infrastructure/getting-started.asciidoc
@@ -1,6 +1,6 @@
[role="xpack"]
[[xpack-metrics-getting-started]]
-== Getting started with infrastructure monitoring
+== Getting started with metrics
To get started with the Metrics app in Kibana, you need to start collecting metrics data for your infrastructure.
@@ -8,4 +8,4 @@ Kibana provides step-by-step instructions to help you add metrics data.
The {metrics-guide}[Metrics Monitoring Guide] is a good source for more detailed information and instructions.
[role="screenshot"]
-image::infrastructure/images/metrics-add-data.png[Screenshot showing Add metric data to Kibana UI]
+image::infrastructure/images/metrics-add-data.png[Screenshot showing Add metric data to Kibana]
diff --git a/docs/infrastructure/index.asciidoc b/docs/infrastructure/index.asciidoc
index 17361ef6a6080..5e2d0f3e757b0 100644
--- a/docs/infrastructure/index.asciidoc
+++ b/docs/infrastructure/index.asciidoc
@@ -4,13 +4,13 @@
[partintro]
--
-The Metrics app enables you to monitor your infrastructure and identify problems in real time.
+The Metrics app enables you to monitor your infrastructure metrics and identify problems in real time.
You start with a visual summary of your infrastructure where you can view basic metrics for common servers, containers, and services.
Then you can drill down to view more detailed metrics or other information for that component.
You can:
-* View an inventory of your infrastructure by hosts, Kubernetes pod or Docker containers.
+* View your infrastructure metrics by hosts, Kubernetes pods or Docker containers.
You can group and filter the data in various ways to help you identify the items that interest you.
* View current and historic values for metrics such as CPU usage, memory usage, and network traffic for each component.
diff --git a/docs/infrastructure/infra-ui.asciidoc b/docs/infrastructure/infra-ui.asciidoc
index 5c8c50a978d63..120a22541717c 100644
--- a/docs/infrastructure/infra-ui.asciidoc
+++ b/docs/infrastructure/infra-ui.asciidoc
@@ -2,12 +2,12 @@
[[infra-ui]]
== Using the Metrics app
-Use the Metrics app in {kib} to monitor your infrastructure and identify problems in real time.
+Use the Metrics app in {kib} to monitor your infrastructure metrics and identify problems in real time.
You can explore metrics for hosts, containers, and services.
You can also drill down to view more detailed metrics, or seamlessly switch to view the corresponding logs, application traces, and uptime information.
Initially, the *Inventory* tab shows an overview of the hosts in of your infrastructure and the current CPU usage for each host.
-From here, you can drill down into areas of interest.
+From here, you can view other metrics or drill down into areas of interest.
[role="screenshot"]
image::infrastructure/images/infra-sysmon.png[Infrastructure Overview in Kibana]
diff --git a/docs/infrastructure/metrics-explorer.asciidoc b/docs/infrastructure/metrics-explorer.asciidoc
index 2919eaa976d6a..c20718dac1c7a 100644
--- a/docs/infrastructure/metrics-explorer.asciidoc
+++ b/docs/infrastructure/metrics-explorer.asciidoc
@@ -15,7 +15,7 @@ image::infrastructure/images/metrics-explorer-screen.png[Metrics Explorer in Kib
* Metrics Explorer uses data collected from {metricbeat-ref}/metricbeat-overview.html[Metricbeat].
* You need read permissions on `metricbeat-*` or the metric index specified in the Metrics configuration.
-* Metrics Explorer uses the timestamp field set in the Infrastructure configuration.
+* Metrics Explorer uses the timestamp field from the *Settings* tab.
By default that is set to `@timestamp`.
* The interval for the X Axis is set to `auto`.
The bucket size is determined by the time range.
diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc
index 65693f4399e53..916ad42a6d221 100644
--- a/docs/logs/using.asciidoc
+++ b/docs/logs/using.asciidoc
@@ -17,7 +17,7 @@ image::logs/images/logs-console.png[Logs Console in Kibana]
Use the search bar to perform ad hoc searches for specific text.
You can also create structured queries using {kibana-ref}/kuery-query.html[Kibana Query Language].
For example, enter `host.hostname : "host1"` to see only the information for `host1`.
-// ++ this isn't quite the same as the corresponding infrastructure description now.
+// ++ this isn't quite the same as the corresponding metrics description now.
[float]
[[logs-configure-source]]
diff --git a/docs/settings/general-infra-logs-ui-settings.asciidoc b/docs/settings/general-infra-logs-ui-settings.asciidoc
index 31a12c6e2e905..7b32372a1f59a 100644
--- a/docs/settings/general-infra-logs-ui-settings.asciidoc
+++ b/docs/settings/general-infra-logs-ui-settings.asciidoc
@@ -1,4 +1,4 @@
-`xpack.infra.enabled`:: Set to `false` to disable the Logs and Metrics UI plugin {kib}. Defaults to `true`.
+`xpack.infra.enabled`:: Set to `false` to disable the Logs and Metrics app plugin {kib}. Defaults to `true`.
`xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`.
@@ -6,7 +6,7 @@
`xpack.infra.sources.default.fields.timestamp`:: Timestamp used to sort log entries. Defaults to `@timestamp`.
-`xpack.infra.sources.default.fields.message`:: Fields used to display messages in the Logs UI. Defaults to `['message', '@message']`.
+`xpack.infra.sources.default.fields.message`:: Fields used to display messages in the Logs app. Defaults to `['message', '@message']`.
`xpack.infra.sources.default.fields.tiebreaker`:: Field used to break ties between two entries with the same timestamp. Defaults to `_doc`.
diff --git a/docs/settings/infrastructure-ui-settings.asciidoc b/docs/settings/infrastructure-ui-settings.asciidoc
index 1617f2847c50e..ed69c27feab72 100644
--- a/docs/settings/infrastructure-ui-settings.asciidoc
+++ b/docs/settings/infrastructure-ui-settings.asciidoc
@@ -1,14 +1,14 @@
[role="xpack"]
[[infrastructure-ui-settings-kb]]
-=== Metrics UI settings in Kibana
+=== Metrics settings in Kibana
++++
-Metrics UI settings
+Metrics settings
++++
-You do not need to configure any settings to use the Metrics UI. It is enabled by default.
+You do not need to configure any settings to use the Metrics app in {kib}. It is enabled by default.
[float]
[[general-infra-ui-settings-kb]]
-==== General Metrics UI settings
+==== General Metrics settings
include::general-infra-logs-ui-settings.asciidoc[]
\ No newline at end of file
diff --git a/docs/settings/logs-ui-settings.asciidoc b/docs/settings/logs-ui-settings.asciidoc
index a2c9e12e22fb0..5b6dd902091ae 100644
--- a/docs/settings/logs-ui-settings.asciidoc
+++ b/docs/settings/logs-ui-settings.asciidoc
@@ -1,14 +1,14 @@
[role="xpack"]
[[logs-ui-settings-kb]]
-=== Logs UI settings in Kibana
+=== Logs app settings in Kibana
++++
-Logs UI settings
+Logs settings
++++
-You do not need to configure any settings to use the Logs UI. It is enabled by default.
+You do not need to configure any settings to use the Logs app in {kib}. It is enabled by default.
[float]
[[general-logs-ui-settings-kb]]
-==== General Logs UI settings
+==== General Logs settings
include::general-infra-logs-ui-settings.asciidoc[]
diff --git a/docs/uptime-guide/overview.asciidoc b/docs/uptime-guide/overview.asciidoc
index 09867e7b05f2a..c6bd71b1f5574 100644
--- a/docs/uptime-guide/overview.asciidoc
+++ b/docs/uptime-guide/overview.asciidoc
@@ -33,8 +33,8 @@ The {kibana-ref}/xpack-uptime.html[Elasticsearch Uptime app] in Kibana provides
[float]
=== Example deployments
-// ++ I like the Infra/logging diagram which shows Infrastructure and Logging apps as separate components inside Kibana
-// ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Infra/logging/APM.
+// ++ I like the Infra/logging diagram which shows Metrics and Logging apps as separate components inside Kibana
+// ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Metrics/Logging/APM.
// ++ Need more whitespace around components.
image::images/uptime-simple-deployment.png[Uptime simple deployment]
diff --git a/docs/uptime/overview.asciidoc b/docs/uptime/overview.asciidoc
index ea7047ae940a8..098ce12a56991 100644
--- a/docs/uptime/overview.asciidoc
+++ b/docs/uptime/overview.asciidoc
@@ -34,7 +34,7 @@ in an `up` or `down` state are displayed, based on the last check reported by He
for each monitor.
Next to the counts, there is a histogram displaying the change over time throughout the
-selected date range.
+selected date range.
[float]
=== Monitor list
@@ -56,7 +56,7 @@ ID and URL, its IP address, and a dedicated sparkline showing its check status o
image::uptime/images/observability_integrations.png[Observability integrations]
The Monitor list also contains a menu of possible integrations. If Uptime detects Kubernetes or
-Docker related host information, it will provide links to open the Metrics UI or Logs UI pre-filtered
+Docker related host information, it will provide links to open the Metrics app or Logs app pre-filtered
for this host. Additionally, this feature supplies links to simply filter the other views on the host's
IP address, to help you quickly determine if these other solutions contain data relevant to your current
interest.
diff --git a/package.json b/package.json
index 01067ab02cb55..4a3b3bfdc2985 100644
--- a/package.json
+++ b/package.json
@@ -109,7 +109,7 @@
"@elastic/charts": "^13.5.9",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "1.0.5",
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.3",
diff --git a/packages/eslint-config-kibana/.eslintrc.js b/packages/eslint-config-kibana/.eslintrc.js
index 4412ce81368a1..36f0b95c8e69b 100644
--- a/packages/eslint-config-kibana/.eslintrc.js
+++ b/packages/eslint-config-kibana/.eslintrc.js
@@ -3,6 +3,7 @@ module.exports = {
'./javascript.js',
'./typescript.js',
'./jest.js',
+ './react.js',
],
plugins: ['@kbn/eslint-plugin-eslint'],
diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js
index 8462da5cac801..0c79669c15e73 100644
--- a/packages/eslint-config-kibana/javascript.js
+++ b/packages/eslint-config-kibana/javascript.js
@@ -1,6 +1,3 @@
-const semver = require('semver');
-const { readdirSync } = require('fs');
-const PKG = require('../../package.json');
const RESTRICTED_GLOBALS = require('./restricted_globals');
const RESTRICTED_MODULES = { paths: ['gulp-util'] };
@@ -16,18 +13,12 @@ module.exports = {
plugins: [
'mocha',
'babel',
- 'react',
- 'react-hooks',
'import',
'no-unsanitized',
'prefer-object-spread',
- 'jsx-a11y',
],
settings: {
- react: {
- version: semver.valid(semver.coerce(PKG.dependencies.react)),
- },
'import/resolver': {
'@kbn/eslint-import-resolver-kibana': {
forceNode: true,
@@ -120,64 +111,6 @@ module.exports = {
'object-curly-spacing': 'off', // overridden with babel/object-curly-spacing
'babel/object-curly-spacing': [ 'error', 'always' ],
- 'jsx-quotes': ['error', 'prefer-double'],
- 'react/jsx-uses-react': 'error',
- 'react/react-in-jsx-scope': 'error',
- 'react/jsx-uses-vars': 'error',
- 'react/jsx-no-undef': 'error',
- 'react/jsx-pascal-case': 'error',
- 'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
- 'react/jsx-closing-tag-location': 'error',
- 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }],
- 'react/jsx-indent-props': ['error', 2],
- 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }],
- 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }],
- 'react/no-danger': 'error',
- 'react/self-closing-comp': 'error',
- 'react/jsx-wrap-multilines': ['error', {
- declaration: true,
- assignment: true,
- return: true,
- arrow: true,
- }],
- 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
- 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
- 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
- 'jsx-a11y/accessible-emoji': 'error',
- 'jsx-a11y/alt-text': 'error',
- 'jsx-a11y/anchor-has-content': 'error',
- 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
- 'jsx-a11y/aria-props': 'error',
- 'jsx-a11y/aria-proptypes': 'error',
- 'jsx-a11y/aria-role': 'error',
- 'jsx-a11y/aria-unsupported-elements': 'error',
- 'jsx-a11y/heading-has-content': 'error',
- 'jsx-a11y/html-has-lang': 'error',
- 'jsx-a11y/iframe-has-title': 'error',
- 'jsx-a11y/interactive-supports-focus': 'error',
- 'jsx-a11y/media-has-caption': 'error',
- 'jsx-a11y/mouse-events-have-key-events': 'error',
- 'jsx-a11y/no-access-key': 'error',
- 'jsx-a11y/no-distracting-elements': 'error',
- 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
- 'jsx-a11y/no-noninteractive-element-interactions': 'error',
- 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
- 'jsx-a11y/no-redundant-roles': 'error',
- 'jsx-a11y/role-has-required-aria-props': 'error',
- 'jsx-a11y/role-supports-aria-props': 'error',
- 'jsx-a11y/scope': 'error',
- 'jsx-a11y/tabindex-no-positive': 'error',
- 'jsx-a11y/label-has-associated-control': 'error',
- 'react/jsx-equals-spacing': ['error', 'never'],
- 'react/jsx-indent': ['error', 2],
- 'react/no-will-update-set-state': 'error',
- 'react/no-is-mounted': 'error',
- 'react/no-multi-comp': ['error', { ignoreStateless: true }],
- 'react/no-unknown-property': 'error',
- 'react/prefer-es6-class': ['error', 'always'],
- 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }],
- 'react/no-unescaped-entities': 'error',
-
'mocha/handle-done-callback': 'error',
'mocha/no-exclusive-tests': 'error',
diff --git a/packages/eslint-config-kibana/react.js b/packages/eslint-config-kibana/react.js
new file mode 100644
index 0000000000000..163bd6ca73a07
--- /dev/null
+++ b/packages/eslint-config-kibana/react.js
@@ -0,0 +1,84 @@
+const semver = require('semver')
+const PKG = require('../../package.json')
+
+module.exports = {
+ plugins: [
+ 'react',
+ 'react-hooks',
+ 'jsx-a11y',
+ ],
+
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true
+ }
+ },
+
+ settings: {
+ react: {
+ version: semver.valid(semver.coerce(PKG.dependencies.react)),
+ },
+ },
+
+ rules: {
+ 'jsx-quotes': ['error', 'prefer-double'],
+ 'react/jsx-uses-react': 'error',
+ 'react/react-in-jsx-scope': 'error',
+ 'react/jsx-uses-vars': 'error',
+ 'react/jsx-no-undef': 'error',
+ 'react/jsx-pascal-case': 'error',
+ 'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
+ 'react/jsx-closing-tag-location': 'error',
+ 'react/jsx-curly-spacing': ['error', 'never', { allowMultiline: true }],
+ 'react/jsx-indent-props': ['error', 2],
+ 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }],
+ 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }],
+ 'react/no-danger': 'error',
+ 'react/self-closing-comp': 'error',
+ 'react/jsx-wrap-multilines': ['error', {
+ declaration: true,
+ assignment: true,
+ return: true,
+ arrow: true,
+ }],
+ 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
+ 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
+ 'react-hooks/exhaustive-deps': 'error', // Checks effect dependencies
+ 'jsx-a11y/accessible-emoji': 'error',
+ 'jsx-a11y/alt-text': 'error',
+ 'jsx-a11y/anchor-has-content': 'error',
+ 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
+ 'jsx-a11y/aria-props': 'error',
+ 'jsx-a11y/aria-proptypes': 'error',
+ 'jsx-a11y/aria-role': 'error',
+ 'jsx-a11y/aria-unsupported-elements': 'error',
+ 'jsx-a11y/click-events-have-key-events': 'error',
+ 'jsx-a11y/heading-has-content': 'error',
+ 'jsx-a11y/html-has-lang': 'error',
+ 'jsx-a11y/iframe-has-title': 'error',
+ 'jsx-a11y/interactive-supports-focus': 'error',
+ 'jsx-a11y/label-has-associated-control': 'error',
+ 'jsx-a11y/media-has-caption': 'error',
+ 'jsx-a11y/mouse-events-have-key-events': 'error',
+ 'jsx-a11y/no-access-key': 'error',
+ 'jsx-a11y/no-distracting-elements': 'error',
+ 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
+ 'jsx-a11y/no-noninteractive-element-interactions': 'error',
+ 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
+ 'jsx-a11y/no-onchange': 'error',
+ 'jsx-a11y/no-redundant-roles': 'error',
+ 'jsx-a11y/role-has-required-aria-props': 'error',
+ 'jsx-a11y/role-supports-aria-props': 'error',
+ 'jsx-a11y/scope': 'error',
+ 'jsx-a11y/tabindex-no-positive': 'error',
+ 'react/jsx-equals-spacing': ['error', 'never'],
+ 'react/jsx-indent': ['error', 2],
+ 'react/no-will-update-set-state': 'error',
+ 'react/no-is-mounted': 'error',
+ 'react/no-multi-comp': ['error', { ignoreStateless: true }],
+ 'react/no-unknown-property': 'error',
+ 'react/prefer-es6-class': ['error', 'always'],
+ 'react/prefer-stateless-function': ['error', { ignorePureComponents: true }],
+ 'react/no-unescaped-entities': 'error',
+ },
+}
diff --git a/packages/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js
index 7653c9bb0c332..757616f36180b 100644
--- a/packages/eslint-config-kibana/typescript.js
+++ b/packages/eslint-config-kibana/typescript.js
@@ -18,7 +18,6 @@ module.exports = {
'@typescript-eslint',
'ban',
'import',
- 'jsx-a11y',
'prefer-object-spread',
],
@@ -171,33 +170,6 @@ module.exports = {
{'name': ['test', 'only'], 'message': 'No exclusive tests.'},
],
- 'jsx-a11y/accessible-emoji': 'error',
- 'jsx-a11y/alt-text': 'error',
- 'jsx-a11y/anchor-has-content': 'error',
- 'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
- 'jsx-a11y/aria-props': 'error',
- 'jsx-a11y/aria-proptypes': 'error',
- 'jsx-a11y/aria-role': 'error',
- 'jsx-a11y/aria-unsupported-elements': 'error',
- 'jsx-a11y/click-events-have-key-events': 'error',
- 'jsx-a11y/heading-has-content': 'error',
- 'jsx-a11y/html-has-lang': 'error',
- 'jsx-a11y/iframe-has-title': 'error',
- 'jsx-a11y/interactive-supports-focus': 'error',
- 'jsx-a11y/media-has-caption': 'error',
- 'jsx-a11y/mouse-events-have-key-events': 'error',
- 'jsx-a11y/no-access-key': 'error',
- 'jsx-a11y/no-distracting-elements': 'error',
- 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
- 'jsx-a11y/no-noninteractive-element-interactions': 'error',
- 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error',
- 'jsx-a11y/no-onchange': 'error',
- 'jsx-a11y/no-redundant-roles': 'error',
- 'jsx-a11y/role-has-required-aria-props': 'error',
- 'jsx-a11y/role-supports-aria-props': 'error',
- 'jsx-a11y/scope': 'error',
- 'jsx-a11y/tabindex-no-positive': 'error',
- 'jsx-a11y/label-has-associated-control': 'error',
'import/no-default-export': 'error',
},
eslintConfigPrettierTypescriptEslintRules
diff --git a/src/core/public/rendering/rendering_service.test.tsx b/src/core/public/rendering/rendering_service.test.tsx
index 317ab5cc8b855..ed835574a32f9 100644
--- a/src/core/public/rendering/rendering_service.test.tsx
+++ b/src/core/public/rendering/rendering_service.test.tsx
@@ -34,7 +34,7 @@ describe('RenderingService#start', () => {
const chrome = chromeServiceMock.createStartContract();
chrome.getHeaderComponent.mockReturnValue(
Hello chrome!
);
const overlays = overlayServiceMock.createStartContract();
- overlays.banners.getComponent.mockReturnValue(I'm a banner!
);
+ overlays.banners.getComponent.mockReturnValue(I'm a banner!
);
const injectedMetadata = injectedMetadataServiceMock.createStartContract();
injectedMetadata.getLegacyMode.mockReturnValue(legacyMode);
diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
index c436e1d45c6d8..a39175077d9e0 100644
--- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
+++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
@@ -25,8 +25,8 @@ import {
createAction,
IncompatibleActionError,
} from '../../../../../../plugins/ui_actions/public';
-import { changeTimeFilter, extractTimeFilter, FilterManager } from '../filter_manager';
-import { TimefilterContract } from '../../timefilter';
+import { FilterManager } from '../../../../../../plugins/data/public';
+import { TimefilterContract, changeTimeFilter, extractTimeFilter } from '../../timefilter';
import { applyFiltersPopover } from '../apply_filters/apply_filters_popover';
import { IndexPatternsStart } from '../../index_patterns';
export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION';
diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx
index 5f7fbc1996433..8fc6b33f3f68a 100644
--- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx
+++ b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx
@@ -33,7 +33,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
import { IndexPattern } from '../../index_patterns';
import { getFilterDisplayText } from '../filter_bar/filter_editor/lib/get_filter_display_text';
-import { mapAndFlattenFilters } from '../filter_manager/lib/map_and_flatten_filters';
+import { mapAndFlattenFilters } from '../../../../../../plugins/data/public';
import { getDisplayValueFromFilter } from '../filter_bar/filter_editor/lib/get_display_value';
interface Props {
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts
index d1cad9a812399..aae9c0754a8d8 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts
+++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.test.ts
@@ -24,7 +24,7 @@ import { FilterStateManager } from './filter_state_manager';
import { StubState } from './test_helpers/stub_state';
import { getFilter } from './test_helpers/get_stub_filter';
-import { FilterManager } from './filter_manager';
+import { FilterManager } from '../../../../../../plugins/data/public';
import { coreMock } from '../../../../../../core/public/mocks';
const setupMock = coreMock.createSetup();
@@ -101,25 +101,29 @@ describe('filter_state_manager', () => {
});
test('should update filter manager global filters', done => {
- const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
- globalStateStub.filters.push(f1);
-
- setTimeout(() => {
+ const updateSubscription = filterManager.getUpdates$().subscribe(() => {
expect(filterManager.getGlobalFilters()).toHaveLength(1);
+ if (updateSubscription) {
+ updateSubscription.unsubscribe();
+ }
done();
- }, 100);
+ });
+
+ const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'age', 34);
+ globalStateStub.filters.push(f1);
});
- test('should update filter manager app filters', done => {
- expect(filterManager.getAppFilters()).toHaveLength(0);
+ test('should update filter manager app filter', done => {
+ const updateSubscription = filterManager.getUpdates$().subscribe(() => {
+ expect(filterManager.getAppFilters()).toHaveLength(1);
+ if (updateSubscription) {
+ updateSubscription.unsubscribe();
+ }
+ done();
+ });
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
appStateStub.filters.push(f1);
-
- setTimeout(() => {
- expect(filterManager.getAppFilters()).toHaveLength(1);
- done();
- }, 100);
});
test('should update URL when filter manager filters are set', () => {
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts
index 06f91e35db96e..af8722c37c703 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts
+++ b/src/legacy/core_plugins/data/public/filter/filter_manager/filter_state_manager.ts
@@ -21,7 +21,7 @@ import { FilterStateStore } from '@kbn/es-query';
import _ from 'lodash';
import { State } from 'ui/state_management/state';
-import { FilterManager } from './filter_manager';
+import { FilterManager } from '../../../../../../plugins/data/public';
type GetAppStateFunc = () => State | undefined | null;
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts
index ac533eaaf89ea..ebb622783c3d1 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts
+++ b/src/legacy/core_plugins/data/public/filter/filter_manager/index.ts
@@ -17,10 +17,4 @@
* under the License.
*/
-export { FilterManager } from './filter_manager';
export { FilterStateManager } from './filter_state_manager';
-
-export { uniqFilters } from './lib/uniq_filters';
-export { extractTimeFilter } from './lib/extract_time_filter';
-export { changeTimeFilter } from './lib/change_time_filter';
-export { onlyDisabledFiltersChanged } from './lib/only_disabled';
diff --git a/src/legacy/core_plugins/data/public/filter/index.tsx b/src/legacy/core_plugins/data/public/filter/index.tsx
index cda7350ecadef..005c4904a4f39 100644
--- a/src/legacy/core_plugins/data/public/filter/index.tsx
+++ b/src/legacy/core_plugins/data/public/filter/index.tsx
@@ -17,8 +17,6 @@
* under the License.
*/
-export * from './filter_service';
-
export { FilterBar } from './filter_bar';
export { ApplyFiltersPopover } from './apply_filters';
diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts
index cb3869ff57711..502ca206e8e12 100644
--- a/src/legacy/core_plugins/data/public/index.ts
+++ b/src/legacy/core_plugins/data/public/index.ts
@@ -43,14 +43,7 @@ export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './s
/** @public static code */
export * from '../common';
-export {
- FilterManager,
- FilterStateManager,
- uniqFilters,
- extractTimeFilter,
- changeTimeFilter,
- onlyDisabledFiltersChanged,
-} from './filter/filter_manager';
+export { FilterStateManager } from './filter/filter_manager';
export {
CONTAINS_SPACES,
getFromSavedObject,
@@ -69,4 +62,11 @@ export {
mockIndexPattern,
} from './index_patterns';
-export { TimeHistoryContract, TimefilterContract, getTime, InputTimeRange } from './timefilter';
+export {
+ TimeHistoryContract,
+ TimefilterContract,
+ getTime,
+ InputTimeRange,
+ extractTimeFilter,
+ changeTimeFilter,
+} from './timefilter';
diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts
index e151726a6d702..b1d838aed992d 100644
--- a/src/legacy/core_plugins/data/public/legacy.ts
+++ b/src/legacy/core_plugins/data/public/legacy.ts
@@ -35,18 +35,13 @@
*/
import { npSetup, npStart } from 'ui/new_platform';
-import { LegacyDependenciesPlugin } from './shim/legacy_dependencies_plugin';
import { plugin } from '.';
const dataPlugin = plugin();
-const legacyPlugin = new LegacyDependenciesPlugin();
-export const setup = dataPlugin.setup(npSetup.core, {
- __LEGACY: legacyPlugin.setup(),
-});
+export const setup = dataPlugin.setup(npSetup.core);
export const start = dataPlugin.start(npStart.core, {
data: npStart.plugins.data,
uiActions: npSetup.plugins.uiActions,
- __LEGACY: legacyPlugin.start(),
});
diff --git a/src/legacy/core_plugins/data/public/mocks.ts b/src/legacy/core_plugins/data/public/mocks.ts
index 2a82927bb3ebf..4a7fe8efa4068 100644
--- a/src/legacy/core_plugins/data/public/mocks.ts
+++ b/src/legacy/core_plugins/data/public/mocks.ts
@@ -17,14 +17,12 @@
* under the License.
*/
-import { filterServiceMock } from './filter/filter_service.mock';
import { indexPatternsServiceMock } from './index_patterns/index_patterns_service.mock';
import { queryServiceMock } from './query/query_service.mock';
import { timefilterServiceMock } from './timefilter/timefilter_service.mock';
function createDataSetupMock() {
return {
- filter: filterServiceMock.createSetupContract(),
indexPatterns: indexPatternsServiceMock.createSetupContract(),
query: queryServiceMock.createSetupContract(),
timefilter: timefilterServiceMock.createSetupContract(),
diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts
index 2b634e54e5cbf..597ad86d39d85 100644
--- a/src/legacy/core_plugins/data/public/plugin.ts
+++ b/src/legacy/core_plugins/data/public/plugin.ts
@@ -20,13 +20,9 @@
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search';
import { QueryService, QuerySetup } from './query';
-import { FilterService, FilterSetup, FilterStart } from './filter';
import { TimefilterService, TimefilterSetup } from './timefilter';
import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns';
-import {
- LegacyDependenciesPluginSetup,
- LegacyDependenciesPluginStart,
-} from './shim/legacy_dependencies_plugin';
+import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
import { DataPublicPluginStart } from '../../../../plugins/data/public';
import { initLegacyModule } from './shim/legacy_module';
import { IUiActionsSetup } from '../../../../plugins/ui_actions/public';
@@ -36,19 +32,9 @@ import {
} from './filter/action/apply_filter_action';
import { APPLY_FILTER_TRIGGER } from '../../../../plugins/embeddable/public';
-/**
- * Interface for any dependencies on other plugins' `setup` contracts.
- *
- * @internal
- */
-export interface DataPluginSetupDependencies {
- __LEGACY: LegacyDependenciesPluginSetup;
-}
-
export interface DataPluginStartDependencies {
data: DataPublicPluginStart;
uiActions: IUiActionsSetup;
- __LEGACY: LegacyDependenciesPluginStart;
}
/**
@@ -60,7 +46,6 @@ export interface DataSetup {
query: QuerySetup;
timefilter: TimefilterSetup;
indexPatterns: IndexPatternsSetup;
- filter: FilterSetup;
}
/**
@@ -72,7 +57,6 @@ export interface DataStart {
query: QuerySetup;
timefilter: TimefilterSetup;
indexPatterns: IndexPatternsStart;
- filter: FilterStart;
search: SearchStart;
ui: {
SearchBar: React.ComponentType;
@@ -90,42 +74,35 @@ export interface DataStart {
* in the setup/start interfaces. The remaining items exported here are either types,
* or static code.
*/
-export class DataPlugin
- implements
- Plugin {
- // Exposed services, sorted alphabetically
- private readonly filter: FilterService = new FilterService();
+
+export class DataPlugin implements Plugin {
private readonly indexPatterns: IndexPatternsService = new IndexPatternsService();
private readonly query: QueryService = new QueryService();
private readonly search: SearchService = new SearchService();
private readonly timefilter: TimefilterService = new TimefilterService();
private setupApi!: DataSetup;
+ private storage!: IStorageWrapper;
- public setup(core: CoreSetup, { __LEGACY }: DataPluginSetupDependencies): DataSetup {
+ public setup(core: CoreSetup): DataSetup {
const { uiSettings } = core;
+ this.storage = new Storage(window.localStorage);
+
const timefilterService = this.timefilter.setup({
uiSettings,
- store: __LEGACY.storage,
- });
- const filterService = this.filter.setup({
- uiSettings,
+ storage: this.storage,
});
this.setupApi = {
indexPatterns: this.indexPatterns.setup(),
query: this.query.setup(),
timefilter: timefilterService,
- filter: filterService,
};
return this.setupApi;
}
- public start(
- core: CoreStart,
- { __LEGACY, data, uiActions }: DataPluginStartDependencies
- ): DataStart {
+ public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart {
const { uiSettings, http, notifications, savedObjects } = core;
const indexPatternsService = this.indexPatterns.start({
@@ -140,14 +117,13 @@ export class DataPlugin
const SearchBar = createSearchBar({
core,
data,
- store: __LEGACY.storage,
+ storage: this.storage,
timefilter: this.setupApi.timefilter,
- filterManager: this.setupApi.filter.filterManager,
});
uiActions.registerAction(
createFilterAction(
- this.setupApi.filter.filterManager,
+ data.query.filterManager,
this.setupApi.timefilter.timefilter,
indexPatternsService
)
@@ -167,7 +143,6 @@ export class DataPlugin
public stop() {
this.indexPatterns.stop();
- this.filter.stop();
this.query.stop();
this.search.stop();
this.timefilter.stop();
diff --git a/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts b/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts
index e0e6a0d0c44e4..553b0bf5ef7e0 100644
--- a/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts
+++ b/src/legacy/core_plugins/data/public/query/persisted_log/persisted_log.ts
@@ -20,7 +20,7 @@
import _ from 'lodash';
import * as Rx from 'rxjs';
import { map } from 'rxjs/operators';
-import { Storage } from '../../types';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
const defaultIsDuplicate = (oldItem: any, newItem: any) => {
return _.isEqual(oldItem, newItem);
@@ -37,12 +37,12 @@ export class PersistedLog {
public maxLength?: number;
public filterDuplicates?: boolean;
public isDuplicate: (oldItem: T, newItem: T) => boolean;
- public storage: Storage;
+ public storage: IStorageWrapper;
public items: T[];
private update$ = new Rx.BehaviorSubject(undefined);
- constructor(name: string, options: PersistedLogOptions = {}, storage: Storage) {
+ constructor(name: string, options: PersistedLogOptions = {}, storage: IStorageWrapper) {
this.name = name;
this.maxLength =
typeof options.maxLength === 'string'
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap
index 106e9339a231c..5dc8702411783 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap
@@ -304,12 +304,12 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
@@ -867,12 +867,12 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
@@ -1418,12 +1418,12 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
@@ -1978,12 +1978,12 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
@@ -2529,12 +2529,12 @@ exports[`QueryBarInput Should render the given query 1`] = `
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
@@ -3089,12 +3089,12 @@ exports[`QueryBarInput Should render the given query 1`] = `
"update": [MockFunction],
},
},
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"get": [MockFunction],
"remove": [MockFunction],
"set": [MockFunction],
- "store": Object {
+ "storage": Object {
"clear": [MockFunction],
"getItem": [MockFunction],
"key": [MockFunction],
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx
index f1249da997dfe..3edb689ca2bfe 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx
@@ -58,7 +58,7 @@ const createMockWebStorage = () => ({
});
const createMockStorage = () => ({
- store: createMockWebStorage(),
+ storage: createMockWebStorage(),
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
@@ -80,7 +80,7 @@ const mockIndexPattern = {
],
} as IndexPattern;
-function wrapQueryBarInputInContext(testProps: any, store?: any) {
+function wrapQueryBarInputInContext(testProps: any, storage?: any) {
const defaultOptions = {
screenTitle: 'Another Screen',
intl: null as any,
@@ -89,7 +89,7 @@ function wrapQueryBarInputInContext(testProps: any, store?: any) {
const services = {
...startMock,
appName: testProps.appName || 'test',
- store: store || createMockStorage(),
+ storage: storage || createMockStorage(),
};
return (
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
index a57018b118185..d73e741b6d5cb 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx
@@ -365,7 +365,7 @@ export class QueryBarInputUI extends Component {
body: JSON.stringify({ opt_in: language === 'kuery' }),
});
- this.services.store.set('kibana.userQueryLanguage', language);
+ this.services.storage.set('kibana.userQueryLanguage', language);
const newQuery = { query: '', language };
this.onChange(newQuery);
@@ -387,10 +387,10 @@ export class QueryBarInputUI extends Component {
};
private initPersistedLog = () => {
- const { uiSettings, store, appName } = this.services;
+ const { uiSettings, storage, appName } = this.services;
this.persistedLog = this.props.persistedLog
? this.props.persistedLog
- : getQueryLog(uiSettings, store, appName, this.props.query.language);
+ : getQueryLog(uiSettings, storage, appName, this.props.query.language);
};
public onMouseEnterSuggestion = (index: number) => {
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx
index 7ab191062e32d..7281eea956fbf 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx
@@ -79,7 +79,7 @@ const createMockWebStorage = () => ({
});
const createMockStorage = () => ({
- store: createMockWebStorage(),
+ storage: createMockWebStorage(),
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
@@ -112,7 +112,7 @@ function wrapQueryBarTopRowInContext(testProps: any) {
const services = {
...startMock,
appName: 'discover',
- store: createMockStorage(),
+ storage: createMockStorage(),
};
return (
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx
index 5fd3886e41737..2b76a3e0a735c 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx
+++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx
@@ -73,14 +73,15 @@ function QueryBarTopRowUI(props: Props) {
const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false);
const kibana = useKibana();
- const { uiSettings, notifications, store, appName, docLinks } = kibana.services;
+ const { uiSettings, notifications, storage, appName, docLinks } = kibana.services;
const kueryQuerySyntaxLink: string = docLinks!.links.query.kueryQuerySyntax;
+ const queryLanguage = props.query && props.query.language;
const persistedLog: PersistedLog | undefined = React.useMemo(
() =>
- props.query ? getQueryLog(uiSettings!, store, appName, props.query.language) : undefined,
- [props.query]
+ queryLanguage ? getQueryLog(uiSettings!, store, appName, queryLanguage) : undefined,
+ [queryLanguage]
);
function onClickSubmitButton(event: React.MouseEvent) {
@@ -209,7 +210,7 @@ function QueryBarTopRowUI(props: Props) {
}
function shouldRenderQueryInput(): boolean {
- return Boolean(props.showQueryInput && props.indexPatterns && props.query && store);
+ return Boolean(props.showQueryInput && props.indexPatterns && props.query && storage);
}
function renderUpdateButton() {
@@ -291,7 +292,7 @@ function QueryBarTopRowUI(props: Props) {
if (
language === 'kuery' &&
typeof query === 'string' &&
- (!store || !store.get('kibana.luceneSyntaxWarningOptOut')) &&
+ (!storage || !storage.get('kibana.luceneSyntaxWarningOptOut')) &&
doesKueryExpressionHaveLuceneSyntaxError(query)
) {
const toast = notifications!.toasts.addWarning({
@@ -335,8 +336,8 @@ function QueryBarTopRowUI(props: Props) {
}
function onLuceneSyntaxWarningOptOut(toast: Toast) {
- if (!store) return;
- store.set('kibana.luceneSyntaxWarningOptOut', true);
+ if (!storage) return;
+ storage.set('kibana.luceneSyntaxWarningOptOut', true);
notifications!.toasts.remove(toast);
}
diff --git a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts b/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts
index 8b26e14c6ed7b..f78eb5e07f189 100644
--- a/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts
+++ b/src/legacy/core_plugins/data/public/query/query_bar/lib/get_query_log.ts
@@ -18,12 +18,12 @@
*/
import { UiSettingsClientContract } from 'src/core/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { PersistedLog } from '../../persisted_log';
-import { Storage } from '../../../types';
export function getQueryLog(
uiSettings: UiSettingsClientContract,
- store: Storage,
+ storage: IStorageWrapper,
appName: string,
language: string
) {
@@ -33,6 +33,6 @@ export function getQueryLog(
maxLength: uiSettings.get('history:limit'),
filterDuplicates: true,
},
- store
+ storage
);
}
diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx
index d801f8a69e2d6..c186edf9a3ac9 100644
--- a/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx
+++ b/src/legacy/core_plugins/data/public/search/search_bar/components/create_search_bar.tsx
@@ -22,27 +22,26 @@ import { Subscription } from 'rxjs';
import { Filter } from '@kbn/es-query';
import { CoreStart } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
-import { Storage } from '../../../types';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { TimefilterSetup } from '../../../timefilter';
-import { FilterManager, SearchBar } from '../../../';
+import { SearchBar } from '../../../';
import { SearchBarOwnProps } from '.';
interface StatefulSearchBarDeps {
core: CoreStart;
data: DataPublicPluginStart;
- store: Storage;
+ storage: IStorageWrapper;
timefilter: TimefilterSetup;
- filterManager: FilterManager;
}
export type StatetfulSearchBarProps = SearchBarOwnProps & {
appName: string;
};
-const defaultFiltersUpdated = (filterManager: FilterManager) => {
+const defaultFiltersUpdated = (data: DataPublicPluginStart) => {
return (filters: Filter[]) => {
- filterManager.setFilters(filters);
+ data.query.filterManager.setFilters(filters);
};
};
@@ -55,16 +54,11 @@ const defaultOnRefreshChange = (timefilter: TimefilterSetup) => {
};
};
-export function createSearchBar({
- core,
- store,
- timefilter,
- filterManager,
- data,
-}: StatefulSearchBarDeps) {
+export function createSearchBar({ core, storage, timefilter, data }: StatefulSearchBarDeps) {
// App name should come from the core application service.
// Until it's available, we'll ask the user to provide it for the pre-wired component.
return (props: StatetfulSearchBarProps) => {
+ const { filterManager } = data.query;
const tfRefreshInterval = timefilter.timefilter.getRefreshInterval();
const fmFilters = filterManager.getFilters();
const [refreshInterval, setRefreshInterval] = useState(tfRefreshInterval.value);
@@ -113,7 +107,7 @@ export function createSearchBar({
services={{
appName: props.appName,
data,
- store,
+ storage,
...core,
}}
>
@@ -124,7 +118,7 @@ export function createSearchBar({
refreshInterval={refreshInterval}
isRefreshPaused={refreshPaused}
filters={filters}
- onFiltersUpdated={defaultFiltersUpdated(filterManager)}
+ onFiltersUpdated={defaultFiltersUpdated(data)}
onRefreshChange={defaultOnRefreshChange(timefilter)}
{...props}
/>
diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx
index 929b5889db0d1..9b77ec369c55b 100644
--- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx
+++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx
@@ -56,7 +56,7 @@ const createMockWebStorage = () => ({
});
const createMockStorage = () => ({
- store: createMockWebStorage(),
+ storage: createMockWebStorage(),
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
@@ -95,7 +95,7 @@ function wrapSearchBarInContext(testProps: any) {
savedObjects: startMock.savedObjects,
notifications: startMock.notifications,
http: startMock.http,
- store: createMockStorage(),
+ storage: createMockStorage(),
};
return (
diff --git a/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts
deleted file mode 100644
index 83d276fe9ba74..0000000000000
--- a/src/legacy/core_plugins/data/public/shim/legacy_dependencies_plugin.ts
+++ /dev/null
@@ -1,44 +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 { Storage } from 'ui/storage';
-import { Plugin } from '../../../../../../src/core/public';
-
-/** @internal */
-export interface LegacyDependenciesPluginSetup {
- storage: Storage;
-}
-
-export interface LegacyDependenciesPluginStart {
- storage: Storage;
-}
-
-export class LegacyDependenciesPlugin implements Plugin {
- public setup() {
- return {
- storage: new Storage(window.localStorage),
- } as LegacyDependenciesPluginSetup;
- }
-
- public start() {
- return {
- storage: new Storage(window.localStorage),
- } as LegacyDependenciesPluginStart;
- }
-}
diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts
index 54f513d07215d..b0ed3d43a4c8c 100644
--- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts
+++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts
@@ -25,9 +25,6 @@ import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
import { npStart } from 'ui/new_platform';
import { FilterBar, ApplyFiltersPopover } from '../filter';
-
-// @ts-ignore
-import { mapAndFlattenFilters } from '../filter/filter_manager/lib/map_and_flatten_filters';
import { IndexPatterns } from '../index_patterns/index_patterns';
/** @internal */
diff --git a/src/legacy/core_plugins/data/public/timefilter/index.ts b/src/legacy/core_plugins/data/public/timefilter/index.ts
index 17564801cf148..a6260e782c12f 100644
--- a/src/legacy/core_plugins/data/public/timefilter/index.ts
+++ b/src/legacy/core_plugins/data/public/timefilter/index.ts
@@ -23,3 +23,5 @@ export * from './types';
export { Timefilter, TimefilterContract } from './timefilter';
export { TimeHistory, TimeHistoryContract } from './time_history';
export { getTime } from './get_time';
+export { changeTimeFilter } from './lib/change_time_filter';
+export { extractTimeFilter } from './lib/extract_time_filter';
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts b/src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.test.ts
similarity index 96%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts
rename to src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.test.ts
index 2e397ff931bb6..5e16120f3b3c2 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.test.ts
+++ b/src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.test.ts
@@ -19,7 +19,7 @@
import { RangeFilter } from '@kbn/es-query';
import { changeTimeFilter } from './change_time_filter';
import { TimeRange } from 'src/plugins/data/public';
-import { timefilterServiceMock } from '../../../timefilter/timefilter_service.mock';
+import { timefilterServiceMock } from '../timefilter_service.mock';
const timefilterMock = timefilterServiceMock.createSetupContract();
const timefilter = timefilterMock.timefilter;
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts b/src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.ts
similarity index 95%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts
rename to src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.ts
index 8cd1ce5ba6c84..4780ddb6b4b44 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/change_time_filter.ts
+++ b/src/legacy/core_plugins/data/public/timefilter/lib/change_time_filter.ts
@@ -20,7 +20,7 @@
import moment from 'moment';
import { keys } from 'lodash';
import { RangeFilter } from '@kbn/es-query';
-import { TimefilterContract } from '../../../timefilter';
+import { TimefilterContract } from '../timefilter';
export function convertRangeFilterToTimeRange(filter: RangeFilter) {
const key = keys(filter.range)[0];
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts b/src/legacy/core_plugins/data/public/timefilter/lib/extract_time_filter.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.test.ts
rename to src/legacy/core_plugins/data/public/timefilter/lib/extract_time_filter.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts b/src/legacy/core_plugins/data/public/timefilter/lib/extract_time_filter.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/extract_time_filter.ts
rename to src/legacy/core_plugins/data/public/timefilter/lib/extract_time_filter.ts
diff --git a/src/legacy/core_plugins/data/public/timefilter/time_history.ts b/src/legacy/core_plugins/data/public/timefilter/time_history.ts
index 22778d1adea3c..36ad1a4427a47 100644
--- a/src/legacy/core_plugins/data/public/timefilter/time_history.ts
+++ b/src/legacy/core_plugins/data/public/timefilter/time_history.ts
@@ -19,13 +19,13 @@
import moment from 'moment';
import { TimeRange } from 'src/plugins/data/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { PersistedLog } from '../query/persisted_log';
-import { Storage } from '../types';
export class TimeHistory {
private history: PersistedLog;
- constructor(store: Storage) {
+ constructor(storage: IStorageWrapper) {
const historyOptions = {
maxLength: 10,
filterDuplicates: true,
@@ -33,7 +33,7 @@ export class TimeHistory {
return oldItem.from === newItem.from && oldItem.to === newItem.to;
},
};
- this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions, store);
+ this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions, storage);
}
add(time: TimeRange) {
diff --git a/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts b/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts
index cda9b93ef08aa..831ccebedc9cc 100644
--- a/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts
+++ b/src/legacy/core_plugins/data/public/timefilter/timefilter_service.ts
@@ -18,8 +18,8 @@
*/
import { UiSettingsClientContract } from 'src/core/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from './index';
-import { Storage } from '../types';
/**
* Filter Service
@@ -28,16 +28,16 @@ import { Storage } from '../types';
export interface TimeFilterServiceDependencies {
uiSettings: UiSettingsClientContract;
- store: Storage;
+ storage: IStorageWrapper;
}
export class TimefilterService {
- public setup({ uiSettings, store }: TimeFilterServiceDependencies): TimefilterSetup {
+ public setup({ uiSettings, storage }: TimeFilterServiceDependencies): TimefilterSetup {
const timefilterConfig = {
timeDefaults: uiSettings.get('timepicker:timeDefaults'),
refreshIntervalDefaults: uiSettings.get('timepicker:refreshIntervalDefaults'),
};
- const history = new TimeHistory(store);
+ const history = new TimeHistory(storage);
const timefilter = new Timefilter(timefilterConfig, history);
return {
diff --git a/src/legacy/core_plugins/data/public/types.ts b/src/legacy/core_plugins/data/public/types.ts
index 2c02a9b764755..b6c9c47cc0ae6 100644
--- a/src/legacy/core_plugins/data/public/types.ts
+++ b/src/legacy/core_plugins/data/public/types.ts
@@ -19,13 +19,7 @@
import { UiSettingsClientContract, CoreStart } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
-
-export interface Storage {
- get: (key: string) => any;
- set: (key: string, value: any) => void;
- remove: (key: string) => any;
- clear: () => void;
-}
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
export interface IDataPluginServices extends Partial {
appName: string;
@@ -33,6 +27,6 @@ export interface IDataPluginServices extends Partial {
savedObjects: CoreStart['savedObjects'];
notifications: CoreStart['notifications'];
http: CoreStart['http'];
- store: Storage;
+ storage: IStorageWrapper;
data: DataPublicPluginStart;
}
diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js
index 44c68c84579c6..86fe6db9b0778 100644
--- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js
+++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.js
@@ -26,6 +26,7 @@ import {
import { PhraseFilterManager } from './filter_manager/phrase_filter_manager';
import { createSearchSource } from './create_search_source';
import { i18n } from '@kbn/i18n';
+import { npStart } from 'ui/new_platform';
import chrome from 'ui/chrome';
import { start as data } from '../../../../core_plugins/data/public/legacy';
@@ -187,9 +188,10 @@ export async function listControlFactory(controlParams, useTimeFilter, SearchSou
// ignore not found error and return control so it can be displayed in disabled state.
}
+ const { filterManager } = npStart.plugins.data.query;
return new ListControl(
controlParams,
- new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, data.filter.filterManager),
+ new PhraseFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager),
useTimeFilter,
SearchSource,
);
diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js
index d5c23c2c1c855..b40a9f8e6efd4 100644
--- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.js
@@ -24,6 +24,28 @@ jest.mock('ui/timefilter', () => ({
createFilter: jest.fn(),
}));
+jest.mock('ui/new_platform', () => ({
+ npStart: {
+ plugins: {
+ data: {
+ query: {
+ filterManager: {
+ fieldName: 'myNumberField',
+ getIndexPattern: () => ({
+ fields: { getByName: name => {
+ const fields = { myField: { name: 'myField' } };
+ return fields[name];
+ } }
+ }),
+ getAppFilters: jest.fn().mockImplementation(() => ([])),
+ getGlobalFilters: jest.fn().mockImplementation(() => ([])),
+ }
+ }
+ }
+ },
+ },
+}));
+
jest.mock('../../../../core_plugins/data/public/legacy', () => ({
start: {
indexPatterns: {
@@ -36,19 +58,6 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({
}),
}
},
- filter: {
- filterManager: {
- fieldName: 'myNumberField',
- getIndexPattern: () => ({
- fields: { getByName: name => {
- const fields = { myField: { name: 'myField' } };
- return fields[name];
- } }
- }),
- getAppFilters: jest.fn().mockImplementation(() => ([])),
- getGlobalFilters: jest.fn().mockImplementation(() => ([])),
- }
- }
}
}));
diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js
index efb208bd80045..2a05a1224aab9 100644
--- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js
+++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.js
@@ -27,6 +27,7 @@ import { RangeFilterManager } from './filter_manager/range_filter_manager';
import { createSearchSource } from './create_search_source';
import { i18n } from '@kbn/i18n';
import { start as data } from '../../../../core_plugins/data/public/legacy';
+import { npStart } from 'ui/new_platform';
const minMaxAgg = (field) => {
const aggBody = {};
@@ -106,9 +107,10 @@ export async function rangeControlFactory(controlParams, useTimeFilter, SearchSo
} catch (err) {
// ignore not found error and return control so it can be displayed in disabled state.
}
+ const { filterManager } = npStart.plugins.data.query;
return new RangeControl(
controlParams,
- new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, data.filter.filterManager),
+ new RangeFilterManager(controlParams.id, controlParams.fieldName, indexPattern, filterManager),
useTimeFilter,
SearchSource,
);
diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js
index c746d116c70b2..3e6d6a49a1118 100644
--- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.js
@@ -32,6 +32,28 @@ jest.mock('ui/timefilter', () => ({
createFilter: jest.fn(),
}));
+jest.mock('ui/new_platform', () => ({
+ npStart: {
+ plugins: {
+ data: {
+ query: {
+ filterManager: {
+ fieldName: 'myNumberField',
+ getIndexPattern: () => ({
+ fields: { getByName: name => {
+ const fields = { myNumberField: { name: 'myNumberField' } };
+ return fields[name];
+ }
+ } }),
+ getAppFilters: jest.fn().mockImplementation(() => ([])),
+ getGlobalFilters: jest.fn().mockImplementation(() => ([])),
+ }
+ }
+ }
+ },
+ },
+}));
+
jest.mock('../../../../core_plugins/data/public/legacy', () => ({
start: {
indexPatterns: {
@@ -44,19 +66,6 @@ jest.mock('../../../../core_plugins/data/public/legacy', () => ({
} }),
}
},
- filter: {
- filterManager: {
- fieldName: 'myNumberField',
- getIndexPattern: () => ({
- fields: { getByName: name => {
- const fields = { myNumberField: { name: 'myNumberField' } };
- return fields[name];
- }
- } }),
- getAppFilters: jest.fn().mockImplementation(() => ([])),
- getGlobalFilters: jest.fn().mockImplementation(() => ([])),
- }
- }
}
}));
diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js
index 1edf5652a76c5..792ff3fe85479 100644
--- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.js
+++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.js
@@ -23,7 +23,7 @@ import { I18nContext } from 'ui/i18n';
import { InputControlVis } from './components/vis/input_control_vis';
import { controlFactory } from './control/control_factory';
import { getLineageMap } from './lineage';
-import { start as data } from '../../../core_plugins/data/public/legacy';
+import { npStart } from 'ui/new_platform';
import { SearchSource } from '../../../ui/public/courier/search_source/search_source';
class VisController {
@@ -34,7 +34,7 @@ class VisController {
this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this);
- this.filterManager = data.filter.filterManager;
+ this.filterManager = npStart.plugins.data.query.filterManager;
this.updateSubsciption = this.filterManager.getUpdates$()
.subscribe(this.queryBarUpdateHandler);
}
diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx
index 1d979f82d39d8..045e8093124a6 100644
--- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx
+++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx
@@ -100,6 +100,7 @@ export function DocViewTableRow({
* Justification for dangerouslySetInnerHTML:
* We just use values encoded by our field formatters
*/
+ // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: value as string }}
/>
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx
index 6ce987028c197..3edcda8c3bea9 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx
@@ -34,7 +34,7 @@ export function ToolBarPagerButtons(props: Props) {
disabled={!props.hasPreviousPage}
data-test-subj="btnPrevPage"
>
-
+
-
+
);
diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
index f748cdae1b4fc..1577b4deece51 100644
--- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search.tsx
@@ -68,7 +68,7 @@ export function DiscoverFieldSearch({
defaultMessage: 'Show field filter settings',
});
const searchPlaceholder = i18n.translate('kbn.discover.fieldChooser.searchPlaceHolder', {
- defaultMessage: 'Search fields',
+ defaultMessage: 'Search field names',
});
return (
@@ -97,7 +97,7 @@ export function DiscoverFieldSearch({
>
diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
index 5f3ebd6d22e24..e777501d35ca0 100644
--- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
@@ -22,8 +22,9 @@ import { Subscription } from 'rxjs';
import { Filter, FilterStateStore } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
+import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../../plugins/data/public';
import { setup as data } from '../../../../data/public/legacy';
-import { getTime, onlyDisabledFiltersChanged, Query } from '../../../../data/public';
+import { Query, getTime } from '../../../../data/public';
import {
APPLY_FILTER_TRIGGER,
Container,
@@ -46,7 +47,6 @@ import {
RequestAdapter,
SearchSource,
} from '../kibana_services';
-import { TimeRange } from '../../../../../../plugins/data/public';
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
interface SearchScope extends ng.IScope {
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 dd0674073f442..b78d05e68acad 100644
--- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
@@ -18,6 +18,7 @@
*/
import 'ui/collapsible_sidebar';
import 'ui/directives/listen';
+import 'ui/directives/storage';
import 'ui/fixed_scroll';
import 'ui/directives/css_truncate';
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
index 50e921e14973b..0ef3cce832bc7 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html
@@ -72,14 +72,6 @@
index-patterns="[indexPattern]"
>
-
-
-
+
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
index b24cf447d21d6..f8cdfa956aa60 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js
@@ -22,9 +22,11 @@ import { Subscription } from 'rxjs';
import { i18n } from '@kbn/i18n';
import '../saved_visualizations/saved_visualizations';
import './visualization_editor';
+import './visualization';
import 'ui/vis/editors/default/sidebar';
import 'ui/visualize';
import 'ui/collapsible_sidebar';
+import 'ui/directives/storage';
import { capabilities } from 'ui/capabilities';
import chrome from 'ui/chrome';
@@ -46,7 +48,6 @@ import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url';
import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
import { timefilter } from 'ui/timefilter';
-import { getVisualizeLoader } from '../../../../../ui/public/visualize/loader';
import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share';
import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
@@ -142,6 +143,7 @@ function VisEditor(
AppState,
$window,
$injector,
+ $timeout,
indexPatterns,
kbnUrl,
redirectWhenMissing,
@@ -403,22 +405,21 @@ function VisEditor(
$appStatus.dirty = status.dirty || !savedVis.id;
});
- $scope.$watch('state.query', (newQuery) => {
- const query = migrateLegacyQuery(newQuery);
- $scope.updateQueryAndFetch({ query });
+ $scope.$watch('state.query', (newQuery, oldQuery) => {
+ if (!_.isEqual(newQuery, oldQuery)) {
+ const query = migrateLegacyQuery(newQuery);
+ if (!_.isEqual(query, newQuery)) {
+ $state.query = query;
+ }
+ $scope.fetch();
+ }
});
$state.replace();
const updateTimeRange = () => {
$scope.timeRange = timefilter.getTime();
- // In case we are running in embedded mode (i.e. we used the visualize loader to embed)
- // the visualization, we need to update the timeRange on the visualize handler.
- if ($scope._handler) {
- $scope._handler.update({
- timeRange: $scope.timeRange,
- });
- }
+ $scope.$broadcast('render');
};
const subscriptions = new Subscription();
@@ -435,9 +436,10 @@ function VisEditor(
// update the searchSource when query updates
$scope.fetch = function () {
$state.save();
+ $scope.query = $state.query;
savedVis.searchSource.setField('query', $state.query);
savedVis.searchSource.setField('filter', $state.filters);
- $scope.vis.forceReload();
+ $scope.$broadcast('render');
};
// update the searchSource when filters update
@@ -460,16 +462,8 @@ function VisEditor(
subscriptions.unsubscribe();
});
- if (!$scope.chrome.getVisible()) {
- getVisualizeLoader().then(loader => {
- $scope._handler = loader.embedVisualizationWithSavedObject($element.find('.visualize')[0], savedVis, {
- timeRange: $scope.timeRange,
- uiState: $scope.uiState,
- appState: $state,
- listenOnChange: false
- });
- });
- }
+
+ $timeout(() => { $scope.$broadcast('render'); });
}
$scope.updateQueryAndFetch = function ({ query, dateRange }) {
@@ -482,7 +476,9 @@ function VisEditor(
timefilter.setTime(dateRange);
// If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes
- if (!isUpdate) $scope.fetch();
+ if (!isUpdate) {
+ $scope.vis.forceReload();
+ }
};
$scope.onRefreshChange = function ({ isPaused, refreshInterval }) {
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js
new file mode 100644
index 0000000000000..ade806bc2fc31
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { uiModules } from 'ui/modules';
+import 'angular-sanitize';
+import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy';
+
+uiModules
+ .get('kibana/directive', ['ngSanitize'])
+ .directive('visualizationEmbedded', function (Private, $timeout, getAppState) {
+
+ return {
+ restrict: 'E',
+ scope: {
+ savedObj: '=',
+ uiState: '=?',
+ timeRange: '=',
+ filters: '=',
+ query: '=',
+ },
+ link: function ($scope, element) {
+ $scope.renderFunction = async () => {
+ if (!$scope._handler) {
+ $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, {
+ timeRange: $scope.timeRange,
+ filters: $scope.filters || [],
+ query: $scope.query,
+ appState: getAppState(),
+ uiState: $scope.uiState,
+ });
+ $scope._handler.render(element[0]);
+
+ } else {
+ $scope._handler.updateInput({
+ timeRange: $scope.timeRange,
+ filters: $scope.filters || [],
+ query: $scope.query,
+ });
+ }
+ };
+
+ $scope.$on('render', (event) => {
+ event.preventDefault();
+ $timeout(() => { $scope.renderFunction(); });
+ });
+
+ $scope.$on('$destroy', () => {
+ $scope._handler.destroy();
+ });
+ }
+ };
+ });
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js
index a2ed44df2f5b0..2cf6e8e7cf86e 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js
@@ -17,7 +17,6 @@
* under the License.
*/
-import { debounce } from 'lodash';
import { uiModules } from 'ui/modules';
import 'angular-sanitize';
import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types';
@@ -34,6 +33,7 @@ uiModules
uiState: '=?',
timeRange: '=',
filters: '=',
+ query: '=',
},
link: function ($scope, element) {
const editorType = $scope.savedObj.vis.type.editor;
@@ -46,6 +46,7 @@ uiModules
uiState: $scope.uiState,
timeRange: $scope.timeRange,
filters: $scope.filters,
+ query: $scope.query,
appState: getAppState(),
});
};
@@ -58,10 +59,6 @@ uiModules
$scope.$on('$destroy', () => {
editor.destroy();
});
-
- $scope.$watchGroup(['timeRange', 'filters'], debounce(() => {
- $scope.renderFunction();
- }, 100));
}
};
});
diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
index b9febc3af54ea..56aaea1c240f1 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
@@ -22,6 +22,7 @@ import { StaticIndexPattern } from 'ui/index_patterns';
import { PersistedState } from 'ui/persisted_state';
import { VisualizeLoader } from 'ui/visualize/loader';
import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler';
+import { AppState } from 'ui/state_management/app_state';
import {
VisSavedObject,
VisualizeLoaderParams,
@@ -30,14 +31,14 @@ import {
import { Subscription } from 'rxjs';
import * as Rx from 'rxjs';
import { Filter } from '@kbn/es-query';
-import { TimeRange } from '../../../../../../plugins/data/public';
+import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../../plugins/data/public';
import {
EmbeddableInput,
EmbeddableOutput,
Embeddable,
Container,
} from '../../../../../../plugins/embeddable/public';
-import { Query, onlyDisabledFiltersChanged } from '../../../../data/public';
+import { Query } from '../../../../data/public';
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
const getKeys = (o: T): Array => Object.keys(o) as Array;
@@ -48,6 +49,8 @@ export interface VisualizeEmbeddableConfiguration {
editUrl: string;
loader: VisualizeLoader;
editable: boolean;
+ appState?: AppState;
+ uiState?: PersistedState;
}
export interface VisualizeInput extends EmbeddableInput {
@@ -57,6 +60,8 @@ export interface VisualizeInput extends EmbeddableInput {
vis?: {
colors?: { [key: string]: string };
};
+ appState?: AppState;
+ uiState?: PersistedState;
}
export interface VisualizeOutput extends EmbeddableOutput {
@@ -69,6 +74,7 @@ export interface VisualizeOutput extends EmbeddableOutput {
export class VisualizeEmbeddable extends Embeddable {
private savedVisualization: VisSavedObject;
private loader: VisualizeLoader;
+ private appState: AppState | undefined;
private uiState: PersistedState;
private handler?: EmbeddedVisualizeHandler;
private timeRange?: TimeRange;
@@ -86,6 +92,8 @@ export class VisualizeEmbeddable extends Embeddable {
this.handleChanges();
@@ -149,7 +163,7 @@ export class VisualizeEmbeddable extends Embeddable & { id: string },
parent?: Container
): Promise {
@@ -140,11 +141,12 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
const savedVisualizations = $injector.get('savedVisualizations');
try {
- const visId = savedObjectId;
+ const visId = savedObject.id as string;
- const editUrl = chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`);
+ const editUrl = visId
+ ? chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`)
+ : '';
const loader = await getVisualizeLoader();
- const savedObject = await savedVisualizations.get(visId);
const isLabsEnabled = config.get('visualize:enableLabs');
if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') {
@@ -160,6 +162,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
indexPatterns,
editUrl,
editable: this.isEditable(),
+ appState: input.appState,
+ uiState: input.uiState,
},
input,
parent
@@ -170,6 +174,25 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory<
}
}
+ public async createFromSavedObject(
+ savedObjectId: string,
+ input: Partial & { id: string },
+ parent?: Container
+ ): Promise {
+ const $injector = await chrome.dangerouslyGetActiveInjector();
+ const savedVisualizations = $injector.get('savedVisualizations');
+
+ try {
+ const visId = savedObjectId;
+
+ const savedObject = await savedVisualizations.get(visId);
+ return this.createFromObject(savedObject, input, parent);
+ } catch (e) {
+ console.error(e); // eslint-disable-line no-console
+ return new ErrorEmbeddable(e, input, parent);
+ }
+ }
+
public async create() {
// TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up
// to allow for in place creation of visualizations without having to navigate away to a new URL.
diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap
index 341a839e9c776..4aa614b68ea23 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap
+++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap
@@ -349,7 +349,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
as="div"
autoFocus={true}
disabled={false}
- lockProps={Object {}}
+ lockProps={
+ Object {
+ "style": undefined,
+ }
+ }
noFocusGuards={false}
persistentFocus={false}
returnFocus={true}
@@ -1627,7 +1631,11 @@ exports[`NewVisModal should render as expected 1`] = `
as="div"
autoFocus={true}
disabled={false}
- lockProps={Object {}}
+ lockProps={
+ Object {
+ "style": undefined,
+ }
+ }
noFocusGuards={false}
persistentFocus={false}
returnFocus={true}
diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
index 5b8bb07577510..ca798b6bf2470 100644
--- a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
+++ b/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js
@@ -21,9 +21,8 @@ import { get } from 'lodash';
import { GeohashLayer } from './geohash_layer';
import { BaseMapsVisualizationProvider } from './base_maps_visualization';
import { TileMapTooltipFormatterProvider } from './editors/_tooltip_formatter';
+import { npStart } from 'ui/new_platform';
import { getFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities';
-import { start as data } from '../../../core_plugins/data/public/legacy';
-const filterManager = data.filter.filterManager;
export const createTileMapVisualization = ({ serviceSettings, $injector }) => {
const BaseMapsVisualization = new BaseMapsVisualizationProvider(serviceSettings);
@@ -189,6 +188,7 @@ export const createTileMapVisualization = ({ serviceSettings, $injector }) => {
filter[filterName] = { ignore_unmapped: true };
filter[filterName][field] = filterData;
+ const { filterManager } = npStart.plugins.data.query;
filterManager.addFilters([filter]);
this.vis.updateState();
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
index 3497a35f5c99d..842d3aa6c4ad7 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js
@@ -32,10 +32,10 @@ import { fetchFields } from '../lib/fetch_fields';
import { extractIndexPatterns } from '../../common/extract_index_patterns';
import { npStart } from 'ui/new_platform';
-import { Storage } from 'ui/storage';
+
import { CoreStartContextProvider } from '../contexts/query_input_bar_context';
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
-const localStorage = new Storage(window.localStorage);
+import { Storage } from '../../../../../plugins/kibana_utils/public';
import { timefilter } from 'ui/timefilter';
const VIS_STATE_DEBOUNCE_DELAY = 200;
@@ -46,6 +46,7 @@ export class VisEditor extends Component {
super(props);
const { vis } = props;
this.appState = vis.API.getAppState();
+ this.localStorage = new Storage(window.localStorage);
this.state = {
model: props.visParams,
dirty: false,
@@ -63,7 +64,7 @@ export class VisEditor extends Component {
appName: APP_NAME,
uiSettings: npStart.core.uiSettings,
savedObjectsClient: npStart.core.savedObjects.client,
- store: localStorage,
+ store: this.localStorage,
};
}
@@ -169,7 +170,7 @@ export class VisEditor extends Component {
{
+ this._subscription = this._handler.handler.data$.subscribe(data => {
this.setPanelInterval(data.visData);
onDataChange(data);
});
@@ -152,10 +150,12 @@ class VisEditorVisualizationUI extends Component {
this._loadVisualization();
}
- componentDidUpdate(prevProps) {
- if (this._handler && !isEqual(this.props.timeRange, prevProps.timeRange)) {
- this._handler.update({
+ componentDidUpdate() {
+ if (this._handler) {
+ this._handler.updateInput({
timeRange: this.props.timeRange,
+ filters: this.props.filters || [],
+ query: this.props.query,
});
}
}
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js
deleted file mode 100644
index e60626f8fbf0a..0000000000000
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor_visualization.test.js
+++ /dev/null
@@ -1,71 +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.
- */
-
-jest.mock('ui/visualize/loader/visualize_loader', () => ({}));
-
-jest.mock('ui/new_platform');
-
-import React from 'react';
-import { mountWithIntl } from 'test_utils/enzyme_helpers';
-import { VisEditorVisualization } from './vis_editor_visualization';
-
-describe('getVisualizeLoader', () => {
- let updateStub;
-
- beforeEach(() => {
- updateStub = jest.fn();
- const handlerMock = {
- update: updateStub,
- data$: {
- subscribe: () => {},
- },
- };
- const loaderMock = {
- embedVisualizationWithSavedObject: () => handlerMock,
- };
- require('ui/visualize/loader/visualize_loader').getVisualizeLoader = async () => loaderMock;
- });
-
- it('should not call _handler.update until getVisualizeLoader returns _handler', async () => {
- const wrapper = mountWithIntl( );
-
- // Set prop to force DOM change and componentDidUpdate to be triggered
- wrapper.setProps({
- timeRange: {
- from: '2019-03-20T20:35:37.637Z',
- to: '2019-03-23T18:40:16.486Z',
- },
- });
-
- expect(updateStub).not.toHaveBeenCalled();
-
- // Ensure all promises resolve
- await new Promise(resolve => process.nextTick(resolve));
-
- // Set prop to force DOM change and componentDidUpdate to be triggered
- wrapper.setProps({
- timeRange: {
- from: 'now/d',
- to: 'now/d',
- },
- });
-
- expect(updateStub).toHaveBeenCalled();
- });
-});
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts b/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts
index 19519571f1ab0..925b483905d01 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/contexts/query_input_bar_context.ts
@@ -18,14 +18,14 @@
*/
import React from 'react';
-import { Storage } from 'ui/storage';
import { UiSettingsClientContract, SavedObjectsClientContract } from 'src/core/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
export interface ICoreStartContext {
appName: string;
uiSettings: UiSettingsClientContract;
savedObjectsClient: SavedObjectsClientContract;
- store: Storage;
+ storage: IStorageWrapper;
}
export const CoreStartContext = React.createContext(null);
diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
index 1dc73d6f9ff20..7aa60bb0cc469 100644
--- a/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
+++ b/src/legacy/core_plugins/vis_type_vega/public/vega_visualization.js
@@ -23,6 +23,7 @@ import { VegaView } from './vega_view/vega_view';
import { VegaMapView } from './vega_view/vega_map_view';
import { timefilter } from 'ui/timefilter';
import { start as data } from '../../../core_plugins/data/public/legacy';
+import { npStart } from 'ui/new_platform';
import { findIndexPatternByTitle } from '../../data/public/index_patterns';
@@ -99,11 +100,12 @@ export const createVegaVisualization = ({ serviceSettings }) => class VegaVisual
this._vegaView = null;
}
+ const { filterManager } = npStart.plugins.data.query;
const vegaViewParams = {
parentEl: this._el,
vegaParser,
serviceSettings,
- queryfilter: data.filter.filterManager,
+ queryfilter: filterManager,
timefilter: timefilter,
findIndex: this.findIndex.bind(this),
};
diff --git a/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js b/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js
deleted file mode 100644
index c85899ca5704e..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/agg_param_writer.js
+++ /dev/null
@@ -1,103 +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 { VisProvider } from '../../vis';
-import { aggTypes } from '..';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { AggGroupNames } from '../../vis/editors/default/agg_groups';
-
-// eslint-disable-next-line import/no-default-export
-export default function AggParamWriterHelper(Private) {
- const Vis = Private(VisProvider);
- const stubbedLogstashIndexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- /**
- * Helper object for writing aggParams. Specify an aggType and it will find a vis & schema, and
- * wire up the supporting objects required to feed in parameters, and get #write() output.
- *
- * Use cases:
- * - Verify that the interval parameter of the histogram visualization casts its input to a number
- * ```js
- * it('casts to a number', function () {
- * let writer = new AggParamWriter({ aggType: 'histogram' });
- * let output = writer.write({ interval : '100/10' });
- * expect(output.params.interval).to.be.a('number');
- * expect(output.params.interval).to.be(100);
- * });
- * ```
- *
- * @class AggParamWriter
- * @param {object} opts - describe the properties of this paramWriter
- * @param {string} opts.aggType - the name of the aggType we want to test. ('histogram', 'filter', etc.)
- */
- class AggParamWriter {
-
- constructor(opts) {
- this.aggType = opts.aggType;
- if (_.isString(this.aggType)) {
- this.aggType = aggTypes.buckets.find(agg => agg.name === this.aggType) || aggTypes.metrics.find(agg => agg.name === this.aggType);
- }
-
- // not configurable right now, but totally required
- this.indexPattern = stubbedLogstashIndexPattern;
-
- // the schema that the aggType satisfies
- this.visAggSchema = null;
-
- this.vis = new Vis(this.indexPattern, {
- type: 'histogram',
- aggs: [{
- id: 1,
- type: this.aggType.name,
- params: {}
- }]
- });
- }
-
- write(paramValues, modifyAggConfig = null) {
- paramValues = _.clone(paramValues);
-
- if (this.aggType.paramByName('field') && !paramValues.field) {
- // pick a field rather than force a field to be specified everywhere
- if (this.aggType.type === AggGroupNames.Metrics) {
- paramValues.field = _.sample(this.indexPattern.fields.getByType('number'));
- } else {
- const type = this.aggType.paramByName('field').filterFieldTypes || 'string';
- let field;
- do {
- field = _.sample(this.indexPattern.fields.getByType(type));
- } while (!field.aggregatable);
- paramValues.field = field.name;
- }
- }
-
- const aggConfig = this.vis.aggs.aggs[0];
- aggConfig.setParams(paramValues);
-
- if (modifyAggConfig) {
- modifyAggConfig(aggConfig);
- }
-
- return aggConfig.write(this.vis.aggs);
- }
- }
-
- return AggParamWriter;
-}
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js
deleted file mode 100644
index 94603dfa69a66..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/_date_range.js
+++ /dev/null
@@ -1,80 +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 { set } from 'lodash';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import ngMock from 'ng_mock';
-import { aggTypes } from '../..';
-import AggParamWriterProvider from '../agg_param_writer';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import chrome from '../../../chrome';
-
-const config = chrome.getUiSettingsClient();
-
-describe('date_range params', function () {
- let paramWriter;
- let timeField;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- const AggParamWriter = Private(AggParamWriterProvider);
- const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- timeField = indexPattern.timeFieldName;
- paramWriter = new AggParamWriter({ aggType: 'date_range' });
- }));
-
- describe('getKey', () => {
- const dateRange = aggTypes.buckets.find(agg => agg.name === 'date_range');
- it('should return object', () => {
- const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' };
- expect(dateRange.getKey(bucket)).to.equal({ from: 'from-date', to: 'to-date' });
- });
- });
-
- describe('time_zone', () => {
- beforeEach(() => {
- sinon.stub(config, 'get');
- sinon.stub(config, 'isDefault');
- });
-
- it('should use the specified time_zone', () => {
- const output = paramWriter.write({ time_zone: 'Europe/Kiev' });
- expect(output.params).to.have.property('time_zone', 'Europe/Kiev');
- });
-
- it('should use the Kibana time_zone if no parameter specified', () => {
- config.isDefault.withArgs('dateFormat:tz').returns(false);
- config.get.withArgs('dateFormat:tz').returns('Europe/Riga');
- const output = paramWriter.write({});
- expect(output.params).to.have.property('time_zone', 'Europe/Riga');
- });
-
- it('should use the fixed time_zone from the index pattern typeMeta', () => {
- set(paramWriter.indexPattern, ['typeMeta', 'aggs', 'date_range', timeField, 'time_zone'], 'Europe/Rome');
- const output = paramWriter.write({ field: timeField });
- expect(output.params).to.have.property('time_zone', 'Europe/Rome');
- });
-
- afterEach(() => {
- config.get.restore();
- config.isDefault.restore();
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js
deleted file mode 100644
index 7172d1f40936e..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/_geo_hash.js
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import { geoHashBucketAgg } from '../../buckets/geo_hash';
-import * as AggConfigModule from '../../agg_config';
-import * as BucketAggTypeModule from '../../buckets/_bucket_agg_type';
-
-describe('Geohash Agg', () => {
-
- const initialZoom = 10;
- const initialMapBounds = {
- top_left: { lat: 1.0, lon: -1.0 },
- bottom_right: { lat: -1.0, lon: 1.0 }
- };
-
- const BucketAggTypeMock = (aggOptions) => {
- return aggOptions;
- };
- const AggConfigMock = (parent, aggOptions) => {
- return aggOptions;
- };
- const createAggregationMock = (aggOptions) => {
- return new AggConfigMock(null, aggOptions);
- };
-
- const aggMock = {
- getField: () => {
- return {
- name: 'location'
- };
- },
- params: {
- isFilteredByCollar: true,
- useGeocentroid: true,
- mapZoom: initialZoom
- },
- aggConfigs: {},
- type: 'geohash_grid',
- };
- aggMock.aggConfigs.createAggConfig = createAggregationMock;
-
-
- before(function () {
- sinon.stub(AggConfigModule, 'AggConfig').callsFake(AggConfigMock);
- sinon.stub(BucketAggTypeModule, 'BucketAggType').callsFake(BucketAggTypeMock);
- });
-
- after(function () {
- AggConfigModule.AggConfig.restore();
- BucketAggTypeModule.BucketAggType.restore();
- });
-
- function initAggParams() {
- aggMock.params.isFilteredByCollar = true;
- aggMock.params.useGeocentroid = true;
- aggMock.params.mapBounds = initialMapBounds;
- }
-
- function zoomMap(zoomChange) {
- aggMock.params.mapZoom += zoomChange;
- }
-
- function moveMap(newBounds) {
- aggMock.params.mapBounds = newBounds;
- }
-
- function resetMap() {
- aggMock.params.mapZoom = initialZoom;
- aggMock.params.mapBounds = initialMapBounds;
- aggMock.params.mapCollar = {
- top_left: { lat: 1.5, lon: -1.5 },
- bottom_right: { lat: -1.5, lon: 1.5 },
- zoom: initialZoom
- };
- }
-
- describe('precision parameter', () => {
-
- const PRECISION_PARAM_INDEX = 2;
- let precisionParam;
- beforeEach(() => {
- precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX];
- });
-
- it('should select precision parameter', () => {
- expect(precisionParam.name).to.equal('precision');
- });
-
- describe('precision parameter write', () => {
-
- const zoomToGeoHashPrecision = {
- 0: 1,
- 1: 2,
- 2: 2,
- 3: 2,
- 4: 3,
- 5: 3,
- 6: 4,
- 7: 4,
- 8: 4,
- 9: 5,
- 10: 5,
- 11: 6,
- 12: 6,
- 13: 6,
- 14: 7,
- 15: 7,
- 16: 7,
- 17: 7,
- 18: 7,
- 19: 7,
- 20: 7,
- 21: 7
- };
-
- Object.keys(zoomToGeoHashPrecision).forEach((zoomLevel) => {
- it(`zoom level ${zoomLevel} should correspond to correct geohash-precision`, () => {
- const output = { params: {} };
- precisionParam.write({
- params: {
- autoPrecision: true,
- mapZoom: zoomLevel
- }
- }, output);
- expect(output.params.precision).to.equal(zoomToGeoHashPrecision[zoomLevel]);
- });
- });
- });
-
- });
-
- describe('getRequestAggs', () => {
-
- describe('initial aggregation creation', () => {
- let requestAggs;
- beforeEach(() => {
- initAggParams();
- requestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- });
-
- it('should create filter, geohash_grid, and geo_centroid aggregations', () => {
- expect(requestAggs.length).to.equal(3);
- expect(requestAggs[0].type).to.equal('filter');
- expect(requestAggs[1].type).to.equal('geohash_grid');
- expect(requestAggs[2].type).to.equal('geo_centroid');
- });
-
- it('should set mapCollar in vis session state', () => {
- expect(aggMock).to.have.property('lastMapCollar');
- expect(aggMock.lastMapCollar).to.have.property('top_left');
- expect(aggMock.lastMapCollar).to.have.property('bottom_right');
- expect(aggMock.lastMapCollar).to.have.property('zoom');
- });
-
- // there was a bug because of an "&& mapZoom" check which excluded 0 as a valid mapZoom, but it is.
- it('should create filter, geohash_grid, and geo_centroid aggregations when zoom level 0', () => {
- aggMock.params.mapZoom = 0;
- requestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- expect(requestAggs.length).to.equal(3);
- expect(requestAggs[0].type).to.equal('filter');
- expect(requestAggs[1].type).to.equal('geohash_grid');
- expect(requestAggs[2].type).to.equal('geo_centroid');
- });
- });
-
- describe('aggregation options', () => {
-
- beforeEach(() => {
- initAggParams();
- });
-
- it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => {
- aggMock.params.isFilteredByCollar = false;
- const requestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- expect(requestAggs.length).to.equal(2);
- expect(requestAggs[0].type).to.equal('geohash_grid');
- expect(requestAggs[1].type).to.equal('geo_centroid');
- });
-
- it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => {
- aggMock.params.useGeocentroid = false;
- const requestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- expect(requestAggs.length).to.equal(2);
- expect(requestAggs[0].type).to.equal('filter');
- expect(requestAggs[1].type).to.equal('geohash_grid');
-
- });
- });
-
- describe('aggregation creation after map interaction', () => {
-
- let origRequestAggs;
- let origMapCollar;
- beforeEach(() => {
- resetMap();
- initAggParams();
- origRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- origMapCollar = JSON.stringify(aggMock.lastMapCollar, null, '');
- });
-
- it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => {
- moveMap({
- top_left: { lat: 1.1, lon: -1.1 },
- bottom_right: { lat: -0.9, lon: 0.9 }
- });
-
- const newRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- expect(JSON.stringify(origRequestAggs[0].params, null, '')).to.equal(JSON.stringify(newRequestAggs[0].params, null, ''));
-
- const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, '');
- expect(origMapCollar).to.equal(newMapCollar);
- });
-
- it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => {
- moveMap({
- top_left: { lat: 10.0, lon: -10.0 },
- bottom_right: { lat: 9.0, lon: -9.0 }
- });
-
- const newRequestAggs = geoHashBucketAgg.getRequestAggs(aggMock);
- expect(JSON.stringify(origRequestAggs[0].params, null, '')).not.to.equal(JSON.stringify(newRequestAggs[0].params, null, ''));
-
- const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, '');
- expect(origMapCollar).not.to.equal(newMapCollar);
- });
-
- it('should change geo_bounding_box filter aggregation and vis session state when map zoom level changes', () => {
- zoomMap(-1);
-
- geoHashBucketAgg.getRequestAggs(aggMock);
-
- const newMapCollar = JSON.stringify(aggMock.lastMapCollar, null, '');
- expect(origMapCollar).not.to.equal(newMapCollar);
- });
-
- });
-
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js
deleted file mode 100644
index 26ad80e28ae9b..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/_histogram.js
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import ngMock from 'ng_mock';
-import { aggTypes } from '../..';
-import chrome from '../../../chrome';
-import AggParamWriterProvider from '../agg_param_writer';
-
-const config = chrome.getUiSettingsClient();
-const histogram = aggTypes.buckets.find(agg => agg.name === 'histogram');
-describe('Histogram Agg', function () {
-
- describe('ordered', function () {
-
- it('is ordered', function () {
- expect(histogram.ordered).to.be.ok();
- });
-
- it('is not ordered by date', function () {
- expect(histogram.ordered).to.not.have.property('date');
- });
- });
-
-
- describe('params', function () {
- let paramWriter;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- const AggParamWriter = Private(AggParamWriterProvider);
- paramWriter = new AggParamWriter({ aggType: 'histogram' });
- }));
-
- describe('intervalBase', () => {
- it('should not be written to the DSL', () => {
- const output = paramWriter.write({ intervalBase: 100 });
- expect(output.params).not.to.have.property('intervalBase');
- });
- });
-
- describe('interval', function () {
- // reads aggConfig.params.interval, writes to dsl.interval
-
- it('accepts a whole number', function () {
- const output = paramWriter.write({ interval: 100 });
- expect(output.params).to.have.property('interval', 100);
- });
-
- it('accepts a decimal number', function () {
- const output = paramWriter.write({ interval: 0.1 });
- expect(output.params).to.have.property('interval', 0.1);
- });
-
- it('accepts a decimal number string', function () {
- const output = paramWriter.write({ interval: '0.1' });
- expect(output.params).to.have.property('interval', 0.1);
- });
-
- it('accepts a whole number string', function () {
- const output = paramWriter.write({ interval: '10' });
- expect(output.params).to.have.property('interval', 10);
- });
-
- it('fails on non-numeric values', function () {
- // template validation prevents this from users, not devs
- const output = paramWriter.write({ interval: [] });
- expect(isNaN(output.params.interval)).to.be.ok();
- });
-
- describe('interval scaling', () => {
-
- beforeEach(() => {
- sinon.stub(config, 'get');
- });
-
- it('will respect the histogram:maxBars setting', () => {
- config.get.withArgs('histogram:maxBars').returns(5);
- const output = paramWriter.write({ interval: 5 },
- aggConfig => aggConfig.setAutoBounds({ min: 0, max: 10000 }));
- expect(output.params).to.have.property('interval', 2000);
- });
-
- it('will return specified interval, if bars are below histogram:maxBars config', () => {
- config.get.withArgs('histogram:maxBars').returns(10000);
- const output = paramWriter.write({ interval: 5 },
- aggConfig => aggConfig.setAutoBounds({ min: 0, max: 10000 }));
- expect(output.params).to.have.property('interval', 5);
- });
-
- it('will set to intervalBase if interval is below base', () => {
- const output = paramWriter.write({ interval: 3, intervalBase: 8 });
- expect(output.params).to.have.property('interval', 8);
- });
-
- it('will round to nearest intervalBase multiple if interval is above base', () => {
- const roundUp = paramWriter.write({ interval: 46, intervalBase: 10 });
- expect(roundUp.params).to.have.property('interval', 50);
- const roundDown = paramWriter.write({ interval: 43, intervalBase: 10 });
- expect(roundDown.params).to.have.property('interval', 40);
- });
-
- it('will not change interval if it is a multiple of base', () => {
- const output = paramWriter.write({ interval: 35, intervalBase: 5 });
- expect(output.params).to.have.property('interval', 35);
- });
-
- it('will round to intervalBase after scaling histogram:maxBars', () => {
- config.get.withArgs('histogram:maxBars').returns(100);
- const output = paramWriter.write({ interval: 5, intervalBase: 6 },
- aggConfig => aggConfig.setAutoBounds({ min: 0, max: 1000 }));
- // 100 buckets in 0 to 1000 would result in an interval of 10, so we should
- // round to the next multiple of 6 -> 12
- expect(output.params).to.have.property('interval', 12);
- });
-
- afterEach(() => {
- config.get.restore();
- });
- });
- });
-
- describe('min_doc_count', function () {
- it('casts true values to 0', function () {
- let output = paramWriter.write({ min_doc_count: true });
- expect(output.params).to.have.property('min_doc_count', 0);
-
- output = paramWriter.write({ min_doc_count: 'yes' });
- expect(output.params).to.have.property('min_doc_count', 0);
-
- output = paramWriter.write({ min_doc_count: 1 });
- expect(output.params).to.have.property('min_doc_count', 0);
-
- output = paramWriter.write({ min_doc_count: {} });
- expect(output.params).to.have.property('min_doc_count', 0);
- });
-
- it('writes 1 for falsy values', function () {
- let output = paramWriter.write({ min_doc_count: '' });
- expect(output.params).to.have.property('min_doc_count', 1);
-
- output = paramWriter.write({ min_doc_count: null });
- expect(output.params).to.have.property('min_doc_count', 1);
-
- output = paramWriter.write({ min_doc_count: undefined });
- expect(output.params).to.have.property('min_doc_count', 1);
- });
- });
-
- describe('extended_bounds', function () {
- it('does not write when only eb.min is set', function () {
- const output = paramWriter.write({
- has_extended_bounds: true,
- extended_bounds: { min: 0 }
- });
- expect(output.params).not.to.have.property('extended_bounds');
- });
-
- it('does not write when only eb.max is set', function () {
- const output = paramWriter.write({
- has_extended_bounds: true,
- extended_bounds: { max: 0 }
- });
- expect(output.params).not.to.have.property('extended_bounds');
- });
-
- it('writes when both eb.min and eb.max are set', function () {
- const output = paramWriter.write({
- has_extended_bounds: true,
- extended_bounds: { min: 99, max: 100 }
- });
- expect(output.params.extended_bounds).to.have.property('min', 99);
- expect(output.params.extended_bounds).to.have.property('max', 100);
- });
-
- it('does not write when nothing is set', function () {
- const output = paramWriter.write({
- has_extended_bounds: true,
- extended_bounds: {}
- });
- expect(output.params).to.not.have.property('extended_bounds');
- });
-
- it('does not write when has_extended_bounds is false', function () {
- const output = paramWriter.write({
- has_extended_bounds: false,
- extended_bounds: { min: 99, max: 100 }
- });
- expect(output.params).to.not.have.property('extended_bounds');
- });
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js
deleted file mode 100644
index e47802aa6f4bf..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/_range.js
+++ /dev/null
@@ -1,71 +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 { values } from 'lodash';
-import ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import resp from 'fixtures/agg_resp/range';
-import { VisProvider } from '../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-
-describe('Range Agg', function () {
- const buckets = values(resp.aggregations[1].buckets);
-
- let Vis;
- let indexPattern;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- indexPattern.stubSetFieldFormat('bytes', 'bytes', {
- pattern: '0,0.[000] b'
- });
- }));
-
- describe('formating', function () {
- it('formats bucket keys properly', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'range',
- schema: 'segment',
- params: {
- field: 'bytes',
- ranges: [
- { from: 0, to: 1000 },
- { from: 1000, to: 2000 }
- ]
- }
- }
- ]
- });
-
- const agg = vis.aggs.byName('range')[0];
- const format = function (val) {
- return agg.fieldFormatter()(agg.getKey(val));
- };
- expect(format(buckets[0])).to.be('≥ -∞ and < 1 KB');
- expect(format(buckets[1])).to.be('≥ 1 KB and < 2.5 KB');
- expect(format(buckets[2])).to.be('≥ 2.5 KB and < +∞');
-
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js
deleted file mode 100644
index 11e410c43b592..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js
+++ /dev/null
@@ -1,121 +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 moment from 'moment';
-import aggResp from 'fixtures/agg_resp/date_histogram';
-import ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterDateHistogram } from '../../../buckets/create_filter/date_histogram';
-import { intervalOptions } from '../../../buckets/_interval_options';
-
-describe('AggConfig Filters', function () {
- describe('date_histogram', function () {
- let vis;
- let agg;
- let field;
- let filter;
- let bucketKey;
- let bucketStart;
-
- let init;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- const Vis = Private(VisProvider);
- const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- init = function (interval, duration) {
- interval = interval || 'auto';
- if (interval === 'custom') interval = agg.params.customInterval;
- duration = duration || moment.duration(15, 'minutes');
- field = _.sample(_.reject(indexPattern.fields.getByType('date'), 'scripted'));
- vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'date_histogram',
- schema: 'segment',
- params: { field: field.name, interval: interval, customInterval: '5d' }
- }
- ]
- });
-
- agg = vis.aggs.aggs[0];
- bucketKey = _.sample(aggResp.aggregations['1'].buckets).key;
- bucketStart = moment(bucketKey);
-
- const timePad = moment.duration(duration / 2);
- agg.buckets.setBounds({
- min: bucketStart.clone().subtract(timePad),
- max: bucketStart.clone().add(timePad),
- });
- agg.buckets.setInterval(interval);
-
- filter = createFilterDateHistogram(agg, bucketKey);
- };
- }));
-
- it('creates a valid range filter', function () {
- init();
-
- expect(filter).to.have.property('range');
- expect(filter.range).to.have.property(field.name);
-
- const fieldParams = filter.range[field.name];
- expect(fieldParams).to.have.property('gte');
- expect(fieldParams.gte).to.be.a('string');
-
- expect(fieldParams).to.have.property('lt');
- expect(fieldParams.lt).to.be.a('string');
-
- expect(fieldParams).to.have.property('format');
- expect(fieldParams.format).to.be('strict_date_optional_time');
-
- expect(fieldParams.gte).to.be.lessThan(fieldParams.lt);
-
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', vis.indexPattern.id);
- });
-
-
- it('extends the filter edge to 1ms before the next bucket for all interval options', function () {
- intervalOptions.forEach(function (option) {
- let duration;
- if (option.val !== 'custom' && moment(1, option.val).isValid()) {
- duration = moment.duration(10, option.val);
-
- if (+duration < 10) {
- throw new Error('unable to create interval for ' + option.val);
- }
- }
-
- init(option.val, duration);
-
- const interval = agg.buckets.getInterval();
- const params = filter.range[field.name];
-
- expect(params.gte).to.be(bucketStart.toISOString());
- expect(params.lt).to.be(bucketStart.clone().add(interval).toISOString());
- });
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js
deleted file mode 100644
index 3ba03f232428f..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/date_range.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import moment from 'moment';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterDateRange } from '../../../buckets/create_filter/date_range';
-
-describe('AggConfig Filters', function () {
- describe('Date range', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return a range filter for date_range agg', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'date_range',
- params: {
- field: '@timestamp',
- ranges: [
- { from: '2014-01-01', to: '2014-12-31' }
- ]
- }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('date_range')[0];
- const from = new Date('1 Feb 2015');
- const to = new Date('7 Feb 2015');
- const filter = createFilterDateRange(aggConfig, { from: from.valueOf(), to: to.valueOf() });
- expect(filter).to.have.property('range');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.range).to.have.property('@timestamp');
- expect(filter.range['@timestamp']).to.have.property('gte', moment(from).toISOString());
- expect(filter.range['@timestamp']).to.have.property('lt', moment(to).toISOString());
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js
deleted file mode 100644
index 409c9a40b19c4..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/filters.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterFilters } from '../../../buckets/create_filter/filters';
-
-describe('AggConfig Filters', function () {
- describe('filters', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return a filters filter', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'filters',
- schema: 'segment',
- params: {
- filters: [
- { input: { query: 'type:apache', language: 'lucene' } },
- { input: { query: 'type:nginx', language: 'lucene' } }
- ]
- }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('filters')[0];
- const filter = createFilterFilters(aggConfig, 'type:nginx');
- expect(filter.query.bool.must[0].query_string.query).to.be('type:nginx');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.meta).to.have.property('alias', 'type:nginx');
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js
deleted file mode 100644
index 6d4534bba4dd1..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/histogram.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterHistogram } from '../../../buckets/create_filter/histogram';
-
-describe('AggConfig Filters', function () {
- describe('histogram', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return an range filter for histogram', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'histogram',
- schema: 'segment',
- params: { field: 'bytes', interval: 1024 }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('histogram')[0];
- const filter = createFilterHistogram(aggConfig, 2048);
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter).to.have.property('range');
- expect(filter.range).to.have.property('bytes');
- expect(filter.range.bytes).to.have.property('gte', 2048);
- expect(filter.range.bytes).to.have.property('lt', 3072);
- expect(filter.meta).to.have.property('formattedValue', '2,048');
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js
deleted file mode 100644
index e29ebd689db20..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/ip_range.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterIpRange } from '../../../buckets/create_filter/ip_range';
-describe('AggConfig Filters', function () {
-
- describe('IP range', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return a range filter for ip_range agg', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'ip_range',
- schema: 'segment',
- params: {
- field: 'ip',
- ipRangeType: 'fromTo',
- ranges: {
- fromTo: [
- { from: '0.0.0.0', to: '1.1.1.1' }
- ]
- }
- }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('ip_range')[0];
- const filter = createFilterIpRange(aggConfig, { type: 'fromTo', from: '0.0.0.0', to: '1.1.1.1' });
- expect(filter).to.have.property('range');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.range).to.have.property('ip');
- expect(filter.range.ip).to.have.property('gte', '0.0.0.0');
- expect(filter.range.ip).to.have.property('lte', '1.1.1.1');
- });
-
- it('should return a range filter for ip_range agg using a CIDR mask', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'ip_range',
- schema: 'segment',
- params: {
- field: 'ip',
- ipRangeType: 'mask',
- ranges: {
- mask: [
- { mask: '67.129.65.201/27' }
- ]
- }
- }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('ip_range')[0];
- const filter = createFilterIpRange(aggConfig, { type: 'mask', mask: '67.129.65.201/27' });
- expect(filter).to.have.property('range');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.range).to.have.property('ip');
- expect(filter.range.ip).to.have.property('gte', '67.129.65.192');
- expect(filter.range.ip).to.have.property('lte', '67.129.65.223');
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js
deleted file mode 100644
index 228fd6bce5cfb..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/range.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterRange } from '../../../buckets/create_filter/range';
-
-describe('AggConfig Filters', function () {
-
- describe('range', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return a range filter for range agg', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- {
- type: 'range',
- schema: 'segment',
- params: {
- field: 'bytes',
- ranges: [
- { from: 1024, to: 2048 }
- ]
- }
- }
- ]
- });
-
- const aggConfig = vis.aggs.byName('range')[0];
- const filter = createFilterRange(aggConfig, { gte: 1024, lt: 2048.0 });
- expect(filter).to.have.property('range');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.range).to.have.property('bytes');
- expect(filter.range.bytes).to.have.property('gte', 1024.0);
- expect(filter.range.bytes).to.have.property('lt', 2048.0);
- expect(filter.meta).to.have.property('formattedValue', '≥ 1,024 and < 2,048');
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js
deleted file mode 100644
index a2812ffb97965..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/create_filter/terms.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { VisProvider } from '../../../../vis';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { createFilterTerms } from '../../../buckets/create_filter/terms';
-
-describe('AggConfig Filters', function () {
-
- describe('terms', function () {
- let indexPattern;
- let Vis;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- Vis = Private(VisProvider);
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
- }));
-
- it('should return a match_phrase filter for terms', function () {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ]
- });
- const aggConfig = vis.aggs.byName('terms')[0];
- const filter = createFilterTerms(aggConfig, 'apache');
- expect(filter).to.have.property('query');
- expect(filter.query).to.have.property('match_phrase');
- expect(filter.query.match_phrase).to.have.property('_type');
- expect(filter.query.match_phrase._type).to.be('apache');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
-
- });
-
- it('should set query to true or false for boolean filter', () => {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [ { type: 'terms', schema: 'segment', params: { field: 'ssl' } } ]
- });
- const aggConfig = vis.aggs.byName('terms')[0];
- const filterFalse = createFilterTerms(aggConfig, 0);
- expect(filterFalse).to.have.property('query');
- expect(filterFalse.query).to.have.property('match_phrase');
- expect(filterFalse.query.match_phrase).to.have.property('ssl');
- expect(filterFalse.query.match_phrase.ssl).to.be(false);
-
- const filterTrue = createFilterTerms(aggConfig, 1);
- expect(filterTrue).to.have.property('query');
- expect(filterTrue.query).to.have.property('match_phrase');
- expect(filterTrue.query.match_phrase).to.have.property('ssl');
- expect(filterTrue.query.match_phrase.ssl).to.be(true);
- });
-
- it('should generate correct __missing__ filter', () => {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ]
- });
- const aggConfig = vis.aggs.byName('terms')[0];
- const filter = createFilterTerms(aggConfig, '__missing__');
- expect(filter).to.have.property('exists');
- expect(filter.exists).to.have.property('field', '_type');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.meta).to.have.property('negate', true);
- });
-
- it('should generate correct __other__ filter', () => {
- const vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [ { type: 'terms', schema: 'segment', params: { field: '_type' } } ]
- });
- const aggConfig = vis.aggs.byName('terms')[0];
- const filter = createFilterTerms(aggConfig, '__other__', { terms: ['apache'] })[0];
- expect(filter).to.have.property('query');
- expect(filter.query).to.have.property('bool');
- expect(filter.query.bool).to.have.property('should');
- expect(filter.query.bool.should[0]).to.have.property('match_phrase');
- expect(filter.query.bool.should[0].match_phrase).to.have.property('_type', 'apache');
- expect(filter).to.have.property('meta');
- expect(filter.meta).to.have.property('index', indexPattern.id);
- expect(filter.meta).to.have.property('negate', true);
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js
deleted file mode 100644
index 7b8f099a1f9b4..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js
+++ /dev/null
@@ -1,124 +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 $ from 'jquery';
-import ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import { VisProvider } from '../../../../vis';
-import { intervalOptions } from '../../../buckets/_interval_options';
-
-describe.skip('editor', function () {
-
- let indexPattern;
- let vis;
- let agg;
- let render;
- let $scope;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private, $injector, $compile) {
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- const Vis = Private(VisProvider);
-
- /**
- * Render the AggParams editor for the date histogram aggregation
- *
- * @param {object} params - the agg params to give to the date_histogram
- * by default
- * @return {object} - object pointing to the different inputs, keys
- * are the aggParam name and the value is an object
- * with $el, $scope, and a few helpers for getting
- * data from them.
- */
- render = function (params) {
- vis = new Vis(indexPattern, {
- type: 'histogram',
- aggs: [
- { schema: 'metric', type: 'avg', params: { field: 'bytes' } },
- { schema: 'segment', type: 'date_histogram', params: params || {} }
- ]
- });
-
- const $el = $('' +
- ' ');
- const $parentScope = $injector.get('$rootScope').$new();
-
- agg = $parentScope.agg = vis.aggs.bySchemaName('segment')[0];
- $parentScope.groupName = 'buckets';
- $parentScope.vis = vis;
-
- $compile($el)($parentScope);
- $scope = $el.scope();
- $scope.$digest();
-
- const $inputs = $('vis-agg-param-editor', $el);
- return _.transform($inputs.toArray(), function (inputs, e) {
- const $el = $(e);
- const $scope = $el.scope();
-
- inputs[$scope.aggParam.name] = {
- $el: $el,
- $scope: $scope,
- $input: function () {
- return $el.find('[ng-model]').first();
- },
- modelValue: function () {
- return this.$input().controller('ngModel').$modelValue;
- }
- };
- }, {});
- };
-
- }));
-
- describe('random field/interval', function () {
- let params;
- let field;
- let interval;
-
- beforeEach(ngMock.inject(function () {
- field = _.sample(indexPattern.fields);
- interval = _.sample(intervalOptions);
- params = render({ field: field, interval: interval.val });
- }));
-
- it('renders the field editor', function () {
- expect(agg.params.field).to.be(field);
-
- expect(params).to.have.property('field');
- expect(params.field).to.have.property('$el');
- expect($scope.agg.params.field).to.be(field);
- });
-
- it('renders the interval editor', function () {
- expect(agg.params.interval).to.be(interval.val);
-
- expect(params).to.have.property('interval');
- expect(params.interval).to.have.property('$el');
- expect($scope.agg.params.interval).to.be(interval.val);
- });
- });
-
-
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js
deleted file mode 100644
index 88646bd36ee80..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_params.js
+++ /dev/null
@@ -1,215 +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 moment from 'moment';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import ngMock from 'ng_mock';
-import AggParamWriterProvider from '../../agg_param_writer';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import chrome from '../../../../chrome';
-import { aggTypes } from '../../..';
-import { AggConfig } from '../../../agg_config';
-import { timefilter } from 'ui/timefilter';
-
-const config = chrome.getUiSettingsClient();
-
-describe('date_histogram params', function () {
-
- let paramWriter;
- let writeInterval;
- let write;
-
- let getTimeBounds;
- let timeField;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- const AggParamWriter = Private(AggParamWriterProvider);
- const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- timeField = indexPattern.timeFieldName;
-
- paramWriter = new AggParamWriter({ aggType: 'date_histogram' });
- writeInterval = function (interval, timeRange, params = {}) {
- return paramWriter.write({ ...params, interval: interval, field: timeField, timeRange: timeRange });
- };
- write = (params) => {
- return paramWriter.write({ interval: '10s', ...params });
- };
-
- const now = moment();
- getTimeBounds = function (n, units) {
- timefilter.enableAutoRefreshSelector();
- timefilter.enableTimeRangeSelector();
- return {
- from: now.clone().subtract(n, units),
- to: now.clone()
- };
- };
- }));
-
- describe('interval', function () {
- it('accepts a valid calendar interval', function () {
- const output = writeInterval('d');
- expect(output.params).to.have.property('calendar_interval', '1d');
- });
-
- it('accepts a valid fixed interval', () => {
- const output = writeInterval('100s');
- expect(output.params).to.have.property('fixed_interval', '100s');
- });
-
- it('throws error when interval is invalid', function () {
- expect(() => writeInterval('foo')).to.throw('TypeError: "foo" is not a valid interval.');
- });
-
- it('automatically picks an interval', function () {
- const timeBounds = getTimeBounds(15, 'm');
- const output = writeInterval('auto', timeBounds);
- expect(output.params).to.have.property('fixed_interval', '30s');
- });
-
- it('does not scale down the interval', () => {
- const timeBounds = getTimeBounds(1, 'm');
- const output = writeInterval('h', timeBounds);
- expect(output.params).to.have.property('calendar_interval', '1h');
- expect(output).not.to.have.property('metricScaleText');
- expect(output).not.to.have.property('metricScale');
- });
-
- describe('scaling behavior', () => {
-
- it('should not scale without scaleMetricValues: true', function () {
- const timeBounds = getTimeBounds(30, 'm');
- const output = writeInterval('s', timeBounds);
- expect(output.params).to.have.property('fixed_interval', '10s');
- expect(output).not.to.have.property('metricScaleText');
- expect(output).not.to.property('metricScale');
- });
-
- describe('only scales when all metrics are sum or count', function () {
- const tests = [
- [ false, 'avg', 'count', 'sum' ],
- [ true, 'count', 'sum' ],
- [ false, 'count', 'cardinality' ]
- ];
-
- tests.forEach(function (test) {
- const should = test.shift();
- const typeNames = test.slice();
-
- it(typeNames.join(', ') + ' should ' + (should ? '' : 'not') + ' scale', function () {
- const timeBounds = getTimeBounds(1, 'y');
-
- const vis = paramWriter.vis;
- vis.aggs.aggs.splice(0);
-
- const histoConfig = new AggConfig(vis.aggs, {
- type: aggTypes.buckets.find(agg => agg.name === 'date_histogram'),
- schema: 'segment',
- params: { interval: 's', field: timeField, timeRange: timeBounds, scaleMetricValues: true }
- });
-
- vis.aggs.aggs.push(histoConfig);
-
- typeNames.forEach(function (type) {
- vis.aggs.aggs.push(new AggConfig(vis.aggs, {
- type: aggTypes.metrics.find(agg => agg.name === type),
- schema: 'metric'
- }));
- });
-
- const output = histoConfig.write(vis.aggs);
- expect(_.has(output, 'metricScale')).to.be(should);
- });
- });
- });
- });
- });
-
- describe('time_zone', () => {
- beforeEach(() => {
- sinon.stub(config, 'get');
- sinon.stub(config, 'isDefault');
- });
-
- it('should use the specified time_zone', () => {
- const output = write({ time_zone: 'Europe/Kiev' });
- expect(output.params).to.have.property('time_zone', 'Europe/Kiev');
- });
-
- it('should use the Kibana time_zone if no parameter specified', () => {
- config.isDefault.withArgs('dateFormat:tz').returns(false);
- config.get.withArgs('dateFormat:tz').returns('Europe/Riga');
- const output = write({});
- expect(output.params).to.have.property('time_zone', 'Europe/Riga');
- });
-
- it('should use the fixed time_zone from the index pattern typeMeta', () => {
- _.set(paramWriter.indexPattern, ['typeMeta', 'aggs', 'date_histogram', timeField, 'time_zone'], 'Europe/Rome');
- const output = write({ field: timeField });
- expect(output.params).to.have.property('time_zone', 'Europe/Rome');
- });
-
- afterEach(() => {
- config.get.restore();
- config.isDefault.restore();
- });
- });
-
- describe('extended_bounds', function () {
- it('should write a long value if a moment passed in', function () {
- const then = moment(0);
- const now = moment(500);
- const output = write({
- extended_bounds: {
- min: then,
- max: now
- }
- });
-
- expect(typeof output.params.extended_bounds.min).to.be('number');
- expect(typeof output.params.extended_bounds.max).to.be('number');
- expect(output.params.extended_bounds.min).to.be(then.valueOf());
- expect(output.params.extended_bounds.max).to.be(now.valueOf());
-
-
- });
-
- it('should write a long if a long is passed', function () {
- const then = 0;
- const now = 500;
- const output = write({
- extended_bounds: {
- min: then,
- max: now
- }
- });
-
- expect(typeof output.params.extended_bounds.min).to.be('number');
- expect(typeof output.params.extended_bounds.max).to.be('number');
- expect(output.params.extended_bounds.min).to.be(then.valueOf());
- expect(output.params.extended_bounds.max).to.be(now.valueOf());
-
-
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js
deleted file mode 100644
index ae52fb476c120..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/significant_terms.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { aggTypes } from '../..';
-
-describe('Significant Terms Agg', function () {
-
- describe('order agg editor UI', function () {
-
- describe('convert include/exclude from old format', function () {
-
- let $rootScope;
-
- function init({ aggParams = {} }) {
- ngMock.module('kibana');
- ngMock.inject(function (_$rootScope_) {
- const significantTerms = aggTypes.buckets.find(agg => agg.name === 'significant_terms');
-
- $rootScope = _$rootScope_;
- $rootScope.agg = {
- id: 'test',
- params: aggParams,
- type: significantTerms,
- getParam: key => aggParams[key],
- };
- });
- }
-
- function testSerializeAndWrite(aggConfig) {
- const includeArg = $rootScope.agg.type.paramByName('include');
- const excludeArg = $rootScope.agg.type.paramByName('exclude');
-
- expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404');
- expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400');
-
- const output = { params: {} };
-
- includeArg.write(aggConfig, output);
- excludeArg.write(aggConfig, output);
-
- expect(output.params.include).to.equal('404');
- expect(output.params.exclude).to.equal('400');
- }
-
- it('it doesnt do anything with string type', function () {
- init({
- aggParams: {
- include: '404',
- exclude: '400',
- field: {
- type: 'string'
- },
- }
- });
-
- testSerializeAndWrite($rootScope.agg);
- });
-
- it('converts object to string type', function () {
- init({
- aggParams: {
- include: {
- pattern: '404'
- }, exclude: {
- pattern: '400'
- },
- field: {
- type: 'string'
- },
- }
- });
-
- testSerializeAndWrite($rootScope.agg);
- });
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js b/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js
deleted file mode 100644
index 600323d32d38c..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/terms.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import { aggTypes } from '../..';
-
-describe('Terms Agg', function () {
- describe('order agg editor UI', function () {
-
- let $rootScope;
-
- function init({ metricAggs = [], aggParams = {} }) {
- ngMock.module('kibana');
- ngMock.inject(function ($controller, _$rootScope_) {
- const terms = aggTypes.buckets.find(agg => agg.name === 'terms');
- const orderAggController = terms.paramByName('orderAgg').controller;
-
- $rootScope = _$rootScope_;
- $rootScope.agg = {
- id: 'test',
- params: aggParams,
- type: terms,
- vis: {
- aggs: []
- },
- getParam: key => aggParams[key],
- };
- $rootScope.metricAggs = metricAggs;
- $controller(orderAggController, { $scope: $rootScope });
- $rootScope.$digest();
- });
- }
-
- // should be rewritten after EUIficate order_agg.html
- it.skip('selects _key if the selected metric becomes incompatible', function () {
- init({
- metricAggs: [
- {
- id: 'agg1',
- type: {
- name: 'count'
- }
- }
- ]
- });
-
- expect($rootScope.agg.params.orderBy).to.be('agg1');
- $rootScope.metricAggs = [
- {
- id: 'agg1',
- type: {
- name: 'top_hits'
- }
- }
- ];
- $rootScope.$digest();
- expect($rootScope.agg.params.orderBy).to.be('_key');
- });
-
- // should be rewritten after EUIficate order_agg.html
- it.skip('selects _key if the selected metric is removed', function () {
- init({
- metricAggs: [
- {
- id: 'agg1',
- type: {
- name: 'count'
- }
- }
- ]
- });
- expect($rootScope.agg.params.orderBy).to.be('agg1');
- $rootScope.metricAggs = [];
- $rootScope.$digest();
- expect($rootScope.agg.params.orderBy).to.be('_key');
- });
-
- describe.skip('custom field formatter', () => {
- beforeEach(() => {
- init({
- metricAggs: [
- {
- id: 'agg1',
- type: {
- name: 'count'
- }
- }
- ],
- aggParams: {
- otherBucketLabel: 'Other',
- missingBucketLabel: 'Missing'
- }
- });
- $rootScope.$digest();
- });
-
- it ('converts __other__ key', () => {
- const formatter = $rootScope.agg.type.getFormat($rootScope.agg).getConverterFor('text');
- expect(formatter('__other__')).to.be('Other');
- });
-
- it ('converts __missing__ key', () => {
- const formatter = $rootScope.agg.type.getFormat($rootScope.agg).getConverterFor('text');
- expect(formatter('__missing__')).to.be('Missing');
- });
- });
-
- it('adds "custom metric" option');
- it('lists all metric agg responses');
- it('lists individual values of a multi-value metric');
- it('displays a metric editor if "custom metric" is selected');
- it('saves the "custom metric" to state and refreshes from it');
- it('invalidates the form if the metric agg form is not complete');
-
- describe.skip('convert include/exclude from old format', function () {
-
- it('it doesnt do anything with string type', function () {
- init({
- aggParams: {
- include: '404',
- exclude: '400',
- field: {
- type: 'string'
- },
- }
- });
-
- const aggConfig = $rootScope.agg;
- const includeArg = $rootScope.agg.type.params.byName.include;
- const excludeArg = $rootScope.agg.type.params.byName.exclude;
-
- expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404');
- expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400');
-
- const output = { params: {} };
-
- includeArg.write(aggConfig, output);
- excludeArg.write(aggConfig, output);
-
- expect(output.params.include).to.equal('404');
- expect(output.params.exclude).to.equal('400');
- });
-
- it('converts object to string type', function () {
- init({
- aggParams: {
- include: {
- pattern: '404'
- }, exclude: {
- pattern: '400'
- },
- field: {
- type: 'string'
- },
- }
- });
-
- const aggConfig = $rootScope.agg;
- const includeArg = $rootScope.agg.type.params.byName.include;
- const excludeArg = $rootScope.agg.type.params.byName.exclude;
-
- expect(includeArg.serialize(aggConfig.params.include, aggConfig)).to.equal('404');
- expect(excludeArg.serialize(aggConfig.params.exclude, aggConfig)).to.equal('400');
-
- const output = { params: {} };
-
- includeArg.write(aggConfig, output);
- excludeArg.write(aggConfig, output);
-
- expect(output.params.include).to.equal('404');
- expect(output.params.exclude).to.equal('400');
- });
-
- });
- });
-});
diff --git a/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js b/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js
deleted file mode 100644
index f30572bcc0ed5..0000000000000
--- a/src/legacy/ui/public/agg_types/__tests__/utils/_stub_agg_params.js
+++ /dev/null
@@ -1,67 +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 sinon from 'sinon';
-import { BaseParamType } from '../../param_types/base';
-import { FieldParamType } from '../../param_types/field';
-import { OptionedParamType } from '../../param_types/optioned';
-import { createLegacyClass } from '../../../utils/legacy_class';
-
-function ParamClassStub(parent, body) {
- const stub = sinon.spy(body || function () {
- stub.Super && stub.Super.call(this);
- });
- if (parent) createLegacyClass(stub).inherits(parent);
- return stub;
-}
-
-/**
- * stub all of the param classes, but ensure that they still inherit properly.
- * This method should be passed directly to ngMock.inject();
- *
- * ```js
- * let stubParamClasses = require('./utils/_stub_agg_params');
- * describe('something', function () {
- * beforeEach(ngMock.inject(stubParamClasses));
- * })
- * ```
- *
- * @param {PrivateLoader} Private - The private module loader, inject by passing this function to ngMock.inject()
- * @return {undefined}
- */
-// eslint-disable-next-line import/no-default-export
-export default function stubParamClasses(Private) {
- const BaseAggParam = Private.stub(
- BaseParamType,
- new ParamClassStub(null, function (config) {
- _.assign(this, config);
- })
- );
-
- Private.stub(
- FieldParamType,
- new ParamClassStub(BaseAggParam)
- );
-
- Private.stub(
- OptionedParamType,
- new ParamClassStub(BaseAggParam)
- );
-}
diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts
index a5b1aa7cf9c0b..eedfc1cc05a84 100644
--- a/src/legacy/ui/public/agg_types/agg_config.ts
+++ b/src/legacy/ui/public/agg_types/agg_config.ts
@@ -332,7 +332,7 @@ export class AggConfig {
return this.type.getValue(this, bucket);
}
- getKey(bucket: any, key: string) {
+ getKey(bucket: any, key?: string) {
if (this.type.getKey) {
return this.type.getKey(bucket, key, this);
} else {
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts
new file mode 100644
index 0000000000000..f67fa55b27859
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts
@@ -0,0 +1,122 @@
+/*
+ * 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 { RangeFilter } from '@kbn/es-query';
+import { createFilterDateHistogram } from './date_histogram';
+import { intervalOptions } from '../_interval_options';
+import { AggConfigs } from '../../agg_configs';
+import { IBucketDateHistogramAggConfig } from '../date_histogram';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('date_histogram', () => {
+ let agg: IBucketDateHistogramAggConfig;
+ let filter: RangeFilter;
+ let bucketStart: any;
+ let field: any;
+
+ const init = (interval: string = 'auto', duration: any = moment.duration(15, 'minutes')) => {
+ field = {
+ name: 'date',
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+ const aggConfigs = new AggConfigs(
+ indexPattern,
+ [
+ {
+ type: BUCKET_TYPES.DATE_HISTOGRAM,
+ schema: 'segment',
+ params: { field: field.name, interval, customInterval: '5d' },
+ },
+ ],
+ null
+ );
+ const bucketKey = 1422579600000;
+
+ agg = aggConfigs.aggs[0] as IBucketDateHistogramAggConfig;
+ bucketStart = moment(bucketKey);
+
+ const timePad = moment.duration(duration / 2);
+
+ agg.buckets.setBounds({
+ min: bucketStart.clone().subtract(timePad),
+ max: bucketStart.clone().add(timePad),
+ });
+ agg.buckets.setInterval(interval);
+ filter = createFilterDateHistogram(agg, bucketKey);
+ };
+
+ it('creates a valid range filter', () => {
+ init();
+
+ expect(filter).toHaveProperty('range');
+ expect(filter.range).toHaveProperty(field.name);
+
+ const fieldParams = filter.range[field.name];
+ expect(fieldParams).toHaveProperty('gte');
+ expect(typeof fieldParams.gte).toBe('string');
+
+ expect(fieldParams).toHaveProperty('lt');
+ expect(typeof fieldParams.lt).toBe('string');
+
+ expect(fieldParams).toHaveProperty('format');
+ expect(fieldParams.format).toBe('strict_date_optional_time');
+
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ });
+
+ it('extends the filter edge to 1ms before the next bucket for all interval options', () => {
+ intervalOptions.forEach(option => {
+ let duration;
+ if (option.val !== 'custom' && moment(1, option.val).isValid()) {
+ // @ts-ignore
+ duration = moment.duration(10, option.val);
+
+ if (+duration < 10) {
+ throw new Error('unable to create interval for ' + option.val);
+ }
+ }
+ init(option.val, duration);
+
+ const interval = agg.buckets.getInterval();
+ const params = filter.range[field.name];
+
+ expect(params.gte).toBe(bucketStart.toISOString());
+ expect(params.lt).toBe(
+ bucketStart
+ .clone()
+ .add(interval)
+ .toISOString()
+ );
+ });
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts
index 6bda085335309..8c6140cc4b37a 100644
--- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts
@@ -21,7 +21,10 @@ import moment from 'moment';
import { buildRangeFilter } from '@kbn/es-query';
import { IBucketDateHistogramAggConfig } from '../date_histogram';
-export const createFilterDateHistogram = (agg: IBucketDateHistogramAggConfig, key: string) => {
+export const createFilterDateHistogram = (
+ agg: IBucketDateHistogramAggConfig,
+ key: string | number
+) => {
const start = moment(key);
const interval = agg.buckets.getInterval();
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts
new file mode 100644
index 0000000000000..35b6c38bad799
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 { createFilterDateRange } from './date_range';
+import { DateFormat } from '../../../../../../plugins/data/common';
+import { AggConfigs } from '../../agg_configs';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('Date range', () => {
+ const getAggConfigs = () => {
+ const field = {
+ name: '@timestamp',
+ format: new DateFormat({}, () => {}),
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ type: BUCKET_TYPES.DATE_RANGE,
+ params: {
+ field: '@timestamp',
+ ranges: [{ from: '2014-01-01', to: '2014-12-31' }],
+ },
+ },
+ ],
+ null
+ );
+ };
+
+ it('should return a range filter for date_range agg', () => {
+ const aggConfigs = getAggConfigs();
+ const from = new Date('1 Feb 2015');
+ const to = new Date('7 Feb 2015');
+ const filter = createFilterDateRange(aggConfigs.aggs[0], {
+ from: from.valueOf(),
+ to: to.valueOf(),
+ });
+
+ expect(filter).toHaveProperty('range');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.range).toHaveProperty('@timestamp');
+ expect(filter.range['@timestamp']).toHaveProperty('gte', moment(from).toISOString());
+ expect(filter.range['@timestamp']).toHaveProperty('lt', moment(to).toISOString());
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts
new file mode 100644
index 0000000000000..125532fe070ba
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts
@@ -0,0 +1,66 @@
+/*
+ * 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 { createFilterFilters } from './filters';
+import { AggConfigs } from '../../agg_configs';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('filters', () => {
+ const getAggConfigs = () => {
+ const field = {
+ name: 'bytes',
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ type: 'filters',
+ schema: 'segment',
+ params: {
+ filters: [
+ { input: { query: 'type:apache', language: 'lucene' } },
+ { input: { query: 'type:nginx', language: 'lucene' } },
+ ],
+ },
+ },
+ ],
+ null
+ );
+ };
+ it('should return a filters filter', () => {
+ const aggConfigs = getAggConfigs();
+ const filter = createFilterFilters(aggConfigs.aggs[0], 'type:nginx');
+
+ expect(filter!.query.bool.must[0].query_string.query).toBe('type:nginx');
+ expect(filter!.meta).toHaveProperty('index', '1234');
+ expect(filter!.meta).toHaveProperty('alias', 'type:nginx');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts
new file mode 100644
index 0000000000000..0095df75b8914
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { createFilterHistogram } from './histogram';
+import { AggConfigs } from '../../agg_configs';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+import { BytesFormat } from '../../../../../../plugins/data/common';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('histogram', () => {
+ const getAggConfigs = () => {
+ const field = {
+ name: 'bytes',
+ format: new BytesFormat({}, () => {}),
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: BUCKET_TYPES.HISTOGRAM,
+ type: BUCKET_TYPES.HISTOGRAM,
+ schema: 'buckets',
+ params: {
+ field: 'bytes',
+ interval: 1024,
+ },
+ },
+ ],
+ null
+ );
+ };
+
+ it('should return an range filter for histogram', () => {
+ const aggConfigs = getAggConfigs();
+ const filter = createFilterHistogram(aggConfigs.aggs[0], '2048');
+
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter).toHaveProperty('range');
+ expect(filter.range).toHaveProperty('bytes');
+ expect(filter.range.bytes).toHaveProperty('gte', 2048);
+ expect(filter.range.bytes).toHaveProperty('lt', 3072);
+ expect(filter.meta).toHaveProperty('formattedValue', '2,048');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts
new file mode 100644
index 0000000000000..2e030d820b396
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts
@@ -0,0 +1,104 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createFilterIpRange } from './ip_range';
+import { AggConfigs } from '../../agg_configs';
+import { IpFormat } from '../../../../../../plugins/data/common';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('IP range', () => {
+ const getAggConfigs = (aggs: Array>) => {
+ const field = {
+ name: 'ip',
+ format: IpFormat,
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(indexPattern, aggs, null);
+ };
+
+ it('should return a range filter for ip_range agg', () => {
+ const aggConfigs = getAggConfigs([
+ {
+ type: BUCKET_TYPES.IP_RANGE,
+ schema: 'segment',
+ params: {
+ field: 'ip',
+ ipRangeType: 'range',
+ ranges: {
+ fromTo: [{ from: '0.0.0.0', to: '1.1.1.1' }],
+ },
+ },
+ },
+ ]);
+
+ const filter = createFilterIpRange(aggConfigs.aggs[0], {
+ type: 'range',
+ from: '0.0.0.0',
+ to: '1.1.1.1',
+ });
+
+ expect(filter).toHaveProperty('range');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.range).toHaveProperty('ip');
+ expect(filter.range.ip).toHaveProperty('gte', '0.0.0.0');
+ expect(filter.range.ip).toHaveProperty('lte', '1.1.1.1');
+ });
+
+ it('should return a range filter for ip_range agg using a CIDR mask', () => {
+ const aggConfigs = getAggConfigs([
+ {
+ type: BUCKET_TYPES.IP_RANGE,
+ schema: 'segment',
+ params: {
+ field: 'ip',
+ ipRangeType: 'mask',
+ ranges: {
+ mask: [{ mask: '67.129.65.201/27' }],
+ },
+ },
+ },
+ ]);
+
+ const filter = createFilterIpRange(aggConfigs.aggs[0], {
+ type: 'mask',
+ mask: '67.129.65.201/27',
+ });
+
+ expect(filter).toHaveProperty('range');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.range).toHaveProperty('ip');
+ expect(filter.range.ip).toHaveProperty('gte', '67.129.65.192');
+ expect(filter.range.ip).toHaveProperty('lte', '67.129.65.223');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts
new file mode 100644
index 0000000000000..04476ba62ccd5
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 { createFilterRange } from './range';
+import { BytesFormat } from '../../../../../../plugins/data/common';
+import { AggConfigs } from '../../agg_configs';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('range', () => {
+ const getAggConfigs = () => {
+ const field = {
+ name: 'bytes',
+ format: new BytesFormat({}, () => {}),
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: BUCKET_TYPES.RANGE,
+ type: BUCKET_TYPES.RANGE,
+ schema: 'buckets',
+ params: {
+ field: 'bytes',
+ ranges: [{ from: 1024, to: 2048 }],
+ },
+ },
+ ],
+ null
+ );
+ };
+
+ it('should return a range filter for range agg', () => {
+ const aggConfigs = getAggConfigs();
+ const filter = createFilterRange(aggConfigs.aggs[0], { gte: 1024, lt: 2048.0 });
+
+ expect(filter).toHaveProperty('range');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.range).toHaveProperty('bytes');
+ expect(filter.range.bytes).toHaveProperty('gte', 1024.0);
+ expect(filter.range.bytes).toHaveProperty('lt', 2048.0);
+ expect(filter.meta).toHaveProperty('formattedValue', '≥ 1,024 and < 2,048');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts
new file mode 100644
index 0000000000000..b00e939eac8d8
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.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 { ExistsFilter, Filter } from '@kbn/es-query';
+import { createFilterTerms } from './terms';
+import { AggConfigs } from '../../agg_configs';
+import { BUCKET_TYPES } from '../bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('AggConfig Filters', () => {
+ describe('terms', () => {
+ const getAggConfigs = (aggs: Array>) => {
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ const field = {
+ name: 'field',
+ indexPattern,
+ };
+
+ return new AggConfigs(indexPattern, aggs, null);
+ };
+
+ it('should return a match_phrase filter for terms', () => {
+ const aggConfigs = getAggConfigs([
+ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } },
+ ]);
+
+ const filter = createFilterTerms(aggConfigs.aggs[0], 'apache', {}) as Filter;
+
+ expect(filter).toHaveProperty('query');
+ expect(filter.query).toHaveProperty('match_phrase');
+ expect(filter.query.match_phrase).toHaveProperty('field');
+ expect(filter.query.match_phrase.field).toBe('apache');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ });
+
+ it('should set query to true or false for boolean filter', () => {
+ const aggConfigs = getAggConfigs([
+ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } },
+ ]);
+
+ const filterFalse = createFilterTerms(aggConfigs.aggs[0], '', {}) as Filter;
+
+ expect(filterFalse).toHaveProperty('query');
+ expect(filterFalse.query).toHaveProperty('match_phrase');
+ expect(filterFalse.query.match_phrase).toHaveProperty('field');
+ expect(filterFalse.query.match_phrase.field).toBeFalsy();
+
+ const filterTrue = createFilterTerms(aggConfigs.aggs[0], '1', {}) as Filter;
+
+ expect(filterTrue).toHaveProperty('query');
+ expect(filterTrue.query).toHaveProperty('match_phrase');
+ expect(filterTrue.query.match_phrase).toHaveProperty('field');
+ expect(filterTrue.query.match_phrase.field).toBeTruthy();
+ });
+ //
+ it('should generate correct __missing__ filter', () => {
+ const aggConfigs = getAggConfigs([
+ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } },
+ ]);
+ const filter = createFilterTerms(aggConfigs.aggs[0], '__missing__', {}) as ExistsFilter;
+
+ expect(filter).toHaveProperty('exists');
+ expect(filter.exists).toHaveProperty('field', 'field');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.meta).toHaveProperty('negate', true);
+ });
+ //
+ it('should generate correct __other__ filter', () => {
+ const aggConfigs = getAggConfigs([
+ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } },
+ ]);
+
+ const [filter] = createFilterTerms(aggConfigs.aggs[0], '__other__', {
+ terms: ['apache'],
+ }) as Filter[];
+
+ expect(filter).toHaveProperty('query');
+ expect(filter.query).toHaveProperty('bool');
+ expect(filter.query.bool).toHaveProperty('should');
+ expect(filter.query.bool.should[0]).toHaveProperty('match_phrase');
+ expect(filter.query.bool.should[0].match_phrase).toHaveProperty('field', 'apache');
+ expect(filter).toHaveProperty('meta');
+ expect(filter.meta).toHaveProperty('index', '1234');
+ expect(filter.meta).toHaveProperty('negate', true);
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.test.ts b/src/legacy/ui/public/agg_types/buckets/date_range.test.ts
new file mode 100644
index 0000000000000..7d9fe002636a2
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/date_range.test.ts
@@ -0,0 +1,112 @@
+/*
+ * 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 { AggConfigs } from '../agg_configs';
+import { BUCKET_TYPES } from './bucket_agg_types';
+import { npStart } from 'ui/new_platform';
+
+jest.mock('ui/new_platform');
+
+describe('date_range params', () => {
+ const getAggConfigs = (params: Record = {}, hasIncludeTypeMeta: boolean = true) => {
+ const field = {
+ name: 'bytes',
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ typeMeta: hasIncludeTypeMeta
+ ? {
+ aggs: {
+ date_range: {
+ bytes: {
+ time_zone: 'defaultTimeZone',
+ },
+ },
+ },
+ }
+ : undefined,
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: BUCKET_TYPES.DATE_RANGE,
+ type: BUCKET_TYPES.DATE_RANGE,
+ schema: 'buckets',
+ params,
+ },
+ ],
+ null
+ );
+ };
+
+ describe('getKey', () => {
+ it('should return object', () => {
+ const aggConfigs = getAggConfigs();
+ const dateRange = aggConfigs.aggs[0];
+ const bucket = { from: 'from-date', to: 'to-date', key: 'from-dateto-date' };
+
+ expect(dateRange.getKey(bucket)).toEqual({ from: 'from-date', to: 'to-date' });
+ });
+ });
+
+ describe('time_zone', () => {
+ it('should use the specified time_zone', () => {
+ const aggConfigs = getAggConfigs({
+ time_zone: 'Europe/Minsk',
+ field: 'bytes',
+ });
+ const dateRange = aggConfigs.aggs[0];
+ const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE];
+
+ expect(params.time_zone).toBe('Europe/Minsk');
+ });
+
+ it('should use the fixed time_zone from the index pattern typeMeta', () => {
+ const aggConfigs = getAggConfigs({
+ field: 'bytes',
+ });
+ const dateRange = aggConfigs.aggs[0];
+ const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE];
+
+ expect(params.time_zone).toBe('defaultTimeZone');
+ });
+
+ it('should use the Kibana time_zone if no parameter specified', () => {
+ npStart.core.uiSettings.get = jest.fn(() => 'kibanaTimeZone');
+
+ const aggConfigs = getAggConfigs(
+ {
+ field: 'bytes',
+ },
+ false
+ );
+ const dateRange = aggConfigs.aggs[0];
+ const params = dateRange.toDsl()[BUCKET_TYPES.DATE_RANGE];
+
+ expect(params.time_zone).toBe('kibanaTimeZone');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/ui/public/agg_types/buckets/filters.ts
index f0450f220f610..44a97abb7a1d7 100644
--- a/src/legacy/ui/public/agg_types/buckets/filters.ts
+++ b/src/legacy/ui/public/agg_types/buckets/filters.ts
@@ -21,7 +21,6 @@ import _ from 'lodash';
import angular from 'angular';
import { i18n } from '@kbn/i18n';
-import { Storage } from 'ui/storage';
import chrome from 'ui/chrome';
import { buildEsQuery } from '@kbn/es-query';
@@ -29,6 +28,7 @@ import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/contr
import { createFilterFilters } from './create_filter/filters';
import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type';
import { setup as data } from '../../../../core_plugins/data/public/legacy';
+import { Storage } from '../../../../../plugins/kibana_utils/public';
const { getQueryLog } = data.query.helpers;
const config = chrome.getUiSettingsClient();
diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
new file mode 100644
index 0000000000000..5c599f16e09c2
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts
@@ -0,0 +1,216 @@
+/*
+ * 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 { geoHashBucketAgg, IBucketGeoHashGridAggConfig } from './geo_hash';
+import { AggConfigs } from '../agg_configs';
+import { BUCKET_TYPES } from './bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('Geohash Agg', () => {
+ const getAggConfigs = (params?: Record) => {
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ const field = {
+ name: 'location',
+ indexPattern,
+ };
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: BUCKET_TYPES.GEOHASH_GRID,
+ type: BUCKET_TYPES.GEOHASH_GRID,
+ schema: 'segment',
+ params: {
+ field: {
+ name: 'location',
+ },
+ isFilteredByCollar: true,
+ useGeocentroid: true,
+ mapZoom: 10,
+ mapBounds: {
+ top_left: { lat: 1.0, lon: -1.0 },
+ bottom_right: { lat: -1.0, lon: 1.0 },
+ },
+ ...params,
+ },
+ },
+ ],
+ null
+ );
+ };
+
+ describe('precision parameter', () => {
+ const PRECISION_PARAM_INDEX = 2;
+
+ let precisionParam: any;
+
+ beforeEach(() => {
+ precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX];
+ });
+
+ it('should select precision parameter', () => {
+ expect(precisionParam.name).toEqual('precision');
+ });
+
+ describe('precision parameter write', () => {
+ const zoomToGeoHashPrecision: Record = {
+ 0: 1,
+ 1: 2,
+ 2: 2,
+ 3: 2,
+ 4: 3,
+ 5: 3,
+ 6: 4,
+ 7: 4,
+ 8: 4,
+ 9: 5,
+ 10: 5,
+ 11: 6,
+ 12: 6,
+ 13: 6,
+ 14: 7,
+ 15: 7,
+ 16: 8,
+ 17: 8,
+ 18: 8,
+ 19: 9,
+ 20: 9,
+ 21: 10,
+ };
+
+ Object.keys(zoomToGeoHashPrecision).forEach((zoomLevel: string) => {
+ it(`zoom level ${zoomLevel} should correspond to correct geohash-precision`, () => {
+ const aggConfigs = getAggConfigs({
+ autoPrecision: true,
+ mapZoom: zoomLevel,
+ });
+
+ const { [BUCKET_TYPES.GEOHASH_GRID]: params } = aggConfigs.aggs[0].toDsl();
+
+ expect(params.precision).toEqual(zoomToGeoHashPrecision[zoomLevel]);
+ });
+ });
+ });
+ });
+
+ describe('getRequestAggs', () => {
+ describe('initial aggregation creation', () => {
+ let aggConfigs: AggConfigs;
+ let geoHashGridAgg: IBucketGeoHashGridAggConfig;
+
+ beforeEach(() => {
+ aggConfigs = getAggConfigs();
+ geoHashGridAgg = aggConfigs.aggs[0] as IBucketGeoHashGridAggConfig;
+ });
+
+ it('should create filter, geohash_grid, and geo_centroid aggregations', () => {
+ const requestAggs = geoHashBucketAgg.getRequestAggs(
+ geoHashGridAgg
+ ) as IBucketGeoHashGridAggConfig[];
+
+ expect(requestAggs.length).toEqual(3);
+ expect(requestAggs[0].type.name).toEqual('filter');
+ expect(requestAggs[1].type.name).toEqual('geohash_grid');
+ expect(requestAggs[2].type.name).toEqual('geo_centroid');
+ });
+
+ it('should set mapCollar in vis session state', () => {
+ const [, geoHashAgg] = geoHashBucketAgg.getRequestAggs(
+ geoHashGridAgg
+ ) as IBucketGeoHashGridAggConfig[];
+
+ expect(geoHashAgg).toHaveProperty('lastMapCollar');
+ expect(geoHashAgg.lastMapCollar).toHaveProperty('top_left');
+ expect(geoHashAgg.lastMapCollar).toHaveProperty('bottom_right');
+ expect(geoHashAgg.lastMapCollar).toHaveProperty('zoom');
+ });
+ });
+ });
+
+ describe('aggregation options', () => {
+ it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => {
+ const aggConfigs = getAggConfigs({ isFilteredByCollar: false });
+ const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs
+ .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+
+ expect(requestAggs.length).toEqual(2);
+ expect(requestAggs[0].type.name).toEqual('geohash_grid');
+ expect(requestAggs[1].type.name).toEqual('geo_centroid');
+ });
+
+ it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => {
+ const aggConfigs = getAggConfigs({ useGeocentroid: false });
+ const requestAggs = geoHashBucketAgg.getRequestAggs(aggConfigs
+ .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+
+ expect(requestAggs.length).toEqual(2);
+ expect(requestAggs[0].type.name).toEqual('filter');
+ expect(requestAggs[1].type.name).toEqual('geohash_grid');
+ });
+ });
+
+ describe('aggregation creation after map interaction', () => {
+ let originalRequestAggs: IBucketGeoHashGridAggConfig[];
+
+ beforeEach(() => {
+ originalRequestAggs = geoHashBucketAgg.getRequestAggs(getAggConfigs()
+ .aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+ });
+
+ it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => {
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
+ mapBounds: {
+ top_left: { lat: 10.0, lon: -10.0 },
+ bottom_right: { lat: 9.0, lon: -9.0 },
+ },
+ }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+
+ expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params);
+ });
+
+ it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => {
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
+ mapBounds: {
+ top_left: { lat: 1, lon: -1 },
+ bottom_right: { lat: -1, lon: 1 },
+ },
+ }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+
+ expect(originalRequestAggs[1].params).toEqual(geoBoxingBox.params);
+ });
+
+ it('should change geo_bounding_box filter aggregation and vis session state when map zoom level changes', () => {
+ const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs(getAggConfigs({
+ mapZoom: -1,
+ }).aggs[0] as IBucketGeoHashGridAggConfig) as IBucketGeoHashGridAggConfig[];
+
+ expect(originalRequestAggs[1].lastMapCollar).not.toEqual(geoBoxingBox.lastMapCollar);
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/histogram.test.ts
new file mode 100644
index 0000000000000..338af2e41cb88
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/histogram.test.ts
@@ -0,0 +1,292 @@
+/*
+ * 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 { npStart } from 'ui/new_platform';
+import { AggConfigs } from '../index';
+import { BUCKET_TYPES } from './bucket_agg_types';
+import { IBucketHistogramAggConfig, histogramBucketAgg, AutoBounds } from './histogram';
+import { BucketAggType } from './_bucket_agg_type';
+
+jest.mock('ui/new_platform');
+
+describe('Histogram Agg', () => {
+ const getAggConfigs = (params: Record = {}) => {
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ const field = {
+ name: 'field',
+ indexPattern,
+ };
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ field: {
+ name: 'field',
+ },
+ id: 'test',
+ type: BUCKET_TYPES.HISTOGRAM,
+ schema: 'segment',
+ params,
+ },
+ ],
+ null
+ );
+ };
+
+ const getParams = (options: Record) => {
+ const aggConfigs = getAggConfigs({
+ ...options,
+ field: {
+ name: 'field',
+ },
+ });
+ return aggConfigs.aggs[0].toDsl()[BUCKET_TYPES.HISTOGRAM];
+ };
+
+ describe('ordered', () => {
+ let histogramType: BucketAggType;
+
+ beforeEach(() => {
+ histogramType = histogramBucketAgg;
+ });
+
+ it('is ordered', () => {
+ expect(histogramType.ordered).toBeDefined();
+ });
+
+ it('is not ordered by date', () => {
+ expect(histogramType.ordered).not.toHaveProperty('date');
+ });
+ });
+
+ describe('params', () => {
+ describe('intervalBase', () => {
+ it('should not be written to the DSL', () => {
+ const aggConfigs = getAggConfigs({
+ intervalBase: 100,
+ field: {
+ name: 'field',
+ },
+ });
+ const { [BUCKET_TYPES.HISTOGRAM]: params } = aggConfigs.aggs[0].toDsl();
+
+ expect(params).not.toHaveProperty('intervalBase');
+ });
+ });
+
+ describe('interval', () => {
+ it('accepts a whole number', () => {
+ const params = getParams({
+ interval: 100,
+ });
+
+ expect(params).toHaveProperty('interval', 100);
+ });
+
+ it('accepts a decimal number', function() {
+ const params = getParams({
+ interval: 0.1,
+ });
+
+ expect(params).toHaveProperty('interval', 0.1);
+ });
+
+ it('accepts a decimal number string', function() {
+ const params = getParams({
+ interval: '0.1',
+ });
+
+ expect(params).toHaveProperty('interval', 0.1);
+ });
+
+ it('accepts a whole number string', function() {
+ const params = getParams({
+ interval: '10',
+ });
+
+ expect(params).toHaveProperty('interval', 10);
+ });
+
+ it('fails on non-numeric values', function() {
+ const params = getParams({
+ interval: [],
+ });
+
+ expect(params.interval).toBeNaN();
+ });
+
+ describe('interval scaling', () => {
+ const getInterval = (
+ maxBars: number,
+ params?: Record,
+ autoBounds?: AutoBounds
+ ) => {
+ const aggConfigs = getAggConfigs({
+ ...params,
+ field: {
+ name: 'field',
+ },
+ });
+ const aggConfig = aggConfigs.aggs[0] as IBucketHistogramAggConfig;
+
+ if (autoBounds) {
+ aggConfig.setAutoBounds(autoBounds);
+ }
+
+ // mock histogram:maxBars value;
+ npStart.core.uiSettings.get = jest.fn(() => maxBars);
+
+ return aggConfig.write(aggConfigs).params;
+ };
+
+ it('will respect the histogram:maxBars setting', () => {
+ const params = getInterval(
+ 5,
+ { interval: 5 },
+ {
+ min: 0,
+ max: 10000,
+ }
+ );
+
+ expect(params).toHaveProperty('interval', 2000);
+ });
+
+ it('will return specified interval, if bars are below histogram:maxBars config', () => {
+ const params = getInterval(100, { interval: 5 });
+
+ expect(params).toHaveProperty('interval', 5);
+ });
+
+ it('will set to intervalBase if interval is below base', () => {
+ const params = getInterval(1000, { interval: 3, intervalBase: 8 });
+
+ expect(params).toHaveProperty('interval', 8);
+ });
+
+ it('will round to nearest intervalBase multiple if interval is above base', () => {
+ const roundUp = getInterval(1000, { interval: 46, intervalBase: 10 });
+ expect(roundUp).toHaveProperty('interval', 50);
+
+ const roundDown = getInterval(1000, { interval: 43, intervalBase: 10 });
+ expect(roundDown).toHaveProperty('interval', 40);
+ });
+
+ it('will not change interval if it is a multiple of base', () => {
+ const output = getInterval(1000, { interval: 35, intervalBase: 5 });
+
+ expect(output).toHaveProperty('interval', 35);
+ });
+
+ it('will round to intervalBase after scaling histogram:maxBars', () => {
+ const output = getInterval(100, { interval: 5, intervalBase: 6 }, { min: 0, max: 1000 });
+
+ // 100 buckets in 0 to 1000 would result in an interval of 10, so we should
+ // round to the next multiple of 6 -> 12
+ expect(output).toHaveProperty('interval', 12);
+ });
+ });
+
+ describe('min_doc_count', () => {
+ let output: Record;
+
+ it('casts true values to 0', () => {
+ output = getParams({ min_doc_count: true });
+ expect(output).toHaveProperty('min_doc_count', 0);
+
+ output = getParams({ min_doc_count: 'yes' });
+ expect(output).toHaveProperty('min_doc_count', 0);
+
+ output = getParams({ min_doc_count: 1 });
+ expect(output).toHaveProperty('min_doc_count', 0);
+
+ output = getParams({ min_doc_count: {} });
+ expect(output).toHaveProperty('min_doc_count', 0);
+ });
+
+ it('writes 1 for falsy values', () => {
+ output = getParams({ min_doc_count: '' });
+ expect(output).toHaveProperty('min_doc_count', 1);
+
+ output = getParams({ min_doc_count: null });
+ expect(output).toHaveProperty('min_doc_count', 1);
+
+ output = getParams({ min_doc_count: undefined });
+ expect(output).toHaveProperty('min_doc_count', 1);
+ });
+ });
+
+ describe('extended_bounds', function() {
+ it('does not write when only eb.min is set', function() {
+ const output = getParams({
+ has_extended_bounds: true,
+ extended_bounds: { min: 0 },
+ });
+ expect(output).not.toHaveProperty('extended_bounds');
+ });
+
+ it('does not write when only eb.max is set', function() {
+ const output = getParams({
+ has_extended_bounds: true,
+ extended_bounds: { max: 0 },
+ });
+
+ expect(output).not.toHaveProperty('extended_bounds');
+ });
+
+ it('writes when both eb.min and eb.max are set', function() {
+ const output = getParams({
+ has_extended_bounds: true,
+ extended_bounds: { min: 99, max: 100 },
+ });
+
+ expect(output.extended_bounds).toHaveProperty('min', 99);
+ expect(output.extended_bounds).toHaveProperty('max', 100);
+ });
+
+ it('does not write when nothing is set', function() {
+ const output = getParams({
+ has_extended_bounds: true,
+ extended_bounds: {},
+ });
+
+ expect(output).not.toHaveProperty('extended_bounds');
+ });
+
+ it('does not write when has_extended_bounds is false', function() {
+ const output = getParams({
+ has_extended_bounds: false,
+ extended_bounds: { min: 99, max: 100 },
+ });
+
+ expect(output).not.toHaveProperty('extended_bounds');
+ });
+ });
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/ui/public/agg_types/buckets/histogram.ts
index 23edefc67d506..74a2da4a0eb67 100644
--- a/src/legacy/ui/public/agg_types/buckets/histogram.ts
+++ b/src/legacy/ui/public/agg_types/buckets/histogram.ts
@@ -21,7 +21,7 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
-import chrome from '../../chrome';
+import { npStart } from 'ui/new_platform';
import { BucketAggType, IBucketAggConfig, BucketAggParam } from './_bucket_agg_type';
import { createFilterHistogram } from './create_filter/histogram';
import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/number_interval';
@@ -32,7 +32,7 @@ import { AggConfig } from '../agg_config';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
import { BUCKET_TYPES } from './bucket_agg_types';
-interface AutoBounds {
+export interface AutoBounds {
min: number;
max: number;
}
@@ -42,7 +42,8 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig {
getAutoBounds: () => AutoBounds;
}
-const config = chrome.getUiSettingsClient();
+const getUIConfig = () => npStart.core.uiSettings;
+
export const histogramBucketAgg = new BucketAggType({
name: BUCKET_TYPES.HISTOGRAM,
title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', {
@@ -135,25 +136,30 @@ export const histogramBucketAgg = new BucketAggType({
if (interval <= 0) {
interval = 1;
}
+ const autoBounds = aggConfig.getAutoBounds();
// ensure interval does not create too many buckets and crash browser
- if (aggConfig.getAutoBounds()) {
- const range = aggConfig.getAutoBounds().max - aggConfig.getAutoBounds().min;
+ if (autoBounds) {
+ const range = autoBounds.max - autoBounds.min;
const bars = range / interval;
+
+ const config = getUIConfig();
if (bars > config.get('histogram:maxBars')) {
const minInterval = range / config.get('histogram:maxBars');
+
// Round interval by order of magnitude to provide clean intervals
// Always round interval up so there will always be less buckets than histogram:maxBars
const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval)));
let roundInterval = orderOfMagnitude;
+
while (roundInterval < minInterval) {
roundInterval += orderOfMagnitude;
}
interval = roundInterval;
}
}
-
const base = aggConfig.params.intervalBase;
+
if (base) {
if (interval < base) {
// In case the specified interval is below the base, just increase it to it's base
diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/ui/public/agg_types/buckets/range.test.ts
new file mode 100644
index 0000000000000..f7cae60cce773
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/range.test.ts
@@ -0,0 +1,95 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { AggConfigs } from '../agg_configs';
+import { BUCKET_TYPES } from './bucket_agg_types';
+import { NumberFormat } from '../../../../../plugins/data/common/';
+
+jest.mock('ui/new_platform');
+
+const buckets = [
+ {
+ to: 1024,
+ to_as_string: '1024.0',
+ doc_count: 20904,
+ },
+ {
+ from: 1024,
+ from_as_string: '1024.0',
+ to: 2560,
+ to_as_string: '2560.0',
+ doc_count: 23358,
+ },
+ {
+ from: 2560,
+ from_as_string: '2560.0',
+ doc_count: 174250,
+ },
+];
+
+describe('Range Agg', () => {
+ const getAggConfigs = () => {
+ const field = {
+ name: 'bytes',
+ format: new NumberFormat(
+ {
+ pattern: '0,0.[000] b',
+ },
+ () => {}
+ ),
+ };
+
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ type: BUCKET_TYPES.RANGE,
+ schema: 'segment',
+ params: {
+ field: 'bytes',
+ ranges: [{ from: 0, to: 1000 }, { from: 1000, to: 2000 }],
+ },
+ },
+ ],
+ null
+ );
+ };
+
+ describe('formating', () => {
+ it('formats bucket keys properly', () => {
+ const aggConfigs = getAggConfigs();
+ const agg = aggConfigs.aggs[0];
+
+ const format = (val: any) => agg.fieldFormatter()(agg.getKey(val));
+
+ expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB');
+ expect(format(buckets[1])).toBe('≥ 1 KB and < 2.5 KB');
+ expect(format(buckets[2])).toBe('≥ 2.5 KB and < +∞');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts
new file mode 100644
index 0000000000000..454f1bf70a790
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts
@@ -0,0 +1,110 @@
+/*
+ * 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 { AggConfigs } from '../index';
+import { BUCKET_TYPES } from './bucket_agg_types';
+import { significantTermsBucketAgg } from './significant_terms';
+
+jest.mock('ui/new_platform');
+
+describe('Significant Terms Agg', () => {
+ describe('order agg editor UI', () => {
+ describe('convert include/exclude from old format', () => {
+ const getAggConfigs = (params: Record = {}) => {
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ const field = {
+ name: 'field',
+ indexPattern,
+ };
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: 'test',
+ type: BUCKET_TYPES.SIGNIFICANT_TERMS,
+ schema: 'segment',
+ params,
+ },
+ ],
+ null
+ );
+ };
+
+ const testSerializeAndWrite = (aggs: AggConfigs) => {
+ const agg = aggs.aggs[0];
+ const { [BUCKET_TYPES.SIGNIFICANT_TERMS]: params } = agg.toDsl();
+
+ expect(params.field).toBe('field');
+ expect(params.include).toBe('404');
+ expect(params.exclude).toBe('400');
+ };
+
+ it('should generate correct label', () => {
+ const aggConfigs = getAggConfigs({
+ size: 'SIZE',
+ field: {
+ name: 'FIELD',
+ },
+ });
+ const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0]);
+
+ expect(label).toBe('Top SIZE unusual terms in FIELD');
+ });
+
+ it('should doesnt do anything with string type', () => {
+ const aggConfigs = getAggConfigs({
+ include: '404',
+ exclude: '400',
+ field: {
+ name: 'field',
+ type: 'string',
+ },
+ });
+
+ testSerializeAndWrite(aggConfigs);
+ });
+
+ it('should converts object to string type', () => {
+ const aggConfigs = getAggConfigs({
+ include: {
+ pattern: '404',
+ },
+ exclude: {
+ pattern: '400',
+ },
+ field: {
+ name: 'field',
+ type: 'string',
+ },
+ });
+
+ testSerializeAndWrite(aggConfigs);
+ });
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/buckets/terms.test.ts b/src/legacy/ui/public/agg_types/buckets/terms.test.ts
new file mode 100644
index 0000000000000..24ac332ae4d55
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/buckets/terms.test.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 { AggConfigs } from '../index';
+import { BUCKET_TYPES } from './bucket_agg_types';
+
+jest.mock('ui/new_platform');
+
+describe('Terms Agg', () => {
+ describe('order agg editor UI', () => {
+ const getAggConfigs = (params: Record = {}) => {
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ },
+ } as any;
+
+ const field = {
+ name: 'field',
+ indexPattern,
+ };
+
+ return new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: 'test',
+ params,
+ type: BUCKET_TYPES.TERMS,
+ },
+ ],
+ null
+ );
+ };
+
+ it('converts object to string type', function() {
+ const aggConfigs = getAggConfigs({
+ include: {
+ pattern: '404',
+ },
+ exclude: {
+ pattern: '400',
+ },
+ field: {
+ name: 'field',
+ },
+ orderAgg: {
+ type: 'count',
+ },
+ });
+
+ const { [BUCKET_TYPES.TERMS]: params } = aggConfigs.aggs[0].toDsl();
+
+ expect(params.field).toBe('field');
+ expect(params.include).toBe('404');
+ expect(params.exclude).toBe('400');
+ });
+ });
+});
diff --git a/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts b/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts
index 0d32c9dc769da..431e1161e0dbd 100644
--- a/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts
+++ b/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts
@@ -27,7 +27,7 @@ describe('prop filter', () => {
nameFilter = propFilter('name');
});
- function getObjects(...names: string[]) {
+ const getObjects = (...names: string[]) => {
const count = new Map();
const objects = [];
@@ -41,8 +41,9 @@ describe('prop filter', () => {
});
count.set(name, count.get(name) + 1);
}
+
return objects;
- }
+ };
it('returns list when no filters are provided', () => {
const objects = getObjects('table', 'table', 'pie');
diff --git a/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts
index aed5bd630d3d2..479ff40b7c0ae 100644
--- a/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts
@@ -18,7 +18,7 @@
*/
import { makeNestedLabel } from './make_nested_label';
-import { IMetricAggConfig } from 'ui/agg_types/metrics/metric_agg_type';
+import { IMetricAggConfig } from '../metric_agg_type';
describe('metric agg make_nested_label', () => {
const generateAggConfig = (metricLabel: string): IMetricAggConfig => {
diff --git a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts
index bf88adcee92b7..7c7a2a68cd7c5 100644
--- a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts
@@ -22,8 +22,8 @@ import { derivativeMetricAgg } from './derivative';
import { cumulativeSumMetricAgg } from './cumulative_sum';
import { movingAvgMetricAgg } from './moving_avg';
import { serialDiffMetricAgg } from './serial_diff';
-import { AggConfigs } from 'ui/agg_types';
-import { IMetricAggConfig, MetricAggType } from 'ui/agg_types/metrics/metric_agg_type';
+import { AggConfigs } from '../agg_configs';
+import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
jest.mock('../../vis/editors/default/schemas', () => {
class MockedSchemas {
diff --git a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts
index a3381aca6f9e7..e038936de07d2 100644
--- a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts
@@ -23,8 +23,8 @@ import { bucketAvgMetricAgg } from './bucket_avg';
import { bucketMinMetricAgg } from './bucket_min';
import { bucketMaxMetricAgg } from './bucket_max';
-import { AggConfigs } from 'ui/agg_types';
-import { IMetricAggConfig, MetricAggType } from 'ui/agg_types/metrics/metric_agg_type';
+import { AggConfigs } from '../agg_configs';
+import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
jest.mock('../../vis/editors/default/schemas', () => {
class MockedSchemas {
diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
index ca81e8daee449..ae09b5cd78977 100644
--- a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts
@@ -18,8 +18,8 @@
*/
import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation';
-import { AggConfigs } from 'ui/agg_types';
-import { METRIC_TYPES } from 'ui/agg_types/metrics/metric_agg_types';
+import { AggConfigs } from '../agg_configs';
+import { METRIC_TYPES } from './metric_agg_types';
jest.mock('ui/new_platform');
diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts
index 4ed6fcdcf641b..e9d1ebb93d3ba 100644
--- a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts
+++ b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts
@@ -19,8 +19,8 @@
import { dropRight, last } from 'lodash';
import { topHitMetricAgg } from './top_hit';
-import { AggConfigs } from 'ui/agg_types';
-import { IMetricAggConfig } from 'ui/agg_types/metrics/metric_agg_type';
+import { AggConfigs } from '../agg_configs';
+import { IMetricAggConfig } from './metric_agg_type';
import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common';
jest.mock('ui/new_platform');
diff --git a/src/legacy/ui/public/agg_types/param_types/json.test.ts b/src/legacy/ui/public/agg_types/param_types/json.test.ts
index fb31385505a76..827299814c62a 100644
--- a/src/legacy/ui/public/agg_types/param_types/json.test.ts
+++ b/src/legacy/ui/public/agg_types/param_types/json.test.ts
@@ -19,7 +19,7 @@
import { BaseParamType } from './base';
import { JsonParamType } from './json';
-import { AggConfig } from 'ui/agg_types';
+import { AggConfig } from '../agg_config';
jest.mock('ui/new_platform');
@@ -28,13 +28,12 @@ describe('JSON', function() {
let aggConfig: AggConfig;
let output: Record;
- function initAggParam(config: Record = {}) {
- return new JsonParamType({
+ const initAggParam = (config: Record = {}) =>
+ new JsonParamType({
...config,
type: 'json',
name: paramName,
});
- }
beforeEach(function() {
aggConfig = { params: {} } as AggConfig;
diff --git a/src/legacy/ui/public/agg_types/param_types/string.test.ts b/src/legacy/ui/public/agg_types/param_types/string.test.ts
index 3d496ecf898e4..fd5ccebde993e 100644
--- a/src/legacy/ui/public/agg_types/param_types/string.test.ts
+++ b/src/legacy/ui/public/agg_types/param_types/string.test.ts
@@ -19,7 +19,7 @@
import { BaseParamType } from './base';
import { StringParamType } from './string';
-import { AggConfig } from 'ui/agg_types';
+import { AggConfig } from '../agg_config';
jest.mock('ui/new_platform');
@@ -28,13 +28,12 @@ describe('String', function() {
let aggConfig: AggConfig;
let output: Record;
- function initAggParam(config: Record = {}) {
- return new StringParamType({
+ const initAggParam = (config: Record = {}) =>
+ new StringParamType({
...config,
type: 'string',
name: paramName,
});
- }
beforeEach(() => {
aggConfig = { params: {} } as AggConfig;
diff --git a/src/legacy/ui/public/agg_types/utils.ts b/src/legacy/ui/public/agg_types/utils.ts
index c6452cf46e0c0..6721262d265f4 100644
--- a/src/legacy/ui/public/agg_types/utils.ts
+++ b/src/legacy/ui/public/agg_types/utils.ts
@@ -49,7 +49,7 @@ function isValidJson(value: string): boolean {
}
}
-function isValidInterval(value: string, baseInterval: string) {
+function isValidInterval(value: string, baseInterval?: string) {
if (baseInterval) {
return _parseWithBase(value, baseInterval);
} else {
diff --git a/src/legacy/ui/public/autoload/modules.js b/src/legacy/ui/public/autoload/modules.js
index d662d479fc86b..e1d897236297e 100644
--- a/src/legacy/ui/public/autoload/modules.js
+++ b/src/legacy/ui/public/autoload/modules.js
@@ -27,7 +27,6 @@ import '../promises';
import '../modals';
import '../state_management/app_state';
import '../state_management/global_state';
-import '../storage';
import '../style_compile';
import '../url';
import '../directives/watch_multi';
diff --git a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx
index dccea2f61d567..ff46b6ec34a86 100644
--- a/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx
+++ b/src/legacy/ui/public/capabilities/react/inject_ui_capabilities.test.tsx
@@ -77,6 +77,7 @@ describe('injectUICapabilities', () => {
uiCapabilities: UICapabilities;
}
+ // eslint-disable-next-line react/prefer-stateless-function
class MyClassComponent extends React.Component {
public render() {
return {this.props.uiCapabilities.uiCapability2.nestedProp} ;
diff --git a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx
index 21f96cf918afd..76f1dd8016313 100644
--- a/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx
+++ b/src/legacy/ui/public/capabilities/react/legacy/inject_ui_capabilities.test.tsx
@@ -77,6 +77,7 @@ describe('injectUICapabilities', () => {
uiCapabilities: UICapabilities;
}
+ // eslint-disable-next-line react/prefer-stateless-function
class MyClassComponent extends React.Component {
public render() {
return {this.props.uiCapabilities.uiCapability2.nestedProp} ;
diff --git a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx
index fb6a1dc55c29f..3871147107439 100644
--- a/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx
+++ b/src/legacy/ui/public/capabilities/react/ui_capabilities_provider.tsx
@@ -17,22 +17,14 @@
* under the License.
*/
-import React, { ReactNode } from 'react';
+import React from 'react';
import { UICapabilitiesContext } from './ui_capabilities_context';
import { capabilities } from '..';
-interface Props {
- children: ReactNode;
-}
+export const UICapabilitiesProvider: React.SFC = props => (
+
+ {props.children}
+
+);
-export class UICapabilitiesProvider extends React.Component {
- public static displayName: string = 'UICapabilitiesProvider';
-
- public render() {
- return (
-
- {this.props.children}
-
- );
- }
-}
+UICapabilitiesProvider.displayName = 'UICapabilitiesProvider';
diff --git a/src/legacy/ui/public/chrome/chrome.js b/src/legacy/ui/public/chrome/chrome.js
index a5a0521013a6e..d644965e09225 100644
--- a/src/legacy/ui/public/chrome/chrome.js
+++ b/src/legacy/ui/public/chrome/chrome.js
@@ -26,7 +26,7 @@ import '../config';
import '../notify';
import '../private';
import '../promises';
-import '../storage';
+import '../directives/storage';
import '../directives/watch_multi';
import './services';
import '../react_components';
diff --git a/src/legacy/ui/public/storage/directive.js b/src/legacy/ui/public/directives/storage/index.js
similarity index 90%
rename from src/legacy/ui/public/storage/directive.js
rename to src/legacy/ui/public/directives/storage/index.js
index a5bb2ee3b6b0b..8c18012672c1b 100644
--- a/src/legacy/ui/public/storage/directive.js
+++ b/src/legacy/ui/public/directives/storage/index.js
@@ -18,8 +18,8 @@
*/
-import { uiModules } from '../modules';
-import { Storage } from './storage';
+import { uiModules } from '../../modules';
+import { Storage } from '../../../../../plugins/kibana_utils/public';
const createService = function (type) {
return function ($window) {
diff --git a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js
index a6bf2e7aa6c4c..ed7e25704d5a5 100644
--- a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js
+++ b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js
@@ -24,7 +24,7 @@ import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import { getFilterGenerator } from '..';
import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter';
-import { uniqFilters } from '../../../../core_plugins/data/public/filter/filter_manager/lib/uniq_filters';
+import { uniqFilters } from '../../../../../plugins/data/public';
import { getPhraseScript } from '@kbn/es-query';
let queryFilter;
let filterGen;
diff --git a/src/legacy/ui/public/filter_manager/query_filter.js b/src/legacy/ui/public/filter_manager/query_filter.js
index 6afe52502df5d..97b3810b7f1c7 100644
--- a/src/legacy/ui/public/filter_manager/query_filter.js
+++ b/src/legacy/ui/public/filter_manager/query_filter.js
@@ -18,12 +18,10 @@
*/
import { FilterStateManager } from 'plugins/data';
+import { npStart } from 'ui/new_platform';
export function FilterBarQueryFilterProvider(getAppState, globalState) {
- // TODO: this is imported here to avoid circular imports.
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const { start } = require('../../../core_plugins/data/public/legacy');
- const filterManager = start.filter.filterManager;
+ const { filterManager } = npStart.plugins.data.query;
const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager);
const queryFilter = {};
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 20ea9c9141aca..c74288c98d79c 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
@@ -39,6 +39,9 @@ export const npSetup = {
},
},
data: {
+ query: {
+ filterManager: sinon.fake(),
+ },
},
inspector: {
registerView: () => undefined,
@@ -73,6 +76,24 @@ export const npStart = {
},
data: {
getSuggestions: sinon.fake(),
+ query: {
+ filterManager: {
+ getFetches$: sinon.fake(),
+ getFilters: sinon.fake(),
+ getAppFilters: sinon.fake(),
+ getGlobalFilters: sinon.fake(),
+ removeFilter: sinon.fake(),
+ addFilters: sinon.fake(),
+ setFilters: sinon.fake(),
+ removeAll: sinon.fake(),
+ getUpdates$: () => {
+ return {
+ subscribe: () => {}
+ };
+ },
+
+ },
+ },
},
inspector: {
isAvailable: () => false,
diff --git a/src/legacy/ui/public/vis/default_feedback_message.js b/src/legacy/ui/public/vis/default_feedback_message.js
index f840ba961b9c3..8b8491d397aad 100644
--- a/src/legacy/ui/public/vis/default_feedback_message.js
+++ b/src/legacy/ui/public/vis/default_feedback_message.js
@@ -19,9 +19,10 @@
import { i18n } from '@kbn/i18n';
-export const defaultFeedbackMessage = i18n.translate('common.ui.vis.defaultFeedbackMessage',
- {
- defaultMessage: 'Have feedback? Please create an issue in {link}.',
- values: { link: 'GitHub ' }
- }
-);
+export const defaultFeedbackMessage = i18n.translate('common.ui.vis.defaultFeedbackMessage', {
+ defaultMessage: 'Have feedback? Please create an issue in {link}.',
+ values: {
+ link:
+ 'GitHub ',
+ },
+});
diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
index 5dfb14156deee..7806b1c0f78fb 100644
--- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
+++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx
@@ -156,7 +156,7 @@ describe('DefaultEditorAgg component', () => {
it('should add schema component', () => {
defaultProps.agg.schema = {
- editorComponent: () =>
,
+ editorComponent: () =>
,
} as any;
const comp = mount( );
diff --git a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
index 2c0a2b6be37f8..4ebe7b0d835d7 100644
--- a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
+++ b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx
@@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n';
import { Query, QueryBarInput } from 'plugins/data';
import { AggConfig } from '../../..';
import { npStart } from '../../../../new_platform';
-import { Storage } from '../../../../storage';
+import { Storage } from '../../../../../../../plugins/kibana_utils/public';
import { KibanaContextProvider } from '../../../../../../../plugins/kibana_react/public';
const localStorage = new Storage(window.localStorage);
@@ -94,7 +94,7 @@ function FilterRow({
{
@@ -66,7 +67,7 @@ const defaultEditor = function ($rootScope, $compile) {
//$scope.$apply();
};
- return new Promise(resolve => {
+ return new Promise(async (resolve) => {
if (!this.$scope) {
this.$scope = $scope = $rootScope.$new();
@@ -157,23 +158,21 @@ const defaultEditor = function ($rootScope, $compile) {
if (!this._handler) {
const visualizationEl = this.el.find('.visEditor__canvas')[0];
- getVisualizeLoader().then(loader => {
- if (!visualizationEl) {
- return;
- }
- this._loader = loader;
- this._handler = this._loader.embedVisualizationWithSavedObject(visualizationEl, this.savedObj, {
- uiState: uiState,
- listenOnChange: false,
- timeRange: timeRange,
- filters: filters,
- appState: appState,
- });
+
+ this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(this.savedObj, {
+ uiState: uiState,
+ appState: getAppState(),
+ timeRange: timeRange,
+ filters: filters || [],
+ query: query,
});
+ this._handler.render(visualizationEl);
+
} else {
- this._handler.update({
+ this._handler.updateInput({
timeRange: timeRange,
- filters: filters,
+ filters: filters || [],
+ query: query,
});
}
diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js
index f19e2440a21e9..9343585fa9508 100644
--- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js
+++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js
@@ -20,7 +20,7 @@
import _ from 'lodash';
import { pushFilterBarFilters } from '../push_filters';
import { onBrushEvent } from './brush_event';
-import { uniqFilters } from '../../../../core_plugins/data/public';
+import { uniqFilters } from '../../../../../plugins/data/public';
import { toggleFilterNegated } from '@kbn/es-query';
/**
* For terms aggregations on `__other__` buckets, this assembles a list of applicable filter
diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
index bc2152911d1ec..135260ac01b17 100644
--- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
+++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
@@ -28,7 +28,8 @@ import { toastNotifications } from 'ui/notify';
import { AggConfigs } from 'ui/agg_types/agg_configs';
import { SearchSource } from 'ui/courier';
import { QueryFilter } from 'ui/filter_manager/query_filter';
-import { TimeRange } from 'src/plugins/data/public';
+
+import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../plugins/data/public';
import { registries } from '../../../../core_plugins/interpreter/public/registries';
import { Inspector } from '../../inspector';
import { Adapters } from '../../inspector/types';
@@ -42,7 +43,7 @@ import { Vis } from '../../vis';
import { VisFiltersProvider } from '../../vis/vis_filters';
import { PipelineDataLoader } from './pipeline_data_loader';
import { visualizationLoader } from './visualization_loader';
-import { onlyDisabledFiltersChanged, Query } from '../../../../core_plugins/data/public';
+import { Query } from '../../../../core_plugins/data/public';
import { DataAdapter, RequestAdapter } from '../../inspector/adapters';
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index 7e1b3801b62a4..51f26a4bd7f31 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -34,3 +34,5 @@ export * from './types';
export { IRequestTypesMap, IResponseTypesMap } from './search';
export * from './search';
+
+export * from './query';
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index 5e60ca93378d9..2269ba3c55bce 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -18,6 +18,7 @@
*/
import { Plugin } from '.';
import { searchSetupMock } from './search/mocks';
+import { queryServiceMock } from './query/mocks';
export type Setup = jest.Mocked>;
export type Start = jest.Mocked>;
@@ -29,19 +30,23 @@ const autocompleteMock: any = {
};
const createSetupContract = (): Setup => {
+ const querySetupMock = queryServiceMock.createSetupContract();
const setupContract: Setup = {
autocomplete: autocompleteMock as Setup['autocomplete'],
search: searchSetupMock,
+ query: querySetupMock,
};
return setupContract;
};
const createStartContract = (): Start => {
+ const queryStartMock = queryServiceMock.createStartContract();
const startContract: Start = {
autocomplete: autocompleteMock as Start['autocomplete'],
getSuggestions: jest.fn(),
search: { search: jest.fn() },
+ query: queryStartMock,
};
return startContract;
};
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 935a3c5754503..a13e912e77846 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -18,23 +18,29 @@
*/
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
-import { AutocompleteProviderRegister } from './autocomplete_provider';
import { DataPublicPluginSetup, DataPublicPluginStart } from './types';
-import { SearchService } from './search/search_service';
+import { AutocompleteProviderRegister } from './autocomplete_provider';
import { getSuggestionsProvider } from './suggestions_provider';
+import { SearchService } from './search/search_service';
+import { QueryService } from './query';
export class DataPublicPlugin implements Plugin {
private readonly autocomplete = new AutocompleteProviderRegister();
private readonly searchService: SearchService;
+ private readonly queryService: QueryService;
constructor(initializerContext: PluginInitializerContext) {
this.searchService = new SearchService(initializerContext);
+ this.queryService = new QueryService();
}
public setup(core: CoreSetup): DataPublicPluginSetup {
return {
autocomplete: this.autocomplete,
search: this.searchService.setup(core),
+ query: this.queryService.setup({
+ uiSettings: core.uiSettings,
+ }),
};
}
@@ -43,6 +49,7 @@ export class DataPublicPlugin implements Plugin {
@@ -38,9 +34,6 @@ setupMock.uiSettings.get.mockImplementation((key: string) => {
});
describe('filter_manager', () => {
- let appStateStub: StubState;
- let globalStateStub: StubState;
-
let updateSubscription: Subscription | undefined;
let fetchSubscription: Subscription | undefined;
let updateListener: sinon.SinonSpy;
@@ -50,20 +43,8 @@ describe('filter_manager', () => {
beforeEach(() => {
updateListener = sinon.stub();
- appStateStub = new StubState();
- globalStateStub = new StubState();
filterManager = new FilterManager(setupMock.uiSettings);
readyFilters = getFiltersArray();
-
- // FilterStateManager is tested indirectly.
- // Therefore, we don't need it's instance.
- new FilterStateManager(
- globalStateStub,
- () => {
- return appStateStub;
- },
- filterManager
- );
});
afterEach(async () => {
@@ -84,32 +65,6 @@ describe('filter_manager', () => {
expect(updateSubscription).toBeInstanceOf(Subscription);
expect(fetchSubscription).toBeInstanceOf(Subscription);
});
-
- test('should observe global state', done => {
- updateSubscription = filterManager.getUpdates$().subscribe(() => {
- expect(filterManager.getGlobalFilters()).toHaveLength(1);
- if (updateSubscription) {
- updateSubscription.unsubscribe();
- }
- done();
- });
-
- const f1 = getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'age', 34);
- globalStateStub.filters.push(f1);
- });
-
- test('should observe app state', done => {
- updateSubscription = filterManager.getUpdates$().subscribe(() => {
- expect(filterManager.getAppFilters()).toHaveLength(1);
- if (updateSubscription) {
- updateSubscription.unsubscribe();
- }
- done();
- });
-
- const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
- appStateStub.filters.push(f1);
- });
});
describe('get \\ set filters', () => {
@@ -222,10 +177,11 @@ describe('filter_manager', () => {
updateSubscription = filterManager.getUpdates$().subscribe(updateListener);
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
filterManager.addFilters(f1);
- expect(filterManager.getAppFilters()).toHaveLength(1);
+ const appFilters = filterManager.getAppFilters();
+ expect(appFilters).toHaveLength(1);
+ expect(appFilters[0]).toEqual(f1);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
expect(updateListener.callCount).toBe(1);
- expect(appStateStub.filters.length).toBe(1);
});
test('app state should accept array', async () => {
@@ -233,9 +189,10 @@ describe('filter_manager', () => {
const f2 = getFilter(FilterStateStore.APP_STATE, false, false, 'gender', 'female');
filterManager.addFilters([f1]);
filterManager.addFilters([f2]);
- expect(filterManager.getAppFilters()).toHaveLength(2);
+ const appFilters = filterManager.getAppFilters();
+ expect(appFilters).toHaveLength(2);
+ expect(appFilters).toEqual([f2, f1]);
expect(filterManager.getGlobalFilters()).toHaveLength(0);
- expect(appStateStub.filters.length).toBe(2);
});
test('global state should accept a single filer', async () => {
@@ -243,9 +200,10 @@ describe('filter_manager', () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
filterManager.addFilters(f1);
expect(filterManager.getAppFilters()).toHaveLength(0);
- expect(filterManager.getGlobalFilters()).toHaveLength(1);
+ const globalFilters = filterManager.getGlobalFilters();
+ expect(globalFilters).toHaveLength(1);
+ expect(globalFilters[0]).toEqual(f1);
expect(updateListener.callCount).toBe(1);
- expect(globalStateStub.filters.length).toBe(1);
});
test('global state should be accept array', async () => {
@@ -253,8 +211,9 @@ describe('filter_manager', () => {
const f2 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'gender', 'female');
filterManager.addFilters([f1, f2]);
expect(filterManager.getAppFilters()).toHaveLength(0);
- expect(filterManager.getGlobalFilters()).toHaveLength(2);
- expect(globalStateStub.filters.length).toBe(2);
+ const globalFilters = filterManager.getGlobalFilters();
+ expect(globalFilters).toHaveLength(2);
+ expect(globalFilters).toEqual([f2, f1]);
});
test('add multiple filters at once', async () => {
@@ -370,7 +329,6 @@ describe('filter_manager', () => {
filterManager.addFilters(negatedFilter);
// The negated filter should overwrite the positive one
- expect(globalStateStub.filters.length).toBe(1);
expect(filterManager.getFilters()).toHaveLength(1);
expect(filterManager.getFilters()[0]).toEqual(negatedFilter);
});
@@ -383,16 +341,16 @@ describe('filter_manager', () => {
filterManager.addFilters(negatedFilter);
// The negated filter should overwrite the positive one
- expect(globalStateStub.filters.length).toBe(1);
- expect(globalStateStub.filters[0]).toEqual(negatedFilter);
+ expect(filterManager.getFilters()).toHaveLength(1);
+ expect(filterManager.getFilters()[0]).toEqual(negatedFilter);
// Add negate: false version of the filter
const filter = _.cloneDeep(readyFilters[0]);
filter.meta.negate = false;
filterManager.addFilters(filter);
- expect(globalStateStub.filters.length).toBe(1);
- expect(globalStateStub.filters[0]).toEqual(filter);
+ expect(filterManager.getFilters()).toHaveLength(1);
+ expect(filterManager.getFilters()[0]).toEqual(filter);
});
test('should fire the update and fetch events', async function() {
@@ -409,10 +367,6 @@ describe('filter_manager', () => {
filterManager.addFilters(readyFilters);
- // updates should trigger state saves
- expect(appStateStub.save.callCount).toBe(1);
- expect(globalStateStub.save.callCount).toBe(1);
-
// this time, events should be emitted
expect(fetchStub).toBeCalledTimes(1);
expect(updateStub).toBeCalledTimes(1);
@@ -420,25 +374,25 @@ describe('filter_manager', () => {
});
describe('filter reconciliation', function() {
- test('should de-dupe appStateStub filters being added', async function() {
+ test('should de-dupe app filters being added', async function() {
const newFilter = _.cloneDeep(readyFilters[1]);
filterManager.addFilters(readyFilters, false);
- expect(appStateStub.filters.length).toBe(3);
+ expect(filterManager.getFilters()).toHaveLength(3);
filterManager.addFilters(newFilter, false);
- expect(appStateStub.filters.length).toBe(3);
+ expect(filterManager.getFilters()).toHaveLength(3);
});
- test('should de-dupe globalStateStub filters being added', async function() {
+ test('should de-dupe global filters being added', async function() {
const newFilter = _.cloneDeep(readyFilters[1]);
filterManager.addFilters(readyFilters, true);
- expect(globalStateStub.filters.length).toBe(3);
+ expect(filterManager.getFilters()).toHaveLength(3);
filterManager.addFilters(newFilter, true);
- expect(globalStateStub.filters.length).toBe(3);
+ expect(filterManager.getFilters()).toHaveLength(3);
});
- test('should de-dupe globalStateStub filters being set', async () => {
+ test('should de-dupe global filters being set', async () => {
const f1 = getFilter(FilterStateStore.GLOBAL_STATE, false, false, 'age', 34);
const f2 = _.cloneDeep(f1);
filterManager.setFilters([f1, f2]);
@@ -447,7 +401,7 @@ describe('filter_manager', () => {
expect(filterManager.getFilters()).toHaveLength(1);
});
- test('should de-dupe appStateStub filters being set', async () => {
+ test('should de-dupe app filters being set', async () => {
const f1 = getFilter(FilterStateStore.APP_STATE, false, false, 'age', 34);
const f2 = _.cloneDeep(f1);
filterManager.setFilters([f1, f2]);
@@ -475,7 +429,7 @@ describe('filter_manager', () => {
});
});
- test('should merge conflicting appStateStub filters', async function() {
+ test('should merge conflicting app filters', async function() {
filterManager.addFilters(readyFilters, true);
const appFilter = _.cloneDeep(readyFilters[1]);
appFilter.meta.negate = true;
@@ -580,16 +534,16 @@ describe('filter_manager', () => {
test('should remove the filter from appStateStub', async function() {
filterManager.addFilters(readyFilters, false);
- expect(appStateStub.filters).toHaveLength(3);
+ expect(filterManager.getAppFilters()).toHaveLength(3);
filterManager.removeFilter(readyFilters[0]);
- expect(appStateStub.filters).toHaveLength(2);
+ expect(filterManager.getAppFilters()).toHaveLength(2);
});
test('should remove the filter from globalStateStub', async function() {
filterManager.addFilters(readyFilters, true);
- expect(globalStateStub.filters).toHaveLength(3);
+ expect(filterManager.getGlobalFilters()).toHaveLength(3);
filterManager.removeFilter(readyFilters[0]);
- expect(globalStateStub.filters).toHaveLength(2);
+ expect(filterManager.getGlobalFilters()).toHaveLength(2);
});
test('should fire the update and fetch events', async function() {
@@ -619,8 +573,8 @@ describe('filter_manager', () => {
filterManager.removeFilter(readyFilters[0]);
- expect(globalStateStub.filters).toHaveLength(1);
- expect(appStateStub.filters).toHaveLength(1);
+ expect(filterManager.getAppFilters()).toHaveLength(1);
+ expect(filterManager.getGlobalFilters()).toHaveLength(1);
});
test('should remove matching filters by comparison', async function() {
@@ -629,12 +583,12 @@ describe('filter_manager', () => {
filterManager.removeFilter(_.cloneDeep(readyFilters[0]));
- expect(globalStateStub.filters).toHaveLength(1);
- expect(appStateStub.filters).toHaveLength(1);
+ expect(filterManager.getAppFilters()).toHaveLength(1);
+ expect(filterManager.getGlobalFilters()).toHaveLength(1);
filterManager.removeFilter(_.cloneDeep(readyFilters[2]));
- expect(globalStateStub.filters).toHaveLength(1);
- expect(appStateStub.filters).toHaveLength(0);
+ expect(filterManager.getAppFilters()).toHaveLength(0);
+ expect(filterManager.getGlobalFilters()).toHaveLength(1);
});
test('should do nothing with a non-matching filter', async function() {
@@ -645,19 +599,19 @@ describe('filter_manager', () => {
missedFilter.meta.negate = !readyFilters[0].meta.negate;
filterManager.removeFilter(missedFilter);
- expect(globalStateStub.filters).toHaveLength(2);
- expect(appStateStub.filters).toHaveLength(1);
+ expect(filterManager.getAppFilters()).toHaveLength(1);
+ expect(filterManager.getGlobalFilters()).toHaveLength(2);
});
test('should remove all the filters from both states', async function() {
filterManager.addFilters([readyFilters[0], readyFilters[1]], true);
filterManager.addFilters([readyFilters[2]], false);
- expect(globalStateStub.filters).toHaveLength(2);
- expect(appStateStub.filters).toHaveLength(1);
+ expect(filterManager.getAppFilters()).toHaveLength(1);
+ expect(filterManager.getGlobalFilters()).toHaveLength(2);
filterManager.removeAll();
- expect(globalStateStub.filters).toHaveLength(0);
- expect(appStateStub.filters).toHaveLength(0);
+ expect(filterManager.getAppFilters()).toHaveLength(0);
+ expect(filterManager.getGlobalFilters()).toHaveLength(0);
});
});
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts
similarity index 99%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts
rename to src/plugins/data/public/query/filter_manager/filter_manager.ts
index b3d6bd6873f50..66b65a40926cb 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/filter_manager.ts
+++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts
@@ -28,7 +28,7 @@ import { compareFilters } from './lib/compare_filters';
import { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
import { uniqFilters } from './lib/uniq_filters';
import { onlyDisabledFiltersChanged } from './lib/only_disabled';
-import { PartitionedFilters } from './partitioned_filters';
+import { PartitionedFilters } from './types';
export class FilterManager {
private filters: Filter[] = [];
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts b/src/plugins/data/public/query/filter_manager/index.ts
similarity index 77%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts
rename to src/plugins/data/public/query/filter_manager/index.ts
index d429fc7f70f38..7955cdd825ee6 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/stub_index_pattern.ts
+++ b/src/plugins/data/public/query/filter_manager/index.ts
@@ -17,12 +17,8 @@
* under the License.
*/
-export class StubIndexPatterns {
- async get(index: string) {
- return {
- fields: {
- getByName: () => undefined,
- },
- };
- }
-}
+export { FilterManager } from './filter_manager';
+
+export { uniqFilters } from './lib/uniq_filters';
+export { mapAndFlattenFilters } from './lib/map_and_flatten_filters';
+export { onlyDisabledFiltersChanged } from './lib/only_disabled';
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/compare_filters.ts
rename to src/plugins/data/public/query/filter_manager/lib/compare_filters.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/dedup_filters.ts
rename to src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/generate_mapping_chain.ts
rename to src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_and_flatten_filters.ts
rename to src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts
similarity index 84%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts
rename to src/plugins/data/public/query/filter_manager/lib/map_filter.ts
index c0d251e647fd1..cda9591e40b33 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_filter.ts
+++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts
@@ -20,16 +20,16 @@
import { Filter } from '@kbn/es-query';
import { reduceRight } from 'lodash';
-import { mapMatchAll } from './map_match_all';
-import { mapPhrase } from './map_phrase';
-import { mapPhrases } from './map_phrases';
-import { mapRange } from './map_range';
-import { mapExists } from './map_exists';
-import { mapMissing } from './map_missing';
-import { mapQueryString } from './map_query_string';
-import { mapGeoBoundingBox } from './map_geo_bounding_box';
-import { mapGeoPolygon } from './map_geo_polygon';
-import { mapDefault } from './map_default';
+import { mapMatchAll } from './mappers/map_match_all';
+import { mapPhrase } from './mappers/map_phrase';
+import { mapPhrases } from './mappers/map_phrases';
+import { mapRange } from './mappers/map_range';
+import { mapExists } from './mappers/map_exists';
+import { mapMissing } from './mappers/map_missing';
+import { mapQueryString } from './mappers/map_query_string';
+import { mapGeoBoundingBox } from './mappers/map_geo_bounding_box';
+import { mapGeoPolygon } from './mappers/map_geo_polygon';
+import { mapDefault } from './mappers/map_default';
import { generateMappingChain } from './generate_mapping_chain';
export function mapFilter(filter: Filter) {
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_default.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_exists.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_bounding_box.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_geo_polygon.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_match_all.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_missing.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrase.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_phrases.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_query_string.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/map_range.ts
rename to src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/only_disabled.ts
rename to src/plugins/data/public/query/filter_manager/lib/only_disabled.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.test.ts
rename to src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/lib/uniq_filters.ts
rename to src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_filters_array.ts b/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/test_helpers/get_filters_array.ts
rename to src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts
diff --git a/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts b/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts
new file mode 100644
index 0000000000000..20d9e236f49be
--- /dev/null
+++ b/src/plugins/data/public/query/filter_manager/test_helpers/get_stub_filter.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { Filter, FilterStateStore } from '@kbn/es-query';
+
+export function getFilter(
+ store: FilterStateStore,
+ disabled: boolean,
+ negated: boolean,
+ queryKey: string,
+ queryValue: any
+): Filter {
+ return {
+ $state: {
+ store,
+ },
+ meta: {
+ index: 'logstash-*',
+ disabled,
+ negate: negated,
+ alias: null,
+ },
+ query: {
+ match: {
+ [queryKey]: queryValue,
+ },
+ },
+ };
+}
diff --git a/src/legacy/core_plugins/data/public/filter/filter_manager/partitioned_filters.ts b/src/plugins/data/public/query/filter_manager/types.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/filter/filter_manager/partitioned_filters.ts
rename to src/plugins/data/public/query/filter_manager/types.ts
diff --git a/src/legacy/ui/public/storage/web_storage.ts b/src/plugins/data/public/query/index.tsx
similarity index 92%
rename from src/legacy/ui/public/storage/web_storage.ts
rename to src/plugins/data/public/query/index.tsx
index d5f775431143d..44b371b6adf19 100644
--- a/src/legacy/ui/public/storage/web_storage.ts
+++ b/src/plugins/data/public/query/index.tsx
@@ -17,4 +17,6 @@
* under the License.
*/
-export type WebStorage = Storage;
+export * from './query_service';
+
+export * from './filter_manager';
diff --git a/src/legacy/core_plugins/data/public/filter/filter_service.mock.ts b/src/plugins/data/public/query/mocks.ts
similarity index 80%
rename from src/legacy/core_plugins/data/public/filter/filter_service.mock.ts
rename to src/plugins/data/public/query/mocks.ts
index 94268ef69c49a..e5030c5765316 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_service.mock.ts
+++ b/src/plugins/data/public/query/mocks.ts
@@ -17,12 +17,12 @@
* under the License.
*/
-import { FilterService, FilterStart, FilterSetup } from '.';
+import { QueryService, QueryStart, QuerySetup } from '.';
-type FilterServiceClientContract = PublicMethodsOf;
+type QueryServiceClientContract = PublicMethodsOf;
const createSetupContractMock = () => {
- const setupContract: jest.Mocked = {
+ const setupContract: jest.Mocked = {
filterManager: jest.fn() as any,
};
@@ -30,7 +30,7 @@ const createSetupContractMock = () => {
};
const createStartContractMock = () => {
- const startContract: jest.Mocked = {
+ const startContract: jest.Mocked = {
filterManager: jest.fn() as any,
};
@@ -38,7 +38,7 @@ const createStartContractMock = () => {
};
const createMock = () => {
- const mocked: jest.Mocked = {
+ const mocked: jest.Mocked = {
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
@@ -49,7 +49,7 @@ const createMock = () => {
return mocked;
};
-export const filterServiceMock = {
+export const queryServiceMock = {
create: createMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
diff --git a/src/legacy/core_plugins/data/public/filter/filter_service.ts b/src/plugins/data/public/query/query_service.ts
similarity index 82%
rename from src/legacy/core_plugins/data/public/filter/filter_service.ts
rename to src/plugins/data/public/query/query_service.ts
index 0c46259ef0e00..d34909a5e03b7 100644
--- a/src/legacy/core_plugins/data/public/filter/filter_service.ts
+++ b/src/plugins/data/public/query/query_service.ts
@@ -21,18 +21,18 @@ import { UiSettingsClientContract } from 'src/core/public';
import { FilterManager } from './filter_manager';
/**
- * Filter Service
+ * Query Service
* @internal
*/
-export interface FilterServiceDependencies {
+export interface QueryServiceDependencies {
uiSettings: UiSettingsClientContract;
}
-export class FilterService {
+export class QueryService {
filterManager!: FilterManager;
- public setup({ uiSettings }: FilterServiceDependencies) {
+ public setup({ uiSettings }: QueryServiceDependencies) {
this.filterManager = new FilterManager(uiSettings);
return {
@@ -52,5 +52,5 @@ export class FilterService {
}
/** @public */
-export type FilterSetup = ReturnType;
-export type FilterStart = ReturnType;
+export type QuerySetup = ReturnType;
+export type QueryStart = ReturnType;
diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts
index 5f94734fef083..9939815c1efd1 100644
--- a/src/plugins/data/public/types.ts
+++ b/src/plugins/data/public/types.ts
@@ -22,15 +22,18 @@ export * from './autocomplete_provider/types';
import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.';
import { ISearchSetup, ISearchStart } from './search';
import { IGetSuggestions } from './suggestions_provider/types';
+import { QuerySetup, QueryStart } from './query';
export interface DataPublicPluginSetup {
autocomplete: AutocompletePublicPluginSetup;
search: ISearchSetup;
+ query: QuerySetup;
}
export interface DataPublicPluginStart {
autocomplete: AutocompletePublicPluginStart;
getSuggestions: IGetSuggestions;
search: ISearchStart;
+ query: QueryStart;
}
export { IGetSuggestions } from './suggestions_provider/types';
diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts
index 7cb4d4a34e971..30b49e5a82e9c 100644
--- a/src/plugins/kibana_utils/public/index.ts
+++ b/src/plugins/kibana_utils/public/index.ts
@@ -23,3 +23,4 @@ export * from './parse';
export * from './render_complete';
export * from './errors';
export * from './field_mapping';
+export * from './storage';
diff --git a/src/legacy/ui/public/storage/__tests__/storage.js b/src/plugins/kibana_utils/public/storage/__tests__/storage.js
similarity index 100%
rename from src/legacy/ui/public/storage/__tests__/storage.js
rename to src/plugins/kibana_utils/public/storage/__tests__/storage.js
diff --git a/src/legacy/ui/public/storage/index.ts b/src/plugins/kibana_utils/public/storage/index.ts
similarity index 94%
rename from src/legacy/ui/public/storage/index.ts
rename to src/plugins/kibana_utils/public/storage/index.ts
index 17bbb61b2b8d5..53956bf21cdf3 100644
--- a/src/legacy/ui/public/storage/index.ts
+++ b/src/plugins/kibana_utils/public/storage/index.ts
@@ -17,6 +17,5 @@
* under the License.
*/
-import './directive';
-
export { Storage } from './storage';
+export { IStorage, IStorageWrapper } from './types';
diff --git a/src/legacy/ui/public/storage/storage.ts b/src/plugins/kibana_utils/public/storage/storage.ts
similarity index 73%
rename from src/legacy/ui/public/storage/storage.ts
rename to src/plugins/kibana_utils/public/storage/storage.ts
index 703886c1e034c..a7d3c5ac70074 100644
--- a/src/legacy/ui/public/storage/storage.ts
+++ b/src/plugins/kibana_utils/public/storage/storage.ts
@@ -17,17 +17,12 @@
* under the License.
*/
-import angular from 'angular';
+import { IStorage, IStorageWrapper } from './types';
-// This is really silly, but I wasn't prepared to rename the kibana Storage class everywhere it is used
-// and this is the only way I could figure out how to use the type definition for a built in object
-// in a file that creates a type with the same name as that built in object.
-import { WebStorage } from './web_storage';
+export class Storage implements IStorageWrapper {
+ public store: IStorage;
-export class Storage {
- public store: WebStorage;
-
- constructor(store: WebStorage) {
+ constructor(store: IStorage) {
this.store = store;
}
@@ -50,7 +45,7 @@ export class Storage {
public set = (key: string, value: any) => {
try {
- return this.store.setItem(key, angular.toJson(value));
+ return this.store.setItem(key, JSON.stringify(value));
} catch (e) {
return false;
}
diff --git a/src/plugins/kibana_utils/public/storage/types.ts b/src/plugins/kibana_utils/public/storage/types.ts
new file mode 100644
index 0000000000000..875bb44bcad17
--- /dev/null
+++ b/src/plugins/kibana_utils/public/storage/types.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 interface IStorageWrapper {
+ get: (key: string) => any;
+ set: (key: string, value: any) => void;
+ remove: (key: string) => any;
+ clear: () => void;
+}
+
+export interface IStorage {
+ getItem: (key: string) => any;
+ setItem: (key: string, value: any) => void;
+ removeItem: (key: string) => any;
+ clear: () => void;
+}
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
index 21b49a9823f62..766e6168002c2 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json
@@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
index 5d464cf0405d0..7c1406f5b20c3 100644
--- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
+++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx
@@ -76,7 +76,7 @@ const PageA = () => (
- Page A's content goes here
+ Page A's content goes here
);
diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
index 7aacfbc22ceee..7c5b6f6be58af 100644
--- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json
@@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"react": "^16.8.0"
}
}
diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
index 80ec89fb0078a..ef472b4026957 100644
--- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"react": "^16.8.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
index 54aba07079eeb..277bb09ac745c 100644
--- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json
@@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"react": "^16.8.0"
},
"scripts": {
diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
index d56ac5f92db88..8678ab705df9c 100644
--- a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
+++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { Component } from 'react';
+import React from 'react';
import {
setup as navSetup,
start as navStart,
@@ -33,22 +33,21 @@ const customExtension = {
navSetup.registerMenuItem(customExtension);
-export class AppWithTopNav extends Component {
- public render() {
- const { TopNavMenu } = navStart.ui;
- const config = [
- {
- id: 'new',
- label: 'New Button',
- description: 'New Demo',
- run() {},
- testId: 'demoNewButton',
- },
- ];
- return (
-
- Hey
-
- );
- }
-}
+export const AppWithTopNav = () => {
+ const { TopNavMenu } = navStart.ui;
+ const config = [
+ {
+ id: 'new',
+ label: 'New Button',
+ description: 'New Demo',
+ run() {},
+ testId: 'demoNewButton',
+ },
+ ];
+
+ return (
+
+ Hey
+
+ );
+};
diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json
index 121f69ada329b..f248a7e4d1f2d 100644
--- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json
+++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json
@@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
diff --git a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx
index 665b3c9f0ae03..8a0dd31e3595f 100644
--- a/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx
+++ b/test/plugin_functional/plugins/search_explorer/public/demo_strategy.tsx
@@ -127,7 +127,7 @@ export class DemoStrategy extends React.Component {
},
]}
demo={this.renderDemo()}
- >
+ />
);
}
diff --git a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx b/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx
index 20de631bc5605..d35c67191a1f8 100644
--- a/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx
+++ b/test/plugin_functional/plugins/search_explorer/public/es_strategy.tsx
@@ -139,7 +139,7 @@ export class EsSearchTest extends React.Component {
},
]}
demo={this.renderDemo()}
- >
+ />
);
}
diff --git a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx b/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx
index fe67e4097b2a3..1562e33b14c2f 100644
--- a/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx
+++ b/test/plugin_functional/plugins/search_explorer/public/guide_section.tsx
@@ -110,7 +110,7 @@ export class GuideSection extends React.Component {
return code.map((codeBlock, i) => (
-
+
{codeBlock.description}
{this.removeLicenseBlock(codeBlock.snippet)}
diff --git a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx b/test/plugin_functional/plugins/search_explorer/public/search_api.tsx
index 3e4768c870064..8ec6225d1f172 100644
--- a/test/plugin_functional/plugins/search_explorer/public/search_api.tsx
+++ b/test/plugin_functional/plugins/search_explorer/public/search_api.tsx
@@ -83,5 +83,5 @@ export const SearchApiPage = () => (
],
},
]}
- >
+ />
);
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx
index 2833b0476428c..79874d6648d0f 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx
@@ -113,7 +113,7 @@ export function ServiceNodeMetrics() {
)
}}
- >
+ />
) : (
@@ -129,7 +129,7 @@ export function ServiceNodeMetrics() {
{host}
}
- >
+ />
{containerId}
}
- >
+ />
)}
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx
index 4f14c4ba85932..1ecf72f6fa3fc 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/index.tsx
@@ -49,7 +49,7 @@ export function TraceOverview() {
return (
-
+
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx
index 7e9171197251d..0f5893772fec8 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx
@@ -224,6 +224,7 @@ export function SpanFlyout({
),
content: (
+
)
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx
index db3e43574023c..2020b8252035b 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx
@@ -11,7 +11,8 @@ import {
EuiFlyoutHeader,
EuiPortal,
EuiSpacer,
- EuiTitle
+ EuiTitle,
+ EuiHorizontalRule
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
@@ -40,7 +41,6 @@ function TransactionPropertiesTable({
Metadata
-
);
@@ -87,7 +87,7 @@ export function TransactionFlyout({
totalDuration={traceRootDuration}
errorCount={errorCount}
/>
-
+
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx
deleted file mode 100644
index aebc86352a774..0000000000000
--- a/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/__test__/DottedKeyValueTable.test.tsx
+++ /dev/null
@@ -1,134 +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 from 'react';
-import { DottedKeyValueTable } from '..';
-import { cleanup, render } from 'react-testing-library';
-
-function getKeys(output: ReturnType) {
- const keys = output.getAllByTestId('dot-key');
- return Array.isArray(keys) ? keys.map(node => node.textContent) : [];
-}
-
-function getValues(output: ReturnType) {
- const values = output.getAllByTestId('value');
- return Array.isArray(values) ? values.map(node => node.textContent) : [];
-}
-
-describe('DottedKeyValueTable', () => {
- afterEach(cleanup);
-
- it('should display a nested object with alpha-ordered, dot notation keys and values', () => {
- const data = {
- name: {
- first: 'Jo',
- last: 'Smith'
- },
- age: 29,
- active: true,
- useless: false,
- start: null,
- end: undefined,
- nested: {
- b: {
- c: 'ccc'
- },
- a: 'aaa'
- }
- };
- const output = render( );
- const rows = output.container.querySelectorAll('tr');
- expect(rows.length).toEqual(9);
-
- expect(getKeys(output)).toEqual([
- 'active',
- 'age',
- 'end',
- 'name.first',
- 'name.last',
- 'nested.a',
- 'nested.b.c',
- 'start',
- 'useless'
- ]);
-
- expect(getValues(output)).toEqual([
- 'true',
- '29',
- 'N/A',
- 'Jo',
- 'Smith',
- 'aaa',
- 'ccc',
- 'N/A',
- 'false'
- ]);
- });
-
- it('should respect max depth', () => {
- const data = {
- nested: { b: { c: 'ccc' }, a: 'aaa' }
- };
- const output = render( );
- const rows = output.container.querySelectorAll('tr');
- expect(rows.length).toEqual(2);
-
- expect(getKeys(output)).toEqual(['nested.a', 'nested.b']);
-
- expect(getValues(output)).toEqual([
- 'aaa',
- JSON.stringify({ c: 'ccc' }, null, 4)
- ]);
- });
-
- it('should prepend a provided parent key to all of the dot-notation keys', () => {
- const data = {
- name: {
- first: 'Jo',
- last: 'Smith'
- },
- age: 29,
- active: true
- };
- const output = render( );
- const rows = output.container.querySelectorAll('tr');
- expect(rows.length).toEqual(4);
-
- expect(getKeys(output)).toEqual([
- 'top.active',
- 'top.age',
- 'top.name.first',
- 'top.name.last'
- ]);
- });
-
- it('should not add 0 sufix if value is an array with one element', () => {
- const data = {
- a: {
- b: {
- c1: ['foo', 'bar'],
- c2: ['foo']
- }
- },
- b: {
- c: ['foo']
- },
- c: {
- d: ['foo', 'bar']
- }
- };
- const output = render( );
-
- expect(getKeys(output)).toEqual([
- 'a.b.c1.0',
- 'a.b.c1.1',
- 'a.b.c2',
- 'b.c',
- 'c.d.0',
- 'c.d.1'
- ]);
- });
-});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx
deleted file mode 100644
index 674df14c62eb0..0000000000000
--- a/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/index.tsx
+++ /dev/null
@@ -1,89 +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, { TableHTMLAttributes } from 'react';
-import { compact, isObject } from 'lodash';
-import {
- EuiTable,
- EuiTableProps,
- EuiTableBody,
- EuiTableRow,
- EuiTableRowCell
-} from '@elastic/eui';
-import { StringMap } from '../../../../typings/common';
-import { FormattedValue } from './FormattedValue';
-
-interface PathifyOptions {
- maxDepth?: number;
- parentKey?: string;
- depth?: number;
-}
-
-interface PathifyResult {
- [key: string]: any;
-}
-
-/**
- * Converts a deeply-nested object into a one-level object
- * with dot-notation paths as keys.
- */
-export function pathify(
- item: StringMap,
- { maxDepth, parentKey = '', depth = 0 }: PathifyOptions
-): PathifyResult {
- const isArrayWithSingleValue = Array.isArray(item) && item.length === 1;
- return Object.keys(item)
- .sort()
- .reduce((pathified, key) => {
- const childKey = isArrayWithSingleValue ? '' : key;
- const currentKey = compact([parentKey, childKey]).join('.');
- if ((!maxDepth || depth + 1 <= maxDepth) && isObject(item[key])) {
- return {
- ...pathified,
- ...pathify(item[key], {
- maxDepth,
- parentKey: currentKey,
- depth: depth + 1
- })
- };
- } else {
- return { ...pathified, [currentKey]: item[key] };
- }
- }, {});
-}
-
-export function DottedKeyValueTable({
- data,
- parentKey,
- maxDepth,
- tableProps = {}
-}: {
- data: StringMap;
- parentKey?: string;
- maxDepth?: number;
- tableProps?: EuiTableProps & TableHTMLAttributes;
-}) {
- const pathified = pathify(data, { maxDepth, parentKey });
- const rows = Object.keys(pathified)
- .sort()
- .map(k => [k, pathified[k]]);
- return (
-
-
- {rows.map(([key, value]) => (
-
-
- {key}
-
-
-
-
-
- ))}
-
-
- );
-}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/HeightRetainer/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/HeightRetainer/index.tsx
new file mode 100644
index 0000000000000..e6f4487312429
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/HeightRetainer/index.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useEffect, useRef } from 'react';
+
+export const HeightRetainer: React.SFC = props => {
+ const containerElement = useRef(null);
+ const minHeight = useRef(0);
+
+ useEffect(() => {
+ if (containerElement.current) {
+ const currentHeight = containerElement.current.clientHeight;
+ if (minHeight.current < currentHeight) {
+ minHeight.current = currentHeight;
+ }
+ }
+ });
+
+ return (
+
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/FormattedValue.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx
similarity index 100%
rename from x-pack/legacy/plugins/apm/public/components/shared/DottedKeyValueTable/FormattedValue.tsx
rename to x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/FormattedValue.tsx
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx
new file mode 100644
index 0000000000000..2ce8feb08d4ad
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx
@@ -0,0 +1,64 @@
+/*
+ * 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 { KeyValueTable } from '..';
+import { cleanup, render } from 'react-testing-library';
+
+function getKeys(output: ReturnType) {
+ const keys = output.getAllByTestId('dot-key');
+ return Array.isArray(keys) ? keys.map(node => node.textContent) : [];
+}
+
+function getValues(output: ReturnType) {
+ const values = output.getAllByTestId('value');
+ return Array.isArray(values) ? values.map(node => node.textContent) : [];
+}
+
+describe('KeyValueTable', () => {
+ afterEach(cleanup);
+
+ it('displays key and value table', () => {
+ const data = [
+ { key: 'name.first', value: 'First Name' },
+ { key: 'name.last', value: 'Last Name' },
+ { key: 'age', value: '29' },
+ { key: 'active', value: true },
+ { key: 'useless', value: false },
+ { key: 'start', value: null },
+ { key: 'end', value: undefined },
+ { key: 'nested.b.c', value: 'ccc' },
+ { key: 'nested.a', value: 'aaa' }
+ ];
+ const output = render( );
+ const rows = output.container.querySelectorAll('tr');
+ expect(rows.length).toEqual(9);
+
+ expect(getKeys(output)).toEqual([
+ 'name.first',
+ 'name.last',
+ 'age',
+ 'active',
+ 'useless',
+ 'start',
+ 'end',
+ 'nested.b.c',
+ 'nested.a'
+ ]);
+
+ expect(getValues(output)).toEqual([
+ 'First Name',
+ 'Last Name',
+ '29',
+ 'true',
+ 'false',
+ 'N/A',
+ 'N/A',
+ 'ccc',
+ 'aaa'
+ ]);
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/index.tsx
new file mode 100644
index 0000000000000..b58f142f1abaa
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/index.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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, { TableHTMLAttributes } from 'react';
+import {
+ EuiTable,
+ EuiTableProps,
+ EuiTableBody,
+ EuiTableRow,
+ EuiTableRowCell
+} from '@elastic/eui';
+import { FormattedValue } from './FormattedValue';
+import { KeyValuePair } from '../../../utils/flattenObject';
+
+export function KeyValueTable({
+ keyValuePairs,
+ tableProps = {}
+}: {
+ keyValuePairs: KeyValuePair[];
+ tableProps?: EuiTableProps & TableHTMLAttributes;
+}) {
+ return (
+
+
+ {keyValuePairs.map(({ key, value }) => (
+
+
+ {key}
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts
index 763ee93df6415..b6caa47d1ae66 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts
@@ -39,6 +39,7 @@ export type APMQueryParams = {
rangeTo?: string;
refreshPaused?: string | boolean;
refreshInterval?: string | number;
+ searchTerm?: string;
} & { [key in LocalUIFilterName]?: string };
// forces every value of T[K] to be type: string
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/FilterBadgeList.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/FilterBadgeList.tsx
index a1140dcbbb3fc..01d2d56b6d142 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/FilterBadgeList.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/FilterBadgeList.tsx
@@ -32,7 +32,7 @@ const FilterBadgeList = ({ onRemove, value }: Props) => (
>
{val}
-
+
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
index aef8e5747d992..412b92d525aa2 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/LocalUIFilters/Filter/index.tsx
@@ -168,7 +168,7 @@ const Filter = ({
}}
value={value}
/>
-
+
>
) : null}
>
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx
index 652bb25afed8e..04a5b60467730 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/index.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { ERROR_METADATA_SECTIONS } from './sections';
import { APMError } from '../../../../../typings/es_schemas/ui/APMError';
+import { getSectionsWithRows } from '../helper';
import { MetadataTable } from '..';
interface Props {
@@ -14,5 +15,9 @@ interface Props {
}
export function ErrorMetadata({ error }: Props) {
- return ;
+ const sectionsWithRows = useMemo(
+ () => getSectionsWithRows(ERROR_METADATA_SECTIONS, error),
+ [error]
+ );
+ return ;
}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx
new file mode 100644
index 0000000000000..6f67b2458ea10
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/Section.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiText } from '@elastic/eui';
+import { KeyValueTable } from '../KeyValueTable';
+import { KeyValuePair } from '../../../utils/flattenObject';
+
+interface Props {
+ keyValuePairs?: KeyValuePair[];
+}
+
+export function Section({ keyValuePairs }: Props) {
+ if (keyValuePairs) {
+ return ;
+ }
+ return (
+
+ {i18n.translate(
+ 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel',
+ { defaultMessage: 'No data available' }
+ )}
+
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx
index 713863639d1b7..03182062d324a 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/index.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { SPAN_METADATA_SECTIONS } from './sections';
import { Span } from '../../../../../typings/es_schemas/ui/Span';
+import { getSectionsWithRows } from '../helper';
import { MetadataTable } from '..';
interface Props {
@@ -14,5 +15,9 @@ interface Props {
}
export function SpanMetadata({ span }: Props) {
- return ;
+ const sectionsWithRows = useMemo(
+ () => getSectionsWithRows(SPAN_METADATA_SECTIONS, span),
+ [span]
+ );
+ return ;
}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx
index fa2b32b403ee7..4216e37e0cb27 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/index.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { TRANSACTION_METADATA_SECTIONS } from './sections';
import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
+import { getSectionsWithRows } from '../helper';
import { MetadataTable } from '..';
interface Props {
@@ -14,10 +15,9 @@ interface Props {
}
export function TransactionMetadata({ transaction }: Props) {
- return (
-
+ const sectionsWithRows = useMemo(
+ () => getSectionsWithRows(TRANSACTION_METADATA_SECTIONS, transaction),
+ [transaction]
);
+ return ;
}
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
index 331a8bf41642e..bdf895f423913 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx
@@ -8,77 +8,44 @@ import React from 'react';
import 'jest-dom/extend-expect';
import { render, cleanup } from 'react-testing-library';
import { MetadataTable } from '..';
-import {
- expectTextsInDocument,
- expectTextsNotInDocument
-} from '../../../../utils/testHelpers';
-import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
+import { expectTextsInDocument } from '../../../../utils/testHelpers';
+import { SectionsWithRows } from '../helper';
describe('MetadataTable', () => {
afterEach(cleanup);
+ it('shows sections', () => {
+ const sectionsWithRows = ([
+ { key: 'foo', label: 'Foo', required: true },
+ {
+ key: 'bar',
+ label: 'Bar',
+ required: false,
+ properties: ['props.A', 'props.B'],
+ rows: [{ key: 'props.A', value: 'A' }, { key: 'props.B', value: 'B' }]
+ }
+ ] as unknown) as SectionsWithRows;
+ const output = render( );
+ expectTextsInDocument(output, [
+ 'Foo',
+ 'No data available',
+ 'Bar',
+ 'props.A',
+ 'A',
+ 'props.B',
+ 'B'
+ ]);
+ });
describe('required sections', () => {
it('shows "empty state message" if no data is available', () => {
- const sections = [
+ const sectionsWithRows = ([
{
key: 'foo',
label: 'Foo',
required: true
}
- ];
- const output = render(
-
- );
+ ] as unknown) as SectionsWithRows;
+ const output = render( );
expectTextsInDocument(output, ['Foo', 'No data available']);
});
- it('shows "empty state message" if property is not available', () => {
- const sections = [
- {
- key: 'foo',
- label: 'Foo',
- required: true,
- properties: ['bar']
- }
- ];
- const item = ({
- foo: {
- foobar: 'bar'
- }
- } as unknown) as Transaction;
-
- const output = render( );
- expectTextsInDocument(output, ['Foo', 'No data available']);
- });
- });
- describe('not required sections', () => {
- it('does not show section when no items are provided', () => {
- const sections = [
- {
- key: 'foo',
- label: 'Foo',
- required: false
- }
- ];
- const output = render(
-
- );
- expectTextsNotInDocument(output, ['Foo']);
- });
- it('does not show section if property is not available', () => {
- const sections = [
- {
- key: 'foo',
- label: 'Foo',
- required: false,
- properties: ['bar']
- }
- ];
- const item = ({
- foo: {
- foobar: 'bar'
- }
- } as unknown) as Transaction;
- const output = render( );
- expectTextsNotInDocument(output, ['Foo']);
- });
});
});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx
new file mode 100644
index 0000000000000..7e68b2f84eead
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+import 'jest-dom/extend-expect';
+import { render } from 'react-testing-library';
+import { Section } from '../Section';
+import { expectTextsInDocument } from '../../../../utils/testHelpers';
+
+describe('Section', () => {
+ it('shows "empty state message" if no data is available', () => {
+ const output = render();
+ expectTextsInDocument(output, ['No data available']);
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts
new file mode 100644
index 0000000000000..aaf73619e481a
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/helper.test.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 { getSectionsWithRows, filterSectionsByTerm } from '../helper';
+import { LABELS, HTTP, SERVICE } from '../sections';
+import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction';
+
+describe('MetadataTable Helper', () => {
+ const sections = [
+ { ...LABELS, required: true },
+ HTTP,
+ { ...SERVICE, properties: ['environment'] }
+ ];
+ const apmDoc = ({
+ http: {
+ headers: {
+ Connection: 'close',
+ Host: 'opbeans:3000',
+ request: { method: 'get' }
+ }
+ },
+ service: {
+ framework: { name: 'express' },
+ environment: 'production'
+ }
+ } as unknown) as Transaction;
+ const metadataItems = getSectionsWithRows(sections, apmDoc);
+
+ it('returns flattened data and required section', () => {
+ expect(metadataItems).toEqual([
+ { key: 'labels', label: 'Labels', required: true, rows: [] },
+ {
+ key: 'http',
+ label: 'HTTP',
+ rows: [
+ { key: 'http.headers.Connection', value: 'close' },
+ { key: 'http.headers.Host', value: 'opbeans:3000' },
+ { key: 'http.headers.request.method', value: 'get' }
+ ]
+ },
+ {
+ key: 'service',
+ label: 'Service',
+ properties: ['environment'],
+ rows: [{ key: 'service.environment', value: 'production' }]
+ }
+ ]);
+ });
+ describe('filter', () => {
+ it('items by key', () => {
+ const filteredItems = filterSectionsByTerm(metadataItems, 'http');
+ expect(filteredItems).toEqual([
+ {
+ key: 'http',
+ label: 'HTTP',
+ rows: [
+ { key: 'http.headers.Connection', value: 'close' },
+ { key: 'http.headers.Host', value: 'opbeans:3000' },
+ { key: 'http.headers.request.method', value: 'get' }
+ ]
+ }
+ ]);
+ });
+
+ it('items by value', () => {
+ const filteredItems = filterSectionsByTerm(metadataItems, 'product');
+ expect(filteredItems).toEqual([
+ {
+ key: 'service',
+ label: 'Service',
+ properties: ['environment'],
+ rows: [{ key: 'service.environment', value: 'production' }]
+ }
+ ]);
+ });
+
+ it('returns empty when no item matches', () => {
+ const filteredItems = filterSectionsByTerm(metadataItems, 'post');
+ expect(filteredItems).toEqual([]);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts
new file mode 100644
index 0000000000000..c272790826d8d
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/helper.ts
@@ -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 { get, pick, isEmpty } from 'lodash';
+import { Section } from './sections';
+import { Transaction } from '../../../../typings/es_schemas/ui/Transaction';
+import { APMError } from '../../../../typings/es_schemas/ui/APMError';
+import { Span } from '../../../../typings/es_schemas/ui/Span';
+import { flattenObject, KeyValuePair } from '../../../utils/flattenObject';
+
+export type SectionsWithRows = ReturnType;
+
+export const getSectionsWithRows = (
+ sections: Section[],
+ apmDoc: Transaction | APMError | Span
+) => {
+ return sections
+ .map(section => {
+ const sectionData: Record = get(apmDoc, section.key);
+ const filteredData:
+ | Record
+ | undefined = section.properties
+ ? pick(sectionData, section.properties)
+ : sectionData;
+
+ const rows: KeyValuePair[] = flattenObject(filteredData, section.key);
+ return { ...section, rows };
+ })
+ .filter(({ required, rows }) => required || !isEmpty(rows));
+};
+
+export const filterSectionsByTerm = (
+ sections: SectionsWithRows,
+ searchTerm: string
+) => {
+ if (!searchTerm) {
+ return sections;
+ }
+ return sections
+ .map(section => {
+ const { rows = [] } = section;
+ const filteredRows = rows.filter(({ key, value }) => {
+ const valueAsString = String(value).toLowerCase();
+ return (
+ key.toLowerCase().includes(searchTerm) ||
+ valueAsString.includes(searchTerm)
+ );
+ });
+ return { ...section, rows: filteredRows };
+ })
+ .filter(({ rows }) => !isEmpty(rows));
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx
index d7f3bf5504d40..53d54ae5de7ad 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/index.tsx
@@ -9,42 +9,50 @@ import {
EuiFlexItem,
EuiIcon,
EuiSpacer,
- EuiTitle
+ EuiTitle,
+ EuiFieldSearch
} from '@elastic/eui';
-import React from 'react';
-import { get, pick, isEmpty } from 'lodash';
-import { EuiText } from '@elastic/eui';
+import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import { DottedKeyValueTable } from '../DottedKeyValueTable';
+import { isEmpty } from 'lodash';
+import { EuiText } from '@elastic/eui';
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
-import { Section as SectionType } from './sections';
-import { Transaction } from '../../../../typings/es_schemas/ui/Transaction';
-import { APMError } from '../../../../typings/es_schemas/ui/APMError';
-import { Span } from '../../../../typings/es_schemas/ui/Span';
-
-type Item = Transaction | APMError | Span;
+import { HeightRetainer } from '../HeightRetainer';
+import { Section } from './Section';
+import { history } from '../../../utils/history';
+import { fromQuery, toQuery } from '../Links/url_helpers';
+import { useLocation } from '../../../hooks/useLocation';
+import { useUrlParams } from '../../../hooks/useUrlParams';
+import { SectionsWithRows, filterSectionsByTerm } from './helper';
interface Props {
- item: Item;
- sections: SectionType[];
+ sections: SectionsWithRows;
}
-const filterSections = (sections: SectionType[], item: Item) =>
- sections
- .map(section => {
- const data: Record = get(item, section.key);
- return {
- ...section,
- data: section.properties ? pick(data, section.properties) : data
- };
- })
- .filter(({ required, data }) => required || !isEmpty(data));
+export function MetadataTable({ sections }: Props) {
+ const location = useLocation();
+ const { urlParams } = useUrlParams();
+ const { searchTerm = '' } = urlParams;
-export function MetadataTable({ item, sections }: Props) {
- const filteredSections = filterSections(sections, item);
+ const filteredSections = filterSectionsByTerm(sections, searchTerm);
+
+ const onSearchChange = useCallback(
+ (e: React.ChangeEvent) => {
+ const value = e.target.value.trim().toLowerCase();
+ history.replace({
+ ...location,
+ search: fromQuery({
+ ...toQuery(location.search),
+ searchTerm: value
+ })
+ });
+ },
+ [location]
+ );
+ const noResultFound = Boolean(searchTerm) && isEmpty(filteredSections);
return (
-
+
@@ -52,40 +60,49 @@ export function MetadataTable({ item, sections }: Props) {
+
+
+
- {filteredSections.map(section => (
-
-
- {section.label}
-
-
-
-
-
- ))}
+
+ {filteredSections.map(section => (
+
+
+ {section.label}
+
+
+
+
+
+ ))}
+ {noResultFound && }
+
);
}
-function Section({
- propData = {},
- propKey
-}: {
- propData?: Record;
- propKey?: string;
-}) {
- if (isEmpty(propData)) {
- return (
+const NoResultFound = ({ value }: { value: string }) => (
+
+
{i18n.translate(
- 'xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel',
- { defaultMessage: 'No data available' }
+ 'xpack.apm.propertiesTable.agentFeature.noResultFound',
+ {
+ defaultMessage: `No results for "{value}".`,
+ values: { value }
+ }
)}
- );
- }
-
- return (
-
- );
-}
+
+
+);
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
index 34c46f84c76b9..534485424a496 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx
@@ -11,7 +11,8 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { borderRadius, px, unit, units } from '../../../style/variables';
import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe';
-import { DottedKeyValueTable } from '../DottedKeyValueTable';
+import { KeyValueTable } from '../KeyValueTable';
+import { flattenObject } from '../../../utils/flattenObject';
const VariablesContainer = styled.div`
background: ${theme.euiColorEmptyShade};
@@ -24,29 +25,27 @@ interface Props {
vars: IStackframe['vars'];
}
-export class Variables extends React.Component {
- public render() {
- if (!this.props.vars) {
- return null;
- }
-
- return (
-
-
-
-
-
-
-
-
-
- );
+export const Variables = ({ vars }: Props) => {
+ if (!vars) {
+ return null;
}
-}
+ const flattenedVariables = flattenObject(vars);
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx
index 8f91b8cc5e2af..b6e783a00b5d6 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx
@@ -61,7 +61,7 @@ const TransactionSummary = ({
) : null
];
- return ;
+ return ;
};
export { TransactionSummary };
diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts
index febe0691d7c1c..7972bdbcf2ee6 100644
--- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts
+++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts
@@ -53,7 +53,8 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) {
refreshInterval = TIMEPICKER_DEFAULTS.refreshInterval,
rangeFrom = TIMEPICKER_DEFAULTS.rangeFrom,
rangeTo = TIMEPICKER_DEFAULTS.rangeTo,
- environment
+ environment,
+ searchTerm
} = query;
const localUIFilters = pickKeys(query, ...localUIFilterNames);
@@ -81,6 +82,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) {
kuery: kuery && decodeURIComponent(kuery),
transactionName,
transactionType,
+ searchTerm: toString(searchTerm),
// path params
processorEvent,
diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts
index ca4e0413aa4bf..5578acaaec34b 100644
--- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts
+++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts
@@ -29,4 +29,5 @@ export type IUrlParams = {
page?: number;
pageSize?: number;
serviceNodeName?: string;
+ searchTerm?: string;
} & Partial>;
diff --git a/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts b/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts
new file mode 100644
index 0000000000000..8ec826c7f3d08
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/utils/__test__/flattenObject.test.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { flattenObject } from '../flattenObject';
+
+describe('FlattenObject', () => {
+ it('flattens multi level item', () => {
+ const data = {
+ foo: {
+ item1: 'value 1',
+ item2: { itemA: 'value 2' }
+ },
+ bar: {
+ item3: { itemA: { itemAB: 'value AB' } },
+ item4: 'value 4',
+ item5: [1],
+ item6: [1, 2, 3]
+ }
+ };
+
+ const flatten = flattenObject(data);
+ expect(flatten).toEqual([
+ { key: 'bar.item3.itemA.itemAB', value: 'value AB' },
+ { key: 'bar.item4', value: 'value 4' },
+ { key: 'bar.item5', value: 1 },
+ { key: 'bar.item6.0', value: 1 },
+ { key: 'bar.item6.1', value: 2 },
+ { key: 'bar.item6.2', value: 3 },
+ { key: 'foo.item1', value: 'value 1' },
+ { key: 'foo.item2.itemA', value: 'value 2' }
+ ]);
+ });
+ it('returns an empty array if no valid object is provided', () => {
+ expect(flattenObject({})).toEqual([]);
+ expect(flattenObject(null)).toEqual([]);
+ expect(flattenObject(undefined)).toEqual([]);
+ });
+});
diff --git a/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts
new file mode 100644
index 0000000000000..01a58ac03d0c3
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { compact, isObject } from 'lodash';
+
+export interface KeyValuePair {
+ key: string;
+ value: unknown;
+}
+
+export const flattenObject = (
+ item: Record | null | undefined,
+ parentKey?: string
+): KeyValuePair[] => {
+ if (item) {
+ const isArrayWithSingleValue = Array.isArray(item) && item.length === 1;
+ return Object.keys(item)
+ .sort()
+ .reduce((acc: KeyValuePair[], key) => {
+ const childKey = isArrayWithSingleValue ? '' : key;
+ const currentKey = compact([parentKey, childKey]).join('.');
+ // item[key] can be a primitive (string, number, boolean, null, undefined) or Object or Array
+ if (isObject(item[key])) {
+ return acc.concat(flattenObject(item[key], currentKey));
+ } else {
+ acc.push({ key: currentKey, value: item[key] });
+ return acc;
+ }
+ }, []);
+ }
+ return [];
+};
diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx
index 64e7bf6b7f09e..2ae475475829f 100644
--- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx
+++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx
@@ -18,29 +18,23 @@ interface SuggestionItemProps {
suggestion: AutocompleteSuggestion;
}
-export class SuggestionItem extends React.Component {
- public static defaultProps: Partial = {
- isSelected: false,
- };
+export const SuggestionItem: React.SFC = props => {
+ const { isSelected, onClick, onMouseEnter, suggestion } = props;
- public render() {
- const { isSelected, onClick, onMouseEnter, suggestion } = this.props;
+ return (
+
+
+
+
+ {suggestion.text}
+ {suggestion.description}
+
+ );
+};
- return (
-
-
-
-
- {suggestion.text}
- {suggestion.description}
-
- );
- }
-}
+SuggestionItem.defaultProps = {
+ isSelected: false,
+};
const SuggestionItemContainer = styled.div<{
isSelected?: boolean;
diff --git a/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx b/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx
index 6ac6643755765..26f2aa80de763 100644
--- a/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx
+++ b/x-pack/legacy/plugins/beats_management/public/pages/walkthrough/initial/index.tsx
@@ -6,93 +6,90 @@
import { EuiBetaBadge, EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
-import React, { Component } from 'react';
+import React from 'react';
import { NoDataLayout } from '../../../components/layouts/no_data';
import { WalkthroughLayout } from '../../../components/layouts/walkthrough';
import { ChildRoutes } from '../../../components/navigation/child_routes';
import { ConnectedLink } from '../../../components/navigation/connected_link';
import { AppPageProps } from '../../../frontend_types';
-class InitialWalkthroughPageComponent extends Component<
- AppPageProps & {
- intl: InjectedIntl;
- }
-> {
- public render() {
- const { intl } = this.props;
+type Props = AppPageProps & {
+ intl: InjectedIntl;
+};
- if (this.props.location.pathname === '/walkthrough/initial') {
- return (
-
- {'Beats central management '}
-
-
-
-
- }
- actionSection={
-
-
- {' '}
-
-
- }
- >
-
-
-
-
- );
- }
+const InitialWalkthroughPageComponent: React.SFC = props => {
+ if (props.location.pathname === '/walkthrough/initial') {
return (
- {
- // FIXME implament goto
- }}
- activePath={this.props.location.pathname}
+
+ {'Beats central management '}
+
+
+
+
+ }
+ actionSection={
+
+
+ {' '}
+
+
+ }
>
-
-
+
+
+
+
);
}
-}
+ return (
+ {
+ // FIXME implament goto
+ }}
+ activePath={props.location.pathname}
+ >
+
+
+ );
+};
+
export const InitialWalkthroughPage = injectI18n(InitialWalkthroughPageComponent);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx
index f933d9009d367..a65ec1ddba081 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx
@@ -58,7 +58,7 @@ export const SnippetsStep: FC<{ onCopy: OnCopyFn }> = ({ onCopy }) => (
- kbn-canvas-shareable="canvas" ({strings.getRequiredLabel()})
+ kbn-canvas-shareable="canvas" ({strings.getRequiredLabel()})
{strings.getShareableParameterDescription()}
diff --git a/x-pack/legacy/plugins/canvas/public/lib/clipboard.js b/x-pack/legacy/plugins/canvas/public/lib/clipboard.js
index 2cebf2a5bad96..f9d68769c9c3a 100644
--- a/x-pack/legacy/plugins/canvas/public/lib/clipboard.js
+++ b/x-pack/legacy/plugins/canvas/public/lib/clipboard.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Storage } from 'ui/storage';
+import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { LOCALSTORAGE_CLIPBOARD } from '../../common/lib/constants';
import { getWindow } from './get_window';
diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
index ea944bd30e9b8..a55c87c2d74a2 100644
--- a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
+++ b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
@@ -32,7 +32,7 @@ describe('Canvas Shareable Workpad API', () => {
test('Placed successfully with default properties', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
- const wrapper = mount(
, {
+ const wrapper = mount(
, {
attachTo: container,
});
@@ -46,11 +46,7 @@ describe('Canvas Shareable Workpad API', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const wrapper = mount(
-
,
+
,
{
attachTo: container,
}
@@ -69,11 +65,7 @@ describe('Canvas Shareable Workpad API', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const wrapper = mount(
-
,
+
,
{
attachTo: container,
}
@@ -97,7 +89,7 @@ describe('Canvas Shareable Workpad API', () => {
kbn-canvas-width="350"
kbn-canvas-height="350"
kbn-canvas-url="workpad.json"
- >,
+ />,
{
attachTo: container,
}
@@ -116,7 +108,7 @@ describe('Canvas Shareable Workpad API', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const wrapper = mount(
-
,
+
,
{
attachTo: container,
}
diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
index baa0b509ccb73..072cf01255a0d 100644
--- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
+++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap
@@ -17,8 +17,11 @@ exports[` can navigate Autoplay Settings 1`] = `
data-focus-lock-disabled="disabled"
>
can navigate Autoplay Settings 2`] = `
data-focus-lock-disabled="disabled"
>
can navigate Toolbar Settings, closes when activated 1`] =
data-focus-lock-disabled="disabled"
>
can navigate Toolbar Settings, closes when activated 1`] =
`;
-exports[`
can navigate Toolbar Settings, closes when activated 2`] = `"
"`;
+exports[`
can navigate Toolbar Settings, closes when activated 2`] = `"
"`;
-exports[`
can navigate Toolbar Settings, closes when activated 3`] = `"
"`;
+exports[`
can navigate Toolbar Settings, closes when activated 3`] = `"
"`;
diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx
index 78fefead027e5..5ebb30d79e046 100644
--- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx
+++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx
@@ -18,6 +18,11 @@ import { Settings } from '../settings';
jest.mock('../../../../supported_renderers');
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
+jest.mock('@elastic/eui/lib/services/accessibility', () => {
+ return {
+ htmlIdGenerator: () => () => `generated-id`,
+ };
+});
jest.mock('@elastic/eui/lib/components/portal/portal', () => {
// eslint-disable-next-line no-shadow
const React = require.requireActual('react');
diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/index.html b/x-pack/legacy/plugins/graph/public/angular/templates/index.html
index 686f0f590a19c..b3f5bce7ea6ec 100644
--- a/x-pack/legacy/plugins/graph/public/angular/templates/index.html
+++ b/x-pack/legacy/plugins/graph/public/angular/templates/index.html
@@ -61,7 +61,7 @@
initial-query="initialQuery"
plugin-data-start="pluginDataStart"
core-start="coreStart"
- store="store"
+ storage="storage"
no-index-patterns="noIndexPatterns"
>
diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js
index 41e5819bcbf37..ab83815457981 100644
--- a/x-pack/legacy/plugins/graph/public/app.js
+++ b/x-pack/legacy/plugins/graph/public/app.js
@@ -100,7 +100,7 @@ export function initGraphApp(angularModule, deps) {
app.directive('graphApp', function (reactDirective) {
return reactDirective(GraphApp, [
- ['store', { watchDepth: 'reference' }],
+ ['storage', { watchDepth: 'reference' }],
['isInitialized', { watchDepth: 'reference' }],
['currentIndexPattern', { watchDepth: 'reference' }],
['indexPatternProvider', { watchDepth: 'reference' }],
@@ -310,7 +310,7 @@ export function initGraphApp(angularModule, deps) {
// register things on scope passed down to react components
$scope.pluginDataStart = npData;
- $scope.store = new Storage(window.localStorage);
+ $scope.storage = new Storage(window.localStorage);
$scope.coreStart = coreStart;
$scope.loading = false;
$scope.reduxStore = store;
diff --git a/x-pack/legacy/plugins/graph/public/components/app.tsx b/x-pack/legacy/plugins/graph/public/components/app.tsx
index aa2221441793f..5ff7fc2e5da93 100644
--- a/x-pack/legacy/plugins/graph/public/components/app.tsx
+++ b/x-pack/legacy/plugins/graph/public/components/app.tsx
@@ -10,8 +10,8 @@ import { DataPublicPluginStart } from 'src/plugins/data/public';
import { Provider } from 'react-redux';
import React, { useState } from 'react';
import { I18nProvider } from '@kbn/i18n/react';
-import { Storage } from 'ui/storage';
import { CoreStart } from 'kibana/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { FieldManager } from './field_manager';
import { SearchBarProps, SearchBar } from './search_bar';
import { GraphStore } from '../state_management';
@@ -23,7 +23,7 @@ export interface GraphAppProps extends SearchBarProps {
coreStart: CoreStart;
// This is not named dataStart because of Angular treating data- prefix differently
pluginDataStart: DataPublicPluginStart;
- store: Storage;
+ storage: IStorageWrapper;
reduxStore: GraphStore;
isInitialized: boolean;
noIndexPatterns: boolean;
@@ -34,7 +34,7 @@ export function GraphApp(props: GraphAppProps) {
const {
coreStart,
pluginDataStart,
- store,
+ storage,
reduxStore,
noIndexPatterns,
...searchBarProps
@@ -45,7 +45,7 @@ export function GraphApp(props: GraphAppProps) {
{},
},
};
diff --git a/x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx b/x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx
index b321ea3a0d8ed..f7ae04ef9dbcc 100644
--- a/x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx
+++ b/x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx
@@ -39,7 +39,7 @@ export function BlacklistForm({
}}
+ values={{ stopSign: }}
/>
}
/>
diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts
index 0249ca74035d6..5e500367ccdc5 100644
--- a/x-pack/legacy/plugins/graph/public/index.ts
+++ b/x-pack/legacy/plugins/graph/public/index.ts
@@ -14,11 +14,11 @@ import chrome from 'ui/chrome';
import { IPrivate } from 'ui/private';
// @ts-ignore
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
-import { Storage } from 'ui/storage';
// @ts-ignore
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
import { npSetup, npStart } from 'ui/new_platform';
+import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy';
import { GraphPlugin } from './plugin';
diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js
index e96fc79967316..31d8337857911 100644
--- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js
+++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/policy_table.test.js
@@ -111,7 +111,7 @@ describe('policy table', () => {
});
test('should show more when per page value is increased', () => {
const rendered = mountWithIntl(component);
- const perPageButton = rendered.find('#customizablePagination').find('button');
+ const perPageButton = rendered.find('EuiTablePagination EuiPopover').find('button');
perPageButton.simulate('click');
rendered.update();
const fiftyButton = rendered.find('.euiContextMenuItem').at(1);
diff --git a/x-pack/legacy/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/legacy/plugins/index_management/__jest__/components/index_table.test.js
index d6a53eefcdc95..278ec918100d9 100644
--- a/x-pack/legacy/plugins/index_management/__jest__/components/index_table.test.js
+++ b/x-pack/legacy/plugins/index_management/__jest__/components/index_table.test.js
@@ -155,7 +155,7 @@ describe('index table', () => {
});
test('should show more when per page value is increased', () => {
const rendered = mountWithIntl(component);
- const perPageButton = rendered.find('#customizablePagination').find('button');
+ const perPageButton = rendered.find('EuiTablePagination EuiPopover').find('button');
perPageButton.simulate('click');
rendered.update();
const fiftyButton = rendered.find('.euiContextMenuItem').at(1);
diff --git a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_aliases.tsx b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_aliases.tsx
index d1ed816bebd45..3a1dcbc7e762d 100644
--- a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_aliases.tsx
+++ b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_aliases.tsx
@@ -34,6 +34,6 @@ export const TabAliases: React.FunctionComponent = ({ templateDetails })
}
iconType="pin"
data-test-subj="noAliasesCallout"
- >
+ />
);
};
diff --git a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_mappings.tsx b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_mappings.tsx
index 91f84a5ecf726..9439b0e31ae84 100644
--- a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_mappings.tsx
+++ b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_mappings.tsx
@@ -34,6 +34,6 @@ export const TabMappings: React.FunctionComponent = ({ templateDetails })
}
iconType="pin"
data-test-subj="noMappingsCallout"
- >
+ />
);
};
diff --git a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_settings.tsx b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_settings.tsx
index 6a83b714519d7..0370a89850721 100644
--- a/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_settings.tsx
+++ b/x-pack/legacy/plugins/index_management/public/sections/home/template_list/template_details/tabs/tab_settings.tsx
@@ -34,6 +34,6 @@ export const TabSettings: React.FunctionComponent = ({ templateDetails })
}
iconType="pin"
data-test-subj="noSettingsCallout"
- >
+ />
);
};
diff --git a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx
index ca67aec9642ac..d7c9876a07a8d 100644
--- a/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/autocomplete_field/suggestion_item.tsx
@@ -11,36 +11,30 @@ import React from 'react';
import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public';
import euiStyled from '../../../../../common/eui_styled_components';
-interface SuggestionItemProps {
+interface Props {
isSelected?: boolean;
onClick?: React.MouseEventHandler;
onMouseEnter?: React.MouseEventHandler;
suggestion: AutocompleteSuggestion;
}
-export class SuggestionItem extends React.Component {
- public static defaultProps: Partial = {
- isSelected: false,
- };
+export const SuggestionItem: React.SFC = props => {
+ const { isSelected, onClick, onMouseEnter, suggestion } = props;
- public render() {
- const { isSelected, onClick, onMouseEnter, suggestion } = this.props;
+ return (
+
+
+
+
+ {suggestion.text}
+ {suggestion.description}
+
+ );
+};
- return (
-
-
-
-
- {suggestion.text}
- {suggestion.description}
-
- );
- }
-}
+SuggestionItem.defaultProps = {
+ isSelected: false,
+};
const SuggestionItemContainer = euiStyled.div<{
isSelected?: boolean;
diff --git a/x-pack/legacy/plugins/infra/public/components/loading_overlay_wrapper.tsx b/x-pack/legacy/plugins/infra/public/components/loading_overlay_wrapper.tsx
new file mode 100644
index 0000000000000..0f70c40059c93
--- /dev/null
+++ b/x-pack/legacy/plugins/infra/public/components/loading_overlay_wrapper.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiLoadingSpinner } from '@elastic/eui';
+import { transparentize } from 'polished';
+import React from 'react';
+
+import { euiStyled } from '../../../../common/eui_styled_components';
+
+export const LoadingOverlayWrapper: React.FC<
+ React.HTMLAttributes & {
+ isLoading: boolean;
+ loadingChildren?: React.ReactNode;
+ }
+> = ({ children, isLoading, loadingChildren, ...rest }) => {
+ return (
+
+ {children}
+ {isLoading ? {loadingChildren} : null}
+
+ );
+};
+
+const Overlay: React.FC = ({ children }) => (
+ {children ? children : }
+);
+
+const RelativeDiv = euiStyled.div`
+ position: relative;
+`;
+
+const OverlayDiv = euiStyled.div`
+ align-items: center;
+ background-color: ${props => transparentize(0.3, props.theme.eui.euiColorEmptyShade)};
+ display: flex;
+ height: 100%;
+ justify-content: center;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+`;
diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx
index 218b42b48c9b9..752a0d6e27a7f 100644
--- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/loading_item_view.tsx
@@ -114,29 +114,28 @@ interface ProgressEntryProps {
isLoading: boolean;
}
-class ProgressEntry extends React.PureComponent {
- public render() {
- const { alignment, children, className, color, isLoading } = this.props;
- // NOTE: styled-components seems to make all props in EuiProgress required, so this
- // style attribute hacking replaces styled-components here for now until that can be fixed
- // see: https://github.com/elastic/eui/issues/1655
- const alignmentStyle =
- alignment === 'top' ? { top: 0, bottom: 'initial' } : { top: 'initial', bottom: 0 };
+const ProgressEntry: React.SFC = props => {
+ const { alignment, children, className, color, isLoading } = props;
- return (
-
-
- {children}
-
- );
- }
-}
+ // NOTE: styled-components seems to make all props in EuiProgress required, so this
+ // style attribute hacking replaces styled-components here for now until that can be fixed
+ // see: https://github.com/elastic/eui/issues/1655
+ const alignmentStyle =
+ alignment === 'top' ? { top: 0, bottom: 'initial' } : { top: 'initial', bottom: 0 };
+
+ return (
+
+
+ {children}
+
+ );
+};
const ProgressEntryWrapper = euiStyled.div`
align-items: center;
diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx
index d439308194d18..fc3c8b3bf2b31 100644
--- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx
@@ -102,6 +102,14 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
targetId: null,
items: [],
};
+ } else if (
+ hasItems &&
+ (nextItems.length !== prevState.items.length || nextItems[0] !== prevState.items[0])
+ ) {
+ return {
+ ...prevState,
+ items: nextItems,
+ };
}
return null;
@@ -180,6 +188,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
hideScrollbar={true}
data-test-subj={'logStream'}
isLocked={scrollLock.isEnabled}
+ entriesCount={items.length}
>
{registerChild => (
<>
diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx
index 7ff4a2cacee09..dd368ef4d8f92 100644
--- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx
@@ -30,6 +30,7 @@ interface VerticalScrollPanelProps {
hideScrollbar?: boolean;
'data-test-subj'?: string;
isLocked: boolean;
+ entriesCount: number;
}
interface VerticalScrollPanelSnapshot {
@@ -226,7 +227,7 @@ export class VerticalScrollPanel extends React.PureComponent<
if (
prevProps.height !== this.props.height ||
prevProps.target !== this.props.target ||
- React.Children.count(prevProps.children) !== React.Children.count(this.props.children)
+ prevProps.entriesCount !== this.props.entriesCount
) {
this.handleUpdatedChildren(snapshot.scrollTarget, snapshot.scrollOffset);
}
diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx
index cf148eefe8e8f..6d8503b333c0b 100644
--- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/chart_section.tsx
@@ -127,7 +127,7 @@ export const ChartSection = ({
key={`series-${section.id}-${series.id}`}
id={`series-${section.id}-${series.id}`}
series={series}
- name={getChartName(section, series.id, series.label)}
+ name={getChartName(section, series.id, series.id)}
type={getChartType(section, series.id)}
color={getChartColor(section, series.id)}
stack={visConfig.stacked}
diff --git a/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx
index 9fd1f3eadfb49..b75f669a6d1ec 100644
--- a/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/metrics/sections/series_chart.tsx
@@ -45,12 +45,6 @@ export const AreaChart = ({ id, color, series, name, type, stack }: Props) => {
strokeWidth: 'area' === type ? 1 : 2,
visible: true,
},
- point: {
- visible: true,
- radius: 1,
- strokeWidth: 2,
- opacity: 1,
- },
};
const colors: DataSeriesColorsValues = {
colorValues: [],
diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx
index 5b70b0cef083a..8c3c80e0a3852 100644
--- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx
+++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx
@@ -63,12 +63,6 @@ export const MetricsExplorerAreaChart = ({ metric, id, series, type, stack }: Pr
opacity: 0.5,
visible: type === MetricsExplorerChartType.area,
},
- point: {
- visible: true,
- radius: 2,
- strokeWidth: 2,
- opacity: 1,
- },
};
return (
{
+export const MetricsExplorerBarChart = ({ metric, id, series, stack }: Props) => {
const color =
(metric.color && colorTransformer(metric.color)) ||
colorTransformer(MetricsExplorerColor.color0);
diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/link_to.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/link_to.tsx
index abb3aa6b26b15..1318e9ca6c2c5 100644
--- a/x-pack/legacy/plugins/infra/public/pages/link_to/link_to.tsx
+++ b/x-pack/legacy/plugins/infra/public/pages/link_to/link_to.tsx
@@ -16,27 +16,21 @@ interface LinkToPageProps {
match: RouteMatch<{}>;
}
-export class LinkToPage extends React.Component {
- public render() {
- const { match } = this.props;
-
- return (
-
-
-
-
-
-
-
- );
- }
-}
+export const LinkToPage: React.SFC = props => (
+
+
+
+
+
+
+
+);
diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx
index ffc48a0af9de9..e740689da50f6 100644
--- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx
+++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx
@@ -6,23 +6,23 @@
import datemath from '@elastic/datemath';
import {
+ EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPanel,
EuiSuperDatePicker,
- EuiBadge,
EuiText,
} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import numeral from '@elastic/numeral';
import { FormattedMessage } from '@kbn/i18n/react';
import moment from 'moment';
import React, { useCallback, useContext, useMemo, useState } from 'react';
+
import euiStyled from '../../../../../../common/eui_styled_components';
import { TimeRange } from '../../../../common/http_api/shared/time_range';
import { bucketSpan } from '../../../../common/log_analysis';
-import { LoadingPage } from '../../../components/loading_page';
+import { LoadingOverlayWrapper } from '../../../components/loading_overlay_wrapper';
import {
LogAnalysisJobs,
StringTimeRange,
@@ -162,89 +162,77 @@ export const AnalysisResultsContent = ({
);
return (
- <>
- {isLoading && !logEntryRate ? (
-
- ) : (
- <>
-
-
+
+
+
+
+
-
-
-
- {!isLoading && logEntryRate ? (
-
-
-
- {numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')}
-
-
- ),
- startTime: (
- {moment(queryTimeRange.value.startTime).format(dateFormat)}
- ),
- endTime: (
- {moment(queryTimeRange.value.endTime).format(dateFormat)}
- ),
- }}
- />
-
- ) : null}
-
-
-
+
+
+
+ {numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')}
+
+
+ ),
+ startTime: (
+ {moment(queryTimeRange.value.startTime).format(dateFormat)}
+ ),
+ endTime: {moment(queryTimeRange.value.endTime).format(dateFormat)} ,
+ }}
/>
-
-
-
-
-
-
- {isFirstUse && !hasResults ? : null}
-
-
+
+
+ ) : null}
-
-
-
+
-
- >
- )}
- >
+
+
+
+
+ {isFirstUse && !hasResults ? : null}
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx
index 5aa5891d7981d..c340de4ad3848 100644
--- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx
+++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx
@@ -8,10 +8,10 @@ import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
- EuiLoadingChart,
EuiSpacer,
EuiStat,
EuiTitle,
+ EuiLoadingSpinner,
} from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
@@ -31,6 +31,7 @@ import { AnomaliesChart } from './chart';
import { AnomaliesTable } from './table';
import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status';
import { AnalyzeInMlButton } from '../analyze_in_ml_button';
+import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
export const AnomaliesResults: React.FunctionComponent<{
isLoading: boolean;
@@ -53,15 +54,6 @@ export const AnomaliesResults: React.FunctionComponent<{
viewSetupForUpdate,
jobId,
}) => {
- const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', {
- defaultMessage: 'Anomalies',
- });
-
- const loadingAriaLabel = i18n.translate(
- 'xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel',
- { defaultMessage: 'Loading anomalies' }
- );
-
const hasAnomalies = useMemo(() => {
return results && results.histogramBuckets
? results.histogramBuckets.some(bucket => {
@@ -117,90 +109,91 @@ export const AnomaliesResults: React.FunctionComponent<{
onRecreateMlJobForUpdate={viewSetupForUpdate}
/>
- {isLoading ? (
-
-
-
-
-
- ) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
-
- {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataTitle', {
- defaultMessage: 'There is no data to display.',
- })}
-
- }
- titleSize="m"
- body={
-
- {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataBody', {
- defaultMessage: 'You may want to adjust your time range.',
- })}
-
- }
- />
- ) : !hasAnomalies ? (
-
- {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoAnomaliesTitle', {
- defaultMessage: 'No anomalies were detected.',
- })}
-
- }
- titleSize="m"
- />
- ) : (
- <>
-
-
-
-
-
-
-
-
-
-
- }>
+ {!results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
+
+ {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataTitle', {
+ defaultMessage: 'There is no data to display.',
+ })}
+
+ }
+ titleSize="m"
+ body={
+
+ {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoDataBody', {
+ defaultMessage: 'You may want to adjust your time range.',
+ })}
+
+ }
+ />
+ ) : !hasAnomalies ? (
+
+ {i18n.translate('xpack.infra.logs.analysis.anomalySectionNoAnomaliesTitle', {
+ defaultMessage: 'No anomalies were detected.',
+ })}
+
+ }
+ titleSize="m"
/>
- >
- )}
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
>
);
};
+const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', {
+ defaultMessage: 'Anomalies',
+});
+
interface ParsedAnnotationDetails {
anomalyScoresByPartition: Array<{ partitionId: string; maximumAnomalyScore: number }>;
}
@@ -211,6 +204,7 @@ const overallAnomalyScoreLabel = i18n.translate(
defaultMessage: 'Max anomaly scores:',
}
);
+
const AnnotationTooltip: React.FunctionComponent<{ details: string }> = ({ details }) => {
const parsedDetails: ParsedAnnotationDetails = JSON.parse(details);
return (
@@ -237,7 +231,7 @@ const AnnotationTooltip: React.FunctionComponent<{ details: string }> = ({ detai
const renderAnnotationTooltip = (details?: string) => {
// Note: Seems to be necessary to get things typed correctly all the way through to elastic-charts components
if (!details) {
- return
;
+ return
;
}
return ;
};
@@ -245,3 +239,10 @@ const renderAnnotationTooltip = (details?: string) => {
const TooltipWrapper = euiStyled('div')`
white-space: nowrap;
`;
+
+const loadingAriaLabel = i18n.translate(
+ 'xpack.infra.logs.analysis.anomaliesSectionLoadingAriaLabel',
+ { defaultMessage: 'Loading anomalies' }
+);
+
+const LoadingOverlayContent = () => ;
diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx
index d4ddd14bfaa28..682eb23fa4774 100644
--- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx
+++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx
@@ -4,15 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingChart,
- EuiSpacer,
- EuiTitle,
- EuiText,
-} from '@elastic/eui';
+import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
@@ -20,6 +12,7 @@ import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/
import { TimeRange } from '../../../../../../common/http_api/shared/time_range';
import { LogEntryRateBarChart } from './bar_chart';
import { getLogEntryRatePartitionedSeries } from '../helpers/data_formatters';
+import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper';
export const LogRateResults = ({
isLoading,
@@ -32,15 +25,6 @@ export const LogRateResults = ({
setTimeRange: (timeRange: TimeRange) => void;
timeRange: TimeRange;
}) => {
- const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', {
- defaultMessage: 'Log entries',
- });
-
- const loadingAriaLabel = i18n.translate(
- 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel',
- { defaultMessage: 'Loading log rate results' }
- );
-
const logEntryRateSeries = useMemo(
() => (results && results.histogramBuckets ? getLogEntryRatePartitionedSeries(results) : []),
[results]
@@ -51,57 +35,61 @@ export const LogRateResults = ({
{title}
- {isLoading ? (
- <>
-
-
-
-
-
-
- >
- ) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
- <>
-
-
- {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', {
- defaultMessage: 'There is no data to display.',
- })}
-
- }
- titleSize="m"
- body={
+ }>
+ {!results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? (
+ <>
+
+
+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', {
+ defaultMessage: 'There is no data to display.',
+ })}
+
+ }
+ titleSize="m"
+ body={
+
+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', {
+ defaultMessage: 'You may want to adjust your time range.',
+ })}
+
+ }
+ />
+ >
+ ) : (
+ <>
+
- {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', {
- defaultMessage: 'You may want to adjust your time range.',
+
+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanLabel', {
+ defaultMessage: 'Bucket span: ',
+ })}
+
+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanValue', {
+ defaultMessage: '15 minutes',
})}
- }
- />
- >
- ) : (
- <>
-
-
-
- {i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanLabel', {
- defaultMessage: 'Bucket span: ',
- })}
-
- {i18n.translate('xpack.infra.logs.analysis.logRateSectionBucketSpanValue', {
- defaultMessage: '15 minutes',
- })}
-
-
-
- >
- )}
+
+
+ >
+ )}
+
>
);
};
+
+const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', {
+ defaultMessage: 'Log entries',
+});
+
+const loadingAriaLabel = i18n.translate(
+ 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel',
+ { defaultMessage: 'Loading log rate results' }
+);
+
+const LoadingOverlayContent = () => ;
diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx
index e966336567e59..17f1aa48c87a2 100644
--- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx
+++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/initial_configuration_step/analysis_setup_timerange_form.tsx
@@ -4,18 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useMemo } from 'react';
-import moment, { Moment } from 'moment';
-
-import { i18n } from '@kbn/i18n';
import {
- EuiDescribedFormGroup,
- EuiFormRow,
EuiDatePicker,
+ EuiDatePickerProps,
+ EuiDescribedFormGroup,
EuiFlexGroup,
EuiFormControlLayout,
+ EuiFormRow,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import moment, { Moment } from 'moment';
+import React, { useMemo } from 'react';
+
+import { euiStyled } from '../../../../../../../../common/eui_styled_components';
const startTimeLabel = i18n.translate('xpack.infra.analysisSetup.startTimeLabel', {
defaultMessage: 'Start time',
@@ -84,7 +86,7 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
setStartTime(undefined) } : undefined}
>
- setStartTime(selectedDateToParam(date))}
@@ -105,7 +107,7 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
setEndTime(undefined) } : undefined}
>
- setEndTime(selectedDateToParam(date))}
@@ -129,3 +131,18 @@ export const AnalysisSetupTimerangeForm: React.FunctionComponent<{
);
};
+
+const FixedDatePicker = euiStyled(
+ ({
+ className,
+ inputClassName,
+ ...datePickerProps
+ }: {
+ className?: string;
+ inputClassName?: string;
+ } & EuiDatePickerProps) => (
+
+ )
+)`
+ z-index: 3 !important;
+`;
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
index 87f0872c52343..a1710d67b31db 100644
--- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx
@@ -10,7 +10,7 @@ import { act } from 'react-dom/test-utils';
import { buildExistsFilter } from '@kbn/es-query';
import { App } from './app';
import { EditorFrameInstance } from '../types';
-import { Storage } from 'ui/storage';
+import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { Document, SavedObjectStore } from '../persistence';
import { mount } from 'enzyme';
@@ -80,7 +80,7 @@ describe('Lens App', () => {
data: typeof dataStartMock;
core: typeof core;
dataShim: DataStart;
- store: Storage;
+ storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
redirectTo: (id?: string) => void;
@@ -97,6 +97,11 @@ describe('Lens App', () => {
},
},
},
+ data: {
+ query: {
+ filterManager: createMockFilterManager(),
+ },
+ },
dataShim: {
indexPatterns: {
indexPatterns: {
@@ -106,11 +111,8 @@ describe('Lens App', () => {
},
},
timefilter: { history: {} },
- filter: {
- filterManager: createMockFilterManager(),
- },
},
- store: {
+ storage: {
get: jest.fn(),
},
docStorage: {
@@ -123,7 +125,7 @@ describe('Lens App', () => {
data: typeof dataStartMock;
core: typeof core;
dataShim: DataStart;
- store: Storage;
+ storage: Storage;
docId?: string;
docStorage: SavedObjectStore;
redirectTo: (id?: string) => void;
@@ -592,7 +594,7 @@ describe('Lens App', () => {
const instance = mount( );
- args.dataShim.filter.filterManager.setFilters([
+ args.data.query.filterManager.setFilters([
buildExistsFilter({ name: 'myfield' }, { id: 'index1' }),
]);
@@ -723,7 +725,7 @@ describe('Lens App', () => {
query: { query: 'new', language: 'lucene' },
});
- args.dataShim.filter.filterManager.setFilters([
+ args.data.query.filterManager.setFilters([
buildExistsFilter({ name: 'myfield' }, { id: 'index1' }),
]);
instance.update();
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx
index bd75198714dc3..a95e0450f614c 100644
--- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx
@@ -8,7 +8,6 @@ import _ from 'lodash';
import React, { useState, useEffect, useCallback } from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
-import { Storage } from 'ui/storage';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
import { CoreStart, NotificationsStart } from 'src/core/public';
@@ -20,6 +19,7 @@ import {
Query,
} from 'src/legacy/core_plugins/data/public';
import { Filter } from '@kbn/es-query';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { Document, SavedObjectStore } from '../persistence';
@@ -49,7 +49,7 @@ export function App({
data,
dataShim,
core,
- store,
+ storage,
docId,
docStorage,
redirectTo,
@@ -58,14 +58,14 @@ export function App({
data: DataPublicPluginStart;
core: CoreStart;
dataShim: DataStart;
- store: Storage;
+ storage: IStorageWrapper;
docId?: string;
docStorage: SavedObjectStore;
redirectTo: (id?: string) => void;
}) {
const timeDefaults = core.uiSettings.get('timepicker:timeDefaults');
const language =
- store.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage');
+ storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage');
const [state, setState] = useState({
isLoading: !!docId,
@@ -82,10 +82,9 @@ export function App({
const { lastKnownDoc } = state;
useEffect(() => {
- const subscription = dataShim.filter.filterManager.getUpdates$().subscribe({
+ const subscription = data.query.filterManager.getUpdates$().subscribe({
next: () => {
- setState(s => ({ ...s, filters: dataShim.filter.filterManager.getFilters() }));
-
+ setState(s => ({ ...s, filters: data.query.filterManager.getFilters() }));
trackUiEvent('app_filters_updated');
},
});
@@ -171,7 +170,7 @@ export function App({
services={{
appName: 'lens',
data,
- store,
+ storage,
...core,
}}
>
@@ -227,9 +226,7 @@ export function App({
setState(s => ({ ...s, savedQuery }));
}}
onSavedQueryUpdated={savedQuery => {
- dataShim.filter.filterManager.setFilters(
- savedQuery.attributes.filters || state.filters
- );
+ data.query.filterManager.setFilters(savedQuery.attributes.filters || state.filters);
setState(s => ({
...s,
savedQuery: { ...savedQuery }, // Shallow query for reference issues
@@ -242,7 +239,7 @@ export function App({
}));
}}
onClearSavedQuery={() => {
- dataShim.filter.filterManager.removeAll();
+ data.query.filterManager.removeAll();
setState(s => ({
...s,
savedQuery: undefined,
@@ -250,7 +247,7 @@ export function App({
query: {
query: '',
language:
- store.get('kibana.userQueryLanguage') ||
+ storage.get('kibana.userQueryLanguage') ||
core.uiSettings.get('search:queryLanguage'),
},
}));
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
index b7960b23651c6..56c19ea2bb9f2 100644
--- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
@@ -8,12 +8,12 @@ import React from 'react';
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom';
import chrome from 'ui/chrome';
-import { Storage } from 'ui/storage';
import { CoreSetup, CoreStart } from 'src/core/public';
import { npSetup, npStart } from 'ui/new_platform';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public';
import { start as dataShimStart } from '../../../../../../src/legacy/core_plugins/data/public/legacy';
+import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { editorFrameSetup, editorFrameStart, editorFrameStop } from '../editor_frame_plugin';
import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin';
import { SavedObjectIndexStore } from '../persistence';
@@ -84,7 +84,7 @@ export class AppPlugin {
data={data}
dataShim={dataShim}
editorFrame={this.instance!}
- store={new Storage(localStorage)}
+ storage={new Storage(localStorage)}
docId={routeProps.match.params.id}
docStorage={store}
redirectTo={id => {
diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
index 083ceff3bb444..25d88fbae5b34 100644
--- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
@@ -12,7 +12,7 @@ import {
DataTableLayer,
} from './visualization';
import { mount } from 'enzyme';
-import { Operation, DataType, FramePublicAPI } from '../types';
+import { Operation, DataType, FramePublicAPI, TableSuggestionColumn } from '../types';
import { generateId } from '../id_generator';
jest.mock('../id_generator');
@@ -72,6 +72,112 @@ describe('Datatable Visualization', () => {
});
});
+ describe('#getSuggestions', () => {
+ function numCol(columnId: string): TableSuggestionColumn {
+ return {
+ columnId,
+ operation: {
+ dataType: 'number',
+ label: `Avg ${columnId}`,
+ isBucketed: false,
+ },
+ };
+ }
+
+ function strCol(columnId: string): TableSuggestionColumn {
+ return {
+ columnId,
+ operation: {
+ dataType: 'string',
+ label: `Top 5 ${columnId}`,
+ isBucketed: true,
+ },
+ };
+ }
+
+ it('should accept a single-layer suggestion', () => {
+ const suggestions = datatableVisualization.getSuggestions({
+ state: {
+ layers: [{ layerId: 'first', columns: ['col1'] }],
+ },
+ table: {
+ isMultiRow: true,
+ layerId: 'first',
+ changeType: 'initial',
+ columns: [numCol('col1'), strCol('col2')],
+ },
+ keptLayerIds: [],
+ });
+
+ expect(suggestions.length).toBeGreaterThan(0);
+ });
+
+ it('should not make suggestions when the table is unchanged', () => {
+ const suggestions = datatableVisualization.getSuggestions({
+ state: {
+ layers: [{ layerId: 'first', columns: ['col1'] }],
+ },
+ table: {
+ isMultiRow: true,
+ layerId: 'first',
+ changeType: 'unchanged',
+ columns: [numCol('col1')],
+ },
+ keptLayerIds: ['first'],
+ });
+
+ expect(suggestions).toEqual([]);
+ });
+
+ it('should not make suggestions when multiple layers are involved', () => {
+ const suggestions = datatableVisualization.getSuggestions({
+ state: {
+ layers: [{ layerId: 'first', columns: ['col1'] }],
+ },
+ table: {
+ isMultiRow: true,
+ layerId: 'first',
+ changeType: 'unchanged',
+ columns: [numCol('col1')],
+ },
+ keptLayerIds: ['first', 'second'],
+ });
+
+ expect(suggestions).toEqual([]);
+ });
+
+ it('should not make suggestions when the suggestion keeps a different layer', () => {
+ const suggestions = datatableVisualization.getSuggestions({
+ state: {
+ layers: [{ layerId: 'older', columns: ['col1'] }],
+ },
+ table: {
+ isMultiRow: true,
+ layerId: 'newer',
+ changeType: 'initial',
+ columns: [numCol('col1'), strCol('col2')],
+ },
+ keptLayerIds: ['older'],
+ });
+
+ expect(suggestions).toEqual([]);
+ });
+
+ it('should suggest unchanged tables when the state is not passed in', () => {
+ const suggestions = datatableVisualization.getSuggestions({
+ table: {
+ isMultiRow: true,
+ layerId: 'first',
+ changeType: 'unchanged',
+ columns: [numCol('col1')],
+ },
+ keptLayerIds: ['first'],
+ });
+
+ expect(suggestions.length).toBeGreaterThan(0);
+ });
+ });
+
describe('DataTableLayer', () => {
it('allows all kinds of operations', () => {
const setState = jest.fn();
diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx
index 65f97b6d4e4b5..f9a7ec419a9b9 100644
--- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx
+++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.tsx
@@ -134,10 +134,15 @@ export const datatableVisualization: Visualization<
getSuggestions({
table,
state,
+ keptLayerIds,
}: SuggestionRequest): Array<
VisualizationSuggestion
> {
- if (state && table.changeType === 'unchanged') {
+ if (
+ keptLayerIds.length > 1 ||
+ (keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
+ (state && table.changeType === 'unchanged')
+ ) {
return [];
}
const title =
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx
index 298b25b5090c4..0a540334f2cad 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.test.tsx
@@ -94,6 +94,7 @@ describe('chart_switch', () => {
layerId: 'a',
changeType: 'unchanged',
},
+ keptLayerIds: ['a'],
},
]);
return {
@@ -180,6 +181,8 @@ describe('chart_switch', () => {
switchTo('subvisB', component);
+ expect(frame.removeLayers).toHaveBeenCalledWith(['a']);
+
expect(dispatch).toHaveBeenCalledWith({
initialState: 'visB initial state',
newVisualizationId: 'visB',
@@ -219,6 +222,7 @@ describe('chart_switch', () => {
isMultiRow: true,
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
datasourceMap.testDatasource.publicAPIMock.getTableSpec.mockReturnValue([
@@ -307,11 +311,7 @@ describe('chart_switch', () => {
it('should not indicate data loss if visualization is not changed', () => {
const dispatch = jest.fn();
- const removeLayers = jest.fn();
- const frame = {
- ...mockFrame(['a', 'b', 'c']),
- removeLayers,
- };
+ const frame = mockFrame(['a', 'b', 'c']);
const visualizations = mockVisualizations();
const switchVisualizationType = jest.fn(() => 'therebedragons');
@@ -332,30 +332,6 @@ describe('chart_switch', () => {
expect(getMenuItem('subvisC2', component).prop('betaBadgeIconType')).toBeUndefined();
});
- it('should remove unused layers', () => {
- const removeLayers = jest.fn();
- const frame = {
- ...mockFrame(['a', 'b', 'c']),
- removeLayers,
- };
- const component = mount(
-
- );
-
- switchTo('subvisB', component);
-
- expect(removeLayers).toHaveBeenCalledTimes(1);
- expect(removeLayers).toHaveBeenCalledWith(['b', 'c']);
- });
-
it('should remove all layers if there is no suggestion', () => {
const dispatch = jest.fn();
const visualizations = mockVisualizations();
@@ -378,15 +354,24 @@ describe('chart_switch', () => {
expect(frame.removeLayers).toHaveBeenCalledTimes(1);
expect(frame.removeLayers).toHaveBeenCalledWith(['a', 'b', 'c']);
+
+ expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith(
+ expect.objectContaining({
+ keptLayerIds: ['a'],
+ })
+ );
+
+ expect(dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'SWITCH_VISUALIZATION',
+ initialState: 'visB initial state',
+ })
+ );
});
it('should not remove layers if the visualization is not changing', () => {
const dispatch = jest.fn();
- const removeLayers = jest.fn();
- const frame = {
- ...mockFrame(['a', 'b', 'c']),
- removeLayers,
- };
+ const frame = mockFrame(['a', 'b', 'c']);
const visualizations = mockVisualizations();
const switchVisualizationType = jest.fn(() => 'therebedragons');
@@ -405,7 +390,6 @@ describe('chart_switch', () => {
);
switchTo('subvisC2', component);
- expect(removeLayers).not.toHaveBeenCalled();
expect(switchVisualizationType).toHaveBeenCalledWith('subvisC2', 'therebegriffins');
expect(dispatch).toHaveBeenCalledWith(
expect.objectContaining({
@@ -447,6 +431,7 @@ describe('chart_switch', () => {
isMultiRow: true,
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx
index 2e95aa10bf4db..8b67646edded9 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx
@@ -88,6 +88,10 @@ export function ChartSwitch(props: Props) {
},
'SWITCH_VISUALIZATION'
);
+
+ if (!selection.datasourceId || selection.dataLoss === 'everything') {
+ props.framePublicAPI.removeLayers(Object.keys(props.framePublicAPI.datasourceLayers));
+ }
};
function getSelection(
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
index fb3f8774be92a..1e8f50f4ddb1f 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
@@ -35,6 +35,7 @@ function generateSuggestion(state = {}): DatasourceSuggestion {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: ['first'],
};
}
@@ -807,30 +808,38 @@ describe('editor_frame', () => {
await waitForPromises();
expect(mockDatasource.getPublicAPI).toHaveBeenCalledWith(
- datasource1State,
- expect.anything(),
- 'first'
+ expect.objectContaining({
+ state: datasource1State,
+ setState: expect.anything(),
+ layerId: 'first',
+ })
);
expect(mockDatasource2.getPublicAPI).toHaveBeenCalledWith(
- datasource2State,
- expect.anything(),
- 'second'
+ expect.objectContaining({
+ state: datasource2State,
+ setState: expect.anything(),
+ layerId: 'second',
+ })
);
expect(mockDatasource2.getPublicAPI).toHaveBeenCalledWith(
- datasource2State,
- expect.anything(),
- 'third'
+ expect.objectContaining({
+ state: datasource2State,
+ setState: expect.anything(),
+ layerId: 'third',
+ })
);
});
it('should give access to the datasource state in the datasource factory function', async () => {
const datasourceState = {};
+ const dateRange = { fromDate: 'now-1w', toDate: 'now' };
mockDatasource.initialize.mockResolvedValue(datasourceState);
mockDatasource.getLayers.mockReturnValue(['first']);
mount(
{
await waitForPromises();
- expect(mockDatasource.getPublicAPI).toHaveBeenCalledWith(
- datasourceState,
- expect.any(Function),
- 'first'
- );
+ expect(mockDatasource.getPublicAPI).toHaveBeenCalledWith({
+ dateRange,
+ state: datasourceState,
+ setState: expect.any(Function),
+ layerId: 'first',
+ });
});
it('should re-create the public api after state has been set', async () => {
@@ -872,15 +882,17 @@ describe('editor_frame', () => {
await waitForPromises();
const updatedState = {};
- const setDatasourceState = mockDatasource.getPublicAPI.mock.calls[0][1];
+ const setDatasourceState = mockDatasource.getPublicAPI.mock.calls[0][0].setState;
act(() => {
setDatasourceState(updatedState);
});
expect(mockDatasource.getPublicAPI).toHaveBeenLastCalledWith(
- updatedState,
- expect.any(Function),
- 'first'
+ expect.objectContaining({
+ state: updatedState,
+ setState: expect.any(Function),
+ layerId: 'first',
+ })
);
});
});
@@ -917,6 +929,7 @@ describe('editor_frame', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
@@ -1062,6 +1075,7 @@ describe('editor_frame', () => {
isMultiRow: true,
layerId: 'first',
},
+ keptLayerIds: [],
},
]);
mount(
@@ -1509,7 +1523,7 @@ describe('editor_frame', () => {
query: { query: '', language: 'lucene' },
filters: [],
},
- title: 'New visualization',
+ title: '',
type: 'lens',
visualizationType: 'testVis',
},
@@ -1528,7 +1542,7 @@ describe('editor_frame', () => {
query: { query: '', language: 'lucene' },
filters: [],
},
- title: 'New visualization',
+ title: '',
type: 'lens',
visualizationType: 'testVis',
},
@@ -1585,7 +1599,7 @@ describe('editor_frame', () => {
query: { query: 'new query', language: 'lucene' },
filters: [],
},
- title: 'New visualization',
+ title: '',
type: 'lens',
visualizationType: 'testVis',
},
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
index 04c0b22c378d7..8e89d8edc9f23 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
@@ -89,9 +89,9 @@ export function EditorFrame(props: EditorFrameProps) {
const layers = datasource.getLayers(datasourceState);
layers.forEach(layer => {
- const publicAPI = props.datasourceMap[id].getPublicAPI(
- datasourceState,
- (newState: unknown) => {
+ const publicAPI = props.datasourceMap[id].getPublicAPI({
+ state: datasourceState,
+ setState: (newState: unknown) => {
dispatch({
type: 'UPDATE_DATASOURCE_STATE',
datasourceId: id,
@@ -99,8 +99,9 @@ export function EditorFrame(props: EditorFrameProps) {
clearStagedPreview: true,
});
},
- layer
- );
+ layerId: layer,
+ dateRange: props.dateRange,
+ });
datasourceLayers[layer] = publicAPI;
});
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.ts
index 5104ace7c79a3..78a9a13f48d6a 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { i18n } from '@kbn/i18n';
import { EditorFrameProps } from '../editor_frame';
import { Document } from '../../persistence/saved_object_store';
@@ -113,7 +112,7 @@ export const getInitialState = (props: EditorFrameProps): EditorFrameState => {
}
return {
- title: i18n.translate('xpack.lens.chartTitle', { defaultMessage: 'New visualization' }),
+ title: '',
datasourceStates,
activeDatasourceId: getInitialDatasourceId(props),
visualization: {
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts
index 7b3e4454a5e39..487a91c22b5d5 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.test.ts
@@ -16,6 +16,7 @@ const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSu
layerId,
changeType: 'unchanged',
},
+ keptLayerIds: [layerId],
});
let datasourceMap: Record;
@@ -235,8 +236,8 @@ describe('suggestion helpers', () => {
changeType: 'unchanged',
};
datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
- { state: {}, table: table1 },
- { state: {}, table: table2 },
+ { state: {}, table: table1, keptLayerIds: ['first'] },
+ { state: {}, table: table2, keptLayerIds: ['first'] },
]);
getSuggestions({
visualizationMap: {
@@ -343,45 +344,4 @@ describe('suggestion helpers', () => {
})
);
});
-
- it('should drop other layers only on visualization switch', () => {
- const mockVisualization1 = createMockVisualization();
- const mockVisualization2 = createMockVisualization();
- datasourceMap.mock.getDatasourceSuggestionsFromCurrentState.mockReturnValue([
- generateSuggestion(),
- ]);
- datasourceMap.mock.getLayers.mockReturnValue(['first', 'second']);
- const suggestions = getSuggestions({
- visualizationMap: {
- vis1: {
- ...mockVisualization1,
- getSuggestions: () => [
- {
- score: 0.8,
- title: 'Test2',
- state: {},
- previewIcon: 'empty',
- },
- ],
- },
- vis2: {
- ...mockVisualization2,
- getSuggestions: () => [
- {
- score: 0.6,
- title: 'Test3',
- state: {},
- previewIcon: 'empty',
- },
- ],
- },
- },
- activeVisualizationId: 'vis1',
- visualizationState: {},
- datasourceMap,
- datasourceStates,
- });
- expect(suggestions[0].keptLayerIds).toEqual(['first', 'second']);
- expect(suggestions[1].keptLayerIds).toEqual(['first']);
- });
});
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts
index d3e768a1932dc..173f64c6292a8 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_helpers.ts
@@ -13,6 +13,7 @@ import {
FramePublicAPI,
TableChangeType,
TableSuggestion,
+ DatasourceSuggestion,
} from '../../types';
import { Action } from './state_management';
@@ -20,7 +21,6 @@ export interface Suggestion {
visualizationId: string;
datasourceState?: unknown;
datasourceId?: string;
- keptLayerIds: string[];
columns: number;
score: number;
title: string;
@@ -29,6 +29,7 @@ export interface Suggestion {
previewIcon: IconType;
hide?: boolean;
changeType: TableChangeType;
+ keptLayerIds: string[];
}
/**
@@ -64,12 +65,6 @@ export function getSuggestions({
([datasourceId]) => datasourceStates[datasourceId] && !datasourceStates[datasourceId].isLoading
);
- const allLayerIds = _.flatten(
- datasources.map(([datasourceId, datasource]) =>
- datasource.getLayers(datasourceStates[datasourceId].state)
- )
- );
-
// Collect all table suggestions from available datasources
const datasourceTableSuggestions = _.flatten(
datasources.map(([datasourceId, datasource]) => {
@@ -90,17 +85,12 @@ export function getSuggestions({
const table = datasourceSuggestion.table;
const currentVisualizationState =
visualizationId === activeVisualizationId ? visualizationState : undefined;
- const keptLayerIds =
- visualizationId !== activeVisualizationId
- ? [datasourceSuggestion.table.layerId]
- : allLayerIds;
return getVisualizationSuggestions(
visualization,
table,
visualizationId,
datasourceSuggestion,
- currentVisualizationState,
- keptLayerIds
+ currentVisualizationState
);
})
)
@@ -118,20 +108,20 @@ function getVisualizationSuggestions(
visualization: Visualization,
table: TableSuggestion,
visualizationId: string,
- datasourceSuggestion: { datasourceId: string; state: unknown; table: TableSuggestion },
- currentVisualizationState: unknown,
- keptLayerIds: string[]
+ datasourceSuggestion: DatasourceSuggestion & { datasourceId: string },
+ currentVisualizationState: unknown
) {
return visualization
.getSuggestions({
table,
state: currentVisualizationState,
+ keptLayerIds: datasourceSuggestion.keptLayerIds,
})
.map(({ state, ...visualizationSuggestion }) => ({
...visualizationSuggestion,
visualizationId,
visualizationState: state,
- keptLayerIds,
+ keptLayerIds: datasourceSuggestion.keptLayerIds,
datasourceState: datasourceSuggestion.state,
datasourceId: datasourceSuggestion.datasourceId,
columns: table.columns.length,
@@ -144,7 +134,7 @@ export function switchToSuggestion(
dispatch: (action: Action) => void,
suggestion: Pick<
Suggestion,
- 'visualizationId' | 'visualizationState' | 'datasourceState' | 'datasourceId' | 'keptLayerIds'
+ 'visualizationId' | 'visualizationState' | 'datasourceState' | 'datasourceId'
>,
type: 'SWITCH_VISUALIZATION' | 'SELECT_SUGGESTION' = 'SELECT_SUGGESTION'
) {
@@ -156,10 +146,4 @@ export function switchToSuggestion(
datasourceId: suggestion.datasourceId!,
};
dispatch(action);
- const layerIds = Object.keys(frame.datasourceLayers).filter(id => {
- return !suggestion.keptLayerIds.includes(id);
- });
- if (layerIds.length > 0) {
- frame.removeLayers(layerIds);
- }
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
index 8840f7dfd3b78..e32fe5f1f866d 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
@@ -218,36 +218,6 @@ describe('suggestion_panel', () => {
);
});
- it('should remove unused layers if suggestion is clicked', () => {
- defaultProps.frame.datasourceLayers.a = mockDatasource.publicAPIMock;
- defaultProps.frame.datasourceLayers.b = mockDatasource.publicAPIMock;
- const wrapper = mount(
-
- );
-
- act(() => {
- wrapper
- .find('button[data-test-subj="lnsSuggestion"]')
- .at(1)
- .simulate('click');
- });
-
- wrapper.update();
-
- act(() => {
- wrapper
- .find('[data-test-subj="lensSubmitSuggestion"]')
- .first()
- .simulate('click');
- });
-
- expect(defaultProps.frame.removeLayers).toHaveBeenCalledWith(['b']);
- });
-
it('should render preview expression if there is one', () => {
mockDatasource.getLayers.mockReturnValue(['first']);
(getSuggestions as jest.Mock).mockReturnValue([
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx
index 6aee215d11591..e29ac84486d0b 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx
@@ -367,7 +367,12 @@ function getPreviewExpression(
const changedLayers = datasource.getLayers(visualizableState.datasourceState);
changedLayers.forEach(layerId => {
if (updatedLayerApis[layerId]) {
- updatedLayerApis[layerId] = datasource.getPublicAPI(datasourceState, () => {}, layerId);
+ updatedLayerApis[layerId] = datasource.getPublicAPI({
+ layerId,
+ dateRange: frame.dateRange,
+ state: datasourceState,
+ setState: () => {},
+ });
}
});
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
index 9cf59f69c0cc2..3b79172ea6572 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
@@ -574,6 +574,7 @@ describe('workspace_panel', () => {
{
state: {},
table: expectedTable,
+ keptLayerIds: [],
},
]);
mockVisualization.getSuggestions.mockReturnValueOnce([
@@ -613,6 +614,7 @@ describe('workspace_panel', () => {
columns: [],
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
mockVisualization.getSuggestions.mockReturnValueOnce([
@@ -639,6 +641,7 @@ describe('workspace_panel', () => {
columns: [],
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
mockVisualization2.getSuggestions.mockReturnValueOnce([
@@ -665,6 +668,7 @@ describe('workspace_panel', () => {
columns: [],
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
mockVisualization.getSuggestions.mockReturnValueOnce([
@@ -694,6 +698,7 @@ describe('workspace_panel', () => {
layerId: '1',
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
{
state: {},
@@ -703,6 +708,7 @@ describe('workspace_panel', () => {
layerId: '1',
changeType: 'unchanged',
},
+ keptLayerIds: [],
},
]);
mockVisualization.getSuggestions.mockReturnValueOnce([
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel_wrapper.tsx
index 18d5feac4edbe..cc91510146f35 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel_wrapper.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel_wrapper.tsx
@@ -15,9 +15,11 @@ interface Props {
export function WorkspacePanelWrapper({ children, title }: Props) {
return (
-
- {title}
-
+ {title && (
+
+ {title}
+
+ )}
{children}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
index f349585ce88a4..c3e91c9debcd0 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
@@ -53,7 +53,7 @@ export function createMockDatasource(): DatasourceMock {
getDatasourceSuggestionsForField: jest.fn((_state, item) => []),
getDatasourceSuggestionsFromCurrentState: jest.fn(_state => []),
getPersistableState: jest.fn(),
- getPublicAPI: jest.fn((_state, _setState, _layerId) => publicAPIMock),
+ getPublicAPI: jest.fn().mockReturnValue(publicAPIMock),
initialize: jest.fn((_state?) => Promise.resolve()),
renderDataPanel: jest.fn(),
toExpression: jest.fn((_frame, _state) => null),
diff --git a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx b/x-pack/legacy/plugins/lens/public/help_menu_util.tsx
index 3865f08862e1c..30a05dbc38537 100644
--- a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx
+++ b/x-pack/legacy/plugins/lens/public/help_menu_util.tsx
@@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Chrome } from 'ui/chrome';
-const docsPage = undefined;
+const docsPage = 'lens';
export function addHelpMenuToAppChrome(chrome: Chrome) {
chrome.helpExtension.set(domElement => {
@@ -47,7 +47,7 @@ function HelpMenu() {
-
+
{i18n.translate('xpack.lens.helpMenu.feedbackLinkText', {
defaultMessage: 'Provide feedback for the Lens application',
})}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts
index 359c6d7c35c3a..566e5bece096d 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts
@@ -10,25 +10,38 @@ import {
ExpressionFunction,
KibanaContext,
} from '../../../../../../src/plugins/expressions/common';
+import { DateRange } from '../../common';
interface LensAutoDateProps {
aggConfigs: string;
}
-export function getAutoInterval(ctx?: KibanaContext | null) {
- if (!ctx || !ctx.timeRange) {
- return;
+export function autoIntervalFromDateRange(dateRange?: DateRange, defaultValue: string = '1h') {
+ if (!dateRange) {
+ return defaultValue;
}
- const { timeRange } = ctx;
const buckets = new TimeBuckets();
buckets.setInterval('auto');
buckets.setBounds({
- min: dateMath.parse(timeRange.from),
- max: dateMath.parse(timeRange.to, { roundUp: true }),
+ min: dateMath.parse(dateRange.fromDate),
+ max: dateMath.parse(dateRange.toDate, { roundUp: true }),
});
- return buckets.getInterval();
+ return buckets.getInterval().expression;
+}
+
+function autoIntervalFromContext(ctx?: KibanaContext | null) {
+ if (!ctx || !ctx.timeRange) {
+ return;
+ }
+
+ const { timeRange } = ctx;
+
+ return autoIntervalFromDateRange({
+ fromDate: timeRange.from,
+ toDate: timeRange.to,
+ });
}
/**
@@ -56,7 +69,7 @@ export const autoDate: ExpressionFunction<
},
},
fn(ctx: KibanaContext, args: LensAutoDateProps) {
- const interval = getAutoInterval(ctx);
+ const interval = autoIntervalFromContext(ctx);
if (!interval) {
return args.aggConfigs;
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
index ec933d8e82876..11294bedbfff0 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
@@ -14,6 +14,7 @@ import { IndexPatternPrivateState } from './types';
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ChangeIndexPattern } from './change_indexpattern';
import { EuiProgress } from '@elastic/eui';
+import { documentField } from './document_field';
jest.mock('ui/new_platform');
jest.mock('../../../../../../src/legacy/ui/public/registry/field_formats');
@@ -121,6 +122,7 @@ const initialState: IndexPatternPrivateState = {
aggregatable: true,
searchable: true,
},
+ documentField,
],
},
'2': {
@@ -174,6 +176,7 @@ const initialState: IndexPatternPrivateState = {
},
},
},
+ documentField,
],
},
'3': {
@@ -199,6 +202,7 @@ const initialState: IndexPatternPrivateState = {
aggregatable: true,
searchable: true,
},
+ documentField,
],
},
},
@@ -565,6 +569,7 @@ describe('IndexPattern Data Panel', () => {
);
expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([
+ 'Records',
'bytes',
'client',
'memory',
@@ -630,6 +635,7 @@ describe('IndexPattern Data Panel', () => {
.simulate('click');
expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([
+ 'Records',
'bytes',
'client',
'memory',
@@ -698,6 +704,7 @@ describe('IndexPattern Data Panel', () => {
const wrapper = shallowWithIntl( );
expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([
+ 'Records',
'bytes',
'memory',
]);
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
index 35bb3d2de0192..8fc80e14dc166 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
@@ -18,12 +18,13 @@ import {
EuiPopoverTitle,
EuiPopoverFooter,
EuiCallOut,
- EuiText,
EuiFormControlLayout,
EuiSwitch,
EuiFacetButton,
EuiIcon,
- EuiButton,
+ EuiButtonEmpty,
+ EuiSpacer,
+ EuiFormLabel,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -60,10 +61,11 @@ function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) {
return fieldA.name.localeCompare(fieldB.name, undefined, { sensitivity: 'base' });
}
-const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip']);
+const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip', 'document']);
const PAGINATION_SIZE = 50;
const fieldTypeNames: Record = {
+ document: i18n.translate('xpack.lens.datatypes.record', { defaultMessage: 'record' }),
string: i18n.translate('xpack.lens.datatypes.string', { defaultMessage: 'string' }),
number: i18n.translate('xpack.lens.datatypes.number', { defaultMessage: 'number' }),
boolean: i18n.translate('xpack.lens.datatypes.boolean', { defaultMessage: 'boolean' }),
@@ -255,7 +257,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
if (!showEmptyFields) {
const indexField = currentIndexPattern && fieldByName[field.name];
const exists =
- indexField && fieldExists(existingFields, currentIndexPattern.title, indexField.name);
+ field.type === 'document' ||
+ (indexField && fieldExists(existingFields, currentIndexPattern.title, indexField.name));
if (localState.typeFilter.length > 0) {
return exists && localState.typeFilter.includes(field.type as DataType);
}
@@ -270,7 +273,12 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
return true;
});
- const paginatedFields = displayedFields.sort(sortFields).slice(0, pageSize);
+ const specialFields = displayedFields.filter(f => f.type === 'document');
+ const paginatedFields = displayedFields
+ .filter(f => f.type !== 'document')
+ .sort(sortFields)
+ .slice(0, pageSize);
+ const hilight = localState.nameFilter.toLowerCase();
return (
@@ -320,7 +328,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
className="euiFieldText euiFieldText--fullWidth lnsInnerIndexPatternDataPanel__textField"
data-test-subj="lnsIndexPatternFieldSearch"
placeholder={i18n.translate('xpack.lens.indexPatterns.filterByNameLabel', {
- defaultMessage: 'Search for fields',
+ defaultMessage: 'Search field names',
description:
'Search the list of fields in the index pattern for the provided text',
})}
@@ -339,7 +347,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
id="dataPanelTypeFilter"
panelClassName="euiFilterGroup__popoverPanel"
panelPaddingSize="none"
- anchorPosition="downLeft"
+ anchorPosition="rightDown"
display="block"
isOpen={localState.isTypeFilterOpen}
closePopover={() => setLocalState(() => ({ ...localState, isTypeFilterOpen: false }))}
@@ -418,6 +426,31 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
onScroll={lazyScroll}
>
+ {specialFields.map(field => (
+
0}
+ dateRange={dateRange}
+ query={query}
+ filters={filters}
+ hideDetails={true}
+ />
+ ))}
+ {specialFields.length > 0 && (
+ <>
+
+
+ {i18n.translate('xpack.lens.indexPattern.individualFieldsLabel', {
+ defaultMessage: 'Individual fields',
+ })}
+
+
+ >
+ )}
{paginatedFields.map(field => {
const overallField = fieldByName[field.name];
return (
@@ -426,7 +459,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
indexPattern={currentIndexPattern}
key={field.name}
field={field}
- highlight={localState.nameFilter.toLowerCase()}
+ highlight={hilight}
exists={
overallField &&
fieldExists(existingFields, currentIndexPattern.title, overallField.name)
@@ -439,9 +472,11 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
})}
{paginatedFields.length === 0 && (
-
-
- {showEmptyFields
+
-
+ })
+ }
+ >
{(!showEmptyFields ||
localState.typeFilter.length ||
localState.nameFilter.length) && (
- {
trackUiEvent('indexpattern_show_all_fields_clicked');
clearLocalState();
@@ -471,9 +508,9 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
{i18n.translate('xpack.lens.indexPatterns.showAllFields.buttonText', {
defaultMessage: 'Show all fields',
})}
-
+
)}
-
+
)}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx
index 49a04e59b7cc9..d4cf4f7ffbaa6 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx
@@ -18,13 +18,13 @@ import {
SavedObjectsClientContract,
HttpServiceBase,
} from 'src/core/public';
-import { Storage } from 'ui/storage';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { IndexPatternPrivateState } from '../types';
+import { documentField } from '../document_field';
jest.mock('ui/new_platform');
jest.mock('../loader');
jest.mock('../state_helpers');
-jest.mock('../operations');
// Used by indexpattern plugin, which is a dependency of a dependency
jest.mock('ui/chrome');
@@ -67,6 +67,7 @@ const expectedIndexPatterns = {
searchable: true,
exists: true,
},
+ documentField,
],
},
};
@@ -129,11 +130,12 @@ describe('IndexPatternDimensionPanel', () => {
dragDropContext,
state,
setState,
+ dateRange: { fromDate: 'now-1d', toDate: 'now' },
columnId: 'col1',
layerId: 'first',
uniqueLabel: 'stuff',
filterOperations: () => true,
- storage: {} as Storage,
+ storage: {} as IStorageWrapper,
uiSettings: {} as UiSettingsClientContract,
savedObjectsClient: {} as SavedObjectsClientContract,
http: {} as HttpServiceBase,
@@ -199,7 +201,7 @@ describe('IndexPatternDimensionPanel', () => {
expect(options).toHaveLength(2);
- expect(options![0].label).toEqual('Document');
+ expect(options![0].label).toEqual('Records');
expect(options![1].options!.map(({ label }) => label)).toEqual([
'timestamp',
@@ -231,7 +233,7 @@ describe('IndexPatternDimensionPanel', () => {
expect(options![1].options!.map(({ label }) => label)).toEqual(['timestamp', 'source']);
});
- it('should indicate fields which are imcompatible for the operation of the current column', () => {
+ it('should indicate fields which are incompatible for the operation of the current column', () => {
wrapper = mount(
{
const options = wrapper.find(EuiComboBox).prop('options');
- expect(options![0]['data-test-subj']).toEqual('lns-documentOptionIncompatible');
+ expect(options![0]['data-test-subj']).toEqual('lns-fieldOptionIncompatible-Records');
expect(
options![1].options!.filter(({ label }) => label === 'timestamp')[0]['data-test-subj']
@@ -658,6 +660,7 @@ describe('IndexPatternDimensionPanel', () => {
isBucketed: false,
label: '',
operationType: 'count',
+ sourceField: 'Records',
},
},
},
@@ -852,6 +855,7 @@ describe('IndexPatternDimensionPanel', () => {
isBucketed: false,
label: '',
operationType: 'count',
+ sourceField: 'Records',
},
},
},
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx
index c0c774a225642..9f558b7354acd 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.tsx
@@ -7,13 +7,13 @@
import _ from 'lodash';
import React, { memo, useMemo } from 'react';
import { EuiButtonIcon } from '@elastic/eui';
-import { Storage } from 'ui/storage';
import { i18n } from '@kbn/i18n';
import {
UiSettingsClientContract,
SavedObjectsClientContract,
HttpServiceBase,
} from 'src/core/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { DatasourceDimensionPanelProps, StateSetter } from '../../types';
import { IndexPatternColumn, OperationType } from '../indexpattern';
import { getAvailableOperationsByMetadata, buildColumn, changeField } from '../operations';
@@ -23,23 +23,24 @@ import { changeColumn, deleteColumn } from '../state_helpers';
import { isDraggedField, hasField } from '../utils';
import { IndexPatternPrivateState, IndexPatternField } from '../types';
import { trackUiEvent } from '../../lens_ui_telemetry';
+import { DateRange } from '../../../common';
export type IndexPatternDimensionPanelProps = DatasourceDimensionPanelProps & {
state: IndexPatternPrivateState;
setState: StateSetter;
dragDropContext: DragContextState;
uiSettings: UiSettingsClientContract;
- storage: Storage;
+ storage: IStorageWrapper;
savedObjectsClient: SavedObjectsClientContract;
layerId: string;
http: HttpServiceBase;
uniqueLabel: string;
+ dateRange: DateRange;
};
export interface OperationFieldSupportMatrix {
operationByField: Partial>;
fieldByOperation: Partial>;
- operationByDocument: OperationType[];
}
export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPanel(
@@ -55,30 +56,25 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan
const supportedOperationsByField: Partial> = {};
const supportedFieldsByOperation: Partial> = {};
- const supportedOperationsByDocument: OperationType[] = [];
+
filteredOperationsByMetadata.forEach(({ operations }) => {
operations.forEach(operation => {
- if (operation.type === 'field') {
- if (supportedOperationsByField[operation.field]) {
- supportedOperationsByField[operation.field]!.push(operation.operationType);
- } else {
- supportedOperationsByField[operation.field] = [operation.operationType];
- }
+ if (supportedOperationsByField[operation.field]) {
+ supportedOperationsByField[operation.field]!.push(operation.operationType);
+ } else {
+ supportedOperationsByField[operation.field] = [operation.operationType];
+ }
- if (supportedFieldsByOperation[operation.operationType]) {
- supportedFieldsByOperation[operation.operationType]!.push(operation.field);
- } else {
- supportedFieldsByOperation[operation.operationType] = [operation.field];
- }
+ if (supportedFieldsByOperation[operation.operationType]) {
+ supportedFieldsByOperation[operation.operationType]!.push(operation.field);
} else {
- supportedOperationsByDocument.push(operation.operationType);
+ supportedFieldsByOperation[operation.operationType] = [operation.field];
}
});
});
return {
operationByField: _.mapValues(supportedOperationsByField, _.uniq),
fieldByOperation: _.mapValues(supportedFieldsByOperation, _.uniq),
- operationByDocument: _.uniq(supportedOperationsByDocument),
};
}, [currentIndexPattern, props.filterOperations]);
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx
index 7eeffdff383e1..ba18cd150c7a5 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/field_select.tsx
@@ -21,9 +21,11 @@ import { IndexPattern, IndexPatternField, IndexPatternPrivateState } from '../ty
import { trackUiEvent } from '../../lens_ui_telemetry';
import { fieldExists } from '../pure_helpers';
-export type FieldChoice =
- | { type: 'field'; field: string; operationType?: OperationType }
- | { type: 'document' };
+export interface FieldChoice {
+ type: 'field';
+ field: string;
+ operationType?: OperationType;
+}
export interface FieldSelectProps {
currentIndexPattern: IndexPattern;
@@ -50,8 +52,7 @@ export function FieldSelect({
onDeleteColumn,
existingFields,
}: FieldSelectProps) {
- const { operationByDocument, operationByField } = operationFieldSupportMatrix;
-
+ const { operationByField } = operationFieldSupportMatrix;
const memoizedFieldOptions = useMemo(() => {
const fields = Object.keys(operationByField).sort();
@@ -65,68 +66,58 @@ export function FieldSelect({
);
}
- const isCurrentOperationApplicableWithoutField =
- (!selectedColumnOperationType && !incompatibleSelectedOperationType) ||
- operationByDocument.includes(
- incompatibleSelectedOperationType || selectedColumnOperationType!
- );
+ const [specialFields, normalFields] = _.partition(
+ fields,
+ field => fieldMap[field].type === 'document'
+ );
- const fieldOptions = [];
-
- if (operationByDocument.length > 0) {
- fieldOptions.push({
- label: i18n.translate('xpack.lens.indexPattern.documentField', {
- defaultMessage: 'Document',
- }),
- value: { type: 'document' },
- className: classNames({
- 'lnFieldSelect__option--incompatible': !isCurrentOperationApplicableWithoutField,
- }),
- 'data-test-subj': `lns-documentOption${
- isCurrentOperationApplicableWithoutField ? '' : 'Incompatible'
- }`,
- });
+ function fieldNamesToOptions(items: string[]) {
+ return items
+ .map(field => ({
+ label: field,
+ value: {
+ type: 'field',
+ field,
+ dataType: fieldMap[field].type,
+ operationType:
+ selectedColumnOperationType && isCompatibleWithCurrentOperation(field)
+ ? selectedColumnOperationType
+ : undefined,
+ },
+ exists:
+ fieldMap[field].type === 'document' ||
+ fieldExists(existingFields, currentIndexPattern.title, field),
+ compatible: isCompatibleWithCurrentOperation(field),
+ }))
+ .filter(field => showEmptyFields || field.exists)
+ .sort((a, b) => {
+ if (a.compatible && !b.compatible) {
+ return -1;
+ }
+ if (!a.compatible && b.compatible) {
+ return 1;
+ }
+ return 0;
+ })
+ .map(({ label, value, compatible, exists }) => ({
+ label,
+ value,
+ className: classNames({
+ 'lnFieldSelect__option--incompatible': !compatible,
+ 'lnFieldSelect__option--nonExistant': !exists,
+ }),
+ 'data-test-subj': `lns-fieldOption${compatible ? '' : 'Incompatible'}-${label}`,
+ }));
}
+ const fieldOptions: unknown[] = fieldNamesToOptions(specialFields);
+
if (fields.length > 0) {
fieldOptions.push({
label: i18n.translate('xpack.lens.indexPattern.individualFieldsLabel', {
defaultMessage: 'Individual fields',
}),
- options: fields
- .map(field => ({
- label: field,
- value: {
- type: 'field',
- field,
- dataType: fieldMap[field].type,
- operationType:
- selectedColumnOperationType && isCompatibleWithCurrentOperation(field)
- ? selectedColumnOperationType
- : undefined,
- },
- exists: fieldExists(existingFields, currentIndexPattern.title, field),
- compatible: isCompatibleWithCurrentOperation(field),
- }))
- .filter(field => showEmptyFields || field.exists)
- .sort((a, b) => {
- if (a.compatible && !b.compatible) {
- return -1;
- }
- if (!a.compatible && b.compatible) {
- return 1;
- }
- return 0;
- })
- .map(({ label, value, compatible, exists }) => ({
- label,
- value,
- className: classNames({
- 'lnFieldSelect__option--incompatible': !compatible,
- 'lnFieldSelect__option--nonExistant': !exists,
- }),
- 'data-test-subj': `lns-fieldOption${compatible ? '' : 'Incompatible'}-${label}`,
- })),
+ options: fieldNamesToOptions(normalFields),
});
}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx
index b9baa489f3382..0555787ec840d 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx
@@ -38,10 +38,13 @@ import { trackUiEvent } from '../../lens_ui_telemetry';
const operationPanels = getOperationDisplay();
-export function asOperationOptions(
- operationTypes: OperationType[],
- compatibleWithCurrentField: boolean
-) {
+export interface PopoverEditorProps extends IndexPatternDimensionPanelProps {
+ selectedColumn?: IndexPatternColumn;
+ operationFieldSupportMatrix: OperationFieldSupportMatrix;
+ currentIndexPattern: IndexPattern;
+}
+
+function asOperationOptions(operationTypes: OperationType[], compatibleWithCurrentField: boolean) {
return [...operationTypes]
.sort((opType1, opType2) => {
return operationPanels[opType1].displayName.localeCompare(
@@ -54,12 +57,6 @@ export function asOperationOptions(
}));
}
-export interface PopoverEditorProps extends IndexPatternDimensionPanelProps {
- selectedColumn?: IndexPatternColumn;
- operationFieldSupportMatrix: OperationFieldSupportMatrix;
- currentIndexPattern: IndexPattern;
-}
-
export function PopoverEditor(props: PopoverEditorProps) {
const {
selectedColumn,
@@ -72,7 +69,7 @@ export function PopoverEditor(props: PopoverEditorProps) {
uniqueLabel,
hideGrouping,
} = props;
- const { operationByDocument, operationByField, fieldByOperation } = operationFieldSupportMatrix;
+ const { operationByField, fieldByOperation } = operationFieldSupportMatrix;
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [
incompatibleSelectedOperationType,
@@ -87,19 +84,12 @@ export function PopoverEditor(props: PopoverEditorProps) {
currentIndexPattern.fields.forEach(field => {
fields[field.name] = field;
});
-
return fields;
}, [currentIndexPattern]);
function getOperationTypes() {
- const possibleOperationTypes = Object.keys(fieldByOperation).concat(
- operationByDocument
- ) as OperationType[];
-
+ const possibleOperationTypes = Object.keys(fieldByOperation) as OperationType[];
const validOperationTypes: OperationType[] = [];
- if (!selectedColumn || !hasField(selectedColumn)) {
- validOperationTypes.push(...operationByDocument);
- }
if (!selectedColumn) {
validOperationTypes.push(...(Object.keys(fieldByOperation) as OperationType[]));
@@ -139,12 +129,8 @@ export function PopoverEditor(props: PopoverEditorProps) {
onClick() {
if (!selectedColumn) {
const possibleFields = fieldByOperation[operationType] || [];
- const isFieldlessPossible = operationByDocument.includes(operationType);
- if (
- possibleFields.length === 1 ||
- (possibleFields.length === 0 && isFieldlessPossible)
- ) {
+ if (possibleFields.length === 1) {
setState(
changeColumn({
state,
@@ -156,8 +142,7 @@ export function PopoverEditor(props: PopoverEditorProps) {
layerId: props.layerId,
op: operationType,
indexPattern: currentIndexPattern,
- field: possibleFields.length === 1 ? fieldMap[possibleFields[0]] : undefined,
- asDocumentOperation: possibleFields.length === 0,
+ field: fieldMap[possibleFields[0]],
}),
})
);
@@ -184,7 +169,7 @@ export function PopoverEditor(props: PopoverEditorProps) {
layerId: props.layerId,
op: operationType,
indexPattern: currentIndexPattern,
- field: hasField(selectedColumn) ? fieldMap[selectedColumn.sourceField] : undefined,
+ field: fieldMap[selectedColumn.sourceField],
});
trackUiEvent(
`indexpattern_dimension_operation_from_${selectedColumn.operationType}_to_${operationType}`
@@ -307,12 +292,11 @@ export function PopoverEditor(props: PopoverEditorProps) {
}
column = buildColumn({
columns: props.state.layers[props.layerId].columns,
- field: 'field' in choice ? fieldMap[choice.field] : undefined,
+ field: fieldMap[choice.field],
indexPattern: currentIndexPattern,
layerId: props.layerId,
suggestedPriority: props.suggestedPriority,
op: operation as OperationType,
- asDocumentOperation: choice.type === 'document',
});
}
@@ -354,7 +338,7 @@ export function PopoverEditor(props: PopoverEditorProps) {
defaultMessage: 'To use this function, select a field.',
})}
iconType="sortUp"
- >
+ />
)}
{!incompatibleSelectedOperationType && ParamEditor && (
<>
@@ -368,6 +352,7 @@ export function PopoverEditor(props: PopoverEditorProps) {
savedObjectsClient={props.savedObjectsClient}
layerId={layerId}
http={props.http}
+ dateRange={props.dateRange}
/>
>
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/document_field.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/document_field.ts
new file mode 100644
index 0000000000000..e0a7f27835e42
--- /dev/null
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/document_field.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+/**
+ * This is a special-case field which allows us to perform
+ * document-level operations such as count.
+ */
+export const documentField = {
+ name: i18n.translate('xpack.lens.indexPattern.records', {
+ defaultMessage: 'Records',
+ }),
+ type: 'document',
+ aggregatable: true,
+ searchable: true,
+};
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
index 79a3ac4137f88..e7444e8a192ea 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
@@ -56,6 +56,7 @@ export interface FieldItemProps {
query: Query;
dateRange: DatasourceDataPanelProps['dateRange'];
filters: Filter[];
+ hideDetails?: boolean;
}
interface State {
@@ -75,7 +76,17 @@ function wrapOnDot(str?: string) {
}
export function FieldItem(props: FieldItemProps) {
- const { core, field, indexPattern, highlight, exists, query, dateRange, filters } = props;
+ const {
+ core,
+ field,
+ indexPattern,
+ highlight,
+ exists,
+ query,
+ dateRange,
+ filters,
+ hideDetails,
+ } = props;
const [infoIsOpen, setOpen] = useState(false);
@@ -140,6 +151,10 @@ export function FieldItem(props: FieldItemProps) {
}
function togglePopover() {
+ if (hideDetails) {
+ return;
+ }
+
setOpen(!infoIsOpen);
if (!infoIsOpen) {
trackUiEvent('indexpattern_field_info_click');
@@ -175,7 +190,7 @@ export function FieldItem(props: FieldItemProps) {
}
}}
aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonLabel', {
- defaultMessage: 'Click for a field preview. Or, drag and drop to visualize.',
+ defaultMessage: 'Click for a field preview, or drag and drop to visualize.',
})}
>
@@ -186,9 +201,15 @@ export function FieldItem(props: FieldItemProps) {
-
+
{Math.round((topValue.count / props.sampledValues!) * 100)}%
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
index b4f01078f1f78..d998437f3e492 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
@@ -5,7 +5,7 @@
*/
import chromeMock from 'ui/chrome';
-import { Storage } from 'ui/storage';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { SavedObjectsClientContract } from 'kibana/public';
import { getIndexPatternDatasource, IndexPatternColumn, uniqueLabels } from './indexpattern';
import { DatasourcePublicAPI, Operation, Datasource } from '../types';
@@ -143,7 +143,7 @@ describe('IndexPattern Data Source', () => {
beforeEach(() => {
indexPatternDatasource = getIndexPatternDatasource({
chrome: chromeMock,
- storage: {} as Storage,
+ storage: {} as IStorageWrapper,
core: coreMock.createStart(),
savedObjectsClient: {} as SavedObjectsClientContract,
data: pluginsMock.createStart().data,
@@ -183,6 +183,7 @@ describe('IndexPattern Data Source', () => {
isBucketed: false,
label: 'Foo',
operationType: 'count',
+ sourceField: 'Records',
};
const map = uniqueLabels({
a: {
@@ -243,16 +244,13 @@ describe('IndexPattern Data Source', () => {
label: 'Count of records',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
col2: {
label: 'Date',
dataType: 'date',
isBucketed: true,
-
- // Private
operationType: 'date_histogram',
sourceField: 'timestamp',
params: {
@@ -272,7 +270,7 @@ describe('IndexPattern Data Source', () => {
metricsAtAllLevels=false
partialRows=false
includeFormatHints=true
- aggConfigs={lens_auto_date aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":0,\\"extended_bounds\\":{}}}]'} | lens_rename_columns idMap='{\\"col-0-col1\\":{\\"label\\":\\"Count of records\\",\\"dataType\\":\\"number\\",\\"isBucketed\\":false,\\"operationType\\":\\"count\\",\\"id\\":\\"col1\\"},\\"col-1-col2\\":{\\"label\\":\\"Date\\",\\"dataType\\":\\"date\\",\\"isBucketed\\":true,\\"operationType\\":\\"date_histogram\\",\\"sourceField\\":\\"timestamp\\",\\"params\\":{\\"interval\\":\\"1d\\"},\\"id\\":\\"col2\\"}}'"
+ aggConfigs={lens_auto_date aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":0,\\"extended_bounds\\":{}}}]'} | lens_rename_columns idMap='{\\"col-0-col1\\":{\\"label\\":\\"Count of records\\",\\"dataType\\":\\"number\\",\\"isBucketed\\":false,\\"sourceField\\":\\"Records\\",\\"operationType\\":\\"count\\",\\"id\\":\\"col1\\"},\\"col-1-col2\\":{\\"label\\":\\"Date\\",\\"dataType\\":\\"date\\",\\"isBucketed\\":true,\\"operationType\\":\\"date_histogram\\",\\"sourceField\\":\\"timestamp\\",\\"params\\":{\\"interval\\":\\"1d\\"},\\"id\\":\\"col2\\"}}'"
`);
});
});
@@ -414,7 +412,15 @@ describe('IndexPattern Data Source', () => {
beforeEach(async () => {
const initialState = stateFromPersistedState(persistedState);
- publicAPI = indexPatternDatasource.getPublicAPI(initialState, () => {}, 'first');
+ publicAPI = indexPatternDatasource.getPublicAPI({
+ state: initialState,
+ setState: () => {},
+ layerId: 'first',
+ dateRange: {
+ fromDate: 'now-30d',
+ toDate: 'now',
+ },
+ });
});
describe('getTableSpec', () => {
@@ -453,8 +459,8 @@ describe('IndexPattern Data Source', () => {
suggestedPriority: 2,
},
};
- const api = indexPatternDatasource.getPublicAPI(
- {
+ const api = indexPatternDatasource.getPublicAPI({
+ state: {
...initialState,
layers: {
first: {
@@ -465,8 +471,12 @@ describe('IndexPattern Data Source', () => {
},
},
setState,
- 'first'
- );
+ layerId: 'first',
+ dateRange: {
+ fromDate: 'now-1y',
+ toDate: 'now',
+ },
+ });
api.removeColumnInTableSpec('b');
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
index 359eb687b5741..443667ab64476 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
@@ -9,13 +9,14 @@ import React from 'react';
import { render } from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { CoreStart, SavedObjectsClientContract } from 'src/core/public';
-import { Storage } from 'ui/storage';
import { i18n } from '@kbn/i18n';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import {
DatasourceDimensionPanelProps,
DatasourceDataPanelProps,
Operation,
DatasourceLayerPanelProps,
+ PublicAPIProps,
} from '../types';
import { loadInitialState, changeIndexPattern, changeLayerIndexPattern } from './loader';
import { toExpression } from './to_expression';
@@ -27,7 +28,7 @@ import {
getDatasourceSuggestionsFromCurrentState,
} from './indexpattern_suggestions';
-import { isDraggedField } from './utils';
+import { isDraggedField, normalizeOperationDataType } from './utils';
import { LayerPanel } from './layerpanel';
import { IndexPatternColumn } from './operations';
import {
@@ -50,7 +51,7 @@ export interface DraggedField {
export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: string): Operation {
const { dataType, label, isBucketed, scale } = column;
return {
- dataType,
+ dataType: normalizeOperationDataType(dataType),
isBucketed,
scale,
label: uniqueLabel || label,
@@ -104,7 +105,7 @@ export function getIndexPatternDatasource({
// Core start is being required here because it contains the savedObject client
// In the new platform, this plugin wouldn't be initialized until after setup
core: CoreStart;
- storage: Storage;
+ storage: IStorageWrapper;
savedObjectsClient: SavedObjectsClientContract;
data: ReturnType;
}) {
@@ -196,11 +197,12 @@ export function getIndexPatternDatasource({
);
},
- getPublicAPI(
- state: IndexPatternPrivateState,
- setState: StateSetter,
- layerId: string
- ) {
+ getPublicAPI({
+ state,
+ setState,
+ layerId,
+ dateRange,
+ }: PublicAPIProps) {
const columnLabelMap = uniqueLabels(state.layers);
return {
@@ -221,7 +223,7 @@ export function getIndexPatternDatasource({
@@ -257,6 +260,7 @@ export function getIndexPatternDatasource({
state,
layerId: props.layerId,
onError: onIndexPatternLoadError,
+ replaceIfPossible: true,
});
}}
{...props}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
index ff6911bd25f84..e86a16c1af9d6 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
@@ -562,6 +562,62 @@ describe('IndexPattern Data Source suggestions', () => {
})
);
});
+
+ it('creates a new layer and replaces layer if no match is found', () => {
+ const suggestions = getDatasourceSuggestionsForField(stateWithEmptyLayer(), '2', {
+ name: 'source',
+ type: 'string',
+ aggregatable: true,
+ searchable: true,
+ });
+
+ expect(suggestions).toContainEqual(
+ expect.objectContaining({
+ state: expect.objectContaining({
+ layers: {
+ previousLayer: expect.objectContaining({
+ indexPatternId: '1',
+ }),
+ id1: expect.objectContaining({
+ indexPatternId: '2',
+ }),
+ },
+ }),
+ table: {
+ changeType: 'initial',
+ label: undefined,
+ isMultiRow: true,
+ columns: expect.arrayContaining([]),
+ layerId: 'id1',
+ },
+ keptLayerIds: ['previousLayer'],
+ })
+ );
+
+ expect(suggestions).toContainEqual(
+ expect.objectContaining({
+ state: expect.objectContaining({
+ layers: {
+ id1: expect.objectContaining({
+ indexPatternId: '2',
+ }),
+ },
+ }),
+ table: {
+ changeType: 'initial',
+ label: undefined,
+ isMultiRow: false,
+ columns: expect.arrayContaining([
+ expect.objectContaining({
+ columnId: expect.any(String),
+ }),
+ ]),
+ layerId: 'id1',
+ },
+ keptLayerIds: [],
+ })
+ );
+ });
});
describe('suggesting extensions to non-empty tables', () => {
@@ -979,12 +1035,25 @@ describe('IndexPattern Data Source suggestions', () => {
};
const result = getDatasourceSuggestionsFromCurrentState(state);
+
expect(result).toContainEqual(
expect.objectContaining({
- table: {
+ table: expect.objectContaining({
isMultiRow: true,
changeType: 'unchanged',
label: undefined,
+ layerId: 'first',
+ }),
+ keptLayerIds: ['first', 'second'],
+ })
+ );
+
+ expect(result).toContainEqual(
+ expect.objectContaining({
+ table: {
+ isMultiRow: true,
+ changeType: 'layers',
+ label: 'Show only layer 1',
columns: [
{
columnId: 'col1',
@@ -1005,8 +1074,8 @@ describe('IndexPattern Data Source suggestions', () => {
expect.objectContaining({
table: {
isMultiRow: true,
- changeType: 'unchanged',
- label: undefined,
+ changeType: 'layers',
+ label: 'Show only layer 2',
columns: [
{
columnId: 'cola',
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts
index 49944614edbb4..d2cf6261835fd 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.ts
@@ -23,6 +23,7 @@ import {
IndexPatternLayer,
IndexPatternField,
} from './types';
+import { documentField } from './document_field';
type IndexPatternSugestion = DatasourceSuggestion;
@@ -80,6 +81,8 @@ function buildSuggestion({
changeType,
label,
},
+
+ keptLayerIds: Object.keys(state.layers),
};
}
@@ -92,10 +95,13 @@ export function getDatasourceSuggestionsForField(
const layerIds = layers.filter(id => state.layers[id].indexPatternId === indexPatternId);
if (layerIds.length === 0) {
- // The field we're suggesting on does not match any existing layer. This will always add
- // a new layer if possible, but that might not be desirable if the layers are too complicated
- // already
- return getEmptyLayerSuggestionsForField(state, generateId(), indexPatternId, field);
+ // The field we're suggesting on does not match any existing layer.
+ // This generates a set of suggestions where we add a layer.
+ // A second set of suggestions is generated for visualizations that don't work with layers
+ const newId = generateId();
+ return getEmptyLayerSuggestionsForField(state, newId, indexPatternId, field).concat(
+ getEmptyLayerSuggestionsForField({ ...state, layers: {} }, newId, indexPatternId, field)
+ );
} else {
// The field we're suggesting on matches an existing layer. In this case we find the layer with
// the fewest configured columns and try to add the field to this table. If this layer does not
@@ -165,7 +171,7 @@ function addFieldAsMetricOperation(
layerId: string,
indexPattern: IndexPattern,
field: IndexPatternField
-) {
+): IndexPatternLayer | undefined {
const operations = getOperationTypesForField(field);
const operationsAlreadyAppliedToThisField = Object.values(layer.columns)
.filter(column => hasField(column) && column.sourceField === field.name)
@@ -175,7 +181,7 @@ function addFieldAsMetricOperation(
);
if (!operationCandidate) {
- return undefined;
+ return;
}
const newColumn = buildColumn({
@@ -205,7 +211,7 @@ function addFieldAsBucketOperation(
layerId: string,
indexPattern: IndexPattern,
field: IndexPatternField
-) {
+): IndexPatternLayer {
const applicableBucketOperation = getBucketOperation(field);
const newColumn = buildColumn({
op: applicableBucketOperation,
@@ -249,7 +255,7 @@ function getEmptyLayerSuggestionsForField(
layerId: string,
indexPatternId: string,
field: IndexPatternField
-) {
+): IndexPatternSugestion[] {
const indexPattern = state.indexPatterns[indexPatternId];
let newLayer: IndexPatternLayer | undefined;
if (getBucketOperation(field)) {
@@ -278,13 +284,14 @@ function createNewLayerWithBucketAggregation(
layerId: string,
indexPattern: IndexPattern,
field: IndexPatternField
-) {
+): IndexPatternLayer {
const countColumn = buildColumn({
op: 'count',
columns: {},
indexPattern,
layerId,
suggestedPriority: undefined,
+ field: documentField,
});
const col1 = generateId();
@@ -316,7 +323,7 @@ function createNewLayerWithMetricAggregation(
layerId: string,
indexPattern: IndexPattern,
field: IndexPatternField
-) {
+): IndexPatternLayer {
const dateField = indexPattern.fields.find(f => f.name === indexPattern.timeFieldName)!;
const operations = getOperationTypesForField(field);
@@ -354,16 +361,59 @@ function createNewLayerWithMetricAggregation(
export function getDatasourceSuggestionsFromCurrentState(
state: IndexPatternPrivateState
): Array> {
+ const layers = Object.entries(state.layers || {});
+ if (layers.length > 1) {
+ // Return suggestions that reduce the data to each layer individually
+ return layers
+ .map(([layerId, layer], index) => {
+ const hasMatchingLayer = layers.some(
+ ([otherLayerId, otherLayer]) =>
+ otherLayerId !== layerId && otherLayer.indexPatternId === layer.indexPatternId
+ );
+
+ const suggestionTitle = hasMatchingLayer
+ ? i18n.translate('xpack.lens.indexPatternSuggestion.removeLayerPositionLabel', {
+ defaultMessage: 'Show only layer {layerNumber}',
+ values: { layerNumber: index + 1 },
+ })
+ : i18n.translate('xpack.lens.indexPatternSuggestion.removeLayerLabel', {
+ defaultMessage: 'Show only {indexPatternTitle}',
+ values: { indexPatternTitle: state.indexPatterns[layer.indexPatternId].title },
+ });
+
+ return buildSuggestion({
+ state: {
+ ...state,
+ layers: {
+ [layerId]: layer,
+ },
+ },
+ layerId,
+ changeType: 'layers',
+ label: suggestionTitle,
+ });
+ })
+ .concat([
+ buildSuggestion({
+ state,
+ layerId: layers[0][0],
+ changeType: 'unchanged',
+ }),
+ ]);
+ }
return _.flatten(
Object.entries(state.layers || {})
- .filter(([_id, layer]) => layer.columnOrder.length)
- .map(([layerId, layer], index) => {
+ .filter(([_id, layer]) => layer.columnOrder.length && layer.indexPatternId)
+ .map(([layerId, layer]) => {
const indexPattern = state.indexPatterns[layer.indexPatternId];
const [buckets, metrics] = separateBucketColumns(layer);
const timeDimension = layer.columnOrder.find(
columnId =>
layer.columns[columnId].isBucketed && layer.columns[columnId].dataType === 'date'
);
+ const timeField = indexPattern.fields.find(
+ ({ name }) => name === indexPattern.timeFieldName
+ );
const suggestions: Array> = [];
if (metrics.length === 0) {
@@ -376,9 +426,9 @@ export function getDatasourceSuggestionsFromCurrentState(
})
);
} else if (buckets.length === 0) {
- if (indexPattern.timeFieldName) {
+ if (timeField) {
// suggest current metric over time if there is a default time field
- suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId));
+ suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField));
}
suggestions.push(...createAlternativeMetricSuggestions(indexPattern, layerId, state));
// also suggest simple current state
@@ -392,10 +442,10 @@ export function getDatasourceSuggestionsFromCurrentState(
} else {
suggestions.push(...createSimplifiedTableSuggestions(state, layerId));
- if (!timeDimension && indexPattern.timeFieldName) {
+ if (!timeDimension && timeField) {
// suggest current configuration over time if there is a default time field
// and no time dimension yet
- suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId));
+ suggestions.push(createSuggestionWithDefaultDateHistogram(state, layerId, timeField));
}
if (buckets.length === 2) {
@@ -427,7 +477,6 @@ function createMetricSuggestion(
state: IndexPatternPrivateState,
field: IndexPatternField
) {
- const layer = state.layers[layerId];
const operationDefinitionsMap = _.indexBy(operationDefinitions, 'type');
const [column] = getOperationTypesForField(field)
.map(type =>
@@ -439,20 +488,33 @@ function createMetricSuggestion(
suggestedPriority: 0,
})
)
- .filter(op => op.dataType === 'number' && !op.isBucketed);
+ .filter(op => (op.dataType === 'number' || op.dataType === 'document') && !op.isBucketed);
if (!column) {
return;
}
const newId = generateId();
+
return buildSuggestion({
layerId,
state,
changeType: 'initial',
updatedLayer: {
- ...layer,
- columns: { [newId]: column },
+ indexPatternId: indexPattern.id,
+ columns: {
+ [newId]:
+ column.dataType !== 'document'
+ ? column
+ : buildColumn({
+ op: 'count',
+ columns: {},
+ indexPattern,
+ layerId,
+ suggestedPriority: undefined,
+ field: documentField,
+ }),
+ },
columnOrder: [newId],
},
});
@@ -462,8 +524,8 @@ function getNestedTitle([outerBucket, innerBucket]: IndexPatternColumn[]) {
return i18n.translate('xpack.lens.indexpattern.suggestions.nestingChangeLabel', {
defaultMessage: '{innerOperation} for each {outerOperation}',
values: {
- innerOperation: hasField(innerBucket) ? innerBucket.sourceField : innerBucket.label,
- outerOperation: hasField(outerBucket) ? outerBucket.sourceField : outerBucket.label,
+ innerOperation: innerBucket.sourceField,
+ outerOperation: outerBucket.sourceField,
},
});
}
@@ -497,7 +559,7 @@ function createAlternativeMetricSuggestions(
suggestedPriority: undefined,
});
const updatedLayer = {
- ...layer,
+ indexPatternId: indexPattern.id,
columns: { [newId]: newColumn },
columnOrder: [newId],
};
@@ -515,7 +577,8 @@ function createAlternativeMetricSuggestions(
function createSuggestionWithDefaultDateHistogram(
state: IndexPatternPrivateState,
- layerId: string
+ layerId: string,
+ timeField: IndexPatternField
) {
const layer = state.layers[layerId];
const indexPattern = state.indexPatterns[layer.indexPatternId];
@@ -526,11 +589,11 @@ function createSuggestionWithDefaultDateHistogram(
op: 'date_histogram',
indexPattern,
columns: layer.columns,
- field: indexPattern.fields.find(({ name }) => name === indexPattern.timeFieldName),
+ field: timeField,
suggestedPriority: undefined,
});
const updatedLayer = {
- ...layer,
+ indexPatternId: layer.indexPatternId,
columns: { ...layer.columns, [newId]: timeColumn },
columnOrder: [...buckets, newId, ...metrics],
};
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/lens_field_icon.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/lens_field_icon.tsx
index 1773022bf6e1a..cd2bb69f6e580 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/lens_field_icon.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/lens_field_icon.tsx
@@ -8,9 +8,10 @@ import React from 'react';
import { palettes } from '@elastic/eui';
import { FieldIcon, typeToEuiIconMap } from '../../../../../../src/plugins/kibana_react/public';
import { DataType } from '../types';
+import { normalizeOperationDataType } from './utils';
export function getColorForDataType(type: string) {
- const iconMap = typeToEuiIconMap[type];
+ const iconMap = typeToEuiIconMap[normalizeOperationDataType(type as DataType)];
if (iconMap) {
return iconMap.color;
}
@@ -18,5 +19,12 @@ export function getColorForDataType(type: string) {
}
export function LensFieldIcon({ type }: { type: DataType }) {
- return ;
+ return (
+
+ );
}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts
index b3f296a96e56d..03bbddb998b3c 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts
@@ -14,6 +14,7 @@ import {
syncExistingFields,
} from './loader';
import { IndexPatternPersistedState, IndexPatternPrivateState } from './types';
+import { documentField } from './document_field';
// TODO: This should not be necessary
jest.mock('ui/new_platform');
@@ -63,6 +64,7 @@ const sampleIndexPatterns = {
searchable: true,
esTypes: ['keyword'],
},
+ documentField,
],
},
b: {
@@ -121,6 +123,7 @@ const sampleIndexPatterns = {
},
esTypes: ['keyword'],
},
+ documentField,
],
},
};
@@ -133,7 +136,7 @@ function indexPatternSavedObject({ id }: { id: keyof typeof sampleIndexPatterns
attributes: {
title: pattern.title,
timeFieldName: pattern.timeFieldName,
- fields: JSON.stringify(pattern.fields),
+ fields: JSON.stringify(pattern.fields.filter(f => f.type !== 'document')),
},
};
}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts
index dd1f43f7b9276..d83777b758da6 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts
@@ -23,6 +23,7 @@ import {
import { updateLayerIndexPattern } from './state_helpers';
import { DateRange, ExistingFields } from '../../common/types';
import { BASE_API_URL } from '../../common';
+import { documentField } from './document_field';
interface SavedIndexPatternAttributes extends SavedObjectAttributes {
title: string;
@@ -171,6 +172,7 @@ export async function changeLayerIndexPattern({
state,
setState,
onError,
+ replaceIfPossible,
}: {
indexPatternId: string;
layerId: string;
@@ -178,6 +180,7 @@ export async function changeLayerIndexPattern({
state: IndexPatternPrivateState;
setState: SetState;
onError: ErrorHandler;
+ replaceIfPossible?: boolean;
}) {
try {
const indexPatterns = await loadIndexPatterns({
@@ -196,6 +199,7 @@ export async function changeLayerIndexPattern({
...s.indexPatterns,
[indexPatternId]: indexPatterns[indexPatternId],
},
+ currentIndexPatternId: replaceIfPossible ? indexPatternId : s.currentIndexPatternId,
}));
} catch (err) {
onError(err);
@@ -211,10 +215,14 @@ async function loadIndexPatternRefs(
perPage: 10000,
});
- return result.savedObjects.map(o => ({
- id: String(o.id),
- title: (o.attributes as { title: string }).title,
- }));
+ return result.savedObjects
+ .map(o => ({
+ id: String(o.id),
+ title: (o.attributes as { title: string }).title,
+ }))
+ .sort((a, b) => {
+ return a.title.localeCompare(b.title);
+ });
}
export async function syncExistingFields({
@@ -278,10 +286,12 @@ function fromSavedObject(
id,
type,
title: attributes.title,
- fields: (JSON.parse(attributes.fields) as IndexPatternField[]).filter(
- ({ type: fieldType, esTypes }) =>
- fieldType !== 'string' || (esTypes && esTypes.includes('keyword'))
- ),
+ fields: (JSON.parse(attributes.fields) as IndexPatternField[])
+ .filter(
+ ({ type: fieldType, esTypes }) =>
+ fieldType !== 'string' || (esTypes && esTypes.includes('keyword'))
+ )
+ .concat(documentField),
typeMeta: attributes.typeMeta
? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo)
: undefined,
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts
index ed0e2fb3c96c5..fe8a3d34d1c1c 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/column_types.ts
@@ -14,6 +14,7 @@ import { Operation, DimensionPriority } from '../../../types';
export interface BaseIndexPatternColumn extends Operation {
// Private
operationType: string;
+ sourceField: string;
suggestedPriority?: DimensionPriority;
}
@@ -35,6 +36,5 @@ export type ParameterlessIndexPatternColumn<
> = TBase & { operationType: TOperationType };
export interface FieldBasedIndexPatternColumn extends BaseIndexPatternColumn {
- sourceField: string;
suggestedPriority?: DimensionPriority;
}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx
index 94cb17b340c36..d86e688fca013 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx
@@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { OperationDefinition } from '.';
import { ParameterlessIndexPatternColumn, BaseIndexPatternColumn } from './column_types';
+import { IndexPatternField } from '../../types';
const countLabel = i18n.translate('xpack.lens.indexPattern.countOf', {
defaultMessage: 'Count of records',
@@ -23,14 +24,23 @@ export const countOperation: OperationDefinition = {
displayName: i18n.translate('xpack.lens.indexPattern.count', {
defaultMessage: 'Count',
}),
- getPossibleOperationForDocument: () => {
+ onFieldChange: (oldColumn, indexPattern, field) => {
return {
- dataType: 'number',
- isBucketed: false,
- scale: 'ratio',
+ ...oldColumn,
+ label: field.name,
+ sourceField: field.name,
};
},
- buildColumn({ suggestedPriority }) {
+ getPossibleOperationForField: (field: IndexPatternField) => {
+ if (field.type === 'document') {
+ return {
+ dataType: 'number',
+ isBucketed: false,
+ scale: 'ratio',
+ };
+ }
+ },
+ buildColumn({ suggestedPriority, field }) {
return {
label: countLabel,
dataType: 'number',
@@ -38,6 +48,7 @@ export const countOperation: OperationDefinition = {
suggestedPriority,
isBucketed: false,
scale: 'ratio',
+ sourceField: field.name,
};
},
toEsAggsConfig: (column, columnId) => ({
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx
index f984597a8eb4b..d19493d579b64 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx
@@ -8,17 +8,37 @@ import React from 'react';
import { DateHistogramIndexPatternColumn } from './date_histogram';
import { dateHistogramOperation } from '.';
import { shallow } from 'enzyme';
-import { EuiRange, EuiSwitch } from '@elastic/eui';
+import { EuiSwitch } from '@elastic/eui';
import {
UiSettingsClientContract,
SavedObjectsClientContract,
HttpServiceBase,
} from 'src/core/public';
-import { Storage } from 'ui/storage';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { createMockedIndexPattern } from '../../mocks';
import { IndexPatternPrivateState } from '../../types';
jest.mock('ui/new_platform');
+jest.mock('ui/chrome', () => ({
+ getUiSettingsClient: () => ({
+ get(path: string) {
+ if (path === 'histogram:maxBars') {
+ return 10;
+ }
+ },
+ }),
+}));
+
+const defaultOptions = {
+ storage: {} as IStorageWrapper,
+ uiSettings: {} as UiSettingsClientContract,
+ savedObjectsClient: {} as SavedObjectsClientContract,
+ dateRange: {
+ fromDate: 'now-1y',
+ toDate: 'now',
+ },
+ http: {} as HttpServiceBase,
+};
describe('date_histogram', () => {
let state: IndexPatternPrivateState;
@@ -72,7 +92,7 @@ describe('date_histogram', () => {
// Private
operationType: 'date_histogram',
params: {
- interval: 'w',
+ interval: '42w',
},
sourceField: 'timestamp',
},
@@ -118,6 +138,27 @@ describe('date_histogram', () => {
};
});
+ function stateWithInterval(interval: string) {
+ return ({
+ ...state,
+ layers: {
+ ...state.layers,
+ first: {
+ ...state.layers.first,
+ columns: {
+ ...state.layers.first.columns,
+ col1: {
+ ...state.layers.first.columns.col1,
+ params: {
+ interval,
+ },
+ },
+ },
+ },
+ },
+ } as unknown) as IndexPatternPrivateState;
+ }
+
describe('buildColumn', () => {
it('should create column object with auto interval for primary time field', () => {
const column = dateHistogramOperation.buildColumn({
@@ -188,7 +229,7 @@ describe('date_histogram', () => {
expect(esAggsConfig).toEqual(
expect.objectContaining({
params: expect.objectContaining({
- interval: 'w',
+ interval: '42w',
field: 'timestamp',
}),
})
@@ -217,7 +258,7 @@ describe('date_histogram', () => {
expect(column.label).toContain('start_date');
});
- it('should change interval from auto when switching to a non primary time field', () => {
+ it('should not change interval from auto when switching to a non primary time field', () => {
const oldColumn: DateHistogramIndexPatternColumn = {
operationType: 'date_histogram',
sourceField: 'timestamp',
@@ -233,7 +274,7 @@ describe('date_histogram', () => {
const column = dateHistogramOperation.onFieldChange(oldColumn, indexPattern, newDateField);
expect(column).toHaveProperty('sourceField', 'start_date');
- expect(column).toHaveProperty('params.interval', 'd');
+ expect(column).toHaveProperty('params.interval', 'auto');
expect(column.label).toContain('start_date');
});
});
@@ -281,7 +322,7 @@ describe('date_histogram', () => {
);
});
- it('should remove time zone param and normalize interval param', () => {
+ it('should retain interval', () => {
const transferedColumn = dateHistogramOperation.transfer!(
{
dataType: 'date',
@@ -309,7 +350,7 @@ describe('date_histogram', () => {
expect(transferedColumn).toEqual(
expect.objectContaining({
params: {
- interval: 'M',
+ interval: '20s',
timeZone: undefined,
},
})
@@ -322,55 +363,49 @@ describe('date_histogram', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
- expect(instance.find(EuiRange).prop('value')).toEqual(1);
+ expect(instance.find('[data-test-subj="lensDateHistogramValue"]').prop('value')).toEqual(42);
+ expect(instance.find('[data-test-subj="lensDateHistogramUnit"]').prop('value')).toEqual('w');
});
it('should render current value for other index pattern', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
- expect(instance.find(EuiRange).prop('value')).toEqual(2);
+ expect(instance.find('[data-test-subj="lensDateHistogramValue"]').prop('value')).toEqual('');
+ expect(instance.find('[data-test-subj="lensDateHistogramUnit"]').prop('value')).toEqual('d');
});
- it('should render disabled switch and no time intervals control for auto interval', () => {
+ it('should render disabled switch and no time interval control for auto interval', () => {
const instance = shallow(
);
- expect(instance.find(EuiRange).exists()).toBe(false);
+ expect(instance.find('[data-test-subj="lensDateHistogramValue"]').exists()).toBeFalsy();
+ expect(instance.find('[data-test-subj="lensDateHistogramUnit"]').exists()).toBeFalsy();
expect(instance.find(EuiSwitch).prop('checked')).toBe(false);
});
@@ -378,15 +413,12 @@ describe('date_histogram', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
instance.find(EuiSwitch).prop('onChange')!({
@@ -394,57 +426,124 @@ describe('date_histogram', () => {
} as React.ChangeEvent);
expect(setStateSpy).toHaveBeenCalled();
const newState = setStateSpy.mock.calls[0][0];
- expect(newState).toHaveProperty('layers.third.columns.col1.params.interval', 'd');
+ expect(newState).toHaveProperty('layers.third.columns.col1.params.interval', '30d');
});
- it('should update state with the interval value', () => {
+ it('should force calendar values to 1', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
+ instance.find('[data-test-subj="lensDateHistogramValue"]').prop('onChange')!({
+ target: {
+ value: '2',
+ },
+ } as React.ChangeEvent);
+ expect(setStateSpy).toHaveBeenCalledWith(stateWithInterval('1w'));
+ });
- instance.find(EuiRange).prop('onChange')!(
- {
- target: {
- value: '2',
- },
- } as React.ChangeEvent,
- true
+ it('should display error if an invalid interval is specified', () => {
+ const setStateSpy = jest.fn();
+ const testState = stateWithInterval('4quid');
+ const instance = shallow(
+
);
- expect(setStateSpy).toHaveBeenCalledWith({
- ...state,
- layers: {
- ...state.layers,
- first: {
- ...state.layers.first,
- columns: {
- ...state.layers.first.columns,
- col1: {
- ...state.layers.first.columns.col1,
- params: {
- interval: 'd',
- },
- },
- },
- },
+ expect(instance.find('[data-test-subj="lensDateHistogramError"]').exists()).toBeTruthy();
+ });
+
+ it('should not display error if interval value is blank', () => {
+ const setStateSpy = jest.fn();
+ const testState = stateWithInterval('d');
+ const instance = shallow(
+
+ );
+ expect(instance.find('[data-test-subj="lensDateHistogramError"]').exists()).toBeFalsy();
+ });
+
+ it('should display error if interval value is 0', () => {
+ const setStateSpy = jest.fn();
+ const testState = stateWithInterval('0d');
+ const instance = shallow(
+
+ );
+ expect(instance.find('[data-test-subj="lensDateHistogramError"]').exists()).toBeTruthy();
+ });
+
+ it('should update the unit', () => {
+ const setStateSpy = jest.fn();
+ const instance = shallow(
+
+ );
+ instance.find('[data-test-subj="lensDateHistogramUnit"]').prop('onChange')!({
+ target: {
+ value: 'd',
},
- });
+ } as React.ChangeEvent);
+ expect(setStateSpy).toHaveBeenCalledWith(stateWithInterval('42d'));
+ });
+
+ it('should update the value', () => {
+ const setStateSpy = jest.fn();
+ const testState = stateWithInterval('42d');
+
+ const instance = shallow(
+
+ );
+ instance.find('[data-test-subj="lensDateHistogramValue"]').prop('onChange')!({
+ target: {
+ value: '9',
+ },
+ } as React.ChangeEvent);
+ expect(setStateSpy).toHaveBeenCalledWith(stateWithInterval('9d'));
});
it('should not render options if they are restricted', () => {
const setStateSpy = jest.fn();
const instance = shallow(
{
columnId="col1"
layerId="first"
currentColumn={state.layers.first.columns.col1 as DateHistogramIndexPatternColumn}
- storage={{} as Storage}
- uiSettings={{} as UiSettingsClientContract}
- savedObjectsClient={{} as SavedObjectsClientContract}
- http={{} as HttpServiceBase}
/>
);
- expect(instance.find(EuiRange)).toHaveLength(0);
+ expect(instance.find('[data-test-subj="lensDateHistogramValue"]').exists()).toBeFalsy();
});
});
});
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx
index e5c00542df7d3..017dccab64607 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx
@@ -7,31 +7,29 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiForm, EuiFormRow, EuiRange, EuiSwitch } from '@elastic/eui';
+
+// TODO: make this new-platform compatible
+import { isValidInterval } from 'ui/agg_types/utils';
+
+import {
+ EuiForm,
+ EuiFormRow,
+ EuiSwitch,
+ EuiFieldNumber,
+ EuiSelect,
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiTextColor,
+ EuiSpacer,
+} from '@elastic/eui';
import { updateColumnParam } from '../../state_helpers';
import { OperationDefinition } from '.';
import { FieldBasedIndexPatternColumn } from './column_types';
-import { IndexPattern } from '../../types';
-
-type PropType = C extends React.ComponentType ? P : unknown;
+import { autoIntervalFromDateRange } from '../../auto_date';
+import { AggregationRestrictions } from '../../types';
const autoInterval = 'auto';
-const supportedIntervals = ['M', 'w', 'd', 'h', 'm'];
-const defaultCustomInterval = supportedIntervals[2];
-
-// Add ticks to EuiRange component props
-const FixedEuiRange = (EuiRange as unknown) as React.ComponentType<
- PropType & {
- ticks?: Array<{
- label: string;
- value: number;
- }>;
- }
->;
-
-function supportsAutoInterval(fieldName: string, indexPattern: IndexPattern): boolean {
- return indexPattern.timeFieldName ? indexPattern.timeFieldName === fieldName : false;
-}
+const calendarOnlyIntervals = new Set(['w', 'M', 'q', 'y']);
export interface DateHistogramIndexPatternColumn extends FieldBasedIndexPatternColumn {
operationType: 'date_histogram';
@@ -59,12 +57,11 @@ export const dateHistogramOperation: OperationDefinition {
return {
...oldColumn,
label: field.name,
sourceField: field.name,
- params: {
- ...oldColumn.params,
- // If we have an "auto" interval but the field we're switching to doesn't support auto intervals
- // we use the default custom interval instead
- interval:
- oldColumn.params.interval === 'auto' && !supportsAutoInterval(field.name, indexPattern)
- ? defaultCustomInterval
- : oldColumn.params.interval,
- },
};
},
toEsAggsConfig: (column, columnId) => ({
@@ -157,7 +135,7 @@ export const dateHistogramOperation: OperationDefinition {
+ paramEditor: ({ state, setState, currentColumn: currentColumn, layerId, dateRange }) => {
const field =
currentColumn &&
state.indexPatterns[state.layers[layerId].indexPatternId].fields.find(
@@ -166,20 +144,35 @@ export const dateHistogramOperation: OperationDefinition) {
- const interval = ev.target.checked ? defaultCustomInterval : autoInterval;
+ const value = ev.target.checked ? autoIntervalFromDateRange(dateRange) : autoInterval;
+ setState(updateColumnParam({ state, layerId, currentColumn, paramName: 'interval', value }));
+ }
+
+ const setInterval = (newInterval: typeof interval) => {
+ const isCalendarInterval = calendarOnlyIntervals.has(newInterval.unit);
+ const value = `${isCalendarInterval ? '1' : newInterval.value}${newInterval.unit || 'd'}`;
+
setState(
- updateColumnParam({ state, layerId, currentColumn, paramName: 'interval', value: interval })
+ updateColumnParam({
+ state,
+ layerId,
+ currentColumn,
+ value,
+ paramName: 'interval',
+ })
);
- }
+ };
return (
@@ -187,7 +180,7 @@ export const dateHistogramOperation: OperationDefinition
{intervalIsRestricted ? (
@@ -209,33 +202,101 @@ export const dateHistogramOperation: OperationDefinition
) : (
- ({
- label: interval,
- value: index,
- }))}
- onChange={(
- e: React.ChangeEvent | React.MouseEvent
- ) =>
- setState(
- updateColumnParam({
- state,
- layerId,
- currentColumn,
- paramName: 'interval',
- value: numericToInterval(Number((e.target as HTMLInputElement).value)),
- })
- )
- }
- aria-label={i18n.translate('xpack.lens.indexPattern.dateHistogram.interval', {
- defaultMessage: 'Time intervals',
- })}
- />
+ <>
+
+
+ {
+ setInterval({
+ ...interval,
+ value: e.target.value,
+ });
+ }}
+ />
+
+
+ {
+ setInterval({
+ ...interval,
+ unit: e.target.value,
+ });
+ }}
+ isInvalid={!isValid}
+ options={[
+ {
+ value: 'ms',
+ text: i18n.translate(
+ 'xpack.lens.indexPattern.dateHistogram.milliseconds',
+ {
+ defaultMessage: 'milliseconds',
+ }
+ ),
+ },
+ {
+ value: 's',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.seconds', {
+ defaultMessage: 'seconds',
+ }),
+ },
+ {
+ value: 'm',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.minutes', {
+ defaultMessage: 'minutes',
+ }),
+ },
+ {
+ value: 'h',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.hours', {
+ defaultMessage: 'hours',
+ }),
+ },
+ {
+ value: 'd',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.days', {
+ defaultMessage: 'days',
+ }),
+ },
+ {
+ value: 'w',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.week', {
+ defaultMessage: 'week',
+ }),
+ },
+ {
+ value: 'M',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.month', {
+ defaultMessage: 'month',
+ }),
+ },
+ // Quarterly intervals appear to be unsupported by esaggs
+ {
+ value: 'y',
+ text: i18n.translate('xpack.lens.indexPattern.dateHistogram.year', {
+ defaultMessage: 'year',
+ }),
+ },
+ ]}
+ />
+
+
+ {!isValid && (
+ <>
+
+
+ {i18n.translate('xpack.lens.indexPattern.invalidInterval', {
+ defaultMessage: 'Invalid interval value',
+ })}
+
+ >
+ )}
+ >
)}
)}
@@ -243,3 +304,26 @@ export const dateHistogramOperation: OperationDefinition {
columnId: string;
layerId: string;
uiSettings: UiSettingsClientContract;
- storage: Storage;
+ storage: IStorageWrapper;
savedObjectsClient: SavedObjectsClientContract;
http: HttpServiceBase;
+ dateRange: DateRange;
}
interface BaseOperationDefinitionProps {
@@ -140,26 +142,14 @@ interface FieldBasedOperationDefinition
onFieldChange: (oldColumn: C, indexPattern: IndexPattern, field: IndexPatternField) => C;
}
-interface DocumentBasedOperationDefinition
- extends BaseOperationDefinitionProps {
- /**
- * Returns the meta data of the operation if applied to documents of the given index pattern.
- * Undefined if the operation is not applicable to the index pattern.
- */
- getPossibleOperationForDocument: (indexPattern: IndexPattern) => OperationMetadata | undefined;
- buildColumn: (arg: BaseBuildColumnArgs) => C;
-}
-
/**
* Shape of an operation definition. If the type parameter of the definition
* indicates a field based column, `getPossibleOperationForField` has to be
* specified, otherwise `getPossibleOperationForDocument` has to be defined.
*/
-export type OperationDefinition<
- C extends BaseIndexPatternColumn
-> = C extends FieldBasedIndexPatternColumn
- ? FieldBasedOperationDefinition
- : DocumentBasedOperationDefinition;
+export type OperationDefinition = FieldBasedOperationDefinition<
+ C
+>;
// Helper to to infer the column type out of the operation definition.
// This is done to avoid it to have to list out the column types along with
@@ -185,9 +175,7 @@ export type OperationType = (typeof internalOperationDefinitions)[number]['type'
* This is an operation definition of an unspecified column out of all possible
* column types. It
*/
-export type GenericOperationDefinition =
- | FieldBasedOperationDefinition
- | DocumentBasedOperationDefinition;
+export type GenericOperationDefinition = FieldBasedOperationDefinition;
/**
* List of all available operation definitions
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx
index df5bfac6d03a4..e57745c11fc69 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/terms.test.tsx
@@ -12,7 +12,7 @@ import {
SavedObjectsClientContract,
HttpServiceBase,
} from 'src/core/public';
-import { Storage } from 'ui/storage';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { createMockedIndexPattern } from '../../mocks';
import { TermsIndexPatternColumn } from './terms';
import { termsOperation } from '.';
@@ -20,6 +20,14 @@ import { IndexPatternPrivateState } from '../../types';
jest.mock('ui/new_platform');
+const defaultProps = {
+ storage: {} as IStorageWrapper,
+ uiSettings: {} as UiSettingsClientContract,
+ savedObjectsClient: {} as SavedObjectsClientContract,
+ dateRange: { fromDate: 'now-1d', toDate: 'now' },
+ http: {} as HttpServiceBase,
+};
+
describe('terms', () => {
let state: IndexPatternPrivateState;
const InlineOptions = termsOperation.paramEditor!;
@@ -40,8 +48,6 @@ describe('terms', () => {
label: 'Top value of category',
dataType: 'string',
isBucketed: true,
-
- // Private
operationType: 'terms',
params: {
orderBy: { type: 'alphabetical' },
@@ -54,8 +60,7 @@ describe('terms', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
},
@@ -206,8 +211,7 @@ describe('terms', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
},
@@ -247,8 +251,7 @@ describe('terms', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
});
@@ -324,15 +327,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
@@ -350,15 +350,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
@@ -398,15 +395,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
@@ -422,15 +416,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
@@ -467,15 +458,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
@@ -486,15 +474,12 @@ describe('terms', () => {
const setStateSpy = jest.fn();
const instance = shallow(
);
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts
index 82a93ee2a05e1..0161b93effc52 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.test.ts
@@ -8,6 +8,7 @@ import { getOperationTypesForField, getAvailableOperationsByMetadata, buildColum
import { AvgIndexPatternColumn, MinIndexPatternColumn } from './definitions/metrics';
import { CountIndexPatternColumn } from './definitions/count';
import { IndexPatternPrivateState } from '../types';
+import { documentField } from '../document_field';
jest.mock('ui/new_platform');
jest.mock('../loader');
@@ -179,6 +180,7 @@ describe('getOperationTypesForField', () => {
columns: state.layers.first.columns,
suggestedPriority: 0,
op: 'count',
+ field: documentField,
});
expect(column.operationType).toEqual('count');
});
@@ -216,7 +218,7 @@ describe('getOperationTypesForField', () => {
indexPattern: expectedIndexPatterns[1],
columns: state.layers.first.columns,
suggestedPriority: 0,
- asDocumentOperation: true,
+ field: documentField,
}) as CountIndexPatternColumn;
expect(column.operationType).toEqual('count');
});
@@ -296,10 +298,6 @@ describe('getOperationTypesForField', () => {
"operationType": "sum",
"type": "field",
},
- Object {
- "operationType": "count",
- "type": "document",
- },
],
},
]
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts
index f2ab68612868c..ecd0942eef7b4 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/operations.ts
@@ -14,6 +14,7 @@ import {
IndexPatternColumn,
} from './definitions';
import { IndexPattern, IndexPatternField } from '../types';
+import { documentField } from '../document_field';
/**
* Returns all available operation types as a list at runtime.
@@ -68,9 +69,21 @@ export function getOperationTypesForField(field: IndexPatternField) {
.map(({ type }) => type);
}
-type OperationFieldTuple =
- | { type: 'field'; operationType: OperationType; field: string }
- | { type: 'document'; operationType: OperationType };
+let documentOperations: Set;
+
+export function isDocumentOperation(type: string) {
+ // This can't be done at the root level, because it breaks tests, thanks to mocking oddities
+ // so we do it here, and cache the result.
+ documentOperations =
+ documentOperations || new Set(getOperationTypesForField(documentField) as string[]);
+ return documentOperations.has(type);
+}
+
+interface OperationFieldTuple {
+ type: 'field';
+ operationType: OperationType;
+ field: string;
+}
/**
* Returns all possible operations (matches between operations and fields of the index
@@ -119,11 +132,6 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) {
};
operationDefinitions.forEach(operationDefinition => {
- addToMap(
- { type: 'document', operationType: operationDefinition.type },
- getPossibleOperationForDocument(operationDefinition, indexPattern)
- );
-
indexPattern.fields.forEach(field => {
addToMap(
{
@@ -139,15 +147,6 @@ export function getAvailableOperationsByMetadata(indexPattern: IndexPattern) {
return Object.values(operationByMetadata);
}
-function getPossibleOperationForDocument(
- operationDefinition: GenericOperationDefinition,
- indexPattern: IndexPattern
-): OperationMetadata | undefined {
- return 'getPossibleOperationForDocument' in operationDefinition
- ? operationDefinition.getPossibleOperationForDocument(indexPattern)
- : undefined;
-}
-
function getPossibleOperationForField(
operationDefinition: GenericOperationDefinition,
field: IndexPatternField
@@ -203,24 +202,18 @@ export function buildColumn({
layerId,
indexPattern,
suggestedPriority,
- asDocumentOperation,
}: {
op?: OperationType;
columns: Partial>;
suggestedPriority: DimensionPriority | undefined;
layerId: string;
indexPattern: IndexPattern;
- field?: IndexPatternField;
- asDocumentOperation?: boolean;
+ field: IndexPatternField;
}): IndexPatternColumn {
let operationDefinition: GenericOperationDefinition | undefined;
if (op) {
operationDefinition = operationDefinitionMap[op];
- } else if (asDocumentOperation) {
- operationDefinition = getDefinition(definition =>
- Boolean(getPossibleOperationForDocument(definition, indexPattern))
- );
} else if (field) {
operationDefinition = getDefinition(definition =>
Boolean(getPossibleOperationForField(definition, field))
@@ -238,19 +231,14 @@ export function buildColumn({
indexPattern,
};
- // check for the operation for field getter to determine whether
- // this is a field based operation type
- if ('getPossibleOperationForField' in operationDefinition) {
- if (!field) {
- throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`);
- }
- return operationDefinition.buildColumn({
- ...baseOptions,
- field,
- });
- } else {
- return operationDefinition.buildColumn(baseOptions);
+ if (!field) {
+ throw new Error(`Invariant error: ${operationDefinition.type} operation requires field`);
}
+
+ return operationDefinition.buildColumn({
+ ...baseOptions,
+ field,
+ });
}
export { operationDefinitionMap } from './definitions';
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
index 9c4835437546e..4dde289259c41 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
@@ -8,8 +8,8 @@ import { Registry } from '@kbn/interpreter/target/common';
import { CoreSetup } from 'src/core/public';
// The following dependencies on ui/* and src/legacy/core_plugins must be mocked when testing
import chrome, { Chrome } from 'ui/chrome';
-import { Storage } from 'ui/storage';
import { npSetup, npStart } from 'ui/new_platform';
+import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public';
import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries';
import { getIndexPatternDatasource } from './indexpattern';
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts
index 9dbf671dffa90..ad3d3f3816262 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/state_helpers.test.ts
@@ -54,8 +54,7 @@ describe('state_helpers', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
},
@@ -102,8 +101,7 @@ describe('state_helpers', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
},
@@ -328,8 +326,7 @@ describe('state_helpers', () => {
label: 'Count',
dataType: 'number',
isBucketed: false,
-
- // Private
+ sourceField: 'Records',
operationType: 'count',
},
},
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts
index 3bf3be41f4c89..9ed5083633314 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts
@@ -20,25 +20,27 @@ export interface IndexPattern {
>;
}
+export type AggregationRestrictions = Partial<
+ Record<
+ string,
+ {
+ agg: string;
+ interval?: number;
+ fixed_interval?: string;
+ calendar_interval?: string;
+ delay?: string;
+ time_zone?: string;
+ }
+ >
+>;
+
export interface IndexPatternField {
name: string;
type: string;
esTypes?: string[];
aggregatable: boolean;
searchable: boolean;
- aggregationRestrictions?: Partial<
- Record<
- string,
- {
- agg: string;
- interval?: number;
- fixed_interval?: string;
- calendar_interval?: string;
- delay?: string;
- time_zone?: string;
- }
- >
- >;
+ aggregationRestrictions?: AggregationRestrictions;
}
export interface IndexPatternLayer {
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts
index aab991a27856a..fadee01e695d5 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/utils.ts
@@ -10,6 +10,15 @@ import {
BaseIndexPatternColumn,
FieldBasedIndexPatternColumn,
} from './operations/definitions/column_types';
+import { DataType } from '../types';
+
+/**
+ * Normalizes the specified operation type. (e.g. document operations
+ * produce 'number')
+ */
+export function normalizeOperationDataType(type: DataType) {
+ return type === 'document' ? 'number' : type;
+}
export function hasField(column: BaseIndexPatternColumn): column is FieldBasedIndexPatternColumn {
return 'sourceField' in column;
diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts
index 6e860c594f4a5..15d36a7c31169 100644
--- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts
+++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts
@@ -11,9 +11,9 @@ import {
trackUiEvent,
trackSuggestionEvent,
} from './factory';
-import { Storage } from 'src/legacy/core_plugins/data/public/types';
import { coreMock } from 'src/core/public/mocks';
import { HttpServiceBase } from 'kibana/public';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
jest.useFakeTimers();
@@ -30,7 +30,7 @@ const createMockStorage = () => {
};
describe('Lens UI telemetry', () => {
- let storage: jest.Mocked;
+ let storage: jest.Mocked;
let http: jest.Mocked;
let dateSpy: jest.SpyInstance;
diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts
index 673910deae339..1a8ec604eda54 100644
--- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts
+++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts
@@ -7,7 +7,7 @@
import moment from 'moment';
import { HttpServiceBase } from 'src/core/public';
-import { Storage } from 'src/legacy/core_plugins/data/public/types';
+import { IStorageWrapper } from 'src/plugins/kibana_utils/public';
import { BASE_API_URL } from '../../common';
const STORAGE_KEY = 'lens-ui-telemetry';
@@ -43,11 +43,11 @@ export class LensReportManager {
private events: Record> = {};
private suggestionEvents: Record> = {};
- private storage: Storage;
+ private storage: IStorageWrapper;
private http: HttpServiceBase;
private timer: ReturnType;
- constructor({ storage, http }: { storage: Storage; http: HttpServiceBase }) {
+ constructor({ storage, http }: { storage: IStorageWrapper; http: HttpServiceBase }) {
this.storage = storage;
this.http = http;
diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts
index 770d8594172f1..c9bfadbefaf5f 100644
--- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts
+++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.test.ts
@@ -80,7 +80,9 @@ describe('metric_suggestions', () => {
layerId: 'l1',
changeType: 'unchanged',
},
- ] as TableSuggestion[]).map(table => expect(getSuggestions({ table })).toEqual([]))
+ ] as TableSuggestion[]).map(table =>
+ expect(getSuggestions({ table, keptLayerIds: ['l1'] })).toEqual([])
+ )
);
});
@@ -92,6 +94,7 @@ describe('metric_suggestions', () => {
layerId: 'l1',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -107,4 +110,32 @@ describe('metric_suggestions', () => {
}
`);
});
+
+ test('does not suggest for multiple layers', () => {
+ const suggestions = getSuggestions({
+ table: {
+ columns: [numCol('bytes')],
+ isMultiRow: false,
+ layerId: 'l1',
+ changeType: 'unchanged',
+ },
+ keptLayerIds: ['l1', 'l2'],
+ });
+
+ expect(suggestions).toHaveLength(0);
+ });
+
+ test('does not suggest when the suggestion keeps a different layer', () => {
+ const suggestions = getSuggestions({
+ table: {
+ columns: [numCol('bytes')],
+ isMultiRow: false,
+ layerId: 'newer',
+ changeType: 'initial',
+ },
+ keptLayerIds: ['older'],
+ });
+
+ expect(suggestions).toHaveLength(0);
+ });
});
diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts
index f2b655a288270..23df3f55f2777 100644
--- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts
+++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_suggestions.ts
@@ -16,11 +16,14 @@ import chartMetricSVG from '../assets/chart_metric.svg';
export function getSuggestions({
table,
state,
+ keptLayerIds,
}: SuggestionRequest): Array> {
// We only render metric charts for single-row queries. We require a single, numeric column.
if (
table.isMultiRow ||
- table.columns.length > 1 ||
+ keptLayerIds.length > 1 ||
+ (keptLayerIds.length && table.layerId !== keptLayerIds[0]) ||
+ table.columns.length !== 1 ||
table.columns[0].operation.dataType !== 'number'
) {
return [];
diff --git a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts
index 8c0b9cb25300d..562d0f0ef6135 100644
--- a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts
+++ b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts
@@ -20,7 +20,7 @@ visualizations.types.registerAlias({
}),
},
title: i18n.translate('xpack.lens.visTypeAlias.title', {
- defaultMessage: 'Lens Visualizations',
+ defaultMessage: 'Lens',
}),
description: i18n.translate('xpack.lens.visTypeAlias.description', {
defaultMessage: `Lens is a simpler way to create basic visualizations`,
diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts
index 1efa123f4f0a3..2b97f193a563c 100644
--- a/x-pack/legacy/plugins/lens/public/types.ts
+++ b/x-pack/legacy/plugins/lens/public/types.ts
@@ -13,19 +13,24 @@ import { SavedQuery } from 'src/legacy/core_plugins/data/public';
import { KibanaDatatable } from '../../../../../src/legacy/core_plugins/interpreter/common';
import { DragContextState } from './drag_drop';
import { Document } from './persistence';
+import { DateRange } from '../common';
// eslint-disable-next-line
export interface EditorFrameOptions {}
export type ErrorCallback = (e: { message: string }) => void;
+export interface PublicAPIProps {
+ state: T;
+ setState: StateSetter;
+ layerId: string;
+ dateRange: DateRange;
+}
+
export interface EditorFrameProps {
onError: ErrorCallback;
doc?: Document;
- dateRange: {
- fromDate: string;
- toDate: string;
- };
+ dateRange: DateRange;
query: Query;
filters: Filter[];
savedQuery?: SavedQuery;
@@ -99,12 +104,14 @@ export interface TableSuggestion {
* * `unchanged` means the table is the same in the currently active configuration
* * `reduced` means the table is a reduced version of the currently active table (some columns dropped, but not all of them)
* * `extended` means the table is an extended version of the currently active table (added one or multiple additional columns)
+ * * `layers` means the change is a change to the layer structure, not to the table
*/
-export type TableChangeType = 'initial' | 'unchanged' | 'reduced' | 'extended';
+export type TableChangeType = 'initial' | 'unchanged' | 'reduced' | 'extended' | 'layers';
export interface DatasourceSuggestion {
state: T;
table: TableSuggestion;
+ keptLayerIds: string[];
}
export interface DatasourceMetaData {
@@ -138,7 +145,7 @@ export interface Datasource {
getDatasourceSuggestionsForField: (state: T, field: unknown) => Array>;
getDatasourceSuggestionsFromCurrentState: (state: T) => Array>;
- getPublicAPI: (state: T, setState: StateSetter, layerId: string) => DatasourcePublicAPI;
+ getPublicAPI: (props: PublicAPIProps) => DatasourcePublicAPI;
}
/**
@@ -171,7 +178,7 @@ export interface DatasourceDataPanelProps {
setState: StateSetter;
core: Pick;
query: Query;
- dateRange: FramePublicAPI['dateRange'];
+ dateRange: DateRange;
filters: Filter[];
}
@@ -200,7 +207,7 @@ export interface DatasourceLayerPanelProps {
layerId: string;
}
-export type DataType = 'string' | 'number' | 'date' | 'boolean' | 'ip';
+export type DataType = 'document' | 'string' | 'number' | 'date' | 'boolean' | 'ip';
// An operation represents a column in a table, not any information
// about how the column was created such as whether it is a sum or average.
@@ -257,6 +264,10 @@ export interface SuggestionRequest {
* State is only passed if the visualization is active.
*/
state?: T;
+ /**
+ * The visualization needs to know which table is being suggested
+ */
+ keptLayerIds: string[];
}
/**
@@ -296,10 +307,7 @@ export interface VisualizationSuggestion {
export interface FramePublicAPI {
datasourceLayers: Record;
- dateRange: {
- fromDate: string;
- toDate: string;
- };
+ dateRange: DateRange;
query: Query;
filters: Filter[];
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap
index 473ee16ffe0c3..5089a3b8a3a22 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap
@@ -150,12 +150,16 @@ exports[`xy_expression XYChart component it renders area 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -164,7 +168,7 @@ exports[`xy_expression XYChart component it renders area 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={Array []}
@@ -332,12 +336,16 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -346,7 +354,7 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={Array []}
@@ -514,12 +522,16 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -528,7 +540,7 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={Array []}
@@ -696,12 +708,16 @@ exports[`xy_expression XYChart component it renders line 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -710,7 +726,7 @@ exports[`xy_expression XYChart component it renders line 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={Array []}
@@ -878,12 +894,16 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -892,7 +912,7 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={
@@ -1064,12 +1084,16 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -1078,7 +1102,7 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={
@@ -1250,12 +1274,16 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
Object {
"Label A": 1,
"Label B": 2,
+ "Label D": "Foo",
"c": "I",
+ "d": "Foo",
},
Object {
"Label A": 1,
"Label B": 5,
+ "Label D": "Bar",
"c": "J",
+ "d": "Bar",
},
]
}
@@ -1264,7 +1292,7 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
key="0"
splitSeriesAccessors={
Array [
- "Label D",
+ "d",
]
}
stackAccessors={
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx
index 58c321f34d094..31f1870ee54b4 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.test.tsx
@@ -26,8 +26,9 @@ function sampleArgs() {
},
{ id: 'b', name: 'b', formatHint: { id: 'number', params: { pattern: '000,0' } } },
{ id: 'c', name: 'c', formatHint: { id: 'string' } },
+ { id: 'd', name: 'ColD', formatHint: { id: 'string' } },
],
- rows: [{ a: 1, b: 2, c: 'I' }, { a: 1, b: 5, c: 'J' }],
+ rows: [{ a: 1, b: 2, c: 'I', d: 'Foo' }, { a: 1, b: 5, c: 'J', d: 'Bar' }],
},
},
};
@@ -338,8 +339,8 @@ describe('xy_expression', () => {
);
expect(component.find(LineSeries).prop('data')).toEqual([
- { 'Label A': 1, 'Label B': 2, c: 'I' },
- { 'Label A': 1, 'Label B': 5, c: 'J' },
+ { 'Label A': 1, 'Label B': 2, c: 'I', 'Label D': 'Foo', d: 'Foo' },
+ { 'Label A': 1, 'Label B': 5, c: 'J', 'Label D': 'Bar', d: 'Bar' },
]);
});
@@ -407,7 +408,6 @@ describe('xy_expression', () => {
/>
);
- expect(getFormatSpy).toHaveBeenCalledTimes(2);
expect(getFormatSpy).toHaveBeenCalledWith({ id: 'number' });
});
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx
index f55cd8f99a213..c4cc81239a485 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx
@@ -19,7 +19,7 @@ import {
Position,
} from '@elastic/charts';
import { I18nProvider } from '@kbn/i18n/react';
-import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types';
+import { ExpressionFunction, KibanaDatatable } from 'src/legacy/core_plugins/interpreter/types';
import { IInterpreterRenderHandlers } from 'src/legacy/core_plugins/expressions/public';
import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -243,33 +243,24 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr
}
const columnToLabelMap = columnToLabel ? JSON.parse(columnToLabel) : {};
-
- const rows = data.tables[layerId].rows.map(row => {
- const newRow: typeof row = {};
-
- // Remap data to { 'Count of records': 5 }
- Object.keys(row).forEach(key => {
- if (columnToLabelMap[key]) {
- newRow[columnToLabelMap[key]] = row[key];
- } else {
- newRow[key] = row[key];
- }
- });
- return newRow;
- });
-
const splitAccessorLabel = columnToLabelMap[splitAccessor];
const yAccessors = accessors.map(accessor => columnToLabelMap[accessor] || accessor);
const idForLegend = splitAccessorLabel || yAccessors;
+ const sanitized = sanitizeRows({
+ splitAccessor,
+ formatFactory,
+ columnToLabelMap,
+ table: data.tables[layerId],
+ });
const seriesProps = {
key: index,
- splitSeriesAccessors: [splitAccessorLabel || splitAccessor],
+ splitSeriesAccessors: sanitized.splitAccessor ? [sanitized.splitAccessor] : [],
stackAccessors: seriesType.includes('stacked') ? [xAccessor] : [],
id: getSpecId(idForLegend),
xAccessor,
yAccessors,
- data: rows,
+ data: sanitized.rows,
xScaleType,
yScaleType,
enableHistogramMode: isHistogram && (seriesType.includes('stacked') || !splitAccessor),
@@ -291,3 +282,40 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr
);
}
+
+/**
+ * Renames the columns to match the user-configured accessors in
+ * columnToLabelMap. If a splitAccessor is provided, formats the
+ * values in that column.
+ */
+function sanitizeRows({
+ splitAccessor,
+ table,
+ formatFactory,
+ columnToLabelMap,
+}: {
+ splitAccessor?: string;
+ table: KibanaDatatable;
+ formatFactory: FormatFactory;
+ columnToLabelMap: Record;
+}) {
+ const column = table.columns.find(c => c.id === splitAccessor);
+ const formatter = formatFactory(column && column.formatHint);
+
+ return {
+ splitAccessor: column && column.id,
+ rows: table.rows.map(r => {
+ const newRow: typeof r = {};
+
+ if (column) {
+ newRow[column.id] = formatter.convert(r[column.id]);
+ }
+
+ Object.keys(r).forEach(key => {
+ const newKey = columnToLabelMap[key] || key;
+ newRow[newKey] = r[key];
+ });
+ return newRow;
+ }),
+ };
+}
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts
index a205fe433106a..04ff720309d62 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.test.ts
@@ -100,7 +100,9 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
- ] as TableSuggestion[]).map(table => expect(getSuggestions({ table })).toEqual([]))
+ ] as TableSuggestion[]).map(table =>
+ expect(getSuggestions({ table, keptLayerIds: [] })).toEqual([])
+ )
);
});
@@ -113,6 +115,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -144,6 +147,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(suggestions).toHaveLength(0);
@@ -157,6 +161,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -184,6 +189,7 @@ describe('xy_suggestions', () => {
changeType: 'unchanged',
label: 'Datasource title',
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -211,6 +217,7 @@ describe('xy_suggestions', () => {
},
],
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -225,6 +232,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'reduced',
},
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -254,6 +262,7 @@ describe('xy_suggestions', () => {
changeType: 'unchanged',
},
state: currentState,
+ keptLayerIds: ['first'],
});
expect(suggestions).toHaveLength(1);
@@ -288,6 +297,7 @@ describe('xy_suggestions', () => {
changeType: 'unchanged',
},
state: currentState,
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -328,6 +338,7 @@ describe('xy_suggestions', () => {
changeType: 'unchanged',
},
state: currentState,
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -358,6 +369,7 @@ describe('xy_suggestions', () => {
changeType: 'unchanged',
},
state: currentState,
+ keptLayerIds: [],
});
const suggestion = suggestions[suggestions.length - 1];
@@ -392,6 +404,7 @@ describe('xy_suggestions', () => {
changeType: 'extended',
},
state: currentState,
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -430,6 +443,7 @@ describe('xy_suggestions', () => {
changeType: 'extended',
},
state: currentState,
+ keptLayerIds: [],
});
expect(rest).toHaveLength(0);
@@ -454,6 +468,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
@@ -490,6 +505,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
@@ -525,6 +541,7 @@ describe('xy_suggestions', () => {
layerId: 'first',
changeType: 'unchanged',
},
+ keptLayerIds: [],
});
expect(suggestionSubset(suggestion)).toMatchInlineSnapshot(`
diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts
index 7c7e9caddd31b..33181b7f3a467 100644
--- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts
+++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_suggestions.ts
@@ -19,11 +19,12 @@ import { generateId } from '../id_generator';
import { getIconForSeries } from './state_helpers';
const columnSortOrder = {
- date: 0,
- string: 1,
- ip: 2,
- boolean: 3,
- number: 4,
+ document: 0,
+ date: 1,
+ string: 2,
+ ip: 3,
+ boolean: 4,
+ number: 5,
};
/**
@@ -34,6 +35,7 @@ const columnSortOrder = {
export function getSuggestions({
table,
state,
+ keptLayerIds,
}: SuggestionRequest): Array> {
if (
// We only render line charts for multi-row queries. We require at least
@@ -47,7 +49,7 @@ export function getSuggestions({
return [];
}
- const suggestions = getSuggestionForColumns(table, state);
+ const suggestions = getSuggestionForColumns(table, keptLayerIds, state);
if (suggestions && suggestions instanceof Array) {
return suggestions;
@@ -58,32 +60,35 @@ export function getSuggestions({
function getSuggestionForColumns(
table: TableSuggestion,
+ keptLayerIds: string[],
currentState?: State
): VisualizationSuggestion | Array> | undefined {
const [buckets, values] = partition(table.columns, col => col.operation.isBucketed);
if (buckets.length === 1 || buckets.length === 2) {
const [x, splitBy] = getBucketMappings(table, currentState);
- return getSuggestionsForLayer(
- table.layerId,
- table.changeType,
- x,
- values,
+ return getSuggestionsForLayer({
+ layerId: table.layerId,
+ changeType: table.changeType,
+ xValue: x,
+ yValues: values,
splitBy,
currentState,
- table.label
- );
+ tableLabel: table.label,
+ keptLayerIds,
+ });
} else if (buckets.length === 0) {
const [x, ...yValues] = prioritizeColumns(values);
- return getSuggestionsForLayer(
- table.layerId,
- table.changeType,
- x,
+ return getSuggestionsForLayer({
+ layerId: table.layerId,
+ changeType: table.changeType,
+ xValue: x,
yValues,
- undefined,
+ splitBy: undefined,
currentState,
- table.label
- );
+ tableLabel: table.label,
+ keptLayerIds,
+ });
}
}
@@ -137,15 +142,25 @@ function prioritizeColumns(columns: TableSuggestionColumn[]) {
);
}
-function getSuggestionsForLayer(
- layerId: string,
- changeType: TableChangeType,
- xValue: TableSuggestionColumn,
- yValues: TableSuggestionColumn[],
- splitBy?: TableSuggestionColumn,
- currentState?: State,
- tableLabel?: string
-): VisualizationSuggestion | Array> {
+function getSuggestionsForLayer({
+ layerId,
+ changeType,
+ xValue,
+ yValues,
+ splitBy,
+ currentState,
+ tableLabel,
+ keptLayerIds,
+}: {
+ layerId: string;
+ changeType: TableChangeType;
+ xValue: TableSuggestionColumn;
+ yValues: TableSuggestionColumn[];
+ splitBy?: TableSuggestionColumn;
+ currentState?: State;
+ tableLabel?: string;
+ keptLayerIds: string[];
+}): VisualizationSuggestion | Array> {
const title = getSuggestionTitle(yValues, xValue, tableLabel);
const seriesType: SeriesType = getSeriesType(currentState, layerId, xValue, changeType);
@@ -158,6 +173,7 @@ function getSuggestionsForLayer(
splitBy,
changeType,
xValue,
+ keptLayerIds,
};
const isSameState = currentState && changeType === 'unchanged';
@@ -323,6 +339,7 @@ function buildSuggestion({
splitBy,
changeType,
xValue,
+ keptLayerIds,
}: {
currentState: XYState | undefined;
seriesType: SeriesType;
@@ -332,6 +349,7 @@ function buildSuggestion({
splitBy: TableSuggestionColumn | undefined;
layerId: string;
changeType: TableChangeType;
+ keptLayerIds: string[];
}) {
const newLayer = {
...(getExistingLayer(currentState, layerId) || {}),
@@ -342,13 +360,16 @@ function buildSuggestion({
accessors: yValues.map(col => col.columnId),
};
+ const keptLayers = currentState
+ ? currentState.layers.filter(
+ layer => layer.layerId !== layerId && keptLayerIds.includes(layer.layerId)
+ )
+ : [];
+
const state: State = {
legend: currentState ? currentState.legend : { isVisible: true, position: Position.Right },
preferredSeriesType: seriesType,
- layers: [
- ...(currentState ? currentState.layers.filter(layer => layer.layerId !== layerId) : []),
- newLayer,
- ],
+ layers: [...keptLayers, newLayer],
};
return {
diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap
index 41b5d5a70ae67..9d5e91c2d5f16 100644
--- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap
+++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.js.snap
@@ -330,7 +330,11 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
as="div"
autoFocus={true}
disabled={false}
- lockProps={Object {}}
+ lockProps={
+ Object {
+ "style": undefined,
+ }
+ }
noFocusGuards={false}
persistentFocus={false}
returnFocus={true}
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
index d8f957bd38199..cef2a70a6d03d 100644
--- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js
+++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
@@ -7,6 +7,7 @@
import _ from 'lodash';
import chrome from 'ui/chrome';
import 'ui/directives/listen';
+import 'ui/directives/storage';
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@@ -54,6 +55,7 @@ import {
} from '../../common/constants';
import { FilterStateStore } from '@kbn/es-query';
import { start as data } from '../../../../../../src/legacy/core_plugins/data/public/legacy';
+import { npStart } from 'ui/new_platform';
const { savedQueryService } = data.search.services;
@@ -62,7 +64,7 @@ const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root';
const app = uiModules.get(MAP_APP_PATH, []);
app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppState, globalState) => {
-
+ const { filterManager } = npStart.plugins.data.query;
const savedMap = $route.current.locals.map;
let unsubscribe;
let initialLayerListConfig;
@@ -98,7 +100,7 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta
$scope.$evalAsync(() => {
// appState
$state.query = $scope.query;
- $state.filters = data.filter.filterManager.getAppFilters();
+ $state.filters = filterManager.getAppFilters();
$state.save();
// globalState
@@ -107,7 +109,7 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta
pause: $scope.refreshConfig.isPaused,
value: $scope.refreshConfig.interval,
};
- globalState.filters = data.filter.filterManager.getGlobalFilters();
+ globalState.filters = filterManager.getGlobalFilters();
globalState.save();
});
}
@@ -198,8 +200,8 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta
/* End of Saved Queries */
async function onQueryChange({ filters, query, time }) {
if (filters) {
- await data.filter.filterManager.setFilters(filters); // Maps and merges filters
- $scope.filters = data.filter.filterManager.getFilters();
+ filterManager.setFilters(filters); // Maps and merges filters
+ $scope.filters = filterManager.getFilters();
}
if (query) {
$scope.query = query;
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
index c3d4ff673e3d5..4377fa4725483 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
@@ -6,7 +6,7 @@ exports[`LayerPanel is rendered 1`] = `
Object {
"appName": "maps",
"data": undefined,
- "store": Storage {
+ "storage": Storage {
"clear": [Function],
"get": [Function],
"remove": [Function],
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js
index 28afabc40bd75..78cb8aa827e35 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js
@@ -30,8 +30,8 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
+import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
-import { Storage } from 'ui/storage';
const localStorage = new Storage(window.localStorage);
// This import will eventually become a dependency injected by the fully deangularized NP plugin.
@@ -154,7 +154,7 @@ export class LayerPanel extends React.Component {
= memo(({ score, multiBucketImp
+ />
{severity}
diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/about_panel/welcome_content.js b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/about_panel/welcome_content.js
index 28c616d3e74a6..5e9cd19ab388a 100644
--- a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/about_panel/welcome_content.js
+++ b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/components/about_panel/welcome_content.js
@@ -133,7 +133,7 @@ export function WelcomeContent() {
values={{
githubLink: (
GitHub
diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
index c8295a1e3d8db..fca2508cb5d14 100644
--- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
+++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx
@@ -64,7 +64,7 @@ export const ActionsPanel: FC = ({ indexPattern }) => {
-
+
diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
index 133adea7cff6e..0c356959cd2af 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
+++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js
@@ -136,6 +136,7 @@ export class StartDatafeedModal extends Component {
onClose={this.closeModal}
style={{ width: '850px' }}
maxWidth={false}
+ data-test-subj="mlStartDatafeedModal"
>
@@ -178,6 +179,7 @@ export class StartDatafeedModal extends Component {
= ({ setCurrentStep, isCurrentStep }) =
{isCurrentStep && (
-
+
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/components/additional_section/additional_section.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/components/additional_section/additional_section.tsx
index 4efac7b9ebf1a..ac2d57ea7c8a9 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/components/additional_section/additional_section.tsx
+++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/components/additional_section/additional_section.tsx
@@ -31,7 +31,7 @@ export const AdditionalSection: FC = ({ additionalExpanded, setAdditional
-
+
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
index b9e9df77d35e3..5f93361982ea0 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
+++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx
@@ -213,7 +213,7 @@ export const AdvancedDetectorModal: FC = ({
-
+
= ({
/>
-
+
= ({
-
+
= ({
/>
-
+
= ({
/>
-
+
= ({
/>
-
+
= ({
placeholder={descriptionPlaceholder}
value={descriptionOption}
onChange={e => setDescriptionOption(e.target.value)}
+ data-test-subj="mlAdvancedDetectorDescriptionInput"
/>
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx
index 8b85c658fcadf..d1ee8a6be557a 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx
+++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx
@@ -28,7 +28,11 @@ interface Props {
export const ModalWrapper: FC = ({ onCreateClick, closeModal, saveEnabled, children }) => {
return (
-
+
= ({ onCreateClick, closeModal, saveEnabled
{children}
-
+
-
+
= ({ isActive, onEditJob, onDeleteJob }) =>
defaultMessage: 'Edit',
}
)}
+ data-test-subj="mlAdvancedDetectorEditButton"
/>
@@ -75,6 +77,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) =>
defaultMessage: 'Delete',
}
)}
+ data-test-subj="mlAdvancedDetectorDeleteButton"
/>
@@ -98,14 +101,16 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) =>
{detectors.map((d, i) => (
-
+
{d.detector_description !== undefined ? (
- {d.detector_description}
+
+ {d.detector_description}
+
) : (
- detectorToString(d)
+
)}
{isActive && (
@@ -117,7 +122,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) =>
{d.detector_description !== undefined && (
- {detectorToString(d)}
+
)}
@@ -142,6 +147,7 @@ const NoDetectorsWarning: FC<{ show: boolean }> = ({ show }) => {
defaultMessage: 'No detectors',
})}
iconType="alert"
+ data-test-subj="mlAdvancedNoDetectorsMessage"
>
= ({ validation
);
};
+
+const StringifiedDetector: FC<{ detector: Detector }> = ({ detector }) => {
+ return {detectorToString(detector)}
;
+};
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/metric_selector.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/metric_selector.tsx
index fd597875f2716..5bc38ca934165 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/metric_selector.tsx
+++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/metric_selector.tsx
@@ -35,7 +35,7 @@ export const MetricSelector: FC = ({
-
+
= ({ setIsValid }) => {
-
+
);
diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx
index b60e225cdff4d..a543dbaaf3c5d 100644
--- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx
+++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx
@@ -78,7 +78,7 @@ export const ValidationStep: FC = ({ setCurrentStep, isCurrentStep })
/>
)}
- {isCurrentStep === false && }
+ {isCurrentStep === false && }
);
};
diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js
index 34c4d14bdf21b..3a6a3d3ff3cd6 100644
--- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js
+++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js
@@ -25,7 +25,7 @@ import {
EuiCheckbox,
} from '@elastic/eui';
import { getInstructionSteps } from '../instruction_steps';
-import { Storage } from 'ui/storage';
+import { Storage } from '../../../../../../../../src/plugins/kibana_utils/public';
import { STORAGE_KEY, ELASTICSEARCH_SYSTEM_ID, KIBANA_SYSTEM_ID } from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
diff --git a/x-pack/legacy/plugins/monitoring/public/monitoring.js b/x-pack/legacy/plugins/monitoring/public/monitoring.js
index e0b504780c838..ff47d6ecf7b7f 100644
--- a/x-pack/legacy/plugins/monitoring/public/monitoring.js
+++ b/x-pack/legacy/plugins/monitoring/public/monitoring.js
@@ -7,6 +7,7 @@
import uiRoutes from 'ui/routes';
import chrome from 'ui/chrome';
import 'ui/kbn_top_nav';
+import 'ui/directives/storage';
import 'ui/autoload/all';
import 'plugins/monitoring/filters';
import 'plugins/monitoring/services/clusters';
diff --git a/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_info_button.test.tsx.snap b/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_info_button.test.tsx.snap
index a34d0388a5e3f..54489f18a93d2 100644
--- a/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_info_button.test.tsx.snap
+++ b/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_info_button.test.tsx.snap
@@ -87,7 +87,11 @@ Array [
as="div"
autoFocus={true}
disabled={false}
- lockProps={Object {}}
+ lockProps={
+ Object {
+ "style": undefined,
+ }
+ }
noFocusGuards={false}
persistentFocus={false}
returnFocus={true}
@@ -496,7 +500,11 @@ Array [
as="div"
autoFocus={true}
disabled={false}
- lockProps={Object {}}
+ lockProps={
+ Object {
+ "style": undefined,
+ }
+ }
noFocusGuards={false}
persistentFocus={false}
returnFocus={true}
diff --git a/x-pack/legacy/plugins/security/public/components/authentication_state_page/authentication_state_page.tsx b/x-pack/legacy/plugins/security/public/components/authentication_state_page/authentication_state_page.tsx
index f28696eb84063..2e02275b39611 100644
--- a/x-pack/legacy/plugins/security/public/components/authentication_state_page/authentication_state_page.tsx
+++ b/x-pack/legacy/plugins/security/public/components/authentication_state_page/authentication_state_page.tsx
@@ -5,32 +5,26 @@
*/
import { EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui';
-import React, { Component } from 'react';
+import React from 'react';
interface Props {
title: React.ReactNode;
}
-export class AuthenticationStatePage extends Component {
- public render() {
- return (
-
-
-
- {this.props.children}
-
+export const AuthenticationStatePage: React.SFC
= props => (
+
+);
diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
index e66b37f8cfe5a..94a42166fbb9e 100644
--- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
-import React, { Component } from 'react';
+import React from 'react';
import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model';
import { ChangePassword } from './change_password';
import { PersonalInfo } from './personal_info';
@@ -13,28 +13,20 @@ interface Props {
user: AuthenticatedUser;
}
-export class AccountManagementPage extends Component {
- constructor(props: Props) {
- super(props);
- }
+export const AccountManagementPage: React.SFC = props => (
+
+
+
+
+ {getUserDisplayName(props.user)}
+
- public render() {
- return (
-
-
-
-
- {getUserDisplayName(this.props.user)}
-
+
-
+
-
-
-
-
-
-
- );
- }
-}
+
+
+
+
+);
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/disabled_login_form/disabled_login_form.tsx b/x-pack/legacy/plugins/security/public/views/login/components/disabled_login_form/disabled_login_form.tsx
index 1b28023b6e3e0..012c16c57716e 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/disabled_login_form/disabled_login_form.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/disabled_login_form/disabled_login_form.tsx
@@ -5,24 +5,20 @@
*/
import { EuiPanel, EuiText } from '@elastic/eui';
-import React, { Component, ReactNode } from 'react';
+import React, { ReactNode } from 'react';
interface Props {
title: ReactNode;
message: ReactNode;
}
-export class DisabledLoginForm extends Component {
- public render() {
- return (
-
-
- {this.props.title}
-
-
- {this.props.message}
-
-
- );
- }
-}
+export const DisabledLoginForm: React.SFC = props => (
+
+
+ {props.title}
+
+
+ {props.message}
+
+
+);
diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx
index ffad36c7f9396..5df961dfceeb5 100644
--- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx
@@ -271,7 +271,7 @@ export const DefaultFieldRendererOverflow = React.memo
+ />
)}
diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx
index e52d44173f656..3334447739fc5 100644
--- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.test.tsx
@@ -12,13 +12,13 @@ import { MatrixOverTimeHistogram } from '.';
jest.mock('@elastic/eui', () => {
return {
EuiPanel: (children: JSX.Element) => <>{children}>,
- EuiLoadingContent: () =>
,
+ EuiLoadingContent: () =>
,
};
});
jest.mock('../loader', () => {
return {
- Loader: () =>
,
+ Loader: () =>
,
};
});
@@ -28,13 +28,13 @@ jest.mock('../../lib/settings/use_kibana_ui_setting', () => {
jest.mock('../header_panel', () => {
return {
- HeaderPanel: () =>
,
+ HeaderPanel: () =>
,
};
});
jest.mock('../charts/barchart', () => {
return {
- BarChart: () =>
,
+ BarChart: () =>
,
};
});
diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx
index 15e9c15744e9c..4dcdd282a12a8 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx
@@ -8,6 +8,8 @@ import { EuiButton, EuiCallOut, EuiPopover, EuiPopoverTitle, EuiSpacer } from '@
import React, { useContext, useReducer, useState } from 'react';
import styled from 'styled-components';
import moment from 'moment';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links';
import * as i18n from './translations';
import { JobsFilters, JobSummary, SiemJob } from './types';
import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions';
@@ -216,7 +218,23 @@ export const MlPopover = React.memo(() => {
iconType="alert"
size="s"
>
- {i18n.MODULE_NOT_COMPATIBLE_DESCRIPTION}
+
+
diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts
index 3fc9f3a484b89..442068dd0e193 100644
--- a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts
@@ -39,14 +39,6 @@ export const MODULE_NOT_COMPATIBLE_TITLE = (incompatibleJobCount: number) =>
'{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job} other {jobs}} are currently unavailable',
});
-export const MODULE_NOT_COMPATIBLE_DESCRIPTION = i18n.translate(
- 'xpack.siem.components.mlPopup.moduleNotCompatibleDescription',
- {
- defaultMessage:
- 'You may be missing the required index patterns. Learn more in our documentation.',
- }
-);
-
export const START_JOB_FAILURE = i18n.translate(
'xpack.siem.components.mlPopup.errors.startJobFailureTitle',
{
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx
index 44da23153fe15..6cf56ad6a770f 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx
@@ -241,7 +241,7 @@ describe('TimelinesTable', () => {
expect(
wrapper
- .find('[id="customizablePagination"]')
+ .find('EuiTablePagination EuiPopover')
.first()
.exists()
).toBe(true);
@@ -272,7 +272,7 @@ describe('TimelinesTable', () => {
expect(
wrapper
- .find('[id="customizablePagination"]')
+ .find('EuiTablePagination EuiPopover')
.first()
.exists()
).toBe(false);
@@ -305,7 +305,7 @@ describe('TimelinesTable', () => {
expect(
wrapper
- .find('[id="customizablePagination"]')
+ .find('EuiTablePagination EuiPopover')
.first()
.text()
).toEqual('Rows per page: 123');
diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx
index 2ee93471b62d0..e024a4e68492b 100644
--- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx
@@ -17,6 +17,7 @@ import { TimeRange, Query } from 'src/plugins/data/common/types';
import { SavedQuery } from 'src/legacy/core_plugins/data/public';
import { OnTimeChangeProps } from '@elastic/eui';
+import { npStart } from 'ui/new_platform';
import { start as data } from '../../../../../../../src/legacy/core_plugins/data/public/legacy';
import { inputsActions } from '../../store/inputs';
@@ -39,12 +40,11 @@ import { timelineActions, hostsActions, networkActions } from '../../store/actio
const {
ui: { SearchBar },
- filter,
search,
timefilter,
} = data;
-export const siemFilterManager = filter.filterManager;
+export const siemFilterManager = npStart.plugins.data.query.filterManager;
export const savedQueryService = search.services.savedQueryService;
interface SiemSearchBarRedux {
diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx
index 4859dc0d05556..6359063798ba8 100644
--- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx
@@ -54,7 +54,7 @@ export interface SkeletonRowProps extends CellProps, RowProps {
export const SkeletonRow = React.memo(
({ cellColor, cellCount = 4, cellMargin, rowHeight, rowPadding, style }) => {
const cells = [...Array(cellCount)].map((_, i) => (
- |
+ |
));
return (
diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx
index 8453ec1cfb5d7..ed3eaa6cf1e91 100644
--- a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx
@@ -38,11 +38,11 @@ const from = new Date('2019-06-15T06:00:00.000Z').valueOf();
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
jest.mock('../charts/areachart', () => {
- return { AreaChart: () =>
};
+ return { AreaChart: () =>
};
});
jest.mock('../charts/barchart', () => {
- return { BarChart: () =>
};
+ return { BarChart: () =>
};
});
describe('Stat Items Component', () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
index 66afd78fd0321..caeb29fc6de7d 100644
--- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.tsx
@@ -55,13 +55,18 @@ interface UpdateReduxTime extends OnTimeChangeProps {
timelineId?: string;
}
+interface ReturnUpdateReduxTime {
+ kqlHaveBeenUpdated: boolean;
+}
+
export type DispatchUpdateReduxTime = ({
end,
id,
isQuickSelection,
+ kql,
start,
timelineId,
-}: UpdateReduxTime) => void;
+}: UpdateReduxTime) => ReturnUpdateReduxTime;
interface SuperDatePickerDispatchProps {
setDuration: ({ id, duration }: { id: InputsModelId; duration: number }) => void;
@@ -105,7 +110,7 @@ export const SuperDatePickerComponent = React.memo(
);
const onRefresh = useCallback(
({ start: newStart, end: newEnd }: OnRefreshProps): void => {
- updateReduxTime({
+ const { kqlHaveBeenUpdated } = updateReduxTime({
end: newEnd,
id,
isInvalid: false,
@@ -118,7 +123,10 @@ export const SuperDatePickerComponent = React.memo(
const currentEnd = isQuickSelection
? formatDate(newEnd, { roundUp: true })
: formatDate(newEnd);
- if (!isQuickSelection || (start === currentStart && end === currentEnd)) {
+ if (
+ !kqlHaveBeenUpdated &&
+ (!isQuickSelection || (start === currentStart && end === currentEnd))
+ ) {
refetchQuery(queries);
}
},
@@ -217,9 +225,10 @@ export const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({
end,
id,
isQuickSelection,
+ kql,
start,
timelineId,
-}: UpdateReduxTime): void => {
+}: UpdateReduxTime): ReturnUpdateReduxTime => {
const fromDate = formatDate(start);
let toDate = formatDate(end, { roundUp: true });
if (isQuickSelection) {
@@ -251,6 +260,15 @@ export const dispatchUpdateReduxTime = (dispatch: Dispatch) => ({
})
);
}
+ if (kql) {
+ return {
+ kqlHaveBeenUpdated: kql.refetch(dispatch),
+ };
+ }
+
+ return {
+ kqlHaveBeenUpdated: false,
+ };
};
export const makeMapStateToProps = () => {
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx
index 68acc58972370..80be9fd339f51 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx
@@ -22,7 +22,7 @@ describe('FileDraggable', () => {
eventId="1"
fileName="[fileName]"
filePath="[filePath]"
- >
+ />
);
expect(wrapper.text()).toEqual('[fileName]in[filePath]');
@@ -38,7 +38,7 @@ describe('FileDraggable', () => {
eventId="1"
fileName={undefined}
filePath={undefined}
- >
+ />
);
expect(wrapper.text()).toEqual('');
diff --git a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx
index 4fd24b9cb42a5..7abe14ae745c5 100644
--- a/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/tls/index.tsx
@@ -143,7 +143,7 @@ class TlsComponentQuery extends QueryTemplatePaginated<
const makeMapStateToProps = () => {
const getTlsSelector = networkSelectors.tlsSelector();
const getQuery = inputsSelectors.globalQueryByIdSelector();
- return (state: State, { flowTarget, id = `${ID}-${flowTarget}`, type }: OwnProps) => {
+ return (state: State, { flowTarget, id = ID, type }: OwnProps) => {
const { isInspected } = getQuery(state, id);
return {
...getTlsSelector(state, type, flowTarget),
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx
index dfc19069b17aa..fc49a1fe464ac 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx
@@ -20,6 +20,7 @@ import {
EuiContextMenu,
EuiButtonIcon,
EuiLink,
+ EuiSpacer,
} from '@elastic/eui';
import { SlmPolicy } from '../../../../../../common/types';
@@ -302,49 +303,40 @@ export const PolicyDetails: React.FunctionComponent = ({
maxWidth={550}
>
-
-
-
-
-
-
- {policyName}
-
-
-
- reload()}
- />
-
-
-
-
- {policyDetails && policyDetails.policy && policyDetails.policy.inProgress ? (
-
-
-
-
-
-
-
- ) : null}
-
+
+
+ {policyName}{' '}
+ reload()}
+ />
+
+
+ {policyDetails && policyDetails.policy && policyDetails.policy.inProgress ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : null}
{renderTabs()}
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
index b544666b67159..01fc904906bf1 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
@@ -327,36 +327,32 @@ export const PolicyTable: React.FunctionComponent = ({
) : (
undefined
),
- toolsRight: (
-
-
-
-
-
-
-
-
-
-
-
-
- ),
+ toolsRight: [
+
+
+ ,
+
+
+ ,
+ ],
box: {
incremental: true,
schema: true,
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_table/repository_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_table/repository_table.tsx
index 119733d891ab4..e8df533cb3496 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_table/repository_table.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_table/repository_table.tsx
@@ -9,8 +9,6 @@ import {
EuiBadge,
EuiButton,
EuiButtonIcon,
- EuiFlexGroup,
- EuiFlexItem,
EuiInMemoryTable,
EuiLink,
EuiToolTip,
@@ -235,36 +233,32 @@ export const RepositoryTable: React.FunctionComponent = ({
) : (
undefined
),
- toolsRight: (
-
-
-
-
-
-
-
-
-
-
-
-
- ),
+ toolsRight: [
+
+
+ ,
+
+
+ ,
+ ],
box: {
incremental: true,
schema: true,
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
index e9ce9cbe0ac74..955cade353d2e 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
@@ -18,7 +18,6 @@ import {
EuiTab,
EuiTabs,
EuiText,
- EuiTitle,
} from '@elastic/eui';
import React, { Fragment, useState, useEffect } from 'react';
@@ -256,33 +255,24 @@ export const SnapshotDetails: React.FunctionComponent = ({
maxWidth={550}
>
-
-
-
-
- {snapshotId}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {snapshotId}
+
+
+
+
+
+
+
+
+
{tabs}
-
{content}
{renderFooter()}
diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx
index 4663b73f1cb7e..7461edcff10e9 100644
--- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx
+++ b/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx
@@ -72,7 +72,7 @@ export const CopySavedObjectsToSpaceFlyout = (props: Props) => {
}),
});
});
- }, []);
+ }, [spacesManager, toastNotifications]);
const eligibleSpaces = spaces.filter(space => space.id !== props.activeSpace.id);
const [copyInProgress, setCopyInProgress] = useState(false);
diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
index d8e06c68af733..d75b8abbe592e 100644
--- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
+++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx
@@ -6,7 +6,7 @@
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
-import React, { Component } from 'react';
+import React from 'react';
interface Props {
onCancel: () => void;
@@ -14,39 +14,35 @@ interface Props {
intl: InjectedIntl;
}
-class ConfirmAlterActiveSpaceModalUI extends Component {
- public render() {
- return (
-
-
- }
- defaultFocusedButton={'confirm'}
- cancelButtonText={this.props.intl.formatMessage({
- id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton',
- defaultMessage: 'Cancel',
- })}
- confirmButtonText={this.props.intl.formatMessage({
- id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton',
- defaultMessage: 'Update space',
- })}
- >
-
-
-
-
-
- );
- }
-}
+const ConfirmAlterActiveSpaceModalUI: React.SFC = props => (
+
+
+ }
+ defaultFocusedButton={'confirm'}
+ cancelButtonText={props.intl.formatMessage({
+ id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton',
+ defaultMessage: 'Cancel',
+ })}
+ confirmButtonText={props.intl.formatMessage({
+ id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton',
+ defaultMessage: 'Update space',
+ })}
+ >
+
+
+
+
+
+);
export const ConfirmAlterActiveSpaceModal = injectI18n(ConfirmAlterActiveSpaceModalUI);
diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx
index 45bb79ee749ee..2fa0f8ca605b0 100644
--- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx
+++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_header_nav_button.tsx
@@ -8,22 +8,18 @@ import {
// @ts-ignore
EuiHeaderSectionItemButton,
} from '@elastic/eui';
-import React, { Component } from 'react';
+import React from 'react';
import { ButtonProps } from '../types';
-export class SpacesHeaderNavButton extends Component {
- public render() {
- return (
-
- {this.props.linkIcon}
-
- );
- }
-}
+export const SpacesHeaderNavButton: React.SFC = props => (
+
+ {props.linkIcon}
+
+);
diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
index 4669bd3608e4f..f0e365c27b8e7 100644
--- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
+++ b/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx
@@ -5,7 +5,7 @@
*/
import { EuiAvatar, EuiPopover, PopoverAnchorPosition } from '@elastic/eui';
-import React, { Component, ComponentClass } from 'react';
+import React, { Component } from 'react';
import { Space } from '../../../common/model/space';
import { SpaceAvatar } from '../../components';
import { SpacesManager } from '../../lib/spaces_manager';
@@ -21,7 +21,7 @@ interface Props {
space: Space;
};
anchorPosition: PopoverAnchorPosition;
- buttonClass: ComponentClass;
+ buttonClass: React.ComponentType;
}
interface State {
diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx
index d4602b99c94b2..12e57fdf6c01c 100644
--- a/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx
+++ b/x-pack/legacy/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx
@@ -195,8 +195,8 @@ export class WarningsFlyoutStep extends React.Component<
values={{
true: true ,
false: false ,
- yes: "yes" ,
- on: "on" ,
+ yes: "yes" ,
+ on: "on" ,
one: 1 ,
}}
/>
diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts b/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts
index c2181b783a089..dc31308a1ea34 100644
--- a/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts
+++ b/x-pack/legacy/plugins/upgrade_assistant/public/components/types.ts
@@ -21,6 +21,7 @@ export interface UpgradeAssistantTabProps {
setSelectedTabIndex: (tabIndex: number) => void;
}
+// eslint-disable-next-line react/prefer-stateless-function
export class UpgradeAssistantTabComponent<
T extends UpgradeAssistantTabProps = UpgradeAssistantTabProps,
S = {}
diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx
index 9bb21a3ef5c71..0077292d91a46 100644
--- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx
@@ -36,7 +36,7 @@ describe('EmptyState component', () => {
it(`doesn't render child components when count is falsey`, () => {
const component = mountWithIntl(
- Shouldn't be rendered
+ Shouldn't be rendered
);
expect(component).toMatchSnapshot();
@@ -58,7 +58,7 @@ describe('EmptyState component', () => {
];
const component = mountWithIntl(
- Shouldn't appear...
+ Shouldn't appear...
);
expect(component).toMatchSnapshot();
diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx
index 7896764d35963..8e730dcc29310 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx
+++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_global_help.tsx
@@ -11,7 +11,7 @@ import React from 'react';
export const renderUptimeKibanaGlobalHelp = (docsSiteUrl: string, docLinkVersion: string) => (
-
+
For Uptime specific information
diff --git a/x-pack/package.json b/x-pack/package.json
index 6c6458b7831b4..1daed1951834b 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -184,7 +184,7 @@
"@elastic/ctags-langserver": "^0.1.11",
"@elastic/datemath": "5.0.2",
"@elastic/ems-client": "1.0.5",
- "@elastic/eui": "14.7.0",
+ "@elastic/eui": "14.8.0",
"@elastic/filesaver": "1.1.2",
"@elastic/javascript-typescript-langserver": "^0.3.3",
"@elastic/lsp-extension": "^0.1.2",
diff --git a/x-pack/plugins/reporting/public/components/general_error.tsx b/x-pack/plugins/reporting/public/components/general_error.tsx
index 8a84b3d1741f4..dc65800ecf112 100644
--- a/x-pack/plugins/reporting/public/components/general_error.tsx
+++ b/x-pack/plugins/reporting/public/components/general_error.tsx
@@ -21,7 +21,7 @@ export const getGeneralErrorToast = (errorText: string, err: Error): ToastInput
+ />
),
iconType: undefined,
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 78c128daafff5..39db6ff3791df 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5074,7 +5074,6 @@
"xpack.infra.logs.index.documentTitle": "ログ",
"xpack.infra.logs.index.settingsTabTitle": "設定",
"xpack.infra.logs.index.streamTabTitle": "ストリーム",
- "xpack.infra.logs.logsAnalysisResults.loadingMessage": "結果を読み込み中...",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "機械学習ロボットがデータの収集を開始するまでしばらくお待ちください。",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!",
"xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | ストリーム",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index dc72e6cf4becd..10ffb2b7d731d 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5075,7 +5075,6 @@
"xpack.infra.logs.index.documentTitle": "Logs",
"xpack.infra.logs.index.settingsTabTitle": "设置",
"xpack.infra.logs.index.streamTabTitle": "流式传输",
- "xpack.infra.logs.logsAnalysisResults.loadingMessage": "正在加载结果......",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "请注意,我们的 Machine Learning 机器人若干分钟后才会开始收集数据。",
"xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!",
"xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | 流式传输",
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts
new file mode 100644
index 0000000000000..8dfbee84515b8
--- /dev/null
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts
@@ -0,0 +1,805 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../../ftr_provider_context';
+
+interface Detector {
+ identifier: string;
+ function: string;
+ field?: string;
+ byField?: string;
+ overField?: string;
+ partitionField?: string;
+ excludeFrequent?: string;
+ description?: string;
+}
+
+interface DatafeedConfig {
+ queryDelay?: string;
+ frequency?: string;
+ scrollSize?: string;
+}
+
+interface PickFieldsConfig {
+ detectors: Detector[];
+ influencers: string[];
+ bucketSpan: string;
+ memoryLimit: string;
+ categorizationField?: string;
+ summaryCountField?: string;
+}
+
+// type guards
+// Detector
+const isDetectorWithField = (arg: any): arg is Required> => {
+ return arg.hasOwnProperty('field');
+};
+const isDetectorWithByField = (arg: any): arg is Required> => {
+ return arg.hasOwnProperty('byField');
+};
+const isDetectorWithOverField = (arg: any): arg is Required> => {
+ return arg.hasOwnProperty('overField');
+};
+const isDetectorWithPartitionField = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('partitionField');
+};
+const isDetectorWithExcludeFrequent = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('excludeFrequent');
+};
+const isDetectorWithDescription = (arg: any): arg is Required> => {
+ return arg.hasOwnProperty('description');
+};
+
+// DatafeedConfig
+const isDatafeedConfigWithQueryDelay = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('queryDelay');
+};
+const isDatafeedConfigWithFrequency = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('frequency');
+};
+const isDatafeedConfigWithScrollSize = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('scrollSize');
+};
+
+// PickFieldsConfig
+const isPickFieldsConfigWithCategorizationField = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('categorizationField');
+};
+const isPickFieldsConfigWithSummaryCountField = (
+ arg: any
+): arg is Required> => {
+ return arg.hasOwnProperty('summaryCountField');
+};
+
+// eslint-disable-next-line import/no-default-export
+export default function({ getService }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const ml = getService('ml');
+
+ const defaultValues = {
+ datafeedQuery: `{
+ "bool": {
+ "must": [
+ {
+ "match_all": {}
+ }
+ ]
+ }
+}`,
+ queryDelay: '60s',
+ frequency: '450s',
+ scrollSize: '1000',
+ };
+
+ const testDataList = [
+ {
+ suiteTitle: 'with multiple metric detectors and custom datafeed settings',
+ jobSource: 'ecommerce',
+ jobId: `ec_advanced_1_${Date.now()}`,
+ get jobIdClone(): string {
+ return `${this.jobId}_clone`;
+ },
+ jobDescription:
+ 'Create advanced job from ecommerce dataset with multiple metric detectors and custom datafeed settings',
+ jobGroups: ['automated', 'ecommerce', 'advanced'],
+ get jobGroupsClone(): string[] {
+ return [...this.jobGroups, 'clone'];
+ },
+ pickFieldsConfig: {
+ detectors: [
+ {
+ identifier: 'high_count',
+ function: 'high_count',
+ description: 'high_count detector without split',
+ } as Detector,
+ {
+ identifier: 'mean("products.base_price") by "category.keyword"',
+ function: 'mean',
+ field: 'products.base_price',
+ byField: 'category.keyword',
+ } as Detector,
+ {
+ identifier: 'sum("products.discount_amount") over customer_id',
+ function: 'sum',
+ field: 'products.discount_amount',
+ overField: 'customer_id',
+ } as Detector,
+ {
+ identifier: 'median(total_quantity) partition_field_name=customer_gender',
+ function: 'median',
+ field: 'total_quantity',
+ partitionField: 'customer_gender',
+ } as Detector,
+ {
+ identifier:
+ 'max(total_quantity) by "geoip.continent_name" over customer_id partition_field_name=customer_gender',
+ function: 'max',
+ field: 'total_quantity',
+ byField: 'geoip.continent_name',
+ overField: 'customer_id',
+ partitionField: 'customer_gender',
+ } as Detector,
+ ],
+ influencers: ['customer_id', 'category.keyword', 'geoip.continent_name', 'customer_gender'],
+ bucketSpan: '1h',
+ memoryLimit: '10mb',
+ } as PickFieldsConfig,
+ datafeedConfig: {
+ queryDelay: '55s',
+ frequency: '350s',
+ scrollSize: '999',
+ } as DatafeedConfig,
+ expected: {
+ wizard: {
+ timeField: 'order_date',
+ },
+ row: {
+ recordCount: '4,675',
+ memoryStatus: 'ok',
+ jobState: 'closed',
+ datafeedState: 'stopped',
+ latestTimestamp: '2019-07-12 23:45:36',
+ },
+ counts: {
+ processed_record_count: '4,675',
+ processed_field_count: '32,725',
+ input_bytes: '1.1 MB',
+ input_field_count: '32,725',
+ invalid_date_count: '0',
+ missing_field_count: '0',
+ out_of_order_timestamp_count: '0',
+ empty_bucket_count: '0',
+ sparse_bucket_count: '0',
+ bucket_count: '743',
+ earliest_record_timestamp: '2019-06-12 00:04:19',
+ latest_record_timestamp: '2019-07-12 23:45:36',
+ input_record_count: '4,675',
+ latest_bucket_timestamp: '2019-07-12 23:00:00',
+ },
+ modelSizeStats: {
+ result_type: 'model_size_stats',
+ model_bytes_exceeded: '0',
+ model_bytes_memory_limit: '10485760',
+ total_by_field_count: '37',
+ total_over_field_count: '92',
+ total_partition_field_count: '8',
+ bucket_allocation_failures_count: '0',
+ memory_status: 'ok',
+ timestamp: '2019-07-12 22:00:00',
+ },
+ },
+ },
+ {
+ suiteTitle: 'with categorization detector and default datafeed settings',
+ jobSource: 'ecommerce',
+ jobId: `ec_advanced_2_${Date.now()}`,
+ get jobIdClone(): string {
+ return `${this.jobId}_clone`;
+ },
+ jobDescription:
+ 'Create advanced job from ecommerce dataset with a categorization detector and default datafeed settings',
+ jobGroups: ['automated', 'ecommerce', 'advanced'],
+ get jobGroupsClone(): string[] {
+ return [...this.jobGroups, 'clone'];
+ },
+ pickFieldsConfig: {
+ categorizationField: 'products.product_name',
+ detectors: [
+ {
+ identifier: 'count by mlcategory',
+ function: 'count',
+ byField: 'mlcategory',
+ } as Detector,
+ ],
+ influencers: ['mlcategory'],
+ bucketSpan: '12h',
+ memoryLimit: '100mb',
+ } as PickFieldsConfig,
+ datafeedConfig: {} as DatafeedConfig,
+ expected: {
+ wizard: {
+ timeField: 'order_date',
+ },
+ row: {
+ recordCount: '4,675',
+ memoryStatus: 'ok',
+ jobState: 'closed',
+ datafeedState: 'stopped',
+ latestTimestamp: '2019-07-12 23:45:36',
+ },
+ counts: {
+ processed_record_count: '4,675',
+ processed_field_count: '4,588',
+ input_bytes: '4.4 MB',
+ input_field_count: '6,154',
+ invalid_date_count: '0',
+ missing_field_count: '87',
+ out_of_order_timestamp_count: '0',
+ empty_bucket_count: '0',
+ sparse_bucket_count: '0',
+ bucket_count: '61',
+ earliest_record_timestamp: '2019-06-12 00:04:19',
+ latest_record_timestamp: '2019-07-12 23:45:36',
+ input_record_count: '4,675',
+ latest_bucket_timestamp: '2019-07-12 12:00:00',
+ },
+ modelSizeStats: {
+ result_type: 'model_size_stats',
+ model_bytes_exceeded: '0',
+ model_bytes_memory_limit: '104857600',
+ total_by_field_count: '3,787',
+ total_over_field_count: '0',
+ total_partition_field_count: '2',
+ bucket_allocation_failures_count: '0',
+ memory_status: 'ok',
+ timestamp: '2019-07-12 00:00:00',
+ },
+ },
+ },
+ ];
+
+ describe('advanced job', function() {
+ this.tags(['smoke', 'mlqa']);
+ before(async () => {
+ await esArchiver.load('ml/ecommerce');
+ });
+
+ after(async () => {
+ await esArchiver.unload('ml/ecommerce');
+ await ml.api.cleanMlIndices();
+ });
+
+ for (const testData of testDataList) {
+ describe(`${testData.suiteTitle}`, function() {
+ it('job creation loads the job management page', async () => {
+ await ml.navigation.navigateToMl();
+ await ml.navigation.navigateToJobManagement();
+ });
+
+ it('job creation loads the new job source selection page', async () => {
+ await ml.jobManagement.navigateToNewJobSourceSelection();
+ });
+
+ it('job creation loads the job type selection page', async () => {
+ await ml.jobSourceSelection.selectSource(testData.jobSource);
+ });
+
+ it('job creation loads the advanced job wizard page', async () => {
+ await ml.jobTypeSelection.selectAdvancedJob();
+ });
+
+ it('job creation displays the configure datafeed step', async () => {
+ await ml.jobWizardCommon.assertConfigureDatafeedSectionExists();
+ });
+
+ it('job creation pre-fills the datafeed query editor', async () => {
+ await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists();
+ await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery);
+ });
+
+ it('job creation inputs the query delay', async () => {
+ await ml.jobWizardAdvanced.assertQueryDelayInputExists();
+ await ml.jobWizardAdvanced.assertQueryDelayValue(defaultValues.queryDelay);
+ if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) {
+ await ml.jobWizardAdvanced.setQueryDelay(testData.datafeedConfig.queryDelay);
+ }
+ });
+
+ it('job creation inputs the frequency', async () => {
+ await ml.jobWizardAdvanced.assertFrequencyInputExists();
+ await ml.jobWizardAdvanced.assertFrequencyValue(defaultValues.frequency);
+ if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) {
+ await ml.jobWizardAdvanced.setFrequency(testData.datafeedConfig.frequency);
+ }
+ });
+
+ it('job creation inputs the scroll size', async () => {
+ await ml.jobWizardAdvanced.assertScrollSizeInputExists();
+ await ml.jobWizardAdvanced.assertScrollSizeValue(defaultValues.scrollSize);
+ if (isDatafeedConfigWithScrollSize(testData.datafeedConfig)) {
+ await ml.jobWizardAdvanced.setScrollSize(testData.datafeedConfig.scrollSize);
+ }
+ });
+
+ it('job creation pre-fills the time field', async () => {
+ await ml.jobWizardAdvanced.assertTimeFieldInputExists();
+ await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]);
+ });
+
+ it('job creation displays the pick fields step', async () => {
+ await ml.jobWizardCommon.advanceToPickFieldsSection();
+ });
+
+ it('job creation selects the categorization field', async () => {
+ await ml.jobWizardAdvanced.assertCategorizationFieldInputExists();
+ if (isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)) {
+ await ml.jobWizardAdvanced.selectCategorizationField(
+ testData.pickFieldsConfig.categorizationField
+ );
+ } else {
+ await ml.jobWizardAdvanced.assertCategorizationFieldSelection([]);
+ }
+ });
+
+ it('job creation selects the summary count field', async () => {
+ await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists();
+ if (isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)) {
+ await ml.jobWizardAdvanced.selectSummaryCountField(
+ testData.pickFieldsConfig.summaryCountField
+ );
+ } else {
+ await ml.jobWizardAdvanced.assertSummaryCountFieldSelection([]);
+ }
+ });
+
+ it('job creation adds detectors', async () => {
+ for (const detector of testData.pickFieldsConfig.detectors) {
+ await ml.jobWizardAdvanced.openCreateDetectorModal();
+ await ml.jobWizardAdvanced.assertDetectorFunctionInputExists();
+ await ml.jobWizardAdvanced.assertDetectorFunctionSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorFieldSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorByFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorByFieldSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorOverFieldSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists();
+ await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection([]);
+ await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists();
+ await ml.jobWizardAdvanced.assertDetectorDescriptionValue('');
+
+ await ml.jobWizardAdvanced.selectDetectorFunction(detector.function);
+ if (isDetectorWithField(detector)) {
+ await ml.jobWizardAdvanced.selectDetectorField(detector.field);
+ }
+ if (isDetectorWithByField(detector)) {
+ await ml.jobWizardAdvanced.selectDetectorByField(detector.byField);
+ }
+ if (isDetectorWithOverField(detector)) {
+ await ml.jobWizardAdvanced.selectDetectorOverField(detector.overField);
+ }
+ if (isDetectorWithPartitionField(detector)) {
+ await ml.jobWizardAdvanced.selectDetectorPartitionField(detector.partitionField);
+ }
+ if (isDetectorWithExcludeFrequent(detector)) {
+ await ml.jobWizardAdvanced.selectDetectorExcludeFrequent(detector.excludeFrequent);
+ }
+ if (isDetectorWithDescription(detector)) {
+ await ml.jobWizardAdvanced.setDetectorDescription(detector.description);
+ }
+
+ await ml.jobWizardAdvanced.confirmAddDetectorModal();
+ }
+ });
+
+ it('job creation displays detector entries', async () => {
+ for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) {
+ await ml.jobWizardAdvanced.assertDetectorEntryExists(
+ index,
+ detector.identifier,
+ isDetectorWithDescription(detector) ? detector.description : undefined
+ );
+ }
+ });
+
+ it('job creation inputs the bucket span', async () => {
+ await ml.jobWizardCommon.assertBucketSpanInputExists();
+ await ml.jobWizardCommon.setBucketSpan(testData.pickFieldsConfig.bucketSpan);
+ });
+
+ it('job creation inputs influencers', async () => {
+ await ml.jobWizardCommon.assertInfluencerInputExists();
+ await ml.jobWizardCommon.assertInfluencerSelection([]);
+ for (const influencer of testData.pickFieldsConfig.influencers) {
+ await ml.jobWizardCommon.addInfluencer(influencer);
+ }
+ });
+
+ it('job creation inputs the model memory limit', async () => {
+ await ml.jobWizardCommon.assertModelMemoryLimitInputExists({
+ withAdvancedSection: false,
+ });
+ await ml.jobWizardCommon.setModelMemoryLimit(testData.pickFieldsConfig.memoryLimit, {
+ withAdvancedSection: false,
+ });
+ });
+
+ it('job creation displays the job details step', async () => {
+ await ml.jobWizardCommon.advanceToJobDetailsSection();
+ });
+
+ it('job creation inputs the job id', async () => {
+ await ml.jobWizardCommon.assertJobIdInputExists();
+ await ml.jobWizardCommon.setJobId(testData.jobId);
+ });
+
+ it('job creation inputs the job description', async () => {
+ await ml.jobWizardCommon.assertJobDescriptionInputExists();
+ await ml.jobWizardCommon.setJobDescription(testData.jobDescription);
+ });
+
+ it('job creation inputs job groups', async () => {
+ await ml.jobWizardCommon.assertJobGroupInputExists();
+ for (const jobGroup of testData.jobGroups) {
+ await ml.jobWizardCommon.addJobGroup(jobGroup);
+ }
+ await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups);
+ });
+
+ it('job creation displays the model plot switch', async () => {
+ await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false });
+ });
+
+ it('job creation enables the dedicated index switch', async () => {
+ await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false });
+ await ml.jobWizardCommon.activateDedicatedIndexSwitch({ withAdvancedSection: false });
+ });
+
+ it('job creation displays the validation step', async () => {
+ await ml.jobWizardCommon.advanceToValidationSection();
+ });
+
+ it('job creation displays the summary step', async () => {
+ await ml.jobWizardCommon.advanceToSummarySection();
+ });
+
+ it('job creation creates the job and finishes processing', async () => {
+ await ml.jobWizardCommon.assertCreateJobButtonExists();
+ await ml.jobWizardAdvanced.createJob();
+ await ml.jobManagement.assertStartDatafeedModalExists();
+ await ml.jobManagement.confirmStartDatafeedModal();
+ await ml.jobManagement.waitForJobCompletion(testData.jobId);
+ });
+
+ it('job creation displays the created job in the job list', async () => {
+ await ml.jobTable.refreshJobList();
+ await ml.jobTable.filterWithSearchString(testData.jobId);
+ const rows = await ml.jobTable.parseJobTable();
+ expect(rows.filter(row => row.id === testData.jobId)).to.have.length(1);
+ });
+
+ it('job creation displays details for the created job in the job list', async () => {
+ await ml.jobTable.assertJobRowFields(testData.jobId, {
+ id: testData.jobId,
+ description: testData.jobDescription,
+ jobGroups: [...new Set(testData.jobGroups)].sort(),
+ recordCount: testData.expected.row.recordCount,
+ memoryStatus: testData.expected.row.memoryStatus,
+ jobState: testData.expected.row.jobState,
+ datafeedState: testData.expected.row.datafeedState,
+ latestTimestamp: testData.expected.row.latestTimestamp,
+ });
+
+ await ml.jobTable.assertJobRowDetailsCounts(
+ testData.jobId,
+ {
+ job_id: testData.jobId,
+ processed_record_count: testData.expected.counts.processed_record_count,
+ processed_field_count: testData.expected.counts.processed_field_count,
+ input_bytes: testData.expected.counts.input_bytes,
+ input_field_count: testData.expected.counts.input_field_count,
+ invalid_date_count: testData.expected.counts.invalid_date_count,
+ missing_field_count: testData.expected.counts.missing_field_count,
+ out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count,
+ empty_bucket_count: testData.expected.counts.empty_bucket_count,
+ sparse_bucket_count: testData.expected.counts.sparse_bucket_count,
+ bucket_count: testData.expected.counts.bucket_count,
+ earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp,
+ latest_record_timestamp: testData.expected.counts.latest_record_timestamp,
+ input_record_count: testData.expected.counts.input_record_count,
+ latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp,
+ },
+ {
+ job_id: testData.jobId,
+ result_type: testData.expected.modelSizeStats.result_type,
+ model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded,
+ model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit,
+ total_by_field_count: testData.expected.modelSizeStats.total_by_field_count,
+ total_over_field_count: testData.expected.modelSizeStats.total_over_field_count,
+ total_partition_field_count:
+ testData.expected.modelSizeStats.total_partition_field_count,
+ bucket_allocation_failures_count:
+ testData.expected.modelSizeStats.bucket_allocation_failures_count,
+ memory_status: testData.expected.modelSizeStats.memory_status,
+ timestamp: testData.expected.modelSizeStats.timestamp,
+ }
+ );
+ });
+
+ it('job creation has detector results', async () => {
+ for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) {
+ await ml.api.assertDetectorResultsExist(testData.jobId, i);
+ }
+ });
+
+ it('job cloning clicks the clone action and loads the advanced wizard', async () => {
+ await ml.jobTable.clickCloneJobAction(testData.jobId);
+ await ml.jobTypeSelection.assertAdvancedJobWizardOpen();
+ });
+
+ it('job cloning displays the configure datafeed step', async () => {
+ await ml.jobWizardCommon.assertConfigureDatafeedSectionExists();
+ });
+
+ it('job cloning pre-fills the datafeed query editor', async () => {
+ await ml.jobWizardAdvanced.assertDatafeedQueryEditorExists();
+ await ml.jobWizardAdvanced.assertDatafeedQueryEditorValue(defaultValues.datafeedQuery);
+ });
+
+ it('job cloning pre-fills the query delay', async () => {
+ await ml.jobWizardAdvanced.assertQueryDelayInputExists();
+ if (isDatafeedConfigWithQueryDelay(testData.datafeedConfig)) {
+ await ml.jobWizardAdvanced.assertQueryDelayValue(testData.datafeedConfig.queryDelay);
+ }
+ });
+
+ it('job cloning pre-fills the frequency', async () => {
+ await ml.jobWizardAdvanced.assertFrequencyInputExists();
+ if (isDatafeedConfigWithFrequency(testData.datafeedConfig)) {
+ await ml.jobWizardAdvanced.assertFrequencyValue(testData.datafeedConfig.frequency);
+ }
+ });
+
+ it('job cloning pre-fills the scroll size', async () => {
+ await ml.jobWizardAdvanced.assertScrollSizeInputExists();
+ await ml.jobWizardAdvanced.assertScrollSizeValue(
+ isDatafeedConfigWithScrollSize(testData.datafeedConfig)
+ ? testData.datafeedConfig.scrollSize
+ : defaultValues.scrollSize
+ );
+ });
+
+ it('job creation pre-fills the time field', async () => {
+ await ml.jobWizardAdvanced.assertTimeFieldInputExists();
+ await ml.jobWizardAdvanced.assertTimeFieldSelection([testData.expected.wizard.timeField]);
+ });
+
+ it('job cloning displays the pick fields step', async () => {
+ await ml.jobWizardCommon.advanceToPickFieldsSection();
+ });
+
+ it('job cloning pre-fills the categorization field', async () => {
+ await ml.jobWizardAdvanced.assertCategorizationFieldInputExists();
+ await ml.jobWizardAdvanced.assertCategorizationFieldSelection(
+ isPickFieldsConfigWithCategorizationField(testData.pickFieldsConfig)
+ ? [testData.pickFieldsConfig.categorizationField]
+ : []
+ );
+ });
+
+ it('job cloning pre-fills the summary count field', async () => {
+ await ml.jobWizardAdvanced.assertSummaryCountFieldInputExists();
+ await ml.jobWizardAdvanced.assertSummaryCountFieldSelection(
+ isPickFieldsConfigWithSummaryCountField(testData.pickFieldsConfig)
+ ? [testData.pickFieldsConfig.summaryCountField]
+ : []
+ );
+ });
+
+ it('job cloning pre-fills detectors', async () => {
+ for (const [index, detector] of testData.pickFieldsConfig.detectors.entries()) {
+ await ml.jobWizardAdvanced.assertDetectorEntryExists(
+ index,
+ detector.identifier,
+ isDetectorWithDescription(detector) ? detector.description : undefined
+ );
+ await ml.jobWizardAdvanced.clickEditDetector(index);
+
+ await ml.jobWizardAdvanced.assertDetectorFunctionInputExists();
+ await ml.jobWizardAdvanced.assertDetectorFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorByFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorOverFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorPartitionFieldInputExists();
+ await ml.jobWizardAdvanced.assertDetectorExcludeFrequentInputExists();
+ await ml.jobWizardAdvanced.assertDetectorDescriptionInputExists();
+
+ await ml.jobWizardAdvanced.assertDetectorFunctionSelection([detector.function]);
+ await ml.jobWizardAdvanced.assertDetectorFieldSelection(
+ isDetectorWithField(detector) ? [detector.field] : []
+ );
+ await ml.jobWizardAdvanced.assertDetectorByFieldSelection(
+ isDetectorWithByField(detector) ? [detector.byField] : []
+ );
+ await ml.jobWizardAdvanced.assertDetectorOverFieldSelection(
+ isDetectorWithOverField(detector) ? [detector.overField] : []
+ );
+ await ml.jobWizardAdvanced.assertDetectorPartitionFieldSelection(
+ isDetectorWithPartitionField(detector) ? [detector.partitionField] : []
+ );
+ await ml.jobWizardAdvanced.assertDetectorExcludeFrequentSelection(
+ isDetectorWithExcludeFrequent(detector) ? [detector.excludeFrequent] : []
+ );
+ // Currently, a description different form the identifier is generated for detectors with partition field
+ await ml.jobWizardAdvanced.assertDetectorDescriptionValue(
+ isDetectorWithDescription(detector)
+ ? detector.description
+ : detector.identifier.replace('partition_field_name', 'partitionfield')
+ );
+
+ await ml.jobWizardAdvanced.cancelAddDetectorModal();
+ }
+ });
+
+ it('job cloning pre-fills the bucket span', async () => {
+ await ml.jobWizardCommon.assertBucketSpanInputExists();
+ await ml.jobWizardCommon.assertBucketSpanValue(testData.pickFieldsConfig.bucketSpan);
+ });
+
+ it('job cloning pre-fills influencers', async () => {
+ await ml.jobWizardCommon.assertInfluencerInputExists();
+ await ml.jobWizardCommon.assertInfluencerSelection(testData.pickFieldsConfig.influencers);
+ });
+
+ it('job cloning pre-fills the model memory limit', async () => {
+ await ml.jobWizardCommon.assertModelMemoryLimitInputExists({
+ withAdvancedSection: false,
+ });
+ await ml.jobWizardCommon.assertModelMemoryLimitValue(
+ testData.pickFieldsConfig.memoryLimit,
+ {
+ withAdvancedSection: false,
+ }
+ );
+ });
+
+ it('job cloning displays the job details step', async () => {
+ await ml.jobWizardCommon.advanceToJobDetailsSection();
+ });
+
+ it('job cloning does not pre-fill the job id', async () => {
+ await ml.jobWizardCommon.assertJobIdInputExists();
+ await ml.jobWizardCommon.assertJobIdValue('');
+ });
+
+ it('job cloning inputs the clone job id', async () => {
+ await ml.jobWizardCommon.setJobId(testData.jobIdClone);
+ });
+
+ it('job cloning pre-fills the job description', async () => {
+ await ml.jobWizardCommon.assertJobDescriptionInputExists();
+ await ml.jobWizardCommon.assertJobDescriptionValue(testData.jobDescription);
+ });
+
+ it('job cloning pre-fills job groups', async () => {
+ await ml.jobWizardCommon.assertJobGroupInputExists();
+ await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups);
+ });
+
+ it('job cloning inputs the clone job group', async () => {
+ await ml.jobWizardCommon.assertJobGroupInputExists();
+ await ml.jobWizardCommon.addJobGroup('clone');
+ await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroupsClone);
+ });
+
+ it('job cloning pre-fills the model plot switch', async () => {
+ await ml.jobWizardCommon.assertModelPlotSwitchExists({ withAdvancedSection: false });
+ await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false, {
+ withAdvancedSection: false,
+ });
+ });
+
+ it('job cloning pre-fills the dedicated index switch', async () => {
+ await ml.jobWizardCommon.assertDedicatedIndexSwitchExists({ withAdvancedSection: false });
+ await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true, {
+ withAdvancedSection: false,
+ });
+ });
+
+ it('job cloning displays the validation step', async () => {
+ await ml.jobWizardCommon.advanceToValidationSection();
+ });
+
+ it('job cloning displays the summary step', async () => {
+ await ml.jobWizardCommon.advanceToSummarySection();
+ });
+
+ it('job cloning creates the job and finishes processing', async () => {
+ await ml.jobWizardCommon.assertCreateJobButtonExists();
+ await ml.jobWizardAdvanced.createJob();
+ await ml.jobManagement.assertStartDatafeedModalExists();
+ await ml.jobManagement.confirmStartDatafeedModal();
+ await ml.jobManagement.waitForJobCompletion(testData.jobIdClone);
+ });
+
+ it('job cloning displays the created job in the job list', async () => {
+ await ml.jobTable.refreshJobList();
+ await ml.jobTable.filterWithSearchString(testData.jobIdClone);
+ const rows = await ml.jobTable.parseJobTable();
+ expect(rows.filter(row => row.id === testData.jobIdClone)).to.have.length(1);
+ });
+
+ it('job creation displays details for the created job in the job list', async () => {
+ await ml.jobTable.assertJobRowFields(testData.jobIdClone, {
+ id: testData.jobIdClone,
+ description: testData.jobDescription,
+ jobGroups: [...new Set(testData.jobGroupsClone)].sort(),
+ recordCount: testData.expected.row.recordCount,
+ memoryStatus: testData.expected.row.memoryStatus,
+ jobState: testData.expected.row.jobState,
+ datafeedState: testData.expected.row.datafeedState,
+ latestTimestamp: testData.expected.row.latestTimestamp,
+ });
+
+ await ml.jobTable.assertJobRowDetailsCounts(
+ testData.jobIdClone,
+ {
+ job_id: testData.jobIdClone,
+ processed_record_count: testData.expected.counts.processed_record_count,
+ processed_field_count: testData.expected.counts.processed_field_count,
+ input_bytes: testData.expected.counts.input_bytes,
+ input_field_count: testData.expected.counts.input_field_count,
+ invalid_date_count: testData.expected.counts.invalid_date_count,
+ missing_field_count: testData.expected.counts.missing_field_count,
+ out_of_order_timestamp_count: testData.expected.counts.out_of_order_timestamp_count,
+ empty_bucket_count: testData.expected.counts.empty_bucket_count,
+ sparse_bucket_count: testData.expected.counts.sparse_bucket_count,
+ bucket_count: testData.expected.counts.bucket_count,
+ earliest_record_timestamp: testData.expected.counts.earliest_record_timestamp,
+ latest_record_timestamp: testData.expected.counts.latest_record_timestamp,
+ input_record_count: testData.expected.counts.input_record_count,
+ latest_bucket_timestamp: testData.expected.counts.latest_bucket_timestamp,
+ },
+ {
+ job_id: testData.jobIdClone,
+ result_type: testData.expected.modelSizeStats.result_type,
+ model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded,
+ model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit,
+ total_by_field_count: testData.expected.modelSizeStats.total_by_field_count,
+ total_over_field_count: testData.expected.modelSizeStats.total_over_field_count,
+ total_partition_field_count:
+ testData.expected.modelSizeStats.total_partition_field_count,
+ bucket_allocation_failures_count:
+ testData.expected.modelSizeStats.bucket_allocation_failures_count,
+ memory_status: testData.expected.modelSizeStats.memory_status,
+ timestamp: testData.expected.modelSizeStats.timestamp,
+ }
+ );
+ });
+
+ it('job creation has detector results', async () => {
+ for (let i = 0; i < testData.pickFieldsConfig.detectors.length; i++) {
+ await ml.api.assertDetectorResultsExist(testData.jobIdClone, i);
+ }
+ });
+ });
+ }
+ });
+}
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts
index 8d2e58bb0614d..ba307a24cd739 100644
--- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts
@@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./multi_metric_job'));
loadTestFile(require.resolve('./population_job'));
loadTestFile(require.resolve('./saved_search_job'));
+ loadTestFile(require.resolve('./advanced_job'));
});
}
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts
index 6163e99b5eaa4..11cb48de260f1 100644
--- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts
@@ -221,6 +221,12 @@ export default function({ getService }: FtrProviderContext) {
);
});
+ it('job creation has detector results', async () => {
+ for (let i = 0; i < aggAndFieldIdentifiers.length; i++) {
+ await ml.api.assertDetectorResultsExist(jobId, i);
+ }
+ });
+
it('job cloning clicks the clone action and loads the multi metric wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertMultiMetricJobWizardOpen();
@@ -258,7 +264,7 @@ export default function({ getService }: FtrProviderContext) {
it('job cloning pre-fills the split field', async () => {
await ml.jobWizardMultiMetric.assertSplitFieldInputExists();
- await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField);
+ await ml.jobWizardMultiMetric.assertSplitFieldSelection([splitField]);
});
it('job cloning pre-fills influencers', async () => {
@@ -351,5 +357,11 @@ export default function({ getService }: FtrProviderContext) {
getExpectedModelSizeStats(jobIdClone)
);
});
+
+ it('job cloning has detector results', async () => {
+ for (let i = 0; i < aggAndFieldIdentifiers.length; i++) {
+ await ml.api.assertDetectorResultsExist(jobId, i);
+ }
+ });
});
}
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts
index 7ccd9214591f2..71e66cc569f4e 100644
--- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts
@@ -248,6 +248,12 @@ export default function({ getService }: FtrProviderContext) {
);
});
+ it('job creation has detector results', async () => {
+ for (let i = 0; i < detectors.length; i++) {
+ await ml.api.assertDetectorResultsExist(jobId, i);
+ }
+ });
+
it('job cloning clicks the clone action and loads the population wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertPopulationJobWizardOpen();
@@ -275,14 +281,16 @@ export default function({ getService }: FtrProviderContext) {
it('job cloning pre-fills the population field', async () => {
await ml.jobWizardPopulation.assertPopulationFieldInputExists();
- await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField);
+ await ml.jobWizardPopulation.assertPopulationFieldSelection([populationField]);
});
it('job cloning pre-fills detectors and shows preview with split cards', async () => {
for (const [index, detector] of detectors.entries()) {
await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER');
- await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, detector.splitField);
+ await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, [
+ detector.splitField,
+ ]);
await ml.jobWizardPopulation.assertDetectorSplitExists(index);
await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle(
index,
@@ -387,5 +395,11 @@ export default function({ getService }: FtrProviderContext) {
getExpectedModelSizeStats(jobIdClone)
);
});
+
+ it('job cloning has detector results', async () => {
+ for (let i = 0; i < detectors.length; i++) {
+ await ml.api.assertDetectorResultsExist(jobId, i);
+ }
+ });
});
}
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts
index 5645bc7277d19..0330e141b0890 100644
--- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts
@@ -468,6 +468,12 @@ export default function({ getService }: FtrProviderContext) {
}
);
});
+
+ it('has detector results', async () => {
+ for (let i = 0; i < testData.aggAndFieldIdentifiers.length; i++) {
+ await ml.api.assertDetectorResultsExist(testData.jobId, i);
+ }
+ });
});
}
});
diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts
index 06ec840b36aae..b5a544b7af9f6 100644
--- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts
+++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts
@@ -202,6 +202,10 @@ export default function({ getService }: FtrProviderContext) {
);
});
+ it('job creation has detector results', async () => {
+ await ml.api.assertDetectorResultsExist(jobId, 0);
+ });
+
it('job cloning clicks the clone action and loads the single metric wizard', async () => {
await ml.jobTable.clickCloneJobAction(jobId);
await ml.jobTypeSelection.assertSingleMetricJobWizardOpen();
@@ -319,6 +323,10 @@ export default function({ getService }: FtrProviderContext) {
);
});
+ it('job cloning has detector results', async () => {
+ await ml.api.assertDetectorResultsExist(jobId, 0);
+ });
+
it('job deletion has results for the job before deletion', async () => {
await ml.api.assertJobResultsExist(jobIdClone);
});
diff --git a/x-pack/test/functional/es_archives/ml/farequote/mappings.json b/x-pack/test/functional/es_archives/ml/farequote/mappings.json
index b4c6be7655805..4fe559cc85fe1 100644
--- a/x-pack/test/functional/es_archives/ml/farequote/mappings.json
+++ b/x-pack/test/functional/es_archives/ml/farequote/mappings.json
@@ -21,24 +21,6 @@
"airline": {
"type": "keyword"
},
- "host": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
- "path": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- },
"responsetime": {
"type": "float"
},
@@ -1102,4 +1084,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts
index 148e276dc4a14..270722a97d6b6 100644
--- a/x-pack/test/functional/services/machine_learning/api.ts
+++ b/x-pack/test/functional/services/machine_learning/api.ts
@@ -8,10 +8,13 @@ import expect from '@kbn/expect';
import { isEmpty } from 'lodash';
import { FtrProviderContext } from '../../ftr_provider_context';
+import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states';
+
export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
const es = getService('es');
const log = getService('log');
const retry = getService('retry');
+ const esSupertest = getService('esSupertest');
return {
async hasJobResults(jobId: string): Promise {
@@ -54,6 +57,54 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
);
},
+ async hasDetectorResults(jobId: string, detectorIndex: number): Promise {
+ const response = await es.search({
+ index: '.ml-anomalies-*',
+ body: {
+ size: 1,
+ query: {
+ bool: {
+ must: [
+ {
+ match: {
+ job_id: jobId,
+ },
+ },
+ {
+ match: {
+ result_type: 'record',
+ },
+ },
+ {
+ match: {
+ detector_index: detectorIndex,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return response.hits.hits.length > 0;
+ },
+
+ async assertDetectorResultsExist(jobId: string, detectorIndex: number) {
+ await retry.waitForWithTimeout(
+ `results for detector ${detectorIndex} on job ${jobId} to exist`,
+ 30 * 1000,
+ async () => {
+ if ((await this.hasDetectorResults(jobId, detectorIndex)) === true) {
+ return true;
+ } else {
+ throw new Error(
+ `expected results for detector ${detectorIndex} on job '${jobId}' to exist`
+ );
+ }
+ }
+ );
+ },
+
async deleteIndices(indices: string) {
log.debug(`Deleting indices: '${indices}'...`);
const deleteResponse = await es.indices.delete({
@@ -79,5 +130,61 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
async cleanMlIndices() {
await this.deleteIndices('.ml-*');
},
+
+ async getJobState(jobId: string): Promise {
+ log.debug(`Fetching job state for job ${jobId}`);
+ const jobStats = await esSupertest
+ .get(`/_ml/anomaly_detectors/${jobId}/_stats`)
+ .expect(200)
+ .then((res: any) => res.body);
+
+ expect(jobStats.jobs).to.have.length(1);
+ const state: JOB_STATE = jobStats.jobs[0].state;
+
+ return state;
+ },
+
+ async waitForJobState(jobId: string, expectedJobState: JOB_STATE) {
+ await retry.waitForWithTimeout(
+ `job state to be ${expectedJobState}`,
+ 2 * 60 * 1000,
+ async () => {
+ const state = await this.getJobState(jobId);
+ if (state === expectedJobState) {
+ return true;
+ } else {
+ throw new Error(`expected job state to be ${expectedJobState} but got ${state}`);
+ }
+ }
+ );
+ },
+
+ async getDatafeedState(datafeedId: string): Promise {
+ log.debug(`Fetching datafeed state for datafeed ${datafeedId}`);
+ const datafeedStats = await esSupertest
+ .get(`/_ml/datafeeds/${datafeedId}/_stats`)
+ .expect(200)
+ .then((res: any) => res.body);
+
+ expect(datafeedStats.datafeeds).to.have.length(1);
+ const state: DATAFEED_STATE = datafeedStats.datafeeds[0].state;
+
+ return state;
+ },
+
+ async waitForDatafeedState(datafeedId: string, expectedDatafeedState: DATAFEED_STATE) {
+ await retry.waitForWithTimeout(
+ `datafeed state to be ${expectedDatafeedState}`,
+ 2 * 60 * 1000,
+ async () => {
+ const state = await this.getDatafeedState(datafeedId);
+ if (state === expectedDatafeedState) {
+ return true;
+ } else {
+ throw new Error(`expected job state to be ${expectedDatafeedState} but got ${state}`);
+ }
+ }
+ );
+ },
};
}
diff --git a/x-pack/test/functional/services/machine_learning/index.ts b/x-pack/test/functional/services/machine_learning/index.ts
index 0dc588ffcc9e5..c5ebe3e9cc156 100644
--- a/x-pack/test/functional/services/machine_learning/index.ts
+++ b/x-pack/test/functional/services/machine_learning/index.ts
@@ -12,6 +12,7 @@ export { MachineLearningJobManagementProvider } from './job_management';
export { MachineLearningJobSourceSelectionProvider } from './job_source_selection';
export { MachineLearningJobTableProvider } from './job_table';
export { MachineLearningJobTypeSelectionProvider } from './job_type_selection';
+export { MachineLearningJobWizardAdvancedProvider } from './job_wizard_advanced';
export { MachineLearningJobWizardCommonProvider } from './job_wizard_common';
export { MachineLearningJobWizardMultiMetricProvider } from './job_wizard_multi_metric';
export { MachineLearningJobWizardPopulationProvider } from './job_wizard_population';
diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts
index 44cf06ab44735..ddab5fd68f13c 100644
--- a/x-pack/test/functional/services/machine_learning/job_management.ts
+++ b/x-pack/test/functional/services/machine_learning/job_management.ts
@@ -3,11 +3,19 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { ProvidedType } from '@kbn/test/types/ftr';
import { FtrProviderContext } from '../../ftr_provider_context';
+import { MachineLearningAPIProvider } from './api';
-export function MachineLearningJobManagementProvider({ getService }: FtrProviderContext) {
+import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states';
+
+export function MachineLearningJobManagementProvider(
+ { getService }: FtrProviderContext,
+ mlApi: ProvidedType
+) {
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
return {
async navigateToNewJobSourceSelection() {
@@ -26,5 +34,22 @@ export function MachineLearningJobManagementProvider({ getService }: FtrProvider
async assertJobStatsBarExists() {
await testSubjects.existOrFail('~mlJobStatsBar');
},
+
+ async assertStartDatafeedModalExists() {
+ // this retry can be removed as soon as #48734 is merged
+ await retry.tryForTime(5000, async () => {
+ await testSubjects.existOrFail('mlStartDatafeedModal');
+ });
+ },
+
+ async confirmStartDatafeedModal() {
+ await testSubjects.click('mlStartDatafeedModalStartButton');
+ await testSubjects.missingOrFail('mlStartDatafeedModal');
+ },
+
+ async waitForJobCompletion(jobId: string) {
+ await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DATAFEED_STATE.STOPPED);
+ await mlApi.waitForJobState(jobId, JOB_STATE.CLOSED);
+ },
};
}
diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts
index 17c5d0f54eaa3..7eded43d1f058 100644
--- a/x-pack/test/functional/services/machine_learning/job_table.ts
+++ b/x-pack/test/functional/services/machine_learning/job_table.ts
@@ -157,6 +157,11 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte
});
}
+ public async refreshJobList() {
+ await testSubjects.click('mlRefreshJobListButton');
+ await this.waitForJobsToLoad();
+ }
+
public async waitForJobsToLoad() {
await testSubjects.existOrFail('~mlJobListTable', { timeout: 60 * 1000 });
await testSubjects.existOrFail('mlJobListTable loaded', { timeout: 30 * 1000 });
@@ -206,7 +211,7 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte
}
public async clickActionsMenu(jobId: string) {
- retry.tryForTime(30 * 1000, async () => {
+ await retry.tryForTime(30 * 1000, async () => {
if (!(await testSubjects.exists('mlActionButtonDeleteJob'))) {
await testSubjects.click(this.rowSelector(jobId, 'euiCollapsedItemActionsButton'));
await testSubjects.existOrFail('mlActionButtonDeleteJob', { timeout: 5000 });
diff --git a/x-pack/test/functional/services/machine_learning/job_type_selection.ts b/x-pack/test/functional/services/machine_learning/job_type_selection.ts
index 0957558f62165..6686b5b28f200 100644
--- a/x-pack/test/functional/services/machine_learning/job_type_selection.ts
+++ b/x-pack/test/functional/services/machine_learning/job_type_selection.ts
@@ -36,5 +36,14 @@ export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProvi
async assertPopulationJobWizardOpen() {
await testSubjects.existOrFail('mlPageJobWizard population');
},
+
+ async selectAdvancedJob() {
+ await testSubjects.clickWhenNotDisabled('mlJobTypeLinkAdvancedJob');
+ await this.assertAdvancedJobWizardOpen();
+ },
+
+ async assertAdvancedJobWizardOpen() {
+ await testSubjects.existOrFail('mlPageJobWizard advanced');
+ },
};
}
diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts
new file mode 100644
index 0000000000000..71b76a6885592
--- /dev/null
+++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts
@@ -0,0 +1,315 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import expect from '@kbn/expect';
+
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export function MachineLearningJobWizardAdvancedProvider({
+ getService,
+ getPageObjects,
+}: FtrProviderContext) {
+ const comboBox = getService('comboBox');
+ const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const aceEditor = getService('aceEditor');
+
+ return {
+ async getValueOrPlaceholder(inputLocator: string): Promise {
+ const value = await testSubjects.getAttribute(inputLocator, 'value');
+ if (value !== '') {
+ return value;
+ } else {
+ return await testSubjects.getAttribute(inputLocator, 'placeholder');
+ }
+ },
+
+ async assertDatafeedQueryEditorExists() {
+ await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > codeEditorContainer');
+ },
+
+ async assertDatafeedQueryEditorValue(expectedValue: string) {
+ const actualValue = await aceEditor.getValue(
+ 'mlAdvancedDatafeedQueryEditor > codeEditorContainer'
+ );
+ expect(actualValue).to.eql(expectedValue);
+ },
+
+ async assertQueryDelayInputExists() {
+ await testSubjects.existOrFail('mlJobWizardInputQueryDelay');
+ },
+
+ async assertQueryDelayValue(expectedValue: string) {
+ const actualQueryDelay = await this.getValueOrPlaceholder('mlJobWizardInputQueryDelay');
+ expect(actualQueryDelay).to.eql(expectedValue);
+ },
+
+ async setQueryDelay(queryDelay: string) {
+ await testSubjects.setValue('mlJobWizardInputQueryDelay', queryDelay, {
+ clearWithKeyboard: true,
+ typeCharByChar: true,
+ });
+ await this.assertQueryDelayValue(queryDelay);
+ },
+
+ async assertFrequencyInputExists() {
+ await testSubjects.existOrFail('mlJobWizardInputFrequency');
+ },
+
+ async assertFrequencyValue(expectedValue: string) {
+ const actualFrequency = await this.getValueOrPlaceholder('mlJobWizardInputFrequency');
+ expect(actualFrequency).to.eql(expectedValue);
+ },
+
+ async setFrequency(frequency: string) {
+ await testSubjects.setValue('mlJobWizardInputFrequency', frequency, {
+ clearWithKeyboard: true,
+ typeCharByChar: true,
+ });
+ await this.assertFrequencyValue(frequency);
+ },
+
+ async assertScrollSizeInputExists() {
+ await testSubjects.existOrFail('mlJobWizardInputScrollSize');
+ },
+
+ async assertScrollSizeValue(expectedValue: string) {
+ const actualScrollSize = await this.getValueOrPlaceholder('mlJobWizardInputScrollSize');
+ expect(actualScrollSize).to.eql(expectedValue);
+ },
+
+ async setScrollSize(scrollSize: string) {
+ await testSubjects.setValue('mlJobWizardInputScrollSize', scrollSize, {
+ clearWithKeyboard: true,
+ typeCharByChar: true,
+ });
+ await this.assertScrollSizeValue(scrollSize);
+ },
+
+ async assertTimeFieldInputExists() {
+ await testSubjects.existOrFail('mlTimeFieldNameSelect > comboBoxInput');
+ },
+
+ async assertTimeFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlTimeFieldNameSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectTimeField(identifier: string) {
+ await comboBox.set('mlTimeFieldNameSelect > comboBoxInput', identifier);
+ await this.assertTimeFieldSelection([identifier]);
+ },
+
+ async assertCategorizationFieldInputExists() {
+ await testSubjects.existOrFail('mlCategorizationFieldNameSelect > comboBoxInput');
+ },
+
+ async assertCategorizationFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlCategorizationFieldNameSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectCategorizationField(identifier: string) {
+ await comboBox.set('mlCategorizationFieldNameSelect > comboBoxInput', identifier);
+ await this.assertCategorizationFieldSelection([identifier]);
+ },
+
+ async assertSummaryCountFieldInputExists() {
+ await testSubjects.existOrFail('mlSummaryCountFieldNameSelect > comboBoxInput');
+ },
+
+ async assertSummaryCountFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlSummaryCountFieldNameSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectSummaryCountField(identifier: string) {
+ await comboBox.set('mlSummaryCountFieldNameSelect > comboBoxInput', identifier);
+ await this.assertSummaryCountFieldSelection([identifier]);
+ },
+
+ async assertAddDetectorButtonExists() {
+ await testSubjects.existOrFail('mlAddDetectorButton');
+ },
+
+ async openCreateDetectorModal() {
+ await testSubjects.click('mlAddDetectorButton');
+ await this.assertCreateDetectorModalExists();
+ },
+
+ async assertCreateDetectorModalExists() {
+ // this retry can be removed as soon as #48734 is merged
+ await retry.tryForTime(5000, async () => {
+ await testSubjects.existOrFail('mlCreateDetectorModal');
+ });
+ },
+
+ async assertDetectorFunctionInputExists() {
+ await testSubjects.existOrFail('mlAdvancedFunctionSelect > comboBoxInput');
+ },
+
+ async assertDetectorFunctionSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedFunctionSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorFunction(identifier: string) {
+ await comboBox.set('mlAdvancedFunctionSelect > comboBoxInput', identifier);
+ await this.assertDetectorFunctionSelection([identifier]);
+ },
+
+ async assertDetectorFieldInputExists() {
+ await testSubjects.existOrFail('mlAdvancedFieldSelect > comboBoxInput');
+ },
+
+ async assertDetectorFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedFieldSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorField(identifier: string) {
+ await comboBox.set('mlAdvancedFieldSelect > comboBoxInput', identifier);
+ await this.assertDetectorFieldSelection([identifier]);
+ },
+
+ async assertDetectorByFieldInputExists() {
+ await testSubjects.existOrFail('mlAdvancedByFieldSelect > comboBoxInput');
+ },
+
+ async assertDetectorByFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedByFieldSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorByField(identifier: string) {
+ await comboBox.set('mlAdvancedByFieldSelect > comboBoxInput', identifier);
+ await this.assertDetectorByFieldSelection([identifier]);
+ },
+
+ async assertDetectorOverFieldInputExists() {
+ await testSubjects.existOrFail('mlAdvancedOverFieldSelect > comboBoxInput');
+ },
+
+ async assertDetectorOverFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedOverFieldSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorOverField(identifier: string) {
+ await comboBox.set('mlAdvancedOverFieldSelect > comboBoxInput', identifier);
+ await this.assertDetectorOverFieldSelection([identifier]);
+ },
+
+ async assertDetectorPartitionFieldInputExists() {
+ await testSubjects.existOrFail('mlAdvancedPartitionFieldSelect > comboBoxInput');
+ },
+
+ async assertDetectorPartitionFieldSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedPartitionFieldSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorPartitionField(identifier: string) {
+ await comboBox.set('mlAdvancedPartitionFieldSelect > comboBoxInput', identifier);
+ await this.assertDetectorPartitionFieldSelection([identifier]);
+ },
+
+ async assertDetectorExcludeFrequentInputExists() {
+ await testSubjects.existOrFail('mlAdvancedExcludeFrequentSelect > comboBoxInput');
+ },
+
+ async assertDetectorExcludeFrequentSelection(expectedIdentifier: string[]) {
+ const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
+ 'mlAdvancedExcludeFrequentSelect > comboBoxInput'
+ );
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
+ },
+
+ async selectDetectorExcludeFrequent(identifier: string) {
+ await comboBox.set('mlAdvancedExcludeFrequentSelect > comboBoxInput', identifier);
+ await this.assertDetectorExcludeFrequentSelection([identifier]);
+ },
+
+ async assertDetectorDescriptionInputExists() {
+ await testSubjects.existOrFail('mlAdvancedDetectorDescriptionInput');
+ },
+
+ async assertDetectorDescriptionValue(expectedValue: string) {
+ const actualDetectorDescription = await testSubjects.getAttribute(
+ 'mlAdvancedDetectorDescriptionInput',
+ 'value'
+ );
+ expect(actualDetectorDescription).to.eql(expectedValue);
+ },
+
+ async setDetectorDescription(description: string) {
+ await testSubjects.setValue('mlAdvancedDetectorDescriptionInput', description, {
+ clearWithKeyboard: true,
+ });
+ await this.assertDetectorDescriptionValue(description);
+ },
+
+ async confirmAddDetectorModal() {
+ await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalSaveButton');
+ await testSubjects.missingOrFail('mlCreateDetectorModal');
+ },
+
+ async cancelAddDetectorModal() {
+ await testSubjects.clickWhenNotDisabled('mlCreateDetectorModalCancelButton');
+ await testSubjects.missingOrFail('mlCreateDetectorModal');
+ },
+
+ async assertDetectorEntryExists(
+ detectorIndex: number,
+ expectedDetectorName: string,
+ expectedDetectorDescription?: string
+ ) {
+ await testSubjects.existOrFail(`mlAdvancedDetector ${detectorIndex}`);
+
+ const actualDetectorIdentifier = await testSubjects.getVisibleText(
+ `mlAdvancedDetector ${detectorIndex} > mlDetectorIdentifier`
+ );
+ expect(actualDetectorIdentifier).to.eql(expectedDetectorName);
+
+ if (expectedDetectorDescription !== undefined) {
+ const actualDetectorDescription = await testSubjects.getVisibleText(
+ `mlAdvancedDetector ${detectorIndex} > mlDetectorDescription`
+ );
+ expect(actualDetectorDescription).to.eql(expectedDetectorDescription);
+ }
+ },
+
+ async clickEditDetector(detectorIndex: number) {
+ await testSubjects.click(
+ `mlAdvancedDetector ${detectorIndex} > mlAdvancedDetectorEditButton`
+ );
+ await this.assertCreateDetectorModalExists();
+ },
+
+ async createJob() {
+ await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
+ // this retry can be removed as soon as #48734 is merged
+ await retry.tryForTime(5000, async () => {
+ await testSubjects.existOrFail('mlStartDatafeedModal');
+ });
+ },
+ };
+}
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 73764e8f36518..3a71f96fa3fbd 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
@@ -12,6 +12,15 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
const retry = getService('retry');
const testSubjects = getService('testSubjects');
+ interface SectionOptions {
+ withAdvancedSection: boolean;
+ }
+
+ function advancedSectionSelector(subSelector?: string) {
+ const subj = 'mlJobWizardAdvancedSection';
+ return !subSelector ? subj : `${subj} > ${subSelector}`;
+ }
+
return {
async clickNextButton() {
await testSubjects.existOrFail('mlJobWizardNavButtonNext');
@@ -38,6 +47,10 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
await testSubjects.existOrFail('mlJobWizardStepTitleSummary');
},
+ async assertConfigureDatafeedSectionExists() {
+ await testSubjects.existOrFail('mlJobWizardStepTitleConfigureDatafeed');
+ },
+
async advanceToPickFieldsSection() {
await this.clickNextButton();
await this.assertPickFieldsSectionExists();
@@ -153,75 +166,127 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
expect(await this.getSelectedJobGroups()).to.contain(jobGroup);
},
- async assertModelPlotSwitchExists() {
- await this.ensureAdvancedSectionOpen();
- await testSubjects.existOrFail('mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot', {
- allowHidden: true,
- });
+ async assertModelPlotSwitchExists(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardSwitchModelPlot';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ await testSubjects.existOrFail(subj, { allowHidden: true });
},
- async getModelPlotSwitchCheckedState(): Promise {
- await this.ensureAdvancedSectionOpen();
- return await testSubjects.isSelected(
- 'mlJobWizardAdvancedSection > mlJobWizardSwitchModelPlot'
- );
+ async getModelPlotSwitchCheckedState(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ): Promise {
+ let subj = 'mlJobWizardSwitchModelPlot';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ return await testSubjects.isSelected(subj);
},
- async assertModelPlotSwitchCheckedState(expectedValue: boolean) {
- await this.ensureAdvancedSectionOpen();
- const actualCheckedState = await this.getModelPlotSwitchCheckedState();
+ async assertModelPlotSwitchCheckedState(
+ expectedValue: boolean,
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ const actualCheckedState = await this.getModelPlotSwitchCheckedState({
+ withAdvancedSection: sectionOptions.withAdvancedSection,
+ });
expect(actualCheckedState).to.eql(expectedValue);
},
- async assertDedicatedIndexSwitchExists() {
- await this.ensureAdvancedSectionOpen();
- await testSubjects.existOrFail(
- 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex',
- { allowHidden: true }
- );
+ async assertDedicatedIndexSwitchExists(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardSwitchUseDedicatedIndex';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ await testSubjects.existOrFail(subj, { allowHidden: true });
},
- async getDedicatedIndexSwitchCheckedState(): Promise {
- await this.ensureAdvancedSectionOpen();
- return await testSubjects.isSelected(
- 'mlJobWizardAdvancedSection > mlJobWizardSwitchUseDedicatedIndex'
- );
+ async getDedicatedIndexSwitchCheckedState(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ): Promise {
+ let subj = 'mlJobWizardSwitchUseDedicatedIndex';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ return await testSubjects.isSelected(subj);
},
- async assertDedicatedIndexSwitchCheckedState(expectedValue: boolean) {
- await this.ensureAdvancedSectionOpen();
- const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState();
+ async assertDedicatedIndexSwitchCheckedState(
+ expectedValue: boolean,
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState({
+ withAdvancedSection: sectionOptions.withAdvancedSection,
+ });
expect(actualCheckedState).to.eql(expectedValue);
},
- async activateDedicatedIndexSwitch() {
- if ((await this.getDedicatedIndexSwitchCheckedState()) === false) {
- await testSubjects.clickWhenNotDisabled('mlJobWizardSwitchUseDedicatedIndex');
+ async activateDedicatedIndexSwitch(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardSwitchUseDedicatedIndex';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ if (
+ (await this.getDedicatedIndexSwitchCheckedState({
+ withAdvancedSection: sectionOptions.withAdvancedSection,
+ })) === false
+ ) {
+ await testSubjects.clickWhenNotDisabled(subj);
}
- await this.assertDedicatedIndexSwitchCheckedState(true);
+ await this.assertDedicatedIndexSwitchCheckedState(true, {
+ withAdvancedSection: sectionOptions.withAdvancedSection,
+ });
},
- async assertModelMemoryLimitInputExists() {
- await this.ensureAdvancedSectionOpen();
- await testSubjects.existOrFail(
- 'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit'
- );
+ async assertModelMemoryLimitInputExists(
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardInputModelMemoryLimit';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ await testSubjects.existOrFail(subj);
},
- async assertModelMemoryLimitValue(expectedValue: string) {
- await this.ensureAdvancedSectionOpen();
- const actualModelMemoryLimit = await testSubjects.getAttribute(
- 'mlJobWizardAdvancedSection > mlJobWizardInputModelMemoryLimit',
- 'value'
- );
+ async assertModelMemoryLimitValue(
+ expectedValue: string,
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardInputModelMemoryLimit';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ const actualModelMemoryLimit = await testSubjects.getAttribute(subj, 'value');
expect(actualModelMemoryLimit).to.eql(expectedValue);
},
- async setModelMemoryLimit(modelMemoryLimit: string) {
- await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit, {
- clearWithKeyboard: true,
+ async setModelMemoryLimit(
+ modelMemoryLimit: string,
+ sectionOptions: SectionOptions = { withAdvancedSection: true }
+ ) {
+ let subj = 'mlJobWizardInputModelMemoryLimit';
+ if (sectionOptions.withAdvancedSection === true) {
+ await this.ensureAdvancedSectionOpen();
+ subj = advancedSectionSelector(subj);
+ }
+ await testSubjects.setValue(subj, modelMemoryLimit, { clearWithKeyboard: true });
+ await this.assertModelMemoryLimitValue(modelMemoryLimit, {
+ withAdvancedSection: sectionOptions.withAdvancedSection,
});
- await this.assertModelMemoryLimitValue(modelMemoryLimit);
},
async assertInfluencerInputExists() {
@@ -237,7 +302,7 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
},
async addInfluencer(influencer: string) {
- await comboBox.setCustom('mlInfluencerSelect > comboBoxInput', influencer);
+ await comboBox.set('mlInfluencerSelect > comboBoxInput', influencer);
expect(await this.getSelectedInfluencers()).to.contain(influencer);
},
@@ -293,16 +358,16 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid
async ensureAdvancedSectionOpen() {
await retry.tryForTime(5000, async () => {
- if ((await testSubjects.exists('mlJobWizardAdvancedSection')) === false) {
+ if ((await testSubjects.exists(advancedSectionSelector())) === false) {
await testSubjects.click('mlJobWizardToggleAdvancedSection');
- await testSubjects.existOrFail('mlJobWizardAdvancedSection', { timeout: 1000 });
+ await testSubjects.existOrFail(advancedSectionSelector(), { timeout: 1000 });
}
});
},
async createJobAndWaitForCompletion() {
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
- await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 5 * 60 * 1000 });
+ await testSubjects.existOrFail('mlJobWizardButtonRunInRealTime', { timeout: 2 * 60 * 1000 });
},
};
}
diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts b/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts
index d9df6a9d682a7..2fb768d924cff 100644
--- a/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts
+++ b/x-pack/test/functional/services/machine_learning/job_wizard_multi_metric.ts
@@ -16,17 +16,16 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP
await testSubjects.existOrFail('mlMultiMetricSplitFieldSelect > comboBoxInput');
},
- async assertSplitFieldSelection(identifier: string) {
+ async assertSplitFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlMultiMetricSplitFieldSelect > comboBoxInput'
);
- expect(comboBoxSelectedOptions.length).to.eql(1);
- expect(comboBoxSelectedOptions[0]).to.eql(identifier);
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectSplitField(identifier: string) {
await comboBox.set('mlMultiMetricSplitFieldSelect > comboBoxInput', identifier);
- await this.assertSplitFieldSelection(identifier);
+ await this.assertSplitFieldSelection([identifier]);
},
async assertDetectorSplitExists(splitField: string) {
diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_population.ts b/x-pack/test/functional/services/machine_learning/job_wizard_population.ts
index 892bdaf394936..8ff9d5c12a642 100644
--- a/x-pack/test/functional/services/machine_learning/job_wizard_population.ts
+++ b/x-pack/test/functional/services/machine_learning/job_wizard_population.ts
@@ -16,17 +16,16 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
await testSubjects.existOrFail('mlPopulationSplitFieldSelect > comboBoxInput');
},
- async assertPopulationFieldSelection(identifier: string) {
+ async assertPopulationFieldSelection(expectedIdentifier: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlPopulationSplitFieldSelect > comboBoxInput'
);
- expect(comboBoxSelectedOptions.length).to.eql(1);
- expect(comboBoxSelectedOptions[0]).to.eql(identifier);
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectPopulationField(identifier: string) {
await comboBox.set('mlPopulationSplitFieldSelect > comboBoxInput', identifier);
- await this.assertPopulationFieldSelection(identifier);
+ await this.assertPopulationFieldSelection([identifier]);
},
async assertDetectorSplitFieldInputExists(detectorPosition: number) {
@@ -35,12 +34,14 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
);
},
- async assertDetectorSplitFieldSelection(detectorPosition: number, identifier: string) {
+ async assertDetectorSplitFieldSelection(
+ detectorPosition: number,
+ expectedIdentifier: string[]
+ ) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
`mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`
);
- expect(comboBoxSelectedOptions.length).to.eql(1);
- expect(comboBoxSelectedOptions[0]).to.eql(identifier);
+ expect(comboBoxSelectedOptions).to.eql(expectedIdentifier);
},
async selectDetectorSplitField(detectorPosition: number, identifier: string) {
@@ -48,7 +49,7 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr
`mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput`,
identifier
);
- await this.assertDetectorSplitFieldSelection(detectorPosition, identifier);
+ await this.assertDetectorSplitFieldSelection(detectorPosition, [identifier]);
},
async assertDetectorSplitExists(detectorPosition: number) {
diff --git a/x-pack/test/functional/services/ml.ts b/x-pack/test/functional/services/ml.ts
index 3feb45ae23bbc..8609552b5dc55 100644
--- a/x-pack/test/functional/services/ml.ts
+++ b/x-pack/test/functional/services/ml.ts
@@ -15,6 +15,7 @@ import {
MachineLearningJobSourceSelectionProvider,
MachineLearningJobTableProvider,
MachineLearningJobTypeSelectionProvider,
+ MachineLearningJobWizardAdvancedProvider,
MachineLearningJobWizardCommonProvider,
MachineLearningJobWizardMultiMetricProvider,
MachineLearningJobWizardPopulationProvider,
@@ -28,10 +29,11 @@ export function MachineLearningProvider(context: FtrProviderContext) {
const api = MachineLearningAPIProvider(context);
const dataFrameAnalytics = MachineLearningDataFrameAnalyticsProvider(context);
const dataVisualizer = MachineLearningDataVisualizerProvider(context);
- const jobManagement = MachineLearningJobManagementProvider(context);
+ const jobManagement = MachineLearningJobManagementProvider(context, api);
const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context);
const jobTable = MachineLearningJobTableProvider(context);
const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context);
+ const jobWizardAdvanced = MachineLearningJobWizardAdvancedProvider(context);
const jobWizardCommon = MachineLearningJobWizardCommonProvider(context);
const jobWizardMultiMetric = MachineLearningJobWizardMultiMetricProvider(context);
const jobWizardPopulation = MachineLearningJobWizardPopulationProvider(context);
@@ -48,6 +50,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
jobSourceSelection,
jobTable,
jobTypeSelection,
+ jobWizardAdvanced,
jobWizardCommon,
jobWizardMultiMetric,
jobWizardPopulation,
diff --git a/yarn.lock b/yarn.lock
index 7097d36d64fce..6cbd920ab3970 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1151,10 +1151,10 @@
tabbable "^1.1.0"
uuid "^3.1.0"
-"@elastic/eui@14.7.0":
- version "14.7.0"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-14.7.0.tgz#dcbd3e9a9307e52a2fdca5833a116de5940de06d"
- integrity sha512-IjYjUqhfqjqG6cbaTANiuHyWq3U62ODzcOnIKACxHOGCK2JVwiDvtDByAuj3PvD0wG/cDN49oNbUZ/o0QCapVw==
+"@elastic/eui@14.8.0":
+ version "14.8.0"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-14.8.0.tgz#777d29852998e52e8fc6dfb1869a4b32d74c72bb"
+ integrity sha512-p6TZv6Z+ENzw6JnCyXVQtvEOo7eEct8Qb/S4aS4EXK1WIyGB35Ra/a/pb3bLQbbZ2mSZtCr1sk+XVUq0qDpytw==
dependencies:
"@types/lodash" "^4.14.116"
"@types/numeral" "^0.0.25"
@@ -1173,7 +1173,7 @@
react-is "~16.3.0"
react-virtualized "^9.18.5"
resize-observer-polyfill "^1.5.0"
- tabbable "^1.1.0"
+ tabbable "^3.0.0"
uuid "^3.1.0"
"@elastic/filesaver@1.1.2":
@@ -26531,6 +26531,11 @@ tabbable@^1.0.3:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.2.tgz#b171680aea6e0a3e9281ff23532e2e5de11c0d94"
integrity sha512-77oqsKEPrxIwgRcXUwipkj9W5ItO97L6eUT1Ar7vh+El16Zm4M6V+YU1cbipHEa6q0Yjw8O3Hoh8oRgatV5s7A==
+tabbable@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-3.1.2.tgz#f2d16cccd01f400e38635c7181adfe0ad965a4a2"
+ integrity sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ==
+
table@^3.7.8:
version "3.8.3"
resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"