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/maps/trouble-shooting.asciidoc b/docs/maps/trouble-shooting.asciidoc index 542138828530b..76383f8953a78 100644 --- a/docs/maps/trouble-shooting.asciidoc +++ b/docs/maps/trouble-shooting.asciidoc @@ -30,4 +30,9 @@ image::maps/images/inspector.png[] * Ensure your tile server has configured https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS[Cross-Origin Resource Sharing (CORS)] so tile requests from your Kibana domain have permission to access your tile server domain. * Ensure tiles have the required coordinate system. Vector data must use EPSG:4326 and tiles must use EPSG:3857. +[float] +==== Coordinate and region map visualizations not available in New Visualization menu + +Kibana’s out-of-the-box settings no longer offers coordinate and region maps as a choice in the New Visualization menu because you can create these maps in *Elastic Maps*. +If you want to create new coordinate and region map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. 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/docs/visualize/regionmap.asciidoc b/docs/visualize/regionmap.asciidoc index faecc207d81a7..c39282963ef7b 100644 --- a/docs/visualize/regionmap.asciidoc +++ b/docs/visualize/regionmap.asciidoc @@ -1,9 +1,12 @@ [[regionmap]] == Region Maps -Region maps are thematic maps in which boundary vector shapes are colored using a gradient: -higher intensity colors indicate larger values, and lower intensity colors indicate smaller values. -These are also known as choropleth maps. +Region maps are thematic maps in which boundary vector shapes are colored using a gradient: +higher intensity colors indicate larger values, and lower intensity colors indicate smaller values. +These are also known as choropleth maps. + +Kibana’s out-of-the-box settings do not show a region map in the New Visualization menu. Use <> instead, which offers more functionality and is easier to use. +If you want to create new region map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. image::images/regionmap.png[] @@ -11,7 +14,7 @@ image::images/regionmap.png[] [[regionmap-configuration]] === Configuration -To create a region map, you configure an inner join that joins the result of an Elasticsearch terms aggregation +To create a region map, you configure an inner join that joins the result of an Elasticsearch terms aggregation and a reference vector file based on a shared key. [float] @@ -23,7 +26,7 @@ and a reference vector file based on a shared key. Select any of the supported _Metric_ or _Sibling Pipeline Aggregations_. [float] -===== Buckets +===== Buckets Configure a _Terms_ aggregation. The term is the _key_ that is used to join the results to the vector data on the map. @@ -35,7 +38,7 @@ Configure a _Terms_ aggregation. The term is the _key_ that is used to join the - *Vector map*: select from a list of vector maps. This list includes the maps that are hosted by the © https://www.elastic.co/elastic-maps-service[Elastic Maps Service], as well as your self-hosted layers that are configured in the *config/kibana.yml* file. To learn more about how to configure Kibana to make self-hosted layers available, see the <> documentation. You can also explore and preview vector layers available in Elastic Maps Service at https://maps.elastic.co[https://maps.elastic.co]. -- *Join field*: this is the property from the selected vector map that will be used to join on the terms in your terms-aggregation. +- *Join field*: this is the property from the selected vector map that will be used to join on the terms in your terms-aggregation. When terms cannot be joined to any of the shapes in the vector layer because there is no exact match in the vector layer, Kibana will display a warning. To turn of these warnings, go to *Management/Kibana/Advanced Settings* and set `visualization:regionmap:showWarnings` to `false`. @@ -46,4 +49,4 @@ To turn of these warnings, go to *Management/Kibana/Advanced Settings* and set ` [float] ===== Basic Settings - *Legend Position*: the location on the screen where the legend should be rendered. -- *Show Tooltip*: indicates whether a tooltip should be displayed when hovering over a shape.. \ No newline at end of file +- *Show Tooltip*: indicates whether a tooltip should be displayed when hovering over a shape.. diff --git a/docs/visualize/tilemap.asciidoc b/docs/visualize/tilemap.asciidoc index 157eb502bedb7..0e89704b8ba0b 100644 --- a/docs/visualize/tilemap.asciidoc +++ b/docs/visualize/tilemap.asciidoc @@ -3,7 +3,10 @@ A coordinate map displays a geographic area overlaid with circles keyed to the data determined by the buckets you specify. -NOTE: By default, Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] +Kibana’s out-of-the-box settings do not show a coordinate map in the New Visualization menu. Use <> instead, which offers more functionality and is easier to use. +If you want to create new coordinate map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. + +Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] to display map tiles. To use other tile service providers, configure the <> in `kibana.yml`. 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 286e60cca9712..37dc0d14730c0 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 9a846ab82f47c..26ee2d80ebf65 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,7 +73,7 @@ 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; @@ -82,7 +82,7 @@ function QueryBarTopRowUI(props: Props) { useEffect(() => { if (!props.query) return; - persistedLog = getQueryLog(uiSettings!, store, appName, props.query.language); + persistedLog = getQueryLog(uiSettings!, storage, appName, props.query.language); }, [queryLanguage]); function onClickSubmitButton(event: React.MouseEvent) { @@ -211,7 +211,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() { @@ -293,7 +293,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({ @@ -337,8 +337,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 73e81a38572c3..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 @@ -33,14 +33,14 @@ const timefilterSetupMock = timefilterServiceMock.createSetupContract(); jest.mock('../../../../../data/public', () => { return { - FilterBar: () =>
, - QueryBarInput: () =>
, + FilterBar: () =>
, + QueryBarInput: () =>
, }; }); jest.mock('../../../query/query_bar', () => { return { - QueryBarTopRow: () =>
, + QueryBarTopRow: () =>
, }; }); @@ -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/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index a03019da4e0d7..c7f8b02caf853 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -368,7 +368,7 @@ class SearchBarUI extends Component { onLoad={this.onLoadSavedQuery} savedQueryService={this.savedQueryService} onClearSavedQuery={this.props.onClearSavedQuery} - > + /> ); let queryBar; 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/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 803119bdac119..4f8c5d11f1916 100644 --- a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -29,7 +29,7 @@ jest.mock('ui/new_platform'); const dataShim = { ui: { - SearchBar: () =>
, + SearchBar: () =>
, }, }; 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/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 5dc0e452eea56..5d7ab12a677cf 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -42,6 +42,7 @@ const createSetupContract = (): VisualizationsSetup => ({ types: { registerVisualization: jest.fn(), registerAlias: jest.fn(), + hideTypes: jest.fn(), }, }); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts index 1556c3716de1a..e0d26e3405048 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/types_service.ts @@ -48,16 +48,30 @@ export interface VisType { */ export class TypesService { private types: Record = {}; + private unregisteredHiddenTypes: string[] = []; public setup() { return { registerVisualization: (registerFn: () => VisType) => { const visDefinition = registerFn(); + if (this.unregisteredHiddenTypes.includes(visDefinition.name)) { + visDefinition.hidden = true; + } + if (this.types[visDefinition.name]) { throw new Error('type already exists!'); } this.types[visDefinition.name] = visDefinition; }, registerAlias: visTypeAliasRegistry.add, + hideTypes: (typeNames: string[]) => { + typeNames.forEach((name: string) => { + if (this.types[name]) { + this.types[name].hidden = true; + } else { + this.unregisteredHiddenTypes.push(name); + } + }); + }, }; } 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_react/public/ui_settings/use_ui_setting.test.tsx b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx index cc2a8c3fbe1fc..badf568ccc193 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx @@ -106,7 +106,7 @@ describe('useUiSetting', () => { }); describe('useUiSetting$', () => { - const TestConsumer$: React.FC<{ + const TestConsumer: React.FC<{ setting: string; newValue?: string; }> = ({ setting, newValue = '' }) => { @@ -126,7 +126,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -143,7 +143,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -159,7 +159,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -174,7 +174,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); 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/common/projections/errors.ts b/x-pack/legacy/plugins/apm/common/projections/errors.ts index c3094f5cbb0b6..adbd2eb1d6d27 100644 --- a/x-pack/legacy/plugins/apm/common/projections/errors.ts +++ b/x-pack/legacy/plugins/apm/common/projections/errors.ts @@ -19,10 +19,10 @@ export function getErrorGroupsProjection({ setup: Setup; serviceName: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; return { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/common/projections/metrics.ts b/x-pack/legacy/plugins/apm/common/projections/metrics.ts index 5c9eeb54744d7..25d1484624e15 100644 --- a/x-pack/legacy/plugins/apm/common/projections/metrics.ts +++ b/x-pack/legacy/plugins/apm/common/projections/metrics.ts @@ -34,7 +34,7 @@ export function getMetricsProjection({ serviceName: string; serviceNodeName?: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; const filter = [ { term: { [SERVICE_NAME]: serviceName } }, @@ -45,7 +45,7 @@ export function getMetricsProjection({ ]; return { - index: config.get('apm_oss.metricsIndices'), + index: indices['apm_oss.metricsIndices'], body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/common/projections/services.ts b/x-pack/legacy/plugins/apm/common/projections/services.ts index ab72211f92aa7..e889899e11634 100644 --- a/x-pack/legacy/plugins/apm/common/projections/services.ts +++ b/x-pack/legacy/plugins/apm/common/projections/services.ts @@ -9,13 +9,13 @@ import { SERVICE_NAME, PROCESSOR_EVENT } from '../elasticsearch_fieldnames'; import { rangeFilter } from '../../server/lib/helpers/range_filter'; export function getServicesProjection({ setup }: { setup: Setup }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; return { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/common/projections/transactions.ts b/x-pack/legacy/plugins/apm/common/projections/transactions.ts index 63abb0572df87..fb249340c867c 100644 --- a/x-pack/legacy/plugins/apm/common/projections/transactions.ts +++ b/x-pack/legacy/plugins/apm/common/projections/transactions.ts @@ -24,7 +24,7 @@ export function getTransactionsProjection({ transactionName?: string; transactionType?: string; }) { - const { start, end, uiFiltersES, config } = setup; + const { start, end, uiFiltersES, indices } = setup; const transactionNameFilter = transactionName ? [{ term: { [TRANSACTION_NAME]: transactionName } }] @@ -48,7 +48,7 @@ export function getTransactionsProjection({ }; return { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { query: { bool diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 4d3d2b210bbae..556bce9d37bb5 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -43,8 +43,7 @@ export const apm: LegacyPluginInitializer = kibana => { apmUiEnabled: config.get('xpack.apm.ui.enabled'), // TODO: rename to apm_oss.indexPatternTitle in 7.0 (breaking change) apmIndexPatternTitle: config.get('apm_oss.indexPattern'), - apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled'), - apmTransactionIndices: config.get('apm_oss.transactionIndices') + apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled') }; }, hacks: ['plugins/apm/hacks/toggle_app_link_in_nav'], diff --git a/x-pack/legacy/plugins/apm/mappings.json b/x-pack/legacy/plugins/apm/mappings.json index 26075d406c993..0b31798242fad 100644 --- a/x-pack/legacy/plugins/apm/mappings.json +++ b/x-pack/legacy/plugins/apm/mappings.json @@ -41,5 +41,30 @@ } } } + }, + "apm-indices": { + "properties": { + "apm_oss.sourcemapIndices": { + "type": "keyword" + }, + "apm_oss.errorIndices": { + "type": "keyword" + }, + "apm_oss.onboardingIndices": { + "type": "keyword" + }, + "apm_oss.spanIndices": { + "type": "keyword" + }, + "apm_oss.transactionIndices": { + "type": "keyword" + }, + "apm_oss.metricsIndices": { + "type": "keyword" + }, + "apm_oss.apmAgentConfigurationIndex": { + "type": "keyword" + } + } } } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx index 6c95095592fe4..4769fe6400ac1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -15,7 +15,9 @@ import { TransactionDetails } from '../../TransactionDetails'; import { Home } from '../../Home'; import { BreadcrumbRoute } from '../ProvideBreadcrumbs'; import { RouteName } from './route_names'; +import { Settings } from '../../Settings'; import { AgentConfigurations } from '../../Settings/AgentConfigurations'; +import { ApmIndices } from '../../Settings/ApmIndices'; import { toQuery } from '../../../shared/Links/url_helpers'; import { ServiceNodeMetrics } from '../../ServiceNodeMetrics'; import { resolveUrlParams } from '../../../../context/UrlParamsContext/resolveUrlParams'; @@ -69,12 +71,41 @@ export const routes: BreadcrumbRoute[] = [ { exact: true, path: '/settings', - component: AgentConfigurations, + render: renderAsRedirectTo('/settings/agent-configuration'), breadcrumb: i18n.translate('xpack.apm.breadcrumb.listSettingsTitle', { defaultMessage: 'Settings' }), name: RouteName.SETTINGS }, + { + exact: true, + path: '/settings/apm-indices', + component: () => ( + + + + ), + breadcrumb: i18n.translate('xpack.apm.breadcrumb.settings.indicesTitle', { + defaultMessage: 'Indices' + }), + name: RouteName.INDICES + }, + { + exact: true, + path: '/settings/agent-configuration', + component: () => ( + + + + ), + breadcrumb: i18n.translate( + 'xpack.apm.breadcrumb.settings.agentConfigurationTitle', + { + defaultMessage: 'Agent Configuration' + } + ), + name: RouteName.AGENT_CONFIGURATION + }, { exact: true, path: '/services/:serviceName', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx index 3af5b217ca738..ab02e38ee9c9d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/route_config/route_names.tsx @@ -19,5 +19,7 @@ export enum RouteName { TRANSACTION_TYPE = 'transaction_type', TRANSACTION_NAME = 'transaction_name', SETTINGS = 'settings', + AGENT_CONFIGURATION = 'agent_configuration', + INDICES = 'indices', SERVICE_NODES = 'nodes' } 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/Settings/AgentConfigurations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index e2630efbcf500..9e95cfd0fad7f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -10,7 +10,6 @@ import { EuiTitle, EuiFlexGroup, EuiFlexItem, - EuiButtonEmpty, EuiPanel, EuiSpacer, EuiButton @@ -18,7 +17,6 @@ import { import { isEmpty } from 'lodash'; import { useFetcher } from '../../../../hooks/useFetcher'; import { AgentConfigurationListAPIResponse } from '../../../../../server/lib/settings/agent_configuration/list_configurations'; -import { HomeLink } from '../../../shared/Links/apm/HomeLink'; import { AgentConfigurationList } from './AgentConfigurationList'; import { useTrackPageview } from '../../../../../../infra/public'; import { AddEditFlyout } from './AddEditFlyout'; @@ -62,30 +60,6 @@ export function AgentConfigurations() { /> )} - - - -

- {i18n.translate('xpack.apm.settings.agentConf.pageTitle', { - defaultMessage: 'Settings' - })} -

-
-
- - - - {i18n.translate( - 'xpack.apm.settings.agentConf.returnToOverviewLinkLabel', - { defaultMessage: 'Return to overview' } - )} - - - -
- - - diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx new file mode 100644 index 0000000000000..8fab7da377eb2 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -0,0 +1,259 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, + EuiForm, + EuiFormRow, + EuiFieldText, + EuiButton, + EuiButtonEmpty +} from '@elastic/eui'; +import { useFetcher } from '../../../../hooks/useFetcher'; +import { useCallApmApi } from '../../../../hooks/useCallApmApi'; +import { APMClient } from '../../../../services/rest/createCallApmApi'; +import { StringMap } from '../../../../../typings/common'; +import { useKibanaCore } from '../../../../../../observability/public'; + +const APM_INDEX_LABELS = [ + { + configurationName: 'apm_oss.sourcemapIndices', + label: i18n.translate( + 'xpack.apm.settings.apmIndices.sourcemapIndicesLabel', + { defaultMessage: 'Sourcemap Indices' } + ) + }, + { + configurationName: 'apm_oss.errorIndices', + label: i18n.translate('xpack.apm.settings.apmIndices.errorIndicesLabel', { + defaultMessage: 'Error Indices' + }) + }, + { + configurationName: 'apm_oss.onboardingIndices', + label: i18n.translate( + 'xpack.apm.settings.apmIndices.onboardingIndicesLabel', + { defaultMessage: 'Onboarding Indices' } + ) + }, + { + configurationName: 'apm_oss.spanIndices', + label: i18n.translate('xpack.apm.settings.apmIndices.spanIndicesLabel', { + defaultMessage: 'Span Indices' + }) + }, + { + configurationName: 'apm_oss.transactionIndices', + label: i18n.translate( + 'xpack.apm.settings.apmIndices.transactionIndicesLabel', + { defaultMessage: 'Transaction Indices' } + ) + }, + { + configurationName: 'apm_oss.metricsIndices', + label: i18n.translate('xpack.apm.settings.apmIndices.metricsIndicesLabel', { + defaultMessage: 'Metrics Indices' + }) + }, + { + configurationName: 'apm_oss.apmAgentConfigurationIndex', + label: i18n.translate( + 'xpack.apm.settings.apmIndices.apmAgentConfigurationIndexLabel', + { defaultMessage: 'Agent Configuration Index' } + ) + } +]; + +async function saveApmIndices({ + callApmApi, + apmIndices +}: { + callApmApi: APMClient; + apmIndices: StringMap; +}) { + await callApmApi({ + method: 'POST', + pathname: '/api/apm/settings/apm-indices/save', + params: { + body: apmIndices + } + }); +} + +export function ApmIndices() { + const { + notifications: { toasts } + } = useKibanaCore(); + + const [apmIndices, setApmIndices] = useState>({}); + const [isSaving, setIsSaving] = useState(false); + + const callApmApiFromHook = useCallApmApi(); + + const { data = [], status, refetch } = useFetcher( + callApmApi => + callApmApi({ pathname: `/api/apm/settings/apm-index-settings` }), + [] + ); + + useEffect(() => { + setApmIndices( + data.reduce( + (acc, { configurationName, savedValue }) => ({ + ...acc, + [configurationName]: savedValue + }), + {} + ) + ); + }, [data]); + + const handleApplyChangesEvent = async ( + event: + | React.FormEvent + | React.MouseEvent + ) => { + event.preventDefault(); + setIsSaving(true); + try { + await saveApmIndices({ + callApmApi: callApmApiFromHook, + apmIndices + }); + toasts.addSuccess({ + title: i18n.translate( + 'xpack.apm.settings.apmIndices.applyChanges.succeeded.title', + { defaultMessage: 'Indices applied' } + ), + text: i18n.translate( + 'xpack.apm.settings.apmIndices.applyChanges.succeeded.text', + { + defaultMessage: + 'The indices changes were successfully applied. These changes are reflected immediately in the APM UI' + } + ) + }); + } catch (error) { + toasts.addDanger({ + title: i18n.translate( + 'xpack.apm.settings.apmIndices.applyChanges.failed.title', + { defaultMessage: 'Indices could not be applied.' } + ), + text: i18n.translate( + 'xpack.apm.settings.apmIndices.applyChanges.failed.text', + { + defaultMessage: + 'Something went wrong when applying indices. Error: {errorMessage}', + values: { errorMessage: error.message } + } + ) + }); + } + setIsSaving(false); + }; + + const handleChangeIndexConfigurationEvent = async ( + event: React.ChangeEvent + ) => { + const { name, value } = event.target; + setApmIndices({ + ...apmIndices, + [name]: value + }); + }; + + return ( + + + + +

+ {i18n.translate('xpack.apm.settings.apmIndices.title', { + defaultMessage: 'Indices' + })} +

+
+ + +

+ {i18n.translate('xpack.apm.settings.apmIndices.description', { + defaultMessage: `The APM UI uses index patterns to query your APM indices. If you've customized the index names that APM Server writes events to, you may need to update these patterns for the APM UI to work. Settings here take precedence over those set in kibana.yml.` + })} +

+ + {APM_INDEX_LABELS.map(({ configurationName, label }) => { + const matchedConfiguration = data.find( + ({ configurationName: configName }) => + configName === configurationName + ); + const defaultValue = matchedConfiguration + ? matchedConfiguration.defaultValue + : ''; + const savedUiIndexValue = apmIndices[configurationName] || ''; + return ( + + + + ); + })} + + + + + {i18n.translate( + 'xpack.apm.settings.apmIndices.cancelButton', + { defaultMessage: 'Cancel' } + )} + + + + + {i18n.translate( + 'xpack.apm.settings.apmIndices.applyButton', + { defaultMessage: 'Apply changes' } + )} + + + + +
+
+
+ + +
+ ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx new file mode 100644 index 0000000000000..f3be5abe4d48b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx @@ -0,0 +1,66 @@ +/* + * 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 { + EuiButtonEmpty, + EuiPage, + EuiSideNav, + EuiPageSideBar, + EuiPageBody +} from '@elastic/eui'; +import { HomeLink } from '../../shared/Links/apm/HomeLink'; +import { useLocation } from '../../../hooks/useLocation'; +import { getAPMHref } from '../../shared/Links/apm/APMLink'; + +export const Settings: React.FC = props => { + const { search, pathname } = useLocation(); + return ( + <> + + + {i18n.translate('xpack.apm.settings.returnToOverviewLinkLabel', { + defaultMessage: 'Return to overview' + })} + + + + + + + {props.children} + + + ); +}; 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/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/Stacktrace/Variables.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index 34c46f84c76b9..b66c154e4be31 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 @@ -24,29 +24,27 @@ interface Props { vars: IStackframe['vars']; } -export class Variables extends React.Component { - public render() { - if (!this.props.vars) { - return null; - } - - return ( - - - - - - - - - - ); +export const Variables: React.SFC = props => { + if (!props.vars) { + return null; } -} + + 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/services/rest/ml.ts b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts index 9150c63127317..28b14fa722bbd 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/ml.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/ml.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npStart } from 'ui/new_platform'; import { HttpServiceBase } from 'kibana/public'; import { PROCESSOR_EVENT, @@ -14,6 +13,7 @@ import { import { getMlJobId, getMlPrefix } from '../../../common/ml_job_constants'; import { callApi } from './callApi'; import { ESFilter } from '../../../typings/elasticsearch'; +import { createCallApmApi, APMClient } from './createCallApmApi'; interface MlResponseItem { id: string; @@ -32,7 +32,14 @@ interface StartedMLJobApiResponse { jobs: MlResponseItem[]; } -const { core } = npStart; +async function getTransactionIndices(http: HttpServiceBase) { + const callApmApi: APMClient = createCallApmApi(http); + const indices = await callApmApi({ + method: 'GET', + pathname: `/api/apm/settings/apm-indices` + }); + return indices['apm_oss.transactionIndices']; +} export async function startMLJob({ serviceName, @@ -43,9 +50,7 @@ export async function startMLJob({ transactionType: string; http: HttpServiceBase; }) { - const indexPatternName = core.injectedMetadata.getInjectedVar( - 'apmTransactionIndices' - ); + const transactionIndices = await getTransactionIndices(http); const groups = ['apm', serviceName.toLowerCase()]; const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, @@ -59,7 +64,7 @@ export async function startMLJob({ body: JSON.stringify({ prefix: getMlPrefix(serviceName, transactionType), groups, - indexPatternName, + indexPatternName: transactionIndices, startDatafeed: true, query: { bool: { diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 2e3c8940680fe..a18882120fe75 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -102,6 +102,15 @@ interface MockSetup { has: any; }; uiFiltersES: ESFilter[]; + indices: { + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + 'apm_oss.apmAgentConfigurationIndex': string; + }; } export async function inspectSearchParams( @@ -127,7 +136,16 @@ export async function inspectSearchParams( { term: { 'service.environment': 'prod' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } }; try { await fn(mockSetup); diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap index 8d89e22fa9864..d336d71424750 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/__snapshots__/get_buckets.test.ts.snap @@ -50,7 +50,7 @@ Array [ }, "size": 0, }, - "index": "myIndex", + "index": "apm-*", }, ], ] diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts index 0b8cd8ae8703f..b7081c43465bf 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts @@ -39,7 +39,16 @@ describe('timeseriesFetcher', () => { { term: { 'service.environment': 'prod' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'apm-*', + 'apm_oss.errorIndices': 'apm-*', + 'apm_oss.onboardingIndices': 'apm-*', + 'apm_oss.spanIndices': 'apm-*', + 'apm_oss.transactionIndices': 'apm-*', + 'apm_oss.metricsIndices': 'apm-*', + 'apm_oss.apmAgentConfigurationIndex': '.apm-agent-configuration' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 6059f2b0051a7..afb8237178a20 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -25,7 +25,7 @@ export async function getBuckets({ bucketSize: number; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const filter: ESFilter[] = [ { term: { [PROCESSOR_EVENT]: 'error' } }, { term: { [SERVICE_NAME]: serviceName } }, @@ -38,7 +38,7 @@ export async function getBuckets({ } const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts index dee5deb4064e4..caadbfef32698 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts @@ -29,10 +29,10 @@ export async function getErrorGroup({ groupId: string; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 1, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts index aaac56d16c599..9a6aed02e2a84 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts @@ -24,10 +24,10 @@ export async function getTraceErrorsPerTransaction( traceId: string, setup: Setup ): Promise { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { - index: config.get('apm_oss.errorIndices'), + index: indices['apm_oss.errorIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts index 9b2c97fab7aa3..84c52b895f20d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts @@ -12,9 +12,10 @@ import { IndicesCreateParams } from 'elasticsearch'; import { Legacy } from 'kibana'; -import { cloneDeep, has, isString, set } from 'lodash'; +import { cloneDeep, has, isString, set, pick } from 'lodash'; import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames'; -import { StringMap } from '../../../typings/common'; +import { StringMap, Omit } from '../../../typings/common'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { ESSearchResponse, ESSearchRequest @@ -23,17 +24,6 @@ import { // `type` was deprecated in 7.0 export type APMIndexDocumentParams = Omit, 'type'>; -function getApmIndices(config: Legacy.KibanaConfig) { - return [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.onboardingIndices'), - config.get('apm_oss.sourcemapIndices'), - config.get('apm_oss.spanIndices'), - config.get('apm_oss.transactionIndices') - ]; -} - export function isApmIndex( apmIndices: string[], indexParam: SearchParams['index'] @@ -76,10 +66,23 @@ async function getParamsForSearchRequest( params: SearchParams, apmOptions?: APMOptions ) { - const config = req.server.config(); const uiSettings = req.getUiSettingsService(); - const apmIndices = getApmIndices(config); - const includeFrozen = await uiSettings.get('search:includeFrozen'); + const [indices, includeFrozen] = await Promise.all([ + getApmIndices(req.server), + uiSettings.get('search:includeFrozen') + ]); + + // Get indices for legacy data filter (only those which apply) + const apmIndices: string[] = Object.values( + pick(indices, [ + 'apm_oss.sourcemapIndices', + 'apm_oss.errorIndices', + 'apm_oss.onboardingIndices', + 'apm_oss.spanIndices', + 'apm_oss.transactionIndices', + 'apm_oss.metricsIndices' + ]) + ); return { ...addFilterForLegacyData(apmIndices, params, apmOptions), // filter out pre-7.0 data ignore_throttled: !includeFrozen // whether to query frozen indices or not diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts index 3070d7b00b684..81dd8b34c8847 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts @@ -6,10 +6,10 @@ import { Server } from 'hapi'; -export function getSavedObjectsClient(server: Server) { +export function getSavedObjectsClient(server: Server, clusterName = 'admin') { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( - 'admin' + clusterName ); const internalRepository = getSavedObjectsRepository(callWithInternalUser); return new SavedObjectsClient(internalRepository); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index e8cf56fcd0802..57de438be7f2a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -3,11 +3,22 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; import { uiSettingsServiceMock } from 'src/core/server/mocks'; +jest.mock('../settings/apm_indices/get_apm_indices', () => ({ + getApmIndices: async () => ({ + 'apm_oss.sourcemapIndices': 'apm-*', + 'apm_oss.errorIndices': 'apm-*', + 'apm_oss.onboardingIndices': 'apm-*', + 'apm_oss.spanIndices': 'apm-*', + 'apm_oss.transactionIndices': 'apm-*', + 'apm_oss.metricsIndices': 'apm-*', + 'apm_oss.apmAgentConfigurationIndex': 'apm-*' + }) +})); + function getMockRequest() { const callWithRequestSpy = jest.fn(); const mockRequest = ({ diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts index 4054537e04b40..3ec519d5e71b5 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts @@ -10,6 +10,7 @@ import moment from 'moment'; import { getESClient } from './es_client'; import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es'; import { PromiseReturnType } from '../../../typings/common'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; function decodeUiFilters(server: Server, uiFiltersEncoded?: string) { if (!uiFiltersEncoded) { @@ -31,12 +32,17 @@ export async function setupRequest(req: Legacy.Request) { const query = (req.query as unknown) as APMRequestQuery; const { server } = req; const config = server.config(); + const [uiFiltersES, indices] = await Promise.all([ + decodeUiFilters(server, query.uiFilters), + getApmIndices(server) + ]); return { start: moment.utc(query.start).valueOf(), end: moment.utc(query.end).valueOf(), - uiFiltersES: await decodeUiFilters(server, query.uiFilters), + uiFiltersES, client: getESClient(req), - config + config, + indices }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 71217ccc36f7b..acd5dc119b737 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -56,9 +56,7 @@ Object { }, "size": 0, }, - "index": Array [ - "myIndex", - ], + "index": "myIndex", "terminateAfter": 1, } `; @@ -228,8 +226,6 @@ Object { }, "size": 0, }, - "index": Array [ - "myIndex", - ], + "index": "myIndex", } `; diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts index b39e35a305b19..bbb18eae7eb09 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -13,14 +13,14 @@ import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; export async function getServiceAgentName(serviceName: string, setup: Setup) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { terminateAfter: 1, index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices'), - config.get('apm_oss.metricsIndices') + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'], + indices['apm_oss.metricsIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts index 9d651247d5cee..00ffa7484bb48 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -16,10 +16,10 @@ export async function getServiceTransactionTypes( serviceName: string, setup: Setup ) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const params = { - index: [config.get('apm_oss.transactionIndices')], + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts index f9a7671abd994..6c28d19a14711 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_agent_status.ts @@ -9,15 +9,15 @@ import { Setup } from '../../helpers/setup_request'; // Note: this logic is duplicated in tutorials/apm/envs/on_prem export async function getAgentStatus(setup: Setup) { - const { client, config } = setup; + const { client, indices } = setup; const params = { terminateAfter: 1, index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.sourcemapIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.errorIndices'], + indices['apm_oss.metricsIndices'], + indices['apm_oss.sourcemapIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts index 1379d79326add..5497eaa13d34b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_legacy_data_status.ts @@ -12,11 +12,11 @@ import { Setup } from '../../helpers/setup_request'; // returns true if 6.x data is found export async function getLegacyDataStatus(setup: Setup) { - const { client, config } = setup; + const { client, indices } = setup; const params = { terminateAfter: 1, - index: [config.get('apm_oss.transactionIndices')], + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 4226c073b1de0..861732ee03923 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -6,15 +6,15 @@ import { InternalCoreSetup } from 'src/core/server'; import { CallCluster } from '../../../../../../../../src/legacy/core_plugins/elasticsearch'; +import { getApmIndices } from '../apm_indices/get_apm_indices'; export async function createApmAgentConfigurationIndex( core: InternalCoreSetup ) { try { const { server } = core.http; - const index = server - .config() - .get('apm_oss.apmAgentConfigurationIndex'); + const indices = await getApmIndices(server); + const index = indices['apm_oss.apmAgentConfigurationIndex']; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( 'admin' ); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts index 9f0d8e2f1c718..25a4f5141498f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts @@ -21,11 +21,11 @@ export async function createOrUpdateConfiguration({ >; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params: APMIndexDocumentParams = { refresh: true, - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { agent_name: configuration.agent_name, service: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts index 82b192125a5cf..896363c054ba7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts @@ -13,11 +13,11 @@ export async function deleteConfiguration({ configurationId: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { refresh: 'wait_for', - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], id: configurationId }; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts index 7742bc938816f..21663b813f01f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts @@ -19,14 +19,14 @@ export async function getAgentNameByService({ serviceName: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { terminateAfter: 1, index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts index aad31feef000b..8215e2b9fd668 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_all_environments.ts @@ -20,7 +20,7 @@ export async function getAllEnvironments({ serviceName: string | undefined; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; // omit filter for service.name if "All" option is selected const serviceNameFilter = serviceName @@ -29,9 +29,9 @@ export async function getAllEnvironments({ const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 6b557f21f3457..d5aa389cea335 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -19,14 +19,14 @@ export async function getExistingEnvironmentsForService({ serviceName: string | undefined; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const bool = serviceName ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } : { must_not: [{ exists: { field: SERVICE_NAME } }] }; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { size: 0, query: { bool }, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index dfc1041c96488..51a4564f53576 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -17,13 +17,13 @@ export type AgentConfigurationServicesAPIResponse = PromiseReturnType< typeof getServiceNames >; export async function getServiceNames({ setup }: { setup: Setup }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts index 9edceb86dda8d..283f30b51441d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts @@ -12,10 +12,10 @@ export type AgentConfigurationListAPIResponse = PromiseReturnType< typeof listConfigurations >; export async function listConfigurations({ setup }: { setup: Setup }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex') + index: indices['apm_oss.apmAgentConfigurationIndex'] }; const resp = await client.search(params); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts index 867045142cea0..e5349edb67f30 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts @@ -16,10 +16,10 @@ export async function markAppliedByAgent({ body: AgentConfiguration; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], id, // by specifying the `id` elasticsearch will do an "upsert" body: { ...body, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts index e8db37891e7ae..400bd0207771a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts @@ -15,7 +15,18 @@ describe('search configurations', () => { environment: 'production', setup: ({ config: { get: () => '' }, - client: { search: async () => searchMocks } + client: { search: async () => searchMocks }, + indices: { + apm_oss: { + sourcemapIndices: 'myIndex', + errorIndices: 'myIndex', + onboardingIndices: 'myIndex', + spanIndices: 'myIndex', + transactionIndices: 'myIndex', + metricsIndices: 'myIndex', + apmAgentConfigurationIndex: 'myIndex' + } + } } as unknown) as Setup }); @@ -29,7 +40,18 @@ describe('search configurations', () => { environment: 'production', setup: ({ config: { get: () => '' }, - client: { search: async () => searchMocks } + client: { search: async () => searchMocks }, + indices: { + apm_oss: { + sourcemapIndices: 'myIndex', + errorIndices: 'myIndex', + onboardingIndices: 'myIndex', + spanIndices: 'myIndex', + transactionIndices: 'myIndex', + metricsIndices: 'myIndex', + apmAgentConfigurationIndex: 'myIndex' + } + } } as unknown) as Setup }); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts index 6b0dc370dd878..35d76d745cf4f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts @@ -20,7 +20,7 @@ export async function searchConfigurations({ environment?: string; setup: Setup; }) { - const { client, config } = setup; + const { client, indices } = setup; // sorting order // 1. exact match: service.name AND service.environment (eg. opbeans-node / production) @@ -33,7 +33,7 @@ export async function searchConfigurations({ : []; const params = { - index: config.get('apm_oss.apmAgentConfigurationIndex'), + index: indices['apm_oss.apmAgentConfigurationIndex'], body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts new file mode 100644 index 0000000000000..cd237a5264099 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -0,0 +1,101 @@ +/* + * 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 { Server } from 'hapi'; +import { merge } from 'lodash'; +import { KibanaConfig } from 'src/legacy/server/kbn_server'; +import { getSavedObjectsClient } from '../../helpers/saved_objects_client'; +import { Setup } from '../../helpers/setup_request'; +import { PromiseReturnType } from '../../../../typings/common'; + +export interface ApmIndicesConfig { + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + 'apm_oss.apmAgentConfigurationIndex': string; +} + +export type ApmIndicesName = keyof ApmIndicesConfig; + +export const APM_INDICES_SAVED_OBJECT_TYPE = 'apm-indices'; +export const APM_INDICES_SAVED_OBJECT_ID = 'apm-indices'; + +async function getApmIndicesSavedObject(server: Server) { + const savedObjectsClient = getSavedObjectsClient(server, 'data'); + const apmIndices = await savedObjectsClient.get>( + APM_INDICES_SAVED_OBJECT_TYPE, + APM_INDICES_SAVED_OBJECT_ID + ); + return apmIndices.attributes; +} + +function getApmIndicesConfig(config: KibanaConfig): ApmIndicesConfig { + return { + 'apm_oss.sourcemapIndices': config.get('apm_oss.sourcemapIndices'), + 'apm_oss.errorIndices': config.get('apm_oss.errorIndices'), + 'apm_oss.onboardingIndices': config.get( + 'apm_oss.onboardingIndices' + ), + 'apm_oss.spanIndices': config.get('apm_oss.spanIndices'), + 'apm_oss.transactionIndices': config.get( + 'apm_oss.transactionIndices' + ), + 'apm_oss.metricsIndices': config.get('apm_oss.metricsIndices'), + 'apm_oss.apmAgentConfigurationIndex': config.get( + 'apm_oss.apmAgentConfigurationIndex' + ) + }; +} + +export async function getApmIndices(server: Server) { + try { + const apmIndicesSavedObject = await getApmIndicesSavedObject(server); + const apmIndicesConfig = getApmIndicesConfig(server.config()); + return merge({}, apmIndicesConfig, apmIndicesSavedObject); + } catch (error) { + return getApmIndicesConfig(server.config()); + } +} + +const APM_UI_INDICES: ApmIndicesName[] = [ + 'apm_oss.sourcemapIndices', + 'apm_oss.errorIndices', + 'apm_oss.onboardingIndices', + 'apm_oss.spanIndices', + 'apm_oss.transactionIndices', + 'apm_oss.metricsIndices', + 'apm_oss.apmAgentConfigurationIndex' +]; + +export async function getApmIndexSettings({ + setup, + server +}: { + setup: Setup; + server: Server; +}) { + const { config } = setup; + let apmIndicesSavedObject: PromiseReturnType; + try { + apmIndicesSavedObject = await getApmIndicesSavedObject(server); + } catch (error) { + if (error.output && error.output.statusCode === 404) { + apmIndicesSavedObject = {}; + } else { + throw error; + } + } + const apmIndicesConfig = getApmIndicesConfig(config); + + return APM_UI_INDICES.map(configurationName => ({ + configurationName, + defaultValue: apmIndicesConfig[configurationName], // value defined in kibana[.dev].yml + savedValue: apmIndicesSavedObject[configurationName] // value saved via Saved Objects service + })); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts new file mode 100644 index 0000000000000..8de47c5c44144 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { getSavedObjectsClient } from '../../helpers/saved_objects_client'; +import { + ApmIndicesConfig, + APM_INDICES_SAVED_OBJECT_TYPE, + APM_INDICES_SAVED_OBJECT_ID +} from './get_apm_indices'; + +export async function saveApmIndices( + server: Server, + apmIndicesSavedObject: Partial +) { + const savedObjectsClient = getSavedObjectsClient(server, 'data'); + return await savedObjectsClient.create( + APM_INDICES_SAVED_OBJECT_TYPE, + apmIndicesSavedObject, + { + id: APM_INDICES_SAVED_OBJECT_ID, + overwrite: true + } + ); +} diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 71b65982db9f0..0df5cc016431d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -17,13 +17,13 @@ import { rangeFilter } from '../helpers/range_filter'; import { Setup } from '../helpers/setup_request'; export async function getTraceItems(traceId: string, setup: Setup) { - const { start, end, client, config } = setup; + const { start, end, client, config, indices } = setup; const maxTraceItems = config.get('xpack.apm.ui.maxTraceItems'); const params = { index: [ - config.get('apm_oss.spanIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.spanIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: maxTraceItems, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts index fc7b1b4127892..99553690359cf 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts @@ -16,15 +16,22 @@ function getSetup() { config: { get: jest.fn((key: string) => { switch (key) { - case 'apm_oss.transactionIndices': - return 'myIndex'; case 'xpack.apm.ui.transactionGroupBucketSize': return 100; } }), has: () => true }, - uiFiltersES: [{ term: { 'service.environment': 'test' } }] + uiFiltersES: [{ term: { 'service.environment': 'test' } }], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts index e2989498181ca..e092942a25ba6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts @@ -21,9 +21,9 @@ export async function getTransactionAvgDurationByCountry({ setup: Setup; serviceName: string; }) { - const { uiFiltersES, client, config, start, end } = setup; + const { uiFiltersES, client, start, end, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts index e707ce3eafe18..67816d67a29a2 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -9,6 +9,16 @@ import * as constants from './constants'; import noDataResponse from './mock-responses/noData.json'; import dataResponse from './mock-responses/data.json'; +const mockIndices = { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' +}; + describe('getTransactionBreakdown', () => { it('returns an empty array if no data is available', async () => { const clientSpy = jest.fn().mockReturnValueOnce(noDataResponse); @@ -24,7 +34,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -47,7 +58,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -87,7 +99,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -126,7 +139,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); @@ -149,7 +163,8 @@ describe('getTransactionBreakdown', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: mockIndices } }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index ecf963fec3fe9..a21c4f38ac30c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -32,7 +32,7 @@ export async function getTransactionBreakdown({ transactionName?: string; transactionType: string; }) { - const { uiFiltersES, client, config, start, end } = setup; + const { uiFiltersES, client, start, end, indices } = setup; const subAggs = { sum_all_self_times: { @@ -88,7 +88,7 @@ export async function getTransactionBreakdown({ } const params = { - index: config.get('apm_oss.metricsIndices'), + index: indices['apm_oss.metricsIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts index 07f1d96618198..cddc66e52cf70 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts @@ -30,7 +30,16 @@ describe('getAnomalySeries', () => { get: () => 'myIndex' as any, has: () => true }, - uiFiltersES: [] + uiFiltersES: [], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts index dc568d653a5ee..5056a100de3ce 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts @@ -29,7 +29,16 @@ describe('timeseriesFetcher', () => { { term: { 'service.environment': 'test' } } - ] + ], + indices: { + 'apm_oss.sourcemapIndices': 'myIndex', + 'apm_oss.errorIndices': 'myIndex', + 'apm_oss.onboardingIndices': 'myIndex', + 'apm_oss.spanIndices': 'myIndex', + 'apm_oss.transactionIndices': 'myIndex', + 'apm_oss.metricsIndices': 'myIndex', + 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + } } }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index 97368b7d92efc..0d9cccb3b56d3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -30,7 +30,7 @@ export function timeseriesFetcher({ transactionName: string | undefined; setup: Setup; }) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const { intervalString } = getBucketSize(start, end, 'auto'); const filter: ESFilter[] = [ @@ -50,7 +50,7 @@ export function timeseriesFetcher({ } const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { bool: { filter } }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index 42b35402aab9d..90d9a925a1f36 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -28,10 +28,10 @@ export async function bucketFetcher( bucketSize: number, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index f49cf2b8c8541..a54fa9c10de13 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -19,10 +19,10 @@ export async function getDistributionMax( transactionType: string, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts index cdaddc3af3e95..20152ecf06480 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -19,10 +19,10 @@ export async function getTransaction( traceId: string, setup: Setup ) { - const { start, end, uiFiltersES, client, config } = setup; + const { start, end, uiFiltersES, client, indices } = setup; const params = { - index: config.get('apm_oss.transactionIndices'), + index: indices['apm_oss.transactionIndices'], body: { size: 1, query: { diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts index 80795ce468c88..ade491be32fc7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -16,7 +16,7 @@ import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_valu import { ESFilter } from '../../../typings/elasticsearch'; export async function getEnvironments(setup: Setup, serviceName?: string) { - const { start, end, client, config } = setup; + const { start, end, client, indices } = setup; const filter: ESFilter[] = [ { terms: { [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'] } }, @@ -31,9 +31,9 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { const params = { index: [ - config.get('apm_oss.metricsIndices'), - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'] ], body: { size: 0, diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index 682ebf27207c4..516f1994c2fb7 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -25,7 +25,12 @@ import { listAgentConfigurationServicesRoute, updateAgentConfigurationRoute, agentConfigurationAgentNameRoute -} from './settings'; +} from './settings/agent_configuration'; +import { + apmIndexSettingsRoute, + apmIndicesRoute, + saveApmIndicesRoute +} from './settings/apm_indices'; import { metricsChartsRoute } from './metrics'; import { serviceNodesRoute } from './service_nodes'; import { tracesRoute, tracesByIdRoute } from './traces'; @@ -75,6 +80,11 @@ const createApmApi = () => { .add(listAgentConfigurationServicesRoute) .add(updateAgentConfigurationRoute) + // APM indices + .add(apmIndexSettingsRoute) + .add(apmIndicesRoute) + .add(saveApmIndicesRoute) + // Metrics .add(metricsChartsRoute) .add(serviceNodesRoute) diff --git a/x-pack/legacy/plugins/apm/server/routes/settings.ts b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts similarity index 80% rename from x-pack/legacy/plugins/apm/server/routes/settings.ts rename to x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts index 65ef5df78f3e1..d25ad949d6dde 100644 --- a/x-pack/legacy/plugins/apm/server/routes/settings.ts +++ b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts @@ -5,18 +5,18 @@ */ import * as t from 'io-ts'; -import { setupRequest } from '../lib/helpers/setup_request'; -import { getServiceNames } from '../lib/settings/agent_configuration/get_service_names'; -import { createOrUpdateConfiguration } from '../lib/settings/agent_configuration/create_or_update_configuration'; -import { searchConfigurations } from '../lib/settings/agent_configuration/search'; -import { listConfigurations } from '../lib/settings/agent_configuration/list_configurations'; -import { getEnvironments } from '../lib/settings/agent_configuration/get_environments'; -import { deleteConfiguration } from '../lib/settings/agent_configuration/delete_configuration'; -import { createRoute } from './create_route'; -import { transactionSampleRateRt } from '../../common/runtime_types/transaction_sample_rate_rt'; -import { transactionMaxSpansRt } from '../../common/runtime_types/transaction_max_spans_rt'; -import { getAgentNameByService } from '../lib/settings/agent_configuration/get_agent_name_by_service'; -import { markAppliedByAgent } from '../lib/settings/agent_configuration/mark_applied_by_agent'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { getServiceNames } from '../../lib/settings/agent_configuration/get_service_names'; +import { createOrUpdateConfiguration } from '../../lib/settings/agent_configuration/create_or_update_configuration'; +import { searchConfigurations } from '../../lib/settings/agent_configuration/search'; +import { listConfigurations } from '../../lib/settings/agent_configuration/list_configurations'; +import { getEnvironments } from '../../lib/settings/agent_configuration/get_environments'; +import { deleteConfiguration } from '../../lib/settings/agent_configuration/delete_configuration'; +import { createRoute } from '../create_route'; +import { transactionSampleRateRt } from '../../../common/runtime_types/transaction_sample_rate_rt'; +import { transactionMaxSpansRt } from '../../../common/runtime_types/transaction_max_spans_rt'; +import { getAgentNameByService } from '../../lib/settings/agent_configuration/get_agent_name_by_service'; +import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_applied_by_agent'; // get list of configurations export const agentConfigurationRoute = createRoute(core => ({ diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts new file mode 100644 index 0000000000000..3c82a35ec7903 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { createRoute } from '../create_route'; +import { + getApmIndices, + getApmIndexSettings +} from '../../lib/settings/apm_indices/get_apm_indices'; +import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices'; + +// get list of apm indices and values +export const apmIndexSettingsRoute = createRoute(core => ({ + method: 'GET', + path: '/api/apm/settings/apm-index-settings', + handler: async req => { + const { server } = core.http; + const setup = await setupRequest(req); + return await getApmIndexSettings({ setup, server }); + } +})); + +// get apm indices configuration object +export const apmIndicesRoute = createRoute(core => ({ + method: 'GET', + path: '/api/apm/settings/apm-indices', + handler: async req => { + const { server } = core.http; + return await getApmIndices(server); + } +})); + +// save ui indices +export const saveApmIndicesRoute = createRoute(core => ({ + method: 'POST', + path: '/api/apm/settings/apm-indices/save', + params: { + body: t.partial({ + 'apm_oss.sourcemapIndices': t.string, + 'apm_oss.errorIndices': t.string, + 'apm_oss.onboardingIndices': t.string, + 'apm_oss.spanIndices': t.string, + 'apm_oss.transactionIndices': t.string, + 'apm_oss.metricsIndices': t.string, + 'apm_oss.apmAgentConfigurationIndex': t.string + }) + }, + handler: async (req, { body }) => { + const { server } = core.http; + return await saveApmIndices(server, body); + } +})); 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/server/routes/shareables.ts b/x-pack/legacy/plugins/canvas/server/routes/shareables.ts index 5159cde48b3ef..e8186ceceb47f 100644 --- a/x-pack/legacy/plugins/canvas/server/routes/shareables.ts +++ b/x-pack/legacy/plugins/canvas/server/routes/shareables.ts @@ -16,7 +16,6 @@ import { SHAREABLE_RUNTIME_FILE, SHAREABLE_RUNTIME_NAME, SHAREABLE_RUNTIME_SRC, - SHAREABLE_RUNTIME_OUTPUT, } from '../../shareable_runtime/constants'; import { CoreSetup } from '../shim'; @@ -26,14 +25,13 @@ export function shareableWorkpads(route: CoreSetup['http']['route']) { route({ method: 'GET', path: API_ROUTE_SHAREABLE_RUNTIME, - options: { - files: { - relativeTo: SHAREABLE_RUNTIME_OUTPUT, - }, - }, + handler: { file: { path: SHAREABLE_RUNTIME_FILE, + // The option setting is not for typical use. We're using it here to avoid + // problems in Cloud environments. See elastic/kibana#47405. + confine: false, }, }, }); @@ -42,14 +40,12 @@ export function shareableWorkpads(route: CoreSetup['http']['route']) { route({ method: 'GET', path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, - options: { - files: { - relativeTo: SHAREABLE_RUNTIME_OUTPUT, - }, - }, + handler(_request, handler) { + // The option setting is not for typical use. We're using it here to avoid + // problems in Cloud environments. See elastic/kibana#47405. // @ts-ignore No type for inert Hapi handler - const file = handler.file(SHAREABLE_RUNTIME_FILE); + const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false }); file.type('application/octet-stream'); return file; }, 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" >

- } - 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/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..79c4cc8d4529c 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 @@ -807,30 +807,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 +881,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', + }) ); }); }); @@ -1509,7 +1520,7 @@ describe('editor_frame', () => { query: { query: '', language: 'lucene' }, filters: [], }, - title: 'New visualization', + title: '', type: 'lens', visualizationType: 'testVis', }, @@ -1528,7 +1539,7 @@ describe('editor_frame', () => { query: { query: '', language: 'lucene' }, filters: [], }, - title: 'New visualization', + title: '', type: 'lens', visualizationType: 'testVis', }, @@ -1585,7 +1596,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_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_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..74de7fe5c4fdc 100644 --- a/x-pack/legacy/plugins/lens/public/help_menu_util.tsx +++ b/x-pack/legacy/plugins/lens/public/help_menu_util.tsx @@ -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.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx index 35bb3d2de0192..bd6dd6dca0c5b 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx @@ -320,7 +320,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 +339,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 }))} 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..81e0a214f93e3 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,7 +18,7 @@ import { SavedObjectsClientContract, HttpServiceBase, } from 'src/core/public'; -import { Storage } from 'ui/storage'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IndexPatternPrivateState } from '../types'; jest.mock('ui/new_platform'); @@ -129,11 +129,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, 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..fd4acef41f0bf 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,17 +23,19 @@ 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 { 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..ce30e1195f4ca 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 @@ -354,7 +354,7 @@ export function PopoverEditor(props: PopoverEditorProps) { defaultMessage: 'To use this function, select a field.', })} iconType="sortUp" - > + /> )} {!incompatibleSelectedOperationType && ParamEditor && ( <> @@ -368,6 +368,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/indexpattern.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts index b4f01078f1f78..9fb7be01642e5 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, @@ -414,7 +414,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 +461,8 @@ describe('IndexPattern Data Source', () => { suggestedPriority: 2, }, }; - const api = indexPatternDatasource.getPublicAPI( - { + const api = indexPatternDatasource.getPublicAPI({ + state: { ...initialState, layers: { first: { @@ -465,8 +473,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..bde5ce01aecda 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'; @@ -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({ 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 { 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..9fba3e205dd45 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!; @@ -324,15 +332,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); @@ -350,15 +355,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); @@ -398,15 +400,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); @@ -422,15 +421,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); @@ -467,15 +463,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); @@ -486,15 +479,12 @@ describe('terms', () => { const setStateSpy = jest.fn(); const instance = shallow( ); 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/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/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/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..93b8f93cbd2ff 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; @@ -138,7 +143,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 +176,7 @@ export interface DatasourceDataPanelProps { setState: StateSetter; core: Pick; query: Query; - dateRange: FramePublicAPI['dateRange']; + dateRange: DateRange; filters: Filter[]; } @@ -296,10 +301,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/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/index.js b/x-pack/legacy/plugins/maps/index.js index 3bb9d48741ab9..a8cc328e532e5 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -43,6 +43,7 @@ export function maps(kibana) { const mapConfig = serverConfig.get('map'); return { + showMapVisualizationTypes: serverConfig.get('xpack.maps.showMapVisualizationTypes'), showMapsInspectorAdapter: serverConfig.get('xpack.maps.showMapsInspectorAdapter'), preserveDrawingBuffer: serverConfig.get('xpack.maps.preserveDrawingBuffer'), isEmsEnabled: mapConfig.includeElasticMapsService, @@ -92,6 +93,7 @@ export function maps(kibana) { config(Joi) { return Joi.object({ enabled: Joi.boolean().default(true), + showMapVisualizationTypes: Joi.boolean().default(false), showMapsInspectorAdapter: Joi.boolean().default(false), // flag used in functional testing preserveDrawingBuffer: Joi.boolean().default(false), // flag used in functional testing }).default(); 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.title}

-
- -
-
-
- {this.props.children} -
+export const AuthenticationStatePage: React.SFC = props => ( +
+
+
+ + + + + +

{props.title}

+
+
- ); - } -} +
+
{props.children}
+
+); 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/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/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 55f3fdf1087e4..39db6ff3791df 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3455,8 +3455,6 @@ "xpack.apm.settings.agentConf.configTable.serviceNameColumnLabel": "サービス名", "xpack.apm.settings.agentConf.configurationsPanelTitle": "構成", "xpack.apm.settings.agentConf.createConfigButtonLabel": "構成の作成", - "xpack.apm.settings.agentConf.pageTitle": "設定", - "xpack.apm.settings.agentConf.returnToOverviewLinkLabel": "概要に戻る", "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionsTable.notFoundLabel": "トランザクションが見つかりませんでした。", @@ -5076,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 f822e90ec3973..10ffb2b7d731d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3456,8 +3456,6 @@ "xpack.apm.settings.agentConf.configTable.serviceNameColumnLabel": "服务名称", "xpack.apm.settings.agentConf.configurationsPanelTitle": "配置", "xpack.apm.settings.agentConf.createConfigButtonLabel": "创建配置", - "xpack.apm.settings.agentConf.pageTitle": "设置", - "xpack.apm.settings.agentConf.returnToOverviewLinkLabel": "返回至概览", "xpack.apm.transactionDetails.traceNotFound": "找不到所选跟踪", "xpack.apm.transactionDetails.traceSampleTitle": "跟踪样例", "xpack.apm.transactionsTable.notFoundLabel": "未找到任何事务。", @@ -5077,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"