diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index e1cbac0528b1f..8ad02b7162b6a 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -34,7 +34,7 @@ stage("Kibana Pipeline") { if (!IS_XPACK) { kibanaPipeline.buildOss() if (CI_GROUP == '1') { - runbld "./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh" + runbld("./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1") } } else { kibanaPipeline.buildXpack() @@ -62,18 +62,18 @@ stage("Kibana Pipeline") { def getWorkerFromParams(isXpack, job, ciGroup) { if (!isXpack) { if (job == 'firefoxSmoke') { - return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }) + return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') }) } else if(job == 'visualRegression') { - return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }) + return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }) } else { return kibanaPipeline.getOssCiGroupWorker(ciGroup) } } if (job == 'firefoxSmoke') { - return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }) + return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld('./test/scripts/jenkins_xpack_firefox_smoke.sh', 'Execute xpack-firefoxSmoke') }) } else if(job == 'visualRegression') { - return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }) + return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }) } else { return kibanaPipeline.getXpackCiGroupWorker(ciGroup) } diff --git a/.eslintrc.js b/.eslintrc.js index 16a80f01278a5..daf49d9d08281 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -214,13 +214,6 @@ module.exports = { '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: { @@ -839,6 +832,8 @@ module.exports = { // might be introduced after the other warns are fixed // 'react/jsx-sort-props': 'error', 'react/jsx-tag-spacing': 'error', + // might be introduced after the other warns are fixed + 'react-hooks/exhaustive-deps': 'off', 'require-atomic-updates': 'error', 'rest-spread-spacing': ['error', 'never'], 'symbol-description': 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 94296d076189b..0f1136fd5334b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,11 +28,6 @@ # Canvas /x-pack/legacy/plugins/canvas/ @elastic/kibana-canvas -# Code -/x-pack/legacy/plugins/code/ @teams/code -/x-pack/test/functional/apps/code/ @teams/code -/x-pack/test/api_integration/apis/code/ @teams/code - # Logs & Metrics UI /x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui /x-pack/legacy/plugins/integrations_manager/ @elastic/epm diff --git a/Jenkinsfile b/Jenkinsfile index 8d8579736f639..c002832d4d51a 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,9 +24,9 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'oss-ciGroup10': kibanaPipeline.getOssCiGroupWorker(10), 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), - 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), - 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld './test/scripts/jenkins_accessibility.sh' }), - 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), + 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') }), + 'oss-accessibility': kibanaPipeline.getPostBuildWorker('accessibility', { runbld('./test/scripts/jenkins_accessibility.sh', 'Execute kibana-accessibility') }), + 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') }), ]), 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), @@ -39,9 +39,9 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'xpack-ciGroup8': kibanaPipeline.getXpackCiGroupWorker(8), 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), - 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), - 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld './test/scripts/jenkins_xpack_accessibility.sh' }), - 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), + 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld('./test/scripts/jenkins_xpack_firefox_smoke.sh', 'Execute xpack-firefoxSmoke') }), + 'xpack-accessibility': kibanaPipeline.getPostBuildWorker('xpack-accessibility', { runbld('./test/scripts/jenkins_xpack_accessibility.sh', 'Execute xpack-accessibility') }), + 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld('./test/scripts/jenkins_xpack_visual_regression.sh', 'Execute xpack-visualRegression') }), ]), ]) } diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 5fd3ef5e8ff4b..461d51a3e76e3 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -120,6 +120,7 @@ You should prefer modern language features in a lot of cases, e.g.: * Prefer arrow function over storing `this` (no `const self = this;`) * Prefer template strings over string concatenation * Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()` +* Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities) ### Avoid mutability and state diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index caff7f5b1fdc6..38fceeb47d6fd 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -9,11 +9,12 @@ for displayed decimal values. . Scroll or search for the setting you want to modify. . Enter a new value for the setting. + [float] [[settings-read-only-access]] === [xpack]#Read only access# -When you have insufficient privileges to edit advanced settings, the following -indicator in Kibana will be displayed. The buttons to edit settings won't be visible. +When you have insufficient privileges to edit advanced settings, the following +indicator in Kibana will be displayed. The buttons to edit settings won't be visible. For more information on granting access to Kibana see <>. [role="screenshot"] @@ -25,9 +26,9 @@ image::images/settings-read-only-badge.png[Example of Advanced Settings Manageme WARNING: Modifying a setting can affect {kib} performance and cause problems that are -difficult to diagnose. Setting a property value to a blank field reverts +difficult to diagnose. Setting a property value to a blank field reverts to the default behavior, which might not be -compatible with other configuration settings. Deleting a custom setting +compatible with other configuration settings. Deleting a custom setting removes it from {kib} permanently. @@ -44,7 +45,7 @@ removes it from {kib} permanently. adapt to the interval between measurements. Keys are http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals]. `dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser. `dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type]. -`defaultIndex`:: The index to access if no index is set. The default is `null`. +`defaultIndex`:: The index to access if no index is set. The default is `null`. `fields:popularLimit`:: The top N most popular fields to show. `filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. `filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. @@ -59,46 +60,46 @@ mentioned use "\_default_". `histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values when necessary. `history:limit`:: In fields that have history, such as query inputs, show this many recent values. -`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names, +`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names, look for this many recent matching patterns from which to query the field mapping. `indexPattern:placeholder`:: The default placeholder value to use in Management > Index Patterns > Create Index Pattern. -`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields +`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields into the document when displaying it. `metrics:max_buckets`:: The maximum numbers of buckets that a single -data source can return. This might arise when the user selects a +data source can return. This might arise when the user selects a short interval (for example, 1s) for a long time period (1 year). -`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character -in a query clause. Only applies when experimental query features are -enabled in the query bar. To disallow leading wildcards in Lucene queries, +`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character +in a query clause. Only applies when experimental query features are +enabled in the query bar. To disallow leading wildcards in Lucene queries, use `query:queryString:options`. `query:queryString:options`:: Options for the Lucene query string parser. Only used when "Query language" is set to Lucene. -`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. +`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects. The default value is 1000. Do not set above 10000. -`savedObjects:perPage`:: The number of objects to show on each page of the +`savedObjects:perPage`:: The number of objects to show on each page of the list of saved objects. The default is 5. `search:queryLanguage`:: The query language to use in the query bar. -Choices are <>, a language built specifically for {kib}, and the <>, a language built specifically for {kib}, and the <>. -`shortDots:enable`:: Set this property to `true` to shorten long +`shortDots:enable`:: Set this property to `true` to shorten long field names in visualizations. For example, show `f.b.baz` instead of `foo.bar.baz`. `sort:options`:: Options for the Elasticsearch {ref}/search-request-body.html#request-body-search-sort[sort] parameter. -`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the -URL, which can lead to problems when there is a lot of state information, -and the URL gets very long. -Enabling this setting stores part of the URL in your browser session to keep the +`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the +URL, which can lead to problems when there is a lot of state information, +and the URL gets very long. +Enabling this setting stores part of the URL in your browser session to keep the URL short. `theme:darkMode`:: Set to `true` to enable a dark mode for the {kib} UI. You must refresh the page to apply the setting. -`timepicker:quickRanges`:: The list of ranges to show in the Quick section of -the time filter. This should be an array of objects, with each object containing -`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), +`timepicker:quickRanges`:: The list of ranges to show in the Quick section of +the time filter. This should be an array of objects, with each object containing +`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]), and `display` (the title to be displayed). `timepicker:refreshIntervalDefaults`:: The default refresh interval for the time filter. Example: `{ "display": "15 seconds", "pause": true, "value": 15000 }`. `timepicker:timeDefaults`:: The default selection in the time filter. `truncate:maxHeight`:: The maximum height that a cell occupies in a table. Set to 0 to disable truncation. -`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as +`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as cluster alert notifications from Monitoring. @@ -107,7 +108,7 @@ cluster alert notifications from Monitoring. === Accessibility settings [horizontal] -`accessibility:disableAnimations`:: Turns off all unnecessary animations in the +`accessibility:disableAnimations`:: Turns off all unnecessary animations in the {kib} UI. Refresh the page to apply the changes. [float] @@ -124,21 +125,21 @@ cluster alert notifications from Monitoring. [horizontal] `context:defaultSize`:: The number of surrounding entries to display in the context view. The default value is 5. `context:step`:: The number by which to increment or decrement the context size. The default value is 5. -`context:tieBreakerFields`:: A comma-separated list of fields to use -for breaking a tie between documents that have the same timestamp value. The first +`context:tieBreakerFields`:: A comma-separated list of fields to use +for breaking a tie between documents that have the same timestamp value. The first field that is present and sortable in the current index pattern is used. `defaultColumns`:: The columns that appear by default on the Discover page. -The default is `_source`. -`discover:aggs:terms:size`:: The number terms that are visualized when clicking +The default is `_source`. +`discover:aggs:terms:size`:: The number terms that are visualized when clicking the Visualize button in the field drop down. The default is `20`. `discover:sampleSize`:: The number of rows to show in the Discover table. `discover:sort:defaultOrder`:: The default sort direction for time-based index patterns. -`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. +`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads. This setting does not have an effect when loading a saved search. `doc_table:hideTimeColumn`:: Hides the "Time" column in Discover and in all saved searches on dashboards. -`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. +`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards. Highlighting slows requests when -working on big documents. +working on big documents. @@ -150,14 +151,14 @@ working on big documents. [horizontal] `notifications:banner`:: A custom banner intended for temporary notices to all users. Supports https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown]. -`notifications:lifetime:banner`:: The duration, in milliseconds, for banner -notification displays. The default value is 3000000. Set this field to `Infinity` +`notifications:lifetime:banner`:: The duration, in milliseconds, for banner +notification displays. The default value is 3000000. Set this field to `Infinity` to disable banner notifications. -`notifications:lifetime:error`:: The duration, in milliseconds, for error +`notifications:lifetime:error`:: The duration, in milliseconds, for error notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications. -`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. +`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. The default value is 5000. Set this field to `Infinity` to disable information notifications. -`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification +`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications. @@ -175,8 +176,8 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa === Rollup settings [horizontal] -`rollups:enableIndexPatterns`:: Enables the creation of index patterns that -capture rollup indices, which in turn enables visualizations based on rollup data. +`rollups:enableIndexPatterns`:: Enables the creation of index patterns that +capture rollup indices, which in turn enables visualizations based on rollup data. Refresh the page to apply the changes. @@ -188,22 +189,22 @@ Refresh the page to apply the changes. `courier:batchSearches`:: When disabled, dashboard panels will load individually, and search requests will terminate when users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and searches will not terminate. -`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] +`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] to use when `courier:setRequestPreference` is set to "custom". -`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. +`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization. Useful when dashboards consist of visualizations from multiple index patterns. -`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] -setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this +`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests] +setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this config and use the {es} default. `courier:setRequestPreference`:: Enables you to set which shards handle your search requests. -* *Session ID:* Restricts operations to execute all search requests on the same shards. +* *Session ID:* Restricts operations to execute all search requests on the same shards. This has the benefit of reusing shard caches across requests. -* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` +* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference` to customize your preference value. -* *None:* Do not set a preference. This might provide better performance -because requests can be spread across all shard copies. However, results might +* *None:* Do not set a preference. This might provide better performance +because requests can be spread across all shard copies. However, results might be inconsistent because different shards might be in different refresh states. -`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. +`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results. Searching through frozen indices might increase the search time. This setting is off by default. Users must opt-in to include frozen indices. @@ -212,8 +213,8 @@ might increase the search time. This setting is off by default. Users must opt-i === SIEM settings [horizontal] -`siem:defaultAnomalyScore`:: The threshold above which anomalies are displayed in the SIEM app. -`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. +`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app. +`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. `siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds. `siem:timeDefaults`:: The default period of time in the SIEM time filter. @@ -226,16 +227,16 @@ might increase the search time. This setting is off by default. Users must opt-i `timelion:default_rows`:: The default number of rows to use on a Timelion sheet. `timelion:es.default_index`:: The default index when using the `.es()` query. `timelion:es.timefield`:: The default field containing a timestamp when using the `.es()` query. -`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host -in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be +`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host +in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be selected from a whitelist configured in the `kibana.yml` under `timelion.graphiteUrls`. `timelion:max_buckets`:: The maximum number of buckets a single data source can return. This value is used for calculating automatic intervals in visualizations. `timelion:min_interval`:: The smallest interval to calculate when using "auto". `timelion:quandl.key`:: [experimental] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com]. -`timelion:showTutorial`:: Shows the Timelion tutorial +`timelion:showTutorial`:: Shows the Timelion tutorial to users when they first open the Timelion app. -`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, +`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations, this is the number of buckets to try to represent. @@ -246,18 +247,18 @@ this is the number of buckets to try to represent. [horizontal] `visualization:colorMapping`:: Maps values to specified colors in visualizations. -`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed -when highlighting another element of the chart. The lower this number, the more +`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed +when highlighting another element of the chart. The lower this number, the more the highlighted element stands out. This must be a number between 0 and 1. -`visualization:loadingDelay`:: The time to wait before dimming visualizations +`visualization:loadingDelay`:: The time to wait before dimming visualizations during a query. -`visualization:regionmap:showWarnings`:: Shows +`visualization:regionmap:showWarnings`:: Shows a warning in a region map when terms cannot be joined to a shape. `visualization:tileMap:WMSdefaults`:: The default properties for the WMS map server support in the coordinate map. `visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high, -and 12 is the maximum. See this +and 12 is the maximum. See this {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions]. -`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. +`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations. If disabled, only visualizations that are considered production-ready are available to the user. @@ -265,6 +266,5 @@ If disabled, only visualizations that are considered production-ready are availa [[kibana-telemetry-settings]] === Usage data settings -Helps improve the Elastic Stack by providing usage statistics for +Helps improve the Elastic Stack by providing usage statistics for basic features. This data will not be shared outside of Elastic. - diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 0f17ffcf26930..f4434ea7a09f4 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -32,7 +32,8 @@ strongly recommend that you keep the default CSP rules that ship with Kibana. `csp.strict:`:: *Default: `true`* Blocks access to Kibana to any browser that does not enforce even rudimentary CSP rules. In practice, this will disable -support for older, less safe browsers like Internet Explorer. +support for older, less safe browsers like Internet Explorer. +See <> for more information. `csp.warnLegacyBrowsers:`:: *Default: `true`* Shows a warning message after loading Kibana to any browser that does not enforce even rudimentary CSP rules, @@ -319,6 +320,18 @@ supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2 setting this to `true` enables unauthenticated users to access the Kibana server status API and status page. +`telemetry.allowChangingOptInStatus`:: *Default: true*. If `true`, +users are able to change the telemetry setting at a later time in +<>. If `false`, +{kib} looks at the value of `telemetry.optIn` to determine whether to send +telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn` +cannot be `false` at the same time. + +`telemetry.optIn`:: *Default: true* If `true`, telemetry data is sent to Elastic. + If `false`, collection of telemetry data is disabled. + To enable telemetry and prevent users from disabling it, + set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. + `vega.enableExternalUrls:`:: *Default: false* Set this value to true to allow Vega to use any URL to access external data sources and images. If false, Vega can only get data from Elasticsearch. `xpack.license_management.enabled`:: *Default: true* Set this value to false to diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index ce0259c690b82..8c03032bb8ac3 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -2,7 +2,15 @@ == Upgrading {kib} Depending on the {kib} version you're upgrading from, the upgrade process to 7.0 -varies. +varies. + +NOTE: {kib} upgrades automatically when starting a new version, as described in +<>. +Although you do not need to manually back up {kib} before upgrading, we recommend +that you have a backup on hand. You can use +<> to back up {kib} +data by targeting `.kibana*` indices. If you are using the Reporting plugin, +you can also target `.reporting*` indices. [float] [[upgrade-before-you-begin]] @@ -12,7 +20,7 @@ Before you upgrade {kib}: * Consult the <>. * Before you upgrade production servers, test the upgrades in a dev environment. -* Backup your data with {es} {ref}/modules-snapshots.html[snapshots]. +* Back up your data with {es} {ref}/modules-snapshots.html[snapshots]. To roll back to an earlier version, you **must** have a backup of your data. * If you are using custom plugins, check that a compatible version is available. diff --git a/docs/user/graph/configuring-graph.asciidoc b/docs/user/graph/configuring-graph.asciidoc index d521f9d8d2846..5427bdee79ecb 100644 --- a/docs/user/graph/configuring-graph.asciidoc +++ b/docs/user/graph/configuring-graph.asciidoc @@ -59,14 +59,14 @@ image::user/graph/images/graph-read-only-badge.png[Example of Graph's read only [discrete] [[disable-drill-down]] -=== Disabling drill down configuration +=== Disabling drilldown configuration -By default, users can configure _drill down_ URLs to display additional +By default, users can configure _drilldown_ URLs to display additional information about a selected vertex in a new browser window. For example, -you could configure a drill down URL to perform a web search for the selected +you could configure a drilldown URL to perform a web search for the selected vertex term. -To prevent users from adding drill down URLs, set +To prevent users from adding drilldown URLs, set `xpack.graph.canEditDrillDownUrls` to `false` in `kibana.yml`: [source,yaml] diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 19f3df341338e..7b3bd10147966 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -2,30 +2,30 @@ [[graph-getting-started]] == Using Graph -Graph is automatically enabled in {es} and {kib}. +You must index data into {es} before you can create a graph. +<> or get started with a <>. +[float] [[exploring-connections]] -To start exploring connections in your data: +=== Graph connections in your data -. From the side navigation, open the graph explorer. - -. Select an index pattern to specify what indices you want to explore. +. From the side navigation, open *Graph*. + -For example, if you are indexing log data with Logstash, you could select the -`logstash-*` index pattern to visualize connections within the log entries. +If this is your first graph, follow the prompts to create it. +For subsequent graphs, click *New*. + +. Select a data source to explore. -. Select one or more multi-value fields that contain the terms you want to +. Add one or more multi-value fields that contain the terms you want to graph. + -The vertices in the graph are selected from these terms. If you're -visualizing connections between Apache log entries, you could select the -`url.raw` field and the `geo.src` field so you can look at which pages are -being accessed from different locations. +The vertices in the graph are selected from these terms. . Enter a search query to discover relationships between terms in the selected fields. + -For example, to generate a graph of the successful requests to +For example, if you are using the {kib} sample web logs data set, and you want +to generate a graph of the successful requests to particular pages from different locations, you could search for the 200 response code. The weight of the connection between two vertices indicates how strongly they are related. @@ -38,25 +38,86 @@ image::user/graph/images/graph-url-connections.png["URL connections"] [role="screenshot"] image::user/graph/images/graph-link-summary.png["Link summary"] -. Use the toolbar buttons to explore +. Use the control bar on the right to explore additional connections: + -* To display additional vertices that connect to your graph, click Expand -image:user/graph/images/graph-expand-button.jpg[Expand Selection]. +* To display additional vertices that connect to your graph, click the expand icon +image:user/graph/images/graph-expand-button.png[Expand Selection]. * To display additional -connections between the displayed vertices, click Link -image:user/graph/images/graph-link-button.jpg[Add links to existing terms] +connections between the displayed vertices, click the link icon +image:user/graph/images/graph-link-button.png[Add links to existing terms]. * To explore a particular area of the -graph, select the vertices you are interested in, and click Expand or Link. -* To step back through your changes to the graph, click Undo -image:user/graph/images/graph-undo-button.jpg[Undo]. +graph, select the vertices you are interested in, and then click expand or link. +* To step back through your changes to the graph, click undo +image:user/graph/images/graph-undo-button.png[Undo] and redo +image:user/graph/images/graph-redo-button.png[Redo]. . To see more relationships in your data, submit additional queries. + [role="screenshot"] image::user/graph/images/graph-add-query.png["Adding networks"] +. *Save* your graph. + +[float] +[[style-vertex-properties]] +=== Style vertex properties + +Each vertex has a color, icon, and label. To change +the color or icon of all vertices +of a certain field, click the field badge below the search bar, and then +select *Edit settings*. + +To change the color and label of selected vertices, +click the style icon image:user/graph/images/graph-style-button.png[Style] +in the control bar on the right. + + +[float] +[[edit-graph-settings]] +=== Edit graph settings + +By default, *Graph* is configured to tune out noise in your data. +If this isn't a good fit for your data, use *Settings > Advanced settings* +to adjust the way *Graph* queries your data. You can tune the graph to show +only the results relevant to you and to improve performance. +For more information, see <>. + +You can configure the number of vertices that a search or +expand operation adds to the graph. +By default, only the five most relevant terms for any given field are added +at a time. This keeps the graph from overflowing. To increase this number, click +a field below the search bar, select *Edit Settings*, and change *Terms per hop*. + +[float] +[[graph-block-terms]] +=== Block terms from the graph +Documents that match a blocked term are not allowed in the graph. +To block a term, select its vertex and click +the block icon +image:user/graph/images/graph-block-button.png[Block selection] +in the control panel. +For a list of blocked terms, go to *Settings > Blocked terms*. + +[float] +[[graph-drill-down]] +=== Drill down into raw documents +With drilldowns, you can display additional information about a +selected vertex in a new browser window. For example, you might +configure a drilldown URL to perform a web search for the selected vertex term. + +Use the drilldown icon image:user/graph/images/graph-info-icon.png[Drilldown selection] +in the control panel to show the drilldown buttons for the selected vertices. +To configure drilldowns, go to *Settings > Drilldowns*. See also +<>. -NOTE: By default, when you submit a search query, Graph searches all available -fields. You can constrain your search to a particular field using the Lucene -query syntax. For example, `machine.os: osx`. +[float] +[[graph-run-layout]] +=== Run and pause layout +Graph uses a "force layout", where vertices behave like magnets, +pushing off of one another. By default, when you add a new vertex to +the graph, all vertices begin moving. In some cases, the movement might +go on for some time. To freeze the current vertex position, +click the pause icon +image:user/graph/images/graph-pause-button.png[Block selection] +in the control panel. diff --git a/docs/user/graph/images/graph-add-query.png b/docs/user/graph/images/graph-add-query.png old mode 100644 new mode 100755 index 7f0cea7fb3aa4..0b978462ae75e Binary files a/docs/user/graph/images/graph-add-query.png and b/docs/user/graph/images/graph-add-query.png differ diff --git a/docs/user/graph/images/graph-block-button.png b/docs/user/graph/images/graph-block-button.png new file mode 100755 index 0000000000000..b378883572f3c Binary files /dev/null and b/docs/user/graph/images/graph-block-button.png differ diff --git a/docs/user/graph/images/graph-expand-button.jpg b/docs/user/graph/images/graph-expand-button.jpg deleted file mode 100644 index 296ba9d25ad5e..0000000000000 Binary files a/docs/user/graph/images/graph-expand-button.jpg and /dev/null differ diff --git a/docs/user/graph/images/graph-expand-button.png b/docs/user/graph/images/graph-expand-button.png new file mode 100755 index 0000000000000..7d9816a8adb93 Binary files /dev/null and b/docs/user/graph/images/graph-expand-button.png differ diff --git a/docs/user/graph/images/graph-info-icon.png b/docs/user/graph/images/graph-info-icon.png new file mode 100644 index 0000000000000..c1e89384b738c Binary files /dev/null and b/docs/user/graph/images/graph-info-icon.png differ diff --git a/docs/user/graph/images/graph-link-button.jpg b/docs/user/graph/images/graph-link-button.jpg deleted file mode 100644 index 7c7f177011576..0000000000000 Binary files a/docs/user/graph/images/graph-link-button.jpg and /dev/null differ diff --git a/docs/user/graph/images/graph-link-button.png b/docs/user/graph/images/graph-link-button.png new file mode 100755 index 0000000000000..43ac85537b9bb Binary files /dev/null and b/docs/user/graph/images/graph-link-button.png differ diff --git a/docs/user/graph/images/graph-link-summary.png b/docs/user/graph/images/graph-link-summary.png old mode 100644 new mode 100755 index e669a2d79e0e3..ca07f350f975e Binary files a/docs/user/graph/images/graph-link-summary.png and b/docs/user/graph/images/graph-link-summary.png differ diff --git a/docs/user/graph/images/graph-pause-button.png b/docs/user/graph/images/graph-pause-button.png new file mode 100755 index 0000000000000..e05418d7df7f9 Binary files /dev/null and b/docs/user/graph/images/graph-pause-button.png differ diff --git a/docs/user/graph/images/graph-redo-button.png b/docs/user/graph/images/graph-redo-button.png new file mode 100755 index 0000000000000..5adcfa478652e Binary files /dev/null and b/docs/user/graph/images/graph-redo-button.png differ diff --git a/docs/user/graph/images/graph-style-button.png b/docs/user/graph/images/graph-style-button.png new file mode 100644 index 0000000000000..5015e39b8095a Binary files /dev/null and b/docs/user/graph/images/graph-style-button.png differ diff --git a/docs/user/graph/images/graph-undo-button.jpg b/docs/user/graph/images/graph-undo-button.jpg deleted file mode 100644 index 5c87a2809a480..0000000000000 Binary files a/docs/user/graph/images/graph-undo-button.jpg and /dev/null differ diff --git a/docs/user/graph/images/graph-undo-button.png b/docs/user/graph/images/graph-undo-button.png new file mode 100755 index 0000000000000..e8a72b8b358c5 Binary files /dev/null and b/docs/user/graph/images/graph-undo-button.png differ diff --git a/docs/user/graph/images/graph-url-connections.png b/docs/user/graph/images/graph-url-connections.png old mode 100644 new mode 100755 index 737c081cd691b..94c9c4341d9b8 Binary files a/docs/user/graph/images/graph-url-connections.png and b/docs/user/graph/images/graph-url-connections.png differ diff --git a/docs/user/graph/troubleshooting.asciidoc b/docs/user/graph/troubleshooting.asciidoc index ff3568ed41afa..4ce287396f809 100644 --- a/docs/user/graph/troubleshooting.asciidoc +++ b/docs/user/graph/troubleshooting.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[graph-troubleshooting]] -== Graph Troubleshooting +== Graph troubleshooting ++++ Troubleshooting ++++ diff --git a/docs/user/monitoring/monitoring-troubleshooting.asciidoc b/docs/user/monitoring/monitoring-troubleshooting.asciidoc index 12a775ad047b0..7e1d6f94f15fa 100644 --- a/docs/user/monitoring/monitoring-troubleshooting.asciidoc +++ b/docs/user/monitoring/monitoring-troubleshooting.asciidoc @@ -23,6 +23,23 @@ You cannot monitor a version 6.3 or later cluster from {kib} version 6.2 or earl To resolve this issue, upgrade {kib} to 6.3 or later. See {stack-ref}/upgrading-elastic-stack.html[Upgrading the {stack}]. +[float] +=== {filebeat} index is corrupt + +*Symptoms:* + +The *Stack Monitoring* application displays a Monitoring Request error indicating +that an illegal argument exception has occurred because fielddata is disabled on +text fields by default. + +*Resolution* + + . Stop all your {filebeat} instances. + . Delete indices beginning with `filebeat-$VERSION`, where `VERSION` corresponds + to the version of {filebeat} running. +. Restart all your {filebeat} instances. + + [float] === No monitoring data is visible in {kib} diff --git a/package.json b/package.json index 8fa9bf1847eb8..b0a74a4a5e04f 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "**/@types/react": "16.8.3", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", - "**/typescript": "3.5.3", + "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9" @@ -327,6 +327,7 @@ "@types/pngjs": "^3.3.2", "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", + "@types/reach__router": "^1.2.6", "@types/react": "^16.8.0", "@types/react-dom": "^16.8.0", "@types/react-redux": "^6.0.6", @@ -346,8 +347,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.5.0", - "@typescript-eslint/parser": "^2.5.0", + "@typescript-eslint/eslint-plugin": "^2.7.0", + "@typescript-eslint/parser": "^2.7.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -360,7 +361,7 @@ "chance": "1.0.18", "cheerio": "0.22.0", "chokidar": "3.2.1", - "chromedriver": "^77.0.0", + "chromedriver": "78.0.1", "classnames": "2.2.6", "dedent": "^0.7.0", "delete-empty": "^2.0.0", @@ -444,7 +445,7 @@ "supertest": "^3.1.0", "supertest-as-promised": "^4.0.2", "tree-kill": "^1.2.1", - "typescript": "3.5.3", + "typescript": "3.7.2", "typings-tester": "^0.3.2", "vinyl-fs": "^3.0.3", "xml2js": "^0.4.22", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index b5079a49c8385..c67629f058d5a 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.5.0", - "@typescript-eslint/parser": "^2.5.0", + "@typescript-eslint/eslint-plugin": "^2.7.0", + "@typescript-eslint/parser": "^2.7.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index b0ac86b465a62..f59fbf4720835 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -17,6 +17,6 @@ "@babel/cli": "7.5.5", "@kbn/dev-utils": "1.0.0", "@kbn/babel-preset": "1.0.0", - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index d2bad41e8de86..d1b7bc20dd9f9 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -27,6 +27,13 @@ const plugins = [ // // See https://github.com/babel/proposals/issues/12 for progress require.resolve('@babel/plugin-proposal-class-properties'), + + // Optional Chaining proposal is stage 3 (https://github.com/tc39/proposal-optional-chaining) + // Need this since we are using TypeScript 3.7+ + require.resolve('@babel/plugin-proposal-optional-chaining'), + // Nullish coalescing proposal is stage 3 (https://github.com/tc39/proposal-nullish-coalescing) + // Need this since we are using TypeScript 3.7+ + require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), ]; const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test'; diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index c22cf175b29e5..1913301e21a76 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -5,6 +5,8 @@ "license": "Apache-2.0", "dependencies": { "@babel/plugin-proposal-class-properties": "^7.5.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", + "@babel/plugin-proposal-optional-chaining": "^7.6.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.5.0", "@babel/preset-env": "^7.5.5", diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index 4880fb4ebfdee..71c0ae4bff1f9 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -10,7 +10,7 @@ "kbn:bootstrap": "yarn build" }, "devDependencies": { - "typescript": "3.5.3" + "typescript": "3.7.2" }, "peerDependencies": { "joi": "^13.5.2", diff --git a/packages/kbn-config-schema/src/types/__snapshots__/uri_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/uri_type.test.ts.snap index 2836c80f5b5ad..81fafdb4a7b33 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/uri_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/uri_type.test.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`#scheme returns error when shorter string 1`] = `"expected URI with scheme [http|https] but but got [ftp://elastic.co]."`; +exports[`#scheme returns error when shorter string 1`] = `"expected URI with scheme [http|https] but got [ftp://elastic.co]."`; -exports[`#scheme returns error when shorter string 2`] = `"expected URI with scheme [http|https] but but got [file:///kibana.log]."`; +exports[`#scheme returns error when shorter string 2`] = `"expected URI with scheme [http|https] but got [file:///kibana.log]."`; exports[`#validate throws when returns string 1`] = `"validator failure"`; diff --git a/packages/kbn-config-schema/src/types/uri_type.ts b/packages/kbn-config-schema/src/types/uri_type.ts index f283554de527e..df1ce9e869d3b 100644 --- a/packages/kbn-config-schema/src/types/uri_type.ts +++ b/packages/kbn-config-schema/src/types/uri_type.ts @@ -36,7 +36,7 @@ export class URIType extends Type { case 'string.base': return `expected value of type [string] but got [${typeDetect(value)}].`; case 'string.uriCustomScheme': - return `expected URI with scheme [${scheme}] but but got [${value}].`; + return `expected URI with scheme [${scheme}] but got [${value}].`; case 'string.uri': return `value is [${value}] but it must be a valid URI (see RFC 3986).`; } diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index e8781f6d901d9..09753afeb120f 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -21,7 +21,7 @@ "tslib": "^1.9.3" }, "devDependencies": { - "typescript": "3.5.3", + "typescript": "3.7.2", "@kbn/expect": "1.0.0", "chance": "1.0.18" } diff --git a/packages/kbn-elastic-idx/package.json b/packages/kbn-elastic-idx/package.json index abfaea75357dd..9532983942d6b 100644 --- a/packages/kbn-elastic-idx/package.json +++ b/packages/kbn-elastic-idx/package.json @@ -20,7 +20,7 @@ "@babel/core": "^7.5.5", "@babel/plugin-transform-async-to-generator": "^7.5.0", "jest": "^24.9.0", - "typescript": "3.5.3" + "typescript": "3.7.2" }, "jest": { "testEnvironment": "node" diff --git a/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js b/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js deleted file mode 100644 index d9f559987f58b..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js +++ /dev/null @@ -1,64 +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 _ from 'lodash'; -import { migrateFilter } from '../migrate_filter'; - -describe('migrateFilter', function () { - - const oldMatchPhraseFilter = { - match: { - fieldFoo: { - query: 'foobar', - type: 'phrase' - } - } - }; - - const newMatchPhraseFilter = { - match_phrase: { - fieldFoo: { - query: 'foobar' - } - } - }; - - // https://github.com/elastic/elasticsearch/pull/17508 - it('should migrate match filters of type phrase', function () { - const migratedFilter = migrateFilter(oldMatchPhraseFilter); - expect(_.isEqual(migratedFilter, newMatchPhraseFilter)).to.be(true); - }); - - it('should not modify the original filter', function () { - const oldMatchPhraseFilterCopy = _.clone(oldMatchPhraseFilter, true); - migrateFilter(oldMatchPhraseFilter); - expect(_.isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).to.be(true); - }); - - it('should return the original filter if no migration is necessary', function () { - const originalFilter = { - match_all: {} - }; - const migratedFilter = migrateFilter(originalFilter); - expect(migratedFilter).to.be(originalFilter); - expect(_.isEqual(migratedFilter, originalFilter)).to.be(true); - }); - -}); diff --git a/packages/kbn-es-query/src/es_query/__tests__/from_filters.js b/packages/kbn-es-query/src/es_query/__tests__/from_filters.js deleted file mode 100644 index 676992e4dddc8..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/from_filters.js +++ /dev/null @@ -1,151 +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 { buildQueryFromFilters } from '../from_filters'; - -describe('build query', function () { - describe('buildQueryFromFilters', function () { - it('should return the parameters of an Elasticsearch bool query', function () { - const result = buildQueryFromFilters([]); - const expected = { - must: [], - filter: [], - should: [], - must_not: [], - }; - expect(result).to.eql(expected); - }); - - it('should transform an array of kibana filters into ES queries combined in the bool clauses', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all' }, - }, - { - exists: { field: 'foo' }, - meta: { type: 'exists' }, - }, - ]; - - const expectedESQueries = [ - { match_all: {} }, - { exists: { field: 'foo' } }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should remove disabled filters', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all', negate: true, disabled: true }, - }, - ]; - - const expectedESQueries = []; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - }); - - it('should remove falsy filters', function () { - const filters = [null, undefined]; - - const expectedESQueries = []; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - expect(result.must).to.eql(expectedESQueries); - }); - - it('should place negated filters in the must_not clause', function () { - const filters = [ - { - match_all: {}, - meta: { type: 'match_all', negate: true }, - }, - ]; - - const expectedESQueries = [{ match_all: {} }]; - - const result = buildQueryFromFilters(filters); - - expect(result.must_not).to.eql(expectedESQueries); - }); - - it('should translate old ES filter syntax into ES 5+ query objects', function () { - const filters = [ - { - query: { exists: { field: 'foo' } }, - meta: { type: 'exists' }, - }, - ]; - - const expectedESQueries = [ - { - exists: { field: 'foo' }, - }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should migrate deprecated match syntax', function () { - const filters = [ - { - query: { match: { extension: { query: 'foo', type: 'phrase' } } }, - meta: { type: 'phrase' }, - }, - ]; - - const expectedESQueries = [ - { - match_phrase: { extension: { query: 'foo' } }, - }, - ]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - - it('should not add query:queryString:options to query_string filters', function () { - const filters = [ - { - query: { query_string: { query: 'foo' } }, - meta: { type: 'query_string' }, - }, - ]; - const expectedESQueries = [{ query_string: { query: 'foo' } }]; - - const result = buildQueryFromFilters(filters); - - expect(result.filter).to.eql(expectedESQueries); - }); - }); -}); diff --git a/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js b/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js deleted file mode 100644 index 4361659021bd5..0000000000000 --- a/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js +++ /dev/null @@ -1,87 +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 { buildQueryFromLucene } from '../from_lucene'; -import { decorateQuery } from '../decorate_query'; -import { luceneStringToDsl } from '../lucene_string_to_dsl'; - -describe('build query', function () { - - describe('buildQueryFromLucene', function () { - - it('should return the parameters of an Elasticsearch bool query', function () { - const result = buildQueryFromLucene(); - const expected = { - must: [], - filter: [], - should: [], - must_not: [], - }; - expect(result).to.eql(expected); - }); - - it('should transform an array of lucene queries into ES queries combined in the bool\'s must clause', function () { - const queries = [ - { query: 'foo:bar', language: 'lucene' }, - { query: 'bar:baz', language: 'lucene' }, - ]; - - const expectedESQueries = queries.map( - (query) => { - return decorateQuery(luceneStringToDsl(query.query), {}); - } - ); - - const result = buildQueryFromLucene(queries, {}); - - expect(result.must).to.eql(expectedESQueries); - }); - - it('should also accept queries in ES query DSL format, simply passing them through', function () { - const queries = [ - { query: { match_all: {} }, language: 'lucene' }, - ]; - - const result = buildQueryFromLucene(queries, {}); - - expect(result.must).to.eql([queries[0].query]); - }); - - }); - - it('should accept a date format in the decorated queries and combine that into the bool\'s must clause', function () { - const queries = [ - { query: 'foo:bar', language: 'lucene' }, - { query: 'bar:baz', language: 'lucene' }, - ]; - const dateFormatTZ = 'America/Phoenix'; - - const expectedESQueries = queries.map( - (query) => { - return decorateQuery(luceneStringToDsl(query.query), {}, dateFormatTZ); - } - ); - - const result = buildQueryFromLucene(queries, {}, dateFormatTZ); - - expect(result.must).to.eql(expectedESQueries); - }); - -}); diff --git a/packages/kbn-es-query/src/es_query/index.d.ts b/packages/kbn-es-query/src/es_query/index.d.ts deleted file mode 100644 index 9510a18441e53..0000000000000 --- a/packages/kbn-es-query/src/es_query/index.d.ts +++ /dev/null @@ -1,39 +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. - */ - -export function buildQueryFromFilters(filters: unknown[], indexPattern: unknown): unknown; -export function buildEsQuery( - indexPattern: unknown, - queries: unknown, - filters: unknown, - config?: { - allowLeadingWildcards: boolean; - queryStringOptions: unknown; - ignoreFilterIfFieldNotInIndex: boolean; - dateFormatTZ?: string | null; - } -): unknown; -export function getEsQueryConfig(config: { - get: (name: string) => unknown; -}): { - allowLeadingWildcards: boolean; - queryStringOptions: unknown; - ignoreFilterIfFieldNotInIndex: boolean; - dateFormatTZ?: string | null; -}; diff --git a/packages/kbn-es-query/src/es_query/migrate_filter.js b/packages/kbn-es-query/src/es_query/migrate_filter.js deleted file mode 100644 index b74fc485a6184..0000000000000 --- a/packages/kbn-es-query/src/es_query/migrate_filter.js +++ /dev/null @@ -1,49 +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 { getConvertedValueForField } from '../utils/filters'; - -export function migrateFilter(filter, indexPattern) { - if (filter.match) { - const fieldName = Object.keys(filter.match)[0]; - - - if (isMatchPhraseFilter(filter, fieldName)) { - const params = _.get(filter, ['match', fieldName]); - if (indexPattern) { - const field = indexPattern.fields.find(f => f.name === fieldName); - if (field) { - params.query = getConvertedValueForField(field, params.query); - } - } - return { - match_phrase: { - [fieldName]: _.omit(params, 'type'), - }, - }; - } - } - - return filter; -} - -function isMatchPhraseFilter(filter, fieldName) { - return _.get(filter, ['match', fieldName, 'type']) === 'phrase'; -} diff --git a/packages/kbn-es-query/src/index.d.ts b/packages/kbn-es-query/src/index.d.ts index c06cef6367fe7..79e6903b18644 100644 --- a/packages/kbn-es-query/src/index.d.ts +++ b/packages/kbn-es-query/src/index.d.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './es_query'; export * from './kuery'; diff --git a/packages/kbn-es-query/src/index.js b/packages/kbn-es-query/src/index.js index 963999bd0999b..79e6903b18644 100644 --- a/packages/kbn-es-query/src/index.js +++ b/packages/kbn-es-query/src/index.js @@ -18,4 +18,3 @@ */ export * from './kuery'; -export * from './es_query'; diff --git a/packages/kbn-es-query/src/kuery/ast/ast.d.ts b/packages/kbn-es-query/src/kuery/ast/ast.d.ts index 06f4940e8ed3b..ef3d0ee828874 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.d.ts +++ b/packages/kbn-es-query/src/kuery/ast/ast.d.ts @@ -25,18 +25,26 @@ import { JsonObject } from '..'; export type KueryNode = any; +export type DslQuery = any; + export interface KueryParseOptions { helpers: { [key: string]: any; }; startRule: string; + allowLeadingWildcards: boolean; } export function fromKueryExpression( - expression: string, - parseOptions?: KueryParseOptions + expression: string | DslQuery, + parseOptions?: Partial ): KueryNode; -export function toElasticsearchQuery(node: KueryNode, indexPattern?: any): JsonObject; +export function toElasticsearchQuery( + node: KueryNode, + indexPattern?: any, + config?: Record, + context?: Record +): JsonObject; export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean; diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 8a88626bffbe8..3e25ceb8714df 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -21,7 +21,7 @@ "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0", - "typescript": "3.5.3" + "typescript": "3.7.2" }, "dependencies": { "intl-format-cache": "^2.1.0", diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index ac46dd02757cf..2f9b177be6532 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -58,7 +58,7 @@ "strip-ansi": "^4.0.0", "strong-log-transformer": "^2.1.0", "tempy": "^0.3.0", - "typescript": "3.5.3", + "typescript": "3.7.2", "unlazy-loader": "^0.1.3", "webpack": "^4.41.0", "webpack-cli": "^3.3.9", diff --git a/renovate.json5 b/renovate.json5 index 0c288bb85c72c..aefbc61e8dc12 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -513,6 +513,14 @@ '@types/podium', ], }, + { + groupSlug: '@reach/router', + groupName: '@reach/router related packages', + packageNames: [ + '@reach/router', + '@types/reach__router', + ], + }, { groupSlug: 'request', groupName: 'request related packages', diff --git a/src/core/public/chrome/constants.ts b/src/core/public/chrome/constants.ts index 3411f6f629a13..c8e53b38c618e 100644 --- a/src/core/public/chrome/constants.ts +++ b/src/core/public/chrome/constants.ts @@ -18,6 +18,6 @@ */ export const ELASTIC_SUPPORT_LINK = 'https://support.elastic.co/'; -export const KIBANA_FEEDBACK_LINK = 'https://www.elastic.co/kibana/feedback'; -export const KIBANA_ASK_ELASTIC_LINK = 'https://www.elastic.co/kibana/ask-elastic'; +export const KIBANA_FEEDBACK_LINK = 'https://www.elastic.co/products/kibana/feedback'; +export const KIBANA_ASK_ELASTIC_LINK = 'https://www.elastic.co/products/kibana/ask-elastic'; export const GITHUB_CREATE_ISSUE_LINK = 'https://github.com/elastic/kibana/issues/new/choose'; diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index 8c135b3c4c49f..5a45491df28e7 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -19,27 +19,34 @@ import { NavLinksService } from './nav_links_service'; import { take, map, takeLast } from 'rxjs/operators'; -import { LegacyApp } from '../../application'; +import { App, LegacyApp } from '../../application'; const mockAppService = { - availableApps: new Map(), - availableLegacyApps: new Map([ - [ - 'legacyApp1', - { id: 'legacyApp1', order: 0, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' }, - ], - [ - 'legacyApp2', + availableApps: new Map( + ([ + { id: 'app1', order: 0, title: 'App 1', icon: 'app1' }, { - id: 'legacyApp2', + id: 'app2', order: -10, + title: 'App 2', + euiIconType: 'canvasApp', + }, + { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }, + ] as App[]).map(app => [app.id, app]) + ), + availableLegacyApps: new Map( + ([ + { id: 'legacyApp1', order: 5, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' }, + { + id: 'legacyApp2', + order: -5, title: 'Legacy App 2', euiIconType: 'canvasApp', appUrl: '/app2', }, - ], - ['legacyApp3', { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3' }], - ]), + { id: 'legacyApp3', order: 15, title: 'Legacy App 3', appUrl: '/app3' }, + ] as LegacyApp[]).map(app => [app.id, app]) + ), } as any; const mockHttp = { @@ -58,6 +65,18 @@ describe('NavLinksService', () => { }); describe('#getNavLinks$()', () => { + it('does not include `chromeless` applications', async () => { + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map(links => links.map(l => l.id)) + ) + .toPromise() + ).not.toContain('chromelessApp'); + }); + it('sorts navlinks by `order` property', async () => { expect( await start @@ -67,7 +86,7 @@ describe('NavLinksService', () => { map(links => links.map(l => l.id)) ) .toPromise() - ).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']); + ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); }); it('emits multiple values', async () => { @@ -78,8 +97,8 @@ describe('NavLinksService', () => { service.stop(); expect(emittedLinks).toEqual([ - ['legacyApp2', 'legacyApp1', 'legacyApp3'], - ['legacyApp2', 'legacyApp1', 'legacyApp3'], + ['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'], + ['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'], ]); }); @@ -105,7 +124,13 @@ describe('NavLinksService', () => { describe('#getAll()', () => { it('returns a sorted array of navlinks', () => { - expect(start.getAll().map(l => l.id)).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']); + expect(start.getAll().map(l => l.id)).toEqual([ + 'app2', + 'legacyApp2', + 'app1', + 'legacyApp1', + 'legacyApp3', + ]); }); }); @@ -130,7 +155,20 @@ describe('NavLinksService', () => { map(links => links.map(l => l.id)) ) .toPromise() - ).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']); + ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); + }); + + it('does nothing on chromeless applications', async () => { + start.showOnly('chromelessApp'); + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map(links => links.map(l => l.id)) + ) + .toPromise() + ).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']); }); it('removes all other links', async () => { @@ -157,7 +195,7 @@ describe('NavLinksService', () => { "icon": "legacyApp1", "id": "legacyApp1", "legacy": true, - "order": 0, + "order": 5, "title": "Legacy App 1", } `); diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index a636ff878dd41..31a729f90cd93 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -99,17 +99,19 @@ export class NavLinksService { private readonly stop$ = new ReplaySubject(1); public start({ application, http }: StartDeps): ChromeNavLinks { - const appLinks = [...application.availableApps].map( - ([appId, app]) => - [ - appId, - new NavLinkWrapper({ - ...app, - legacy: false, - baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)), - }), - ] as [string, NavLinkWrapper] - ); + const appLinks = [...application.availableApps] + .filter(([, app]) => !app.chromeless) + .map( + ([appId, app]) => + [ + appId, + new NavLinkWrapper({ + ...app, + legacy: false, + baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)), + }), + ] as [string, NavLinkWrapper] + ); const legacyAppLinks = [...application.availableLegacyApps].map( ([appId, app]) => diff --git a/src/core/server/config/config_service.test.ts b/src/core/server/config/config_service.test.ts index 61da9af7baa7c..131e1dd501792 100644 --- a/src/core/server/config/config_service.test.ts +++ b/src/core/server/config/config_service.test.ts @@ -55,7 +55,7 @@ test('throws if config at path does not match schema', async () => { await expect( configService.setSchema('key', schema.string()) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"[key]: expected value of type [string] but got [number]"` + `"[config validation of [key]]: expected value of type [string] but got [number]"` ); }); @@ -78,11 +78,11 @@ test('re-validate config when updated', async () => { config$.next(new ObjectToConfigAdapter({ key: 123 })); await expect(valuesReceived).toMatchInlineSnapshot(` -Array [ - "value", - [Error: [key]: expected value of type [string] but got [number]], -] -`); + Array [ + "value", + [Error: [config validation of [key]]: expected value of type [string] but got [number]], + ] + `); }); test("returns undefined if fetching optional config at a path that doesn't exist", async () => { @@ -143,7 +143,7 @@ test("throws error if 'schema' is not defined for a key", async () => { const configs = configService.atPath('key'); await expect(configs.pipe(first()).toPromise()).rejects.toMatchInlineSnapshot( - `[Error: No validation schema has been defined for key]` + `[Error: No validation schema has been defined for [key]]` ); }); @@ -153,7 +153,7 @@ test("throws error if 'setSchema' called several times for the same key", async const addSchema = async () => await configService.setSchema('key', schema.string()); await addSchema(); await expect(addSchema()).rejects.toMatchInlineSnapshot( - `[Error: Validation schema for key was already registered.]` + `[Error: Validation schema for [key] was already registered.]` ); }); @@ -280,6 +280,33 @@ test('handles disabled path and marks config as used', async () => { expect(unusedPaths).toEqual([]); }); +test('does not throw if schema does not define "enabled" schema', async () => { + const initialConfig = { + pid: { + file: '/some/file.pid', + }, + }; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + expect( + configService.setSchema( + 'pid', + schema.object({ + file: schema.string(), + }) + ) + ).resolves.toBeUndefined(); + + const value$ = configService.atPath('pid'); + const value: any = await value$.pipe(first()).toPromise(); + expect(value.enabled).toBe(undefined); + + const valueOptional$ = configService.optionalAtPath('pid'); + const valueOptional: any = await valueOptional$.pipe(first()).toPromise(); + expect(valueOptional.enabled).toBe(undefined); +}); + test('treats config as enabled if config path is not present in config', async () => { const initialConfig = {}; @@ -292,3 +319,45 @@ test('treats config as enabled if config path is not present in config', async ( const unusedPaths = await configService.getUnusedPaths(); expect(unusedPaths).toEqual([]); }); + +test('read "enabled" even if its schema is not present', async () => { + const initialConfig = { + foo: { + enabled: true, + }, + }; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + + const isEnabled = await configService.isEnabledAtPath('foo'); + expect(isEnabled).toBe(true); +}); + +test('allows plugins to specify "enabled" flag via validation schema', async () => { + const initialConfig = {}; + + const config$ = new BehaviorSubject(new ObjectToConfigAdapter(initialConfig)); + const configService = new ConfigService(config$, defaultEnv, logger); + + await configService.setSchema( + 'foo', + schema.object({ enabled: schema.boolean({ defaultValue: false }) }) + ); + + expect(await configService.isEnabledAtPath('foo')).toBe(false); + + await configService.setSchema( + 'bar', + schema.object({ enabled: schema.boolean({ defaultValue: true }) }) + ); + + expect(await configService.isEnabledAtPath('bar')).toBe(true); + + await configService.setSchema( + 'baz', + schema.object({ different: schema.boolean({ defaultValue: true }) }) + ); + + expect(await configService.isEnabledAtPath('baz')).toBe(true); +}); diff --git a/src/core/server/config/config_service.ts b/src/core/server/config/config_service.ts index 8d3cc733cf250..c18a5b2000e01 100644 --- a/src/core/server/config/config_service.ts +++ b/src/core/server/config/config_service.ts @@ -54,7 +54,7 @@ export class ConfigService { public async setSchema(path: ConfigPath, schema: Type) { const namespace = pathToString(path); if (this.schemas.has(namespace)) { - throw new Error(`Validation schema for ${path} was already registered.`); + throw new Error(`Validation schema for [${path}] was already registered.`); } this.schemas.set(namespace, schema); @@ -98,14 +98,28 @@ export class ConfigService { } public async isEnabledAtPath(path: ConfigPath) { - const enabledPath = createPluginEnabledPath(path); + const namespace = pathToString(path); + + const validatedConfig = this.schemas.has(namespace) + ? await this.atPath<{ enabled?: boolean }>(path) + .pipe(first()) + .toPromise() + : undefined; + const enabledPath = createPluginEnabledPath(path); const config = await this.config$.pipe(first()).toPromise(); - if (!config.has(enabledPath)) { + + // if plugin hasn't got a config schema, we try to read "enabled" directly + const isEnabled = + validatedConfig && validatedConfig.enabled !== undefined + ? validatedConfig.enabled + : config.get(enabledPath); + + // not declared. consider that plugin is enabled by default + if (isEnabled === undefined) { return true; } - const isEnabled = config.get(enabledPath); if (isEnabled === false) { // If the plugin is _not_ enabled, we mark the entire plugin path as // handled, as it's expected that it won't be used. @@ -138,7 +152,7 @@ export class ConfigService { const namespace = pathToString(path); const schema = this.schemas.get(namespace); if (!schema) { - throw new Error(`No validation schema has been defined for ${namespace}`); + throw new Error(`No validation schema has been defined for [${namespace}]`); } return schema.validate( config, @@ -147,7 +161,7 @@ export class ConfigService { prod: this.env.mode.prod, ...this.env.packageInfo, }, - namespace + `config validation of [${namespace}]` ); } diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts index ff7fee0198f68..cde35f3cbe995 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/src/core/server/http/base_path_proxy_server.ts @@ -143,6 +143,7 @@ export class BasePathProxyServer { return responseToolkit.continue; }, ], + validate: { payload: true }, }, path: `${this.httpConfig.basePath}/{kbnPath*}`, }); @@ -175,6 +176,7 @@ export class BasePathProxyServer { return responseToolkit.continue; }, ], + validate: { payload: true }, }, path: `/__UNSAFE_bypassBasePath/{kbnPath*}`, }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 3354324c12407..da97ab535516c 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -128,6 +128,8 @@ export class HttpServer { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); const { authRequired = true, tags } = route.options; + // Hapi does not allow payload validation to be specified for 'head' or 'get' requests + const validate = ['head', 'get'].includes(route.method) ? undefined : { payload: true }; this.server.route({ handler: route.handler, method: route.method, @@ -135,6 +137,11 @@ export class HttpServer { options: { auth: authRequired ? undefined : false, tags: tags ? Array.from(tags) : undefined, + // TODO: This 'validate' section can be removed once the legacy platform is completely removed. + // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default + // validation applied in ./http_tools#getServerOptions + // (All NP routes are already required to specify their own validation in order to access the payload) + validate, }, }); } diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 88164a76c66f0..22468a5b252f4 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -23,6 +23,7 @@ import Hoek from 'hoek'; import { ServerOptions as TLSOptions } from 'https'; import { ValidationError } from 'joi'; import { HttpConfig } from './http_config'; +import { validateObject } from './prototype_pollution'; /** * Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server. @@ -45,6 +46,11 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = { options: { abortEarly: false, }, + // TODO: This payload validation can be removed once the legacy platform is completely removed. + // This is a default payload validation which applies to all LP routes which do not specify their own + // `validate.payload` handler, in order to reduce the likelyhood of prototype pollution vulnerabilities. + // (All NP routes are already required to specify their own validation in order to access the payload) + payload: value => Promise.resolve(validateObject(value)), }, }, state: { diff --git a/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap b/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap new file mode 100644 index 0000000000000..937e040c771ee --- /dev/null +++ b/src/core/server/http/prototype_pollution/__snapshots__/validate_object.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can't submit {"__proto__":null} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"constructor":{"prototype":null}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"__proto__":true}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"__proto__":{}}}} 1`] = `"'__proto__' is an invalid key"`; + +exports[`can't submit {"foo":{"bar":{"constructor":{"prototype":null}}}} 1`] = `"'constructor.prototype' is an invalid key"`; + +exports[`can't submit {"foo":{"constructor":{"prototype":null}}} 1`] = `"'constructor.prototype' is an invalid key"`; diff --git a/src/core/server/http/prototype_pollution/index.ts b/src/core/server/http/prototype_pollution/index.ts new file mode 100644 index 0000000000000..e1a33ffba155e --- /dev/null +++ b/src/core/server/http/prototype_pollution/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { validateObject } from './validate_object'; diff --git a/src/core/server/http/prototype_pollution/validate_object.test.ts b/src/core/server/http/prototype_pollution/validate_object.test.ts new file mode 100644 index 0000000000000..9e23d6cec6444 --- /dev/null +++ b/src/core/server/http/prototype_pollution/validate_object.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { validateObject } from './validate_object'; + +test(`fails on circular references`, () => { + const foo: Record = {}; + foo.myself = foo; + + expect(() => + validateObject({ + payload: foo, + }) + ).toThrowErrorMatchingInlineSnapshot(`"circular reference detected"`); +}); + +[ + { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + { + foo: { + foo: true, + bar: '__proto__', + baz: 1.1, + qux: undefined, + quux: () => null, + quuz: Object.create(null), + }, + }, + { constructor: { foo: { prototype: null } } }, + { prototype: { foo: { constructor: null } } }, +].forEach(value => { + ['headers', 'payload', 'query', 'params'].forEach(property => { + const obj = { + [property]: value, + }; + test(`can submit ${JSON.stringify(obj)}`, () => { + expect(() => validateObject(obj)).not.toThrowError(); + }); + }); +}); + +// if we use the object literal syntax to create the following values, we end up +// actually reassigning the __proto__ which makes it be a non-enumerable not-own property +// which isn't what we want to test here +[ + JSON.parse(`{ "__proto__": null }`), + JSON.parse(`{ "foo": { "__proto__": true } }`), + JSON.parse(`{ "foo": { "bar": { "__proto__": {} } } }`), + JSON.parse(`{ "constructor": { "prototype" : null } }`), + JSON.parse(`{ "foo": { "constructor": { "prototype" : null } } }`), + JSON.parse(`{ "foo": { "bar": { "constructor": { "prototype" : null } } } }`), +].forEach(value => { + test(`can't submit ${JSON.stringify(value)}`, () => { + expect(() => validateObject(value)).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/src/core/server/http/prototype_pollution/validate_object.ts b/src/core/server/http/prototype_pollution/validate_object.ts new file mode 100644 index 0000000000000..cab6ce295ce92 --- /dev/null +++ b/src/core/server/http/prototype_pollution/validate_object.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ + +interface StackItem { + value: any; + previousKey: string | null; +} + +// we have to do Object.prototype.hasOwnProperty because when you create an object using +// Object.create(null), and I assume other methods, you get an object without a prototype, +// so you can't use current.hasOwnProperty +const hasOwnProperty = (obj: any, property: string) => + Object.prototype.hasOwnProperty.call(obj, property); + +const isObject = (obj: any) => typeof obj === 'object' && obj !== null; + +// we're using a stack instead of recursion so we aren't limited by the call stack +export function validateObject(obj: any) { + if (!isObject(obj)) { + return; + } + + const stack: StackItem[] = [ + { + value: obj, + previousKey: null, + }, + ]; + const seen = new WeakSet([obj]); + + while (stack.length > 0) { + const { value, previousKey } = stack.pop()!; + + if (!isObject(value)) { + continue; + } + + if (hasOwnProperty(value, '__proto__')) { + throw new Error(`'__proto__' is an invalid key`); + } + + if (hasOwnProperty(value, 'prototype') && previousKey === 'constructor') { + throw new Error(`'constructor.prototype' is an invalid key`); + } + + // iterating backwards through an array is reportedly more performant + const entries = Object.entries(value); + for (let i = entries.length - 1; i >= 0; --i) { + const [key, childValue] = entries[i]; + if (isObject(childValue)) { + if (seen.has(childValue)) { + throw new Error('circular reference detected'); + } + + seen.add(childValue); + } + + stack.push({ + value: childValue, + previousKey: key, + }); + } + } +} diff --git a/src/dev/i18n/extractors/code.js b/src/dev/i18n/extractors/code.js index fa0d834824e97..6439f8ceff332 100644 --- a/src/dev/i18n/extractors/code.js +++ b/src/dev/i18n/extractors/code.js @@ -67,7 +67,16 @@ export function* extractCodeMessages(buffer, reporter) { try { ast = parse(buffer.toString(), { sourceType: 'module', - plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators', 'dynamicImport'], + plugins: [ + 'jsx', + 'typescript', + 'objectRestSpread', + 'classProperties', + 'asyncGenerators', + 'dynamicImport', + 'nullishCoalescingOperator', + 'optionalChaining', + ], }); } catch (error) { if (error instanceof SyntaxError) { diff --git a/src/es_archiver/lib/indices/delete_index.js b/src/es_archiver/lib/indices/delete_index.js index 44a83be741063..b732989f02cb6 100644 --- a/src/es_archiver/lib/indices/delete_index.js +++ b/src/es_archiver/lib/indices/delete_index.js @@ -32,7 +32,7 @@ export async function deleteIndex(options) { stats, index, log, - retryIfSnapshottingCount = 3 + retryIfSnapshottingCount = 10 } = options; const getIndicesToDelete = async () => { diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index ab96b69851b71..f054c4d53fd8d 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -17,9 +17,10 @@ * under the License. */ -import { castEsToKbnFieldTypeName } from '../plugins/data/common'; -// eslint-disable-next-line max-len -import { shouldReadFieldFromDocValues } from '../plugins/data/server'; +import { + shouldReadFieldFromDocValues, + castEsToKbnFieldTypeName, +} from '../plugins/data/server'; function stubbedLogstashFields() { return [ diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx index 87558a73087d8..03d5b3f1d8f44 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -62,10 +62,6 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { updateCurrentState: () => {}, }, }, - // eslint-disable-next-line - ResizeChecker: function() { - return { on: () => {} }; - }, docLinkVersion: 'NA', }; editor = mount( diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx index b2a38a996f6a2..10f1ef34602a6 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -63,7 +63,6 @@ const DEFAULT_INPUT_VALUE = `GET _search function _Editor({ previousStateLocation = 'stored' }: EditorProps) { const { services: { history, notifications }, - ResizeChecker, docLinkVersion, } = useAppContext(); @@ -130,7 +129,6 @@ function _Editor({ previousStateLocation = 'stored' }: EditorProps) { mappings.retrieveAutoCompleteInfo(); const unsubscribeResizer = subscribeResizeChecker( - ResizeChecker, editorRef.current!, editorInstanceRef.current ); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx index fcf9f17e3ebd7..d38e86df41464 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx @@ -31,7 +31,6 @@ function _EditorOuput() { const editorInstanceRef = useRef(null); const { services: { settings }, - ResizeChecker, } = useAppContext(); const dispatch = useEditorActionContext(); @@ -42,11 +41,7 @@ function _EditorOuput() { const editor$ = $(editorRef.current!); editorInstanceRef.current = initializeOutput(editor$, settings); editorInstanceRef.current.update(''); - const unsubscribe = subscribeResizeChecker( - ResizeChecker, - editorRef.current!, - editorInstanceRef.current - ); + const unsubscribe = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current); dispatch({ type: 'setOutputEditor', value: editorInstanceRef.current }); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx index 881b59e6b3a1c..fdfe9ecc7b94c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/console_history.tsx @@ -45,7 +45,6 @@ const CHILD_ELEMENT_PREFIX = 'historyReq'; export function ConsoleHistory({ close }: Props) { const { services: { history }, - ResizeChecker, } = useAppContext(); const dispatch = useEditorActionContext(); @@ -200,7 +199,6 @@ export function ConsoleHistory({ close }: Props) { diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx index d531e143a79d0..c15bec0563049 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_history/history_viewer.tsx @@ -31,10 +31,9 @@ import { applyCurrentSettings } from '../console_editor/apply_editor_settings'; interface Props { settings: DevToolsSettings; req: any | null; - ResizeChecker: any; } -export function HistoryViewer({ settings, ResizeChecker, req }: Props) { +export function HistoryViewer({ settings, req }: Props) { const divRef = useRef(null); const viewerRef = useRef(null); @@ -43,7 +42,7 @@ export function HistoryViewer({ settings, ResizeChecker, req }: Props) { viewerRef.current = viewer; viewer.renderer.setShowPrintMargin(false); viewer.$blockScrolling = Infinity; - const unsubscribe = subscribeResizeChecker(ResizeChecker, divRef.current!, viewer); + const unsubscribe = subscribeResizeChecker(divRef.current!, viewer); return () => unsubscribe(); }, []); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts index c83c593ef404d..4ecd5d415833c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/subscribe_console_resize_checker.ts @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +import { ResizeChecker } from '../../../../../../../../../plugins/kibana_utils/public'; -export function subscribeResizeChecker(ResizeChecker: any, $el: any, ...editors: any[]) { - const checker = new ResizeChecker($el); +export function subscribeResizeChecker(el: HTMLElement, ...editors: any[]) { + const checker = new ResizeChecker(el); checker.on('resize', () => editors.forEach(e => { e.resize(); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx b/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx index 7bbdf731407e3..be7aa87ac2894 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/context/app_context.tsx @@ -29,7 +29,6 @@ interface ContextValue { notifications: NotificationsSetup; }; docLinkVersion: string; - ResizeChecker: any; } interface ContextProps { diff --git a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx b/src/legacy/core_plugins/console/np_ready/public/application/index.tsx index d8933e60470c2..aaacfd3894d18 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/index.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/index.tsx @@ -32,10 +32,9 @@ export function legacyBackDoorToSettings() { export function boot(deps: { docLinkVersion: string; I18nContext: any; - ResizeChecker: any; notifications: NotificationsSetup; }) { - const { I18nContext, ResizeChecker, notifications, docLinkVersion } = deps; + const { I18nContext, notifications, docLinkVersion } = deps; const storage = createStorage({ engine: window.localStorage, @@ -51,7 +50,6 @@ export function boot(deps: { value={{ docLinkVersion, services: { storage, history, settings, notifications }, - ResizeChecker, }} > diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index 8c60ff23648be..463aac74da944 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -26,7 +26,6 @@ import 'brace/mode/text'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { npSetup, npStart } from 'ui/new_platform'; import { I18nContext } from 'ui/i18n'; -import { ResizeChecker } from 'ui/resize_checker'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export interface XPluginSet { @@ -34,7 +33,6 @@ export interface XPluginSet { feature_catalogue: FeatureCatalogueSetup; __LEGACY: { I18nContext: any; - ResizeChecker: any; }; } @@ -48,7 +46,6 @@ pluginInstance.setup(npSetup.core, { ...npSetup.plugins, __LEGACY: { I18nContext, - ResizeChecker, }, }); pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/console/np_ready/public/plugin.ts b/src/legacy/core_plugins/console/np_ready/public/plugin.ts index f02b0b5e72999..301b85b6e7395 100644 --- a/src/legacy/core_plugins/console/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/console/np_ready/public/plugin.ts @@ -30,7 +30,7 @@ export class ConsoleUIPlugin implements Plugin { async setup({ notifications }: CoreSetup, pluginSet: XPluginSet) { const { - __LEGACY: { I18nContext, ResizeChecker }, + __LEGACY: { I18nContext }, devTools, feature_catalogue, } = pluginSet; @@ -62,7 +62,6 @@ export class ConsoleUIPlugin implements Plugin { boot({ docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, I18nContext, - ResizeChecker, notifications, }), element diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.js index 8ce828879a677..856128f3d4c03 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.js @@ -71,6 +71,7 @@ export const createProxyRoute = ({ parse: false, }, validate: { + payload: true, query: Joi.object() .keys({ method: Joi.string() diff --git a/src/legacy/core_plugins/console/server/request.test.ts b/src/legacy/core_plugins/console/server/request.test.ts index 463649a090295..d5504c0f3a3c2 100644 --- a/src/legacy/core_plugins/console/server/request.test.ts +++ b/src/legacy/core_plugins/console/server/request.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import http from 'http'; +import http, { ClientRequest } from 'http'; import * as sinon from 'sinon'; import { sendRequest } from './request'; import { URL } from 'url'; @@ -24,7 +24,7 @@ import { fail } from 'assert'; describe(`Console's send request`, () => { let sandbox: sinon.SinonSandbox; - let stub: sinon.SinonStub; + let stub: sinon.SinonStub, ClientRequest>; let fakeRequest: http.ClientRequest; beforeEach(() => { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 333e1e328651d..5b389f5b98aba 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -22,13 +22,12 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; import { CoreStart } from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; import { IndexPattern } from '../../index_patterns'; import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; import { useKibana, KibanaContextProvider } from '../../../../../../plugins/kibana_react/public'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { DataPublicPluginStart, esFilters } from '../../../../../../plugins/data/public'; interface Props { filters: esFilters.Filter[]; diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts index dc5023795bf19..6084b4c106452 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts @@ -22,40 +22,24 @@ import { fieldFormats } from 'ui/registry/field_formats'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { ObjDefine } from './obj_define'; -import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; // @ts-ignore import { shortenDottedString } from '../../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; import { IndexPattern } from '../index_patterns'; import { getNotifications } from '../services'; -import { getKbnFieldType } from '../../../../../../plugins/data/public'; - -interface FieldSubType { - multi?: { parent: string }; - nested?: { path: string }; -} +import { + FieldFormat, + getKbnFieldType, + IFieldType, + IFieldSubType, +} from '../../../../../../plugins/data/public'; export type FieldSpec = Record; -export interface FieldType { - name: string; - type: string; - script?: string; - lang?: string; - count?: number; - // esTypes might be undefined on old index patterns that have not been refreshed since we added - // this prop. It is also undefined on scripted fields. - esTypes?: string[]; - aggregatable?: boolean; - filterable?: boolean; - searchable?: boolean; - sortable?: boolean; - visualizable?: boolean; - readFromDocValues?: boolean; - scripted?: boolean; - subType?: FieldSubType; - displayName?: string; - format?: any; -} + +/** @deprecated + * Please use IFieldType instead + * */ +export type FieldType = IFieldType; export class Field implements FieldType { name: string; @@ -72,7 +56,7 @@ export class Field implements FieldType { sortable?: boolean; visualizable?: boolean; scripted?: boolean; - subType?: FieldSubType; + subType?: IFieldSubType; displayName?: string; format: any; routes: Record = { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index bf0d79e960d9b..12aa3c2fb0d51 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -37,21 +37,18 @@ import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; -import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { ES_FIELD_TYPES, IIndexPattern } from '../../../../../../plugins/data/public'; import { getNotifications } from '../services'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; -export interface StaticIndexPattern { - fields: FieldType[]; - title: string; - id?: string; - type?: string; - timeFieldName?: string; -} +/** @deprecated + * Please use IIndexPattern instead + * */ +export type StaticIndexPattern = IIndexPattern; -export class IndexPattern implements StaticIndexPattern { +export class IndexPattern implements IIndexPattern { [key: string]: any; public id?: string; 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 ca0ac3c371849..cd64b1ecf2549 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 @@ -82,8 +82,11 @@ function QueryBarTopRowUI(props: Props) { const queryLanguage = props.query && props.query.language; const persistedLog: PersistedLog | undefined = React.useMemo( - () => (queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined), - [queryLanguage] + () => + queryLanguage && uiSettings && storage && appName + ? getQueryLog(uiSettings!, storage, appName, queryLanguage) + : undefined, + [appName, queryLanguage, uiSettings, storage] ); function onClickSubmitButton(event: React.MouseEvent) { 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 4485b74ca0901..125c6b8dad006 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 @@ -20,12 +20,11 @@ import React, { useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; import { CoreStart } from 'src/core/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; import { SearchBar } from '../../../'; import { SearchBarOwnProps } from '.'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { DataPublicPluginStart, esFilters } from '../../../../../../../plugins/data/public'; interface StatefulSearchBarDeps { core: CoreStart; 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 ea0f6775e4831..e97c06ace1579 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 @@ -24,7 +24,6 @@ import React, { Component } from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { get, isEqual } from 'lodash'; -import { TimeRange, Query, TimeHistoryContract } from 'src/plugins/data/public'; import { IndexPattern, FilterBar } from '../../../../../data/public'; import { QueryBarTopRow } from '../../../query'; import { SavedQuery, SavedQueryAttributes } from '../index'; @@ -37,7 +36,12 @@ import { KibanaReactContextValue, } from '../../../../../../../plugins/kibana_react/public'; import { IDataPluginServices } from '../../../types'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { + TimeRange, + Query, + esFilters, + TimeHistoryContract, +} from '../../../../../../../plugins/data/public'; interface SearchBarInjectedDeps { kibana: KibanaReactContextValue; @@ -72,6 +76,7 @@ export interface SearchBarOwnProps { // Show when user has privileges to save showSaveQuery?: boolean; savedQuery?: SavedQuery; + onQueryChange?: (payload: { dateRange: TimeRange; query?: Query }) => void; onQuerySubmit?: (payload: { dateRange: TimeRange; query?: Query }) => void; // User has saved the current state as a saved query onSaved?: (savedQuery: SavedQuery) => void; @@ -206,6 +211,18 @@ class SearchBarUI extends Component { ); } + /* + * This Function is here to show the toggle in saved query form + * in case you the date range (from/to) + */ + private shouldRenderTimeFilterInSavedQueryForm() { + const { dateRangeFrom, dateRangeTo, showDatePicker } = this.props; + return ( + showDatePicker || + (!showDatePicker && dateRangeFrom !== undefined && dateRangeTo !== undefined) + ); + } + public setFilterBarHeight = () => { requestAnimationFrame(() => { const height = @@ -299,6 +316,9 @@ class SearchBarUI extends Component { dateRangeFrom: queryAndDateRange.dateRange.from, dateRangeTo: queryAndDateRange.dateRange.to, }); + if (this.props.onQueryChange) { + this.props.onQueryChange(queryAndDateRange); + } }; public onQueryBarSubmit = (queryAndDateRange: { dateRange?: TimeRange; query?: Query }) => { @@ -440,7 +460,7 @@ class SearchBarUI extends Component { onSave={this.onSave} onClose={() => this.setState({ showSaveQueryModal: false })} showFilterOption={this.props.showFilterBar} - showTimeFilterOption={this.props.showDatePicker} + showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()} /> ) : null} {this.state.showSaveNewQueryModal ? ( @@ -449,7 +469,7 @@ class SearchBarUI extends Component { onSave={savedQueryMeta => this.onSave(savedQueryMeta, true)} onClose={() => this.setState({ showSaveNewQueryModal: false })} showFilterOption={this.props.showFilterBar} - showTimeFilterOption={this.props.showDatePicker} + showTimeFilterOption={this.shouldRenderTimeFilterInSavedQueryForm()} /> ) : null} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts index 9d7b4fb6d0480..c8870b9f97957 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts @@ -17,9 +17,8 @@ * under the License. */ -import { TimeRange } from '../../../../../../plugins/data/public'; import { Adapters } from '../../../../../../plugins/inspector/public'; -import { Query } from '../../../../../../plugins/data/public'; +import { TimeRange, Query } from '../../../../../../plugins/data/public'; export { TimeRange, Adapters, Query }; export * from '../../../../../../plugins/expressions/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index b9825ceeecdb6..d5da4ba51e55b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -26,16 +26,12 @@ import { IInjector } from 'ui/chrome'; // @ts-ignore import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter'; -// @ts-ignore -import { getFilterGenerator } from 'ui/filter_manager'; - import { AppStateClass as TAppStateClass, AppState as TAppState, } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; -import { TimeRange, Query } from 'src/plugins/data/public'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { StaticIndexPattern, SavedQuery } from 'plugins/data'; @@ -45,7 +41,7 @@ import { Subscription } from 'rxjs'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 1a42ed837a9de..d5af4c93d0e0c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -27,9 +27,8 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import { Query } from 'src/plugins/data/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { Query, esFilters } from '../../../../../../src/plugins/data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js index f472ff9250eb5..b3d37083b37f7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js @@ -26,7 +26,8 @@ export function createIndexPatternsStub() { get: sinon.spy(indexPatternId => Promise.resolve({ id: indexPatternId, - isTimeNanosBased: () => false + isTimeNanosBased: () => false, + popularizeField: () => {}, }) ), }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js index b136b03bd500b..5a445a65939ed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js @@ -19,32 +19,33 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import sinon from 'sinon'; import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; import { QueryParameterActionsProvider } from '../actions'; - +import { createIndexPatternsStub } from '../../api/__tests__/_stubs'; +import { npStart } from 'ui/new_platform'; describe('context app', function () { beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.module(function createServiceStubs($provide) { + $provide.value('indexPatterns', createIndexPatternsStub()); + })); + describe('action addFilter', function () { - let filterManagerStub; let addFilter; beforeEach(ngMock.inject(function createPrivateStubs(Private) { - filterManagerStub = createQueryFilterStub(); - Private.stub(getServices().FilterBarQueryFilterProvider, filterManagerStub); - + Private.stub(getServices().FilterBarQueryFilterProvider); addFilter = Private(QueryParameterActionsProvider).addFilter; })); it('should pass the given arguments to the filterManager', function () { const state = createStateStub(); + const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - const filterManagerAddStub = filterManagerStub.addFilters; //get the generated filter const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; const queryKeys = Object.keys(generatedFilter.query.match_phrase); @@ -55,20 +56,12 @@ describe('context app', function () { it('should pass the index pattern id to the filterManager', function () { const state = createStateStub(); + const filterManagerAddStub = npStart.plugins.data.query.filterManager.addFilters; addFilter(state)('FIELD_NAME', 'FIELD_VALUE', 'FILTER_OPERATION'); - const filterManagerAddStub = filterManagerStub.addFilters; const generatedFilter = filterManagerAddStub.firstCall.args[0][0]; - expect(filterManagerAddStub.calledOnce).to.be(true); expect(generatedFilter.meta.index).to.eql('INDEX_PATTERN_ID'); }); }); }); - -function createQueryFilterStub() { - return { - addFilters: sinon.stub(), - getAppFilters: sinon.stub(), - }; -} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 9f7b180e8fe7d..10fe6c0e2eda1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -18,7 +18,8 @@ */ import _ from 'lodash'; -import { getServices, getFilterGenerator } from '../../../kibana_services'; +import { generateFilters } from '../../../../../../../../plugins/data/public'; +import { npStart } from 'ui/new_platform'; import { MAX_CONTEXT_SIZE, @@ -27,9 +28,8 @@ import { } from './constants'; -export function QueryParameterActionsProvider(indexPatterns, Private) { - const queryFilter = Private(getServices().FilterBarQueryFilterProvider); - const filterGen = getFilterGenerator(queryFilter); +export function QueryParameterActionsProvider(indexPatterns) { + const { filterManager } = npStart.plugins.data.query; const setPredecessorCount = (state) => (predecessorCount) => ( state.queryParameters.predecessorCount = clamp( @@ -55,13 +55,13 @@ export function QueryParameterActionsProvider(indexPatterns, Private) { ); const updateFilters = () => filters => { - queryFilter.setFilters(filters); + filterManager.setFilters(filters); }; const addFilter = (state) => async (field, values, operation) => { const indexPatternId = state.queryParameters.indexPatternId; - const newFilters = filterGen.generate(field, values, operation, indexPatternId); - queryFilter.addFilters(newFilters); + const newFilters = generateFilters(filterManager, field, values, operation, indexPatternId); + filterManager.addFilters(newFilters); const indexPattern = await indexPatterns.get(indexPatternId); indexPattern.popularizeField(field.name, 1); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 3a3f58ca83af0..8ee23bfb005a2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -31,7 +31,6 @@ import './doc_table'; import { getSort } from './doc_table/lib/get_sort'; import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; import * as columnActions from './doc_table/actions/columns'; -import * as filterActions from './doc_table/actions/filter'; import indexTemplate from './discover.html'; import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; @@ -41,7 +40,6 @@ import { getPainlessError } from './get_painless_error'; import { angular, buildVislibDimensions, - getFilterGenerator, getRequestInspectorStats, getResponseInspectorStats, getServices, @@ -76,7 +74,7 @@ const { import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; - +import { generateFilters } from '../../../../../../plugins/data/public'; const { savedQueryService } = data.search.services; @@ -195,7 +193,6 @@ function discoverController( const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const queryFilter = Private(FilterBarQueryFilterProvider); - const filterGen = getFilterGenerator(queryFilter); const inspectorAdapters = { requests: new RequestAdapter() @@ -900,7 +897,8 @@ function discoverController( // TODO: On array fields, negating does not negate the combination, rather all terms $scope.filterQuery = function (field, values, operation) { $scope.indexPattern.popularizeField(field, 1); - filterActions.addFilter(field, values, operation, $scope.indexPattern.id, $scope.state, filterGen); + const newFilters = generateFilters(queryFilter, field, values, operation, $scope.indexPattern.id); + return queryFilter.addFilters(newFilters); }; $scope.addColumn = function addColumn(columnName) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js deleted file mode 100644 index 1f5db791469b9..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.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 { addFilter } from '../../actions/filter'; -import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import NoDigestPromises from 'test_utils/no_digest_promises'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import sinon from 'sinon'; - -function getFilterGeneratorStub() { - return { - add: sinon.stub() - }; -} - -describe('doc table filter actions', function () { - NoDigestPromises.activateForSuite(); - - let filterGen; - let indexPattern; - - beforeEach(ngMock.module( - 'kibana', - function ($provide) { - $provide.service('indexPatterns', require('fixtures/mock_index_patterns')); - } - )); - - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(StubbedLogstashIndexPatternProvider); - filterGen = getFilterGeneratorStub(); - })); - - describe('add', function () { - - it('should defer to the FilterManager when dealing with a lucene query', function () { - const state = { - query: { query: 'foo', language: 'lucene' } - }; - const args = ['foo', ['bar'], '+', indexPattern, ]; - addFilter('foo', ['bar'], '+', indexPattern, state, filterGen); - expect(filterGen.add.calledOnce).to.be(true); - expect(filterGen.add.calledWith(...args)).to.be(true); - }); - - }); - - -}); 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 d719864d99447..ef79cda476e51 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 @@ -25,9 +25,12 @@ import { npStart } from 'ui/new_platform'; import { esFilters, TimeRange, + FilterManager, onlyDisabledFiltersChanged, + generateFilters, getTime, Query, + IFieldType, } from '../../../../../../plugins/data/public'; import { APPLY_FILTER_TRIGGER, @@ -43,7 +46,6 @@ import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_se import { Adapters, angular, - getFilterGenerator, getRequestInspectorStats, getResponseInspectorStats, getServices, @@ -65,25 +67,13 @@ interface SearchScope extends ng.IScope { removeColumn?: (column: string) => void; addColumn?: (column: string) => void; moveColumn?: (column: string, index: number) => void; - filter?: (field: { name: string; scripted: boolean }, value: string[], operator: string) => void; + filter?: (field: IFieldType, value: string[], operator: string) => void; hits?: any[]; indexPattern?: IndexPattern; totalHitCount?: number; isLoading?: boolean; } -export interface FilterManager { - generate: ( - field: { - name: string; - scripted: boolean; - }, - values: string | string[], - operation: string, - index: number - ) => esFilters.Filter[]; -} - interface SearchEmbeddableConfig { $rootScope: ng.IRootScopeService; $compile: ng.ICompileService; @@ -107,7 +97,7 @@ export class SearchEmbeddable extends Embeddable private autoRefreshFetchSubscription?: Subscription; private subscription?: Subscription; public readonly type = SEARCH_EMBEDDABLE_TYPE; - private filterGen: FilterManager; + private filterManager: FilterManager; private abortController?: AbortController; private prevTimeRange?: TimeRange; @@ -134,7 +124,7 @@ export class SearchEmbeddable extends Embeddable parent ); - this.filterGen = getFilterGenerator(queryFilter); + this.filterManager = queryFilter as FilterManager; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; @@ -251,7 +241,7 @@ export class SearchEmbeddable extends Embeddable }; searchScope.filter = async (field, value, operator) => { - let filters = this.filterGen.generate(field, value, operator, indexPattern.id); + let filters = generateFilters(this.filterManager, field, value, operator, indexPattern.id); filters = filters.map(filter => ({ ...filter, $state: { store: esFilters.FilterStateStore.APP_STATE }, 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 a220cf59f6cf6..d0eb115e32676 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -84,8 +84,6 @@ export { angular }; export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; // @ts-ignore export { callAfterBindingsWorkaround } from 'ui/compat'; -// @ts-ignore -export { getFilterGenerator } from 'ui/filter_manager'; export { getRequestInspectorStats, getResponseInspectorStats, diff --git a/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js b/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js index bc27e1a0ac2a1..1c63d2efc7e0b 100644 --- a/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js +++ b/src/legacy/core_plugins/kibana/public/field_formats/__tests__/_conformance.js @@ -20,8 +20,8 @@ import _ from 'lodash'; import expect from '@kbn/expect'; import { fieldFormats } from 'ui/registry/field_formats'; -import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; import { npStart } from 'ui/new_platform'; +import { FieldFormat } from '../../../../../../plugins/data/public'; const config = npStart.core.uiSettings; diff --git a/src/legacy/core_plugins/kibana/server/field_formats/register.js b/src/legacy/core_plugins/kibana/server/field_formats/register.js index 95818f22b397e..bade66ce2c881 100644 --- a/src/legacy/core_plugins/kibana/server/field_formats/register.js +++ b/src/legacy/core_plugins/kibana/server/field_formats/register.js @@ -33,7 +33,7 @@ import { BoolFormat, SourceFormat, StaticLookupFormat -} from '../../../../../plugins/data/common'; +} from '../../../../../plugins/data/server'; export function registerFieldFormats(server) { server.registerFieldFormat(UrlFormat); diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index d4d1c159906aa..4d3b72bae6411 100644 --- a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -45,6 +45,7 @@ export function TopNavMenuItem(props: TopNavMenuData) { isDisabled={isDisabled()} onClick={handleClick} data-test-subj={props.testId} + className={props.className} > {capitalize(props.label || props.id!)} diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index ef6b2eaea1e52..c5ccc3acba610 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -23,23 +23,26 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; +import { Vis } from 'ui/vis'; +import { RegionMapVisParams } from '../../../region_map/public/types'; import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; -import { RegionMapOptionsProps } from '../../../region_map/public/components/region_map_options'; import { WmsInternalOptions } from './wms_internal_options'; -import { TileMapOptionsProps } from './tile_map_options'; -import { TileMapVisParams } from '../types'; +import { WMSOptions, TileMapVisParams } from '../types'; + +interface Props { + stateParams: TileMapVisParams | RegionMapVisParams; + setValue: (title: 'wms', options: WMSOptions) => void; + vis: Vis; +} const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id }); -function WmsOptions({ stateParams, setValue, vis }: TileMapOptionsProps | RegionMapOptionsProps) { +function WmsOptions({ stateParams, setValue, vis }: Props) { const { wms } = stateParams; const { tmsLayers } = vis.type.editorConfig.collections; const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]); - const setWmsOption = ( - paramName: T, - value: TileMapVisParams['wms'][T] - ) => + const setWmsOption = (paramName: T, value: WMSOptions[T]) => setValue('wms', { ...wms, [paramName]: value, diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 35dea4a0deb9b..74111bf794877 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -17,15 +17,13 @@ * under the License. */ -// @ts-ignore -import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; // @ts-ignore import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { Query, TimeRange, esFilters } from 'src/plugins/data/public'; import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; import { TimelionVisualizationDependencies } from '../plugin'; +import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; interface Stats { cacheCount: number; @@ -74,7 +72,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep ); } - const esQueryConfigs = getEsQueryConfig(uiSettings); + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); // parse the time range client side to make sure it behaves like other charts const timeRangeBounds = timefilter.calculateBounds(timeRange); @@ -85,7 +83,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep sheet: [expression], extended: { es: { - filter: buildEsQuery(undefined, query, filters, esQueryConfigs), + filter: esQuery.buildEsQuery(null, query, filters, esQueryConfigs), }, }, time: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js index 92558559845be..6974389897812 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/filter_ratio.js @@ -35,7 +35,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; import { METRIC_TYPES } from '../../../common/metric_types'; export const FilterRatioAgg = props => { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js index fafb2621aafe0..ec16a0f2eb3ee 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js @@ -34,7 +34,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; import { Percentiles, newPercentile } from './percentile_ui'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js index 85aded0ca248b..069ea9706e927 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile_rank/percentile_rank.js @@ -36,7 +36,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js index 4b8b356f2af48..67fe9403e402b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_agg.js @@ -33,7 +33,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; import { METRIC_TYPES } from '../../../common/metric_types'; export function StandardAgg(props) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js index c078bac8d7f9c..1f0347b210886 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/std_deviation.js @@ -36,7 +36,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js index 1439768b95643..fa92713046aca 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/top_hit.js @@ -35,7 +35,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../../plugins/data/public'; import { PANEL_TYPES } from '../../../common/panel_types'; const isFieldTypeEnabled = (fieldRestrictions, fieldType) => diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js index e91c182ad1d5a..19bf807f1b315 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js @@ -19,7 +19,7 @@ import { getBucketSize } from '../../helpers/get_bucket_size'; import { getTimerange } from '../../helpers/get_timerange'; -import { buildEsQuery } from '@kbn/es-query'; +import { esQuery } from '../../../../../../../../plugins/data/server'; export function query(req, panel, annotation, esQueryConfig, indexPattern, capabilities) { return next => doc => { @@ -30,7 +30,7 @@ export function query(req, panel, annotation, esQueryConfig, indexPattern, capab doc.size = 0; const queries = !annotation.ignore_global_filters ? req.payload.query : []; const filters = !annotation.ignore_global_filters ? req.payload.filters : []; - doc.query = buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); const timerange = { range: { [timeField]: { @@ -44,12 +44,12 @@ export function query(req, panel, annotation, esQueryConfig, indexPattern, capab if (annotation.query_string) { doc.query.bool.must.push( - buildEsQuery(indexPattern, [annotation.query_string], [], esQueryConfig) + esQuery.buildEsQuery(indexPattern, [annotation.query_string], [], esQueryConfig) ); } if (!annotation.ignore_panel_filters && panel.filter) { - doc.query.bool.must.push(buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)); + doc.query.bool.must.push(esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)); } if (annotation.fields) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js index 75de84ae33462..a287b394422e1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js @@ -19,7 +19,7 @@ import { offsetTime } from '../../offset_time'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; -import { buildEsQuery } from '@kbn/es-query'; +import { esQuery } from '../../../../../../../../plugins/data/server'; export function query(req, panel, series, esQueryConfig, indexPatternObject) { return next => doc => { @@ -29,7 +29,7 @@ export function query(req, panel, series, esQueryConfig, indexPatternObject) { doc.size = 0; const queries = !panel.ignore_global_filter ? req.payload.query : []; const filters = !panel.ignore_global_filter ? req.payload.filters : []; - doc.query = buildEsQuery(indexPatternObject, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig); const timerange = { range: { @@ -43,12 +43,12 @@ export function query(req, panel, series, esQueryConfig, indexPatternObject) { doc.query.bool.must.push(timerange); if (panel.filter) { - doc.query.bool.must.push(buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)); + doc.query.bool.must.push(esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)); } if (series.filter) { doc.query.bool.must.push( - buildEsQuery(indexPatternObject, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(indexPatternObject, [series.filter], [], esQueryConfig) ); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js index 8b9ebf9319efe..1548c9e17c2e1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js @@ -17,17 +17,21 @@ * under the License. */ -import _ from 'lodash'; -import { buildEsQuery } from '@kbn/es-query'; +import { set } from 'lodash'; +import { esQuery } from '../../../../../../../../plugins/data/server'; export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) { return next => doc => { - if (series.split_mode !== 'filter') return next(doc); - _.set( + if (series.split_mode !== 'filter') { + return next(doc); + } + + set( doc, `aggs.${series.id}.filter`, - buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) ); + return next(doc); }; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js index 60b29141aaa3e..4295bceed43cc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js @@ -17,14 +17,16 @@ * under the License. */ -import _ from 'lodash'; -import { buildEsQuery } from '@kbn/es-query'; +import { set } from 'lodash'; +import { esQuery } from '../../../../../../../../plugins/data/server'; + export function splitByFilters(req, panel, series, esQueryConfig, indexPattern) { return next => doc => { if (series.split_mode === 'filters' && series.split_filters) { series.split_filters.forEach(filter => { - const builtEsQuery = buildEsQuery(indexPattern, [filter.filter], [], esQueryConfig); - _.set(doc, `aggs.${series.id}.filters.filters.${filter.id}`, builtEsQuery); + const builtEsQuery = esQuery.buildEsQuery(indexPattern, [filter.filter], [], esQueryConfig); + + set(doc, `aggs.${series.id}.filters.filters.${filter.id}`, builtEsQuery); }); } return next(doc); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js index 212e7a615dcad..f3b5413c63d79 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { buildEsQuery } from '@kbn/es-query'; import { getTimerange } from '../../helpers/get_timerange'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; +import { esQuery } from '../../../../../../../../plugins/data/server'; export function query(req, panel, esQueryConfig, indexPatternObject) { return next => doc => { @@ -29,7 +29,7 @@ export function query(req, panel, esQueryConfig, indexPatternObject) { const queries = !panel.ignore_global_filter ? req.payload.query : []; const filters = !panel.ignore_global_filter ? req.payload.filters : []; - doc.query = buildEsQuery(indexPatternObject, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig); const timerange = { range: { @@ -42,7 +42,7 @@ export function query(req, panel, esQueryConfig, indexPatternObject) { }; doc.query.bool.must.push(timerange); if (panel.filter) { - doc.query.bool.must.push(buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)); + doc.query.bool.must.push(esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)); } return next(doc); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js index 01d33ca86e6d1..17f99ea431fd3 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js @@ -17,21 +17,22 @@ * under the License. */ -import _ from 'lodash'; -import { buildEsQuery } from '@kbn/es-query'; +import { set } from 'lodash'; +import { esQuery } from '../../../../../../../../plugins/data/server'; + export function splitByEverything(req, panel, esQueryConfig, indexPattern) { return next => doc => { panel.series .filter(c => !(c.aggregate_by && c.aggregate_function)) .forEach(column => { if (column.filter) { - _.set( + set( doc, `aggs.pivot.aggs.${column.id}.filter`, - buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) ); } else { - _.set(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {}); + set(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {}); } }); return next(doc); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js index 829f7d8c5a0de..042e4d98e2767 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js @@ -17,20 +17,22 @@ * under the License. */ -import _ from 'lodash'; -import { buildEsQuery } from '@kbn/es-query'; +import { set } from 'lodash'; +import { esQuery } from '../../../../../../../../plugins/data/server'; + export function splitByTerms(req, panel, esQueryConfig, indexPattern) { return next => doc => { panel.series .filter(c => c.aggregate_by && c.aggregate_function) .forEach(column => { - _.set(doc, `aggs.pivot.aggs.${column.id}.terms.field`, column.aggregate_by); - _.set(doc, `aggs.pivot.aggs.${column.id}.terms.size`, 100); + set(doc, `aggs.pivot.aggs.${column.id}.terms.field`, column.aggregate_by); + set(doc, `aggs.pivot.aggs.${column.id}.terms.size`, 100); + if (column.filter) { - _.set( + set( doc, `aggs.pivot.aggs.${column.id}.column_filter.filter`, - buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) ); } }); diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts index accc52c1e5a14..83ae31bf87400 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts @@ -18,9 +18,7 @@ */ import { timefilter } from 'ui/timefilter'; - -import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; -import { esFilters, TimeRange, Query } from '../../../../plugins/data/public'; +import { esFilters, esQuery, TimeRange, Query } from '../../../../plugins/data/public'; // @ts-ignore import { VegaParser } from './data_model/vega_parser'; @@ -50,8 +48,8 @@ export function createVegaRequestHandler({ return ({ timeRange, filters, query, visParams }: VegaRequestHandlerParams) => { timeCache.setTimeRange(timeRange); - const esQueryConfigs = getEsQueryConfig(uiSettings); - const filtersDsl = buildEsQuery(undefined, query, filters, esQueryConfigs); + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); + const filtersDsl = esQuery.buildEsQuery(null, query, filters, esQueryConfigs); const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings); return vp.parseAsync(); diff --git a/src/legacy/server/http/integration_tests/xsrf.test.js b/src/legacy/server/http/integration_tests/xsrf.test.js index 562a94e198631..baeb61bff6113 100644 --- a/src/legacy/server/http/integration_tests/xsrf.test.js +++ b/src/legacy/server/http/integration_tests/xsrf.test.js @@ -57,7 +57,8 @@ describe('xsrf request filter', () => { // Disable payload parsing to make HapiJS server accept any content-type header. payload: { parse: false - } + }, + validate: { payload: null } }, handler: async function () { return 'ok'; @@ -71,7 +72,8 @@ describe('xsrf request filter', () => { // Disable payload parsing to make HapiJS server accept any content-type header. payload: { parse: false - } + }, + validate: { payload: null } }, handler: async function () { return 'ok'; diff --git a/src/legacy/server/http/version_check.js b/src/legacy/server/http/version_check.js index 8bd2bb6e22df1..12666c9a0f3f6 100644 --- a/src/legacy/server/http/version_check.js +++ b/src/legacy/server/http/version_check.js @@ -27,10 +27,11 @@ export function setupVersionCheck(server, config) { const versionRequested = req.headers[versionHeader]; if (versionRequested && versionRequested !== actualVersion) { - throw badRequest('Browser client is out of date, please refresh the page', { - expected: actualVersion, - got: versionRequested - }); + throw badRequest( + `Browser client is out of date, \ + please refresh the page ("${versionHeader}" header was "${versionRequested}" but should be "${actualVersion}")`, + { expected: actualVersion, got: versionRequested } + ); } return h.continue; diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index eedfc1cc05a84..becfaf8c89e27 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -32,7 +32,7 @@ import { AggGroupNames } from '../vis/editors/default/agg_groups'; import { writeParams } from './agg_params'; import { AggConfigs } from './agg_configs'; import { Schema } from '../vis/editors/default/schemas'; -import { ContentType } from '../../../../plugins/data/common'; +import { ContentType } from '../../../../plugins/data/public'; // @ts-ignore import { fieldFormats } from '../registry/field_formats'; diff --git a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts b/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts index 74e4017b28728..c151f9101d090 100644 --- a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts +++ b/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts @@ -19,7 +19,7 @@ import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../../vis'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; export type IBucketAggConfig = AggConfig; diff --git a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js b/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js index 70bca2e40ae3f..d0d712704964b 100644 --- a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js +++ b/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js @@ -18,9 +18,8 @@ */ import _ from 'lodash'; -import { buildQueryFromFilters } from '@kbn/es-query'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; -import { esFilters } from '../../../../../plugins/data/public'; +import { esFilters, esQuery } from '../../../../../plugins/data/public'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId @@ -197,7 +196,7 @@ export const buildOtherBucketAgg = (aggConfigs, aggWithOtherBucket, response) => }); resultAgg.filters.filters[key] = { - bool: buildQueryFromFilters(filters, indexPattern), + bool: esQuery.buildQueryFromFilters(filters, indexPattern), }; }; walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], ''); 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 index 35b6c38bad799..0399e8d382320 100644 --- 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 @@ -19,7 +19,7 @@ import moment from 'moment'; import { createFilterDateRange } from './date_range'; -import { DateFormat } from '../../../../../../plugins/data/common'; +import { DateFormat } from '../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; 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 index 0095df75b8914..ac8e55f096fb4 100644 --- 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 @@ -19,7 +19,7 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { BytesFormat } from '../../../../../../plugins/data/common'; +import { BytesFormat } from '../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); 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 index 2e030d820b396..569735a60298d 100644 --- 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 @@ -19,7 +19,7 @@ import { createFilterIpRange } from './ip_range'; import { AggConfigs } from '../../agg_configs'; -import { IpFormat } from '../../../../../../plugins/data/common'; +import { IpFormat } from '../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; jest.mock('ui/new_platform'); 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 index 04476ba62ccd5..e7344f16ba0b1 100644 --- 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 @@ -18,7 +18,7 @@ */ import { createFilterRange } from './range'; -import { BytesFormat } from '../../../../../../plugins/data/common'; +import { BytesFormat } from '../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; diff --git a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts index e86d561a1c79b..03e358af5f1f0 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts @@ -34,7 +34,7 @@ import { writeParams } from '../agg_params'; import { AggConfigs } from '../agg_configs'; import { isMetricAggType } from '../metrics/metric_agg_type'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { TimeBuckets } from '../../time_buckets'; diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.ts b/src/legacy/ui/public/agg_types/buckets/date_range.ts index 4de6002e2e374..908d921d12313 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_range.ts @@ -23,14 +23,13 @@ import { npStart } from 'ui/new_platform'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; -import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; +import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { DateRangesParamEditor } from '../../vis/editors/default/controls/date_ranges'; // @ts-ignore import { fieldFormats } from '../../registry/field_formats'; // @ts-ignore import { dateRange } from '../../utils/date_range'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; const dateRangeTitle = i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', { defaultMessage: 'Date Range', diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/ui/public/agg_types/buckets/filters.ts index a8d509d507c6b..caebf2d7d974e 100644 --- a/src/legacy/ui/public/agg_types/buckets/filters.ts +++ b/src/legacy/ui/public/agg_types/buckets/filters.ts @@ -23,12 +23,11 @@ import angular from 'angular'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; -import { buildEsQuery } from '@kbn/es-query'; import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/controls/filters'; import { createFilterFilters } from './create_filter/filters'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { getQueryLog } from '../../../../../plugins/data/public'; +import { getQueryLog, esQuery } from '../../../../../plugins/data/public'; const config = chrome.getUiSettingsClient(); const storage = new Storage(window.localStorage); @@ -68,7 +67,7 @@ export const filtersBucketAgg = new BucketAggType({ return; } - const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); + const query = esQuery.buildEsQuery(aggConfig.getIndexPattern(), [input], [], config); if (!query) { console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts index 1716891231b83..700f5a048fce2 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts @@ -26,7 +26,7 @@ import { IsFilteredByCollarParamEditor } from '../../vis/editors/default/control import { PrecisionParamEditor } from '../../vis/editors/default/controls/precision'; import { geohashColumns } from '../../utils/decode_geo_hash'; import { AggGroupNames } from '../../vis/editors/default/agg_groups'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { geoContains, scaleBounds } from '../../utils/geo_utils'; diff --git a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts b/src/legacy/ui/public/agg_types/buckets/geo_tile.ts index 87373a064086f..3afb35a035690 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_tile.ts @@ -24,7 +24,7 @@ import { AggConfigOptions } from 'ui/agg_types/agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const geotileGridTitle = i18n.translate('common.ui.aggTypes.buckets.geotileGridTitle', { defaultMessage: 'Geotile', diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/ui/public/agg_types/buckets/histogram.ts index fba2f47010c34..7bd3d565003be 100644 --- a/src/legacy/ui/public/agg_types/buckets/histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/histogram.ts @@ -28,7 +28,7 @@ import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/nu import { MinDocCountParamEditor } from '../../vis/editors/default/controls/min_doc_count'; import { HasExtendedBoundsParamEditor } from '../../vis/editors/default/controls/has_extended_bounds'; import { ExtendedBoundsParamEditor } from '../../vis/editors/default/controls/extended_bounds'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { BUCKET_TYPES } from './bucket_agg_types'; export interface AutoBounds { diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/ip_range.ts index bbc91b0768f6b..7ef415ff8d0c4 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.ts @@ -24,13 +24,12 @@ import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_ra import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges'; // @ts-ignore import { fieldFormats } from '../../registry/field_formats'; -import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; +import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { ipRange } from '../../utils/ip_range'; import { BUCKET_TYPES } from './bucket_agg_types'; // @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; const ipRangeTitle = i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/ui/public/agg_types/buckets/range.test.ts index 1b423e64c48ae..5db7eb3c2d8e9 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/range.test.ts @@ -19,7 +19,7 @@ import { AggConfigs } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { NumberFormat } from '../../../../../plugins/data/common/'; +import { NumberFormat } from '../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/buckets/range.ts b/src/legacy/ui/public/agg_types/buckets/range.ts index 230675ab487ad..89529442b24a6 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.ts +++ b/src/legacy/ui/public/agg_types/buckets/range.ts @@ -20,14 +20,13 @@ import { i18n } from '@kbn/i18n'; import { IBucketAggConfig } from './_bucket_agg_type'; import { BucketAggType } from './_bucket_agg_type'; -import { FieldFormat } from '../../../../../plugins/data/common/field_formats'; +import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { RangeKey } from './range_key'; import { RangesEditor } from './range_editor'; // @ts-ignore import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; const keyCaches = new WeakMap(); const formats = new WeakMap(); diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts b/src/legacy/ui/public/agg_types/buckets/significant_terms.ts index 865ede2b1bd23..65c73e5f9b7dd 100644 --- a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/significant_terms.ts @@ -23,7 +23,7 @@ import { BucketAggType, BucketAggParam } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const significantTermsTitle = i18n.translate('common.ui.aggTypes.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/ui/public/agg_types/buckets/terms.ts index bc6dd4860561e..c0f870c27f10d 100644 --- a/src/legacy/ui/public/agg_types/buckets/terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/terms.ts @@ -23,7 +23,6 @@ import { SearchSource } from 'ui/courier'; import { i18n } from '@kbn/i18n'; import { BucketAggType, BucketAggParam } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; import { AggConfigOptions } from '../agg_config'; import { IBucketAggConfig } from './_bucket_agg_type'; import { @@ -39,10 +38,10 @@ import { OrderByParamEditor, aggFilter } from '../../vis/editors/default/control import { SizeParamEditor } from '../../vis/editors/default/controls/size'; import { MissingBucketParamEditor } from '../../vis/editors/default/controls/missing_bucket'; import { OtherBucketParamEditor } from '../../vis/editors/default/controls/other_bucket'; -import { ContentType } from '../../../../../plugins/data/common'; import { AggConfigs } from '../agg_configs'; import { Adapters } from '../../../../../plugins/inspector/public'; +import { ContentType, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { Schemas } from '../../vis/editors/default/schemas'; diff --git a/src/legacy/ui/public/agg_types/metrics/avg.ts b/src/legacy/ui/public/agg_types/metrics/avg.ts index cd069f3133af1..0222a8e543223 100644 --- a/src/legacy/ui/public/agg_types/metrics/avg.ts +++ b/src/legacy/ui/public/agg_types/metrics/avg.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const averageTitle = i18n.translate('common.ui.aggTypes.metrics.averageTitle', { defaultMessage: 'Average', diff --git a/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts b/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts index b5c479bde5cb0..b8ce03cdf11ec 100644 --- a/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts +++ b/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const geoBoundsTitle = i18n.translate('common.ui.aggTypes.metrics.geoBoundsTitle', { defaultMessage: 'Geo Bounds', diff --git a/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts b/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts index 25b45ff7d6b1c..5313e31796a5b 100644 --- a/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts +++ b/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const geoCentroidTitle = i18n.translate('common.ui.aggTypes.metrics.geoCentroidTitle', { defaultMessage: 'Geo Centroid', diff --git a/src/legacy/ui/public/agg_types/metrics/max.ts b/src/legacy/ui/public/agg_types/metrics/max.ts index f6e460be9e624..5c43511acee72 100644 --- a/src/legacy/ui/public/agg_types/metrics/max.ts +++ b/src/legacy/ui/public/agg_types/metrics/max.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const maxTitle = i18n.translate('common.ui.aggTypes.metrics.maxTitle', { defaultMessage: 'Max', diff --git a/src/legacy/ui/public/agg_types/metrics/median.ts b/src/legacy/ui/public/agg_types/metrics/median.ts index 1a3d6cf4b8d23..8797bed5105c5 100644 --- a/src/legacy/ui/public/agg_types/metrics/median.ts +++ b/src/legacy/ui/public/agg_types/metrics/median.ts @@ -22,7 +22,7 @@ import { METRIC_TYPES } from './metric_agg_types'; // @ts-ignore import { percentilesMetricAgg } from './percentiles'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const medianTitle = i18n.translate('common.ui.aggTypes.metrics.medianTitle', { defaultMessage: 'Median', diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts index c1f5528825bcc..7428bd6caa22d 100644 --- a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts +++ b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts @@ -25,7 +25,7 @@ import { METRIC_TYPES } from './metric_agg_types'; // @ts-ignore import { fieldFormats } from '../../registry/field_formats'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; export type IMetricAggConfig = AggConfig; diff --git a/src/legacy/ui/public/agg_types/metrics/min.ts b/src/legacy/ui/public/agg_types/metrics/min.ts index 4761985c75a43..5f8ca72954cc2 100644 --- a/src/legacy/ui/public/agg_types/metrics/min.ts +++ b/src/legacy/ui/public/agg_types/metrics/min.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const minTitle = i18n.translate('common.ui.aggTypes.metrics.minTitle', { defaultMessage: 'Min', diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts index 8b923092772db..4fabe137f1bc8 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts @@ -26,7 +26,7 @@ import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; // @ts-ignore import { fieldFormats } from '../../registry/field_formats'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // required by the values editor diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.ts index 0ac0455468472..1a3606d677951 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts index ebd5fceb9c751..b2e6d3b3ca4d0 100644 --- a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts +++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; interface ValProp { valProp: string[]; diff --git a/src/legacy/ui/public/agg_types/metrics/sum.ts b/src/legacy/ui/public/agg_types/metrics/sum.ts index 4e428a35a5383..ce79c761ce799 100644 --- a/src/legacy/ui/public/agg_types/metrics/sum.ts +++ b/src/legacy/ui/public/agg_types/metrics/sum.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; const sumTitle = i18n.translate('common.ui.aggTypes.metrics.sumTitle', { defaultMessage: 'Sum', 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 e9d1ebb93d3ba..051174c388c1b 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 @@ -21,7 +21,7 @@ import { dropRight, last } from 'lodash'; import { topHitMetricAgg } from './top_hit'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig } from './metric_agg_type'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.ts index 7ff83e326fc8f..49c4a7951fab9 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.ts +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.ts @@ -27,7 +27,7 @@ import { TopSizeParamEditor } from '../../vis/editors/default/controls/top_size' import { TopAggregateParamEditor } from '../../vis/editors/default/controls/top_aggregate'; import { aggTypeFieldFilters } from '../param_types/filter'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { wrapWithInlineComp } from '../buckets/inline_comp_wrapper'; diff --git a/src/legacy/ui/public/agg_types/param_types/field.test.ts b/src/legacy/ui/public/agg_types/param_types/field.test.ts index 2434f95056b78..9cea2934d7459 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.test.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.test.ts @@ -19,7 +19,7 @@ import { BaseParamType } from './base'; import { FieldParamType } from './field'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/utils.ts b/src/legacy/ui/public/agg_types/utils.ts index 6721262d265f4..fd405d49625ed 100644 --- a/src/legacy/ui/public/agg_types/utils.ts +++ b/src/legacy/ui/public/agg_types/utils.ts @@ -17,7 +17,7 @@ * under the License. */ -import { isValidEsInterval } from '../../../core_plugins/data/common'; +import { isValidEsInterval } from '../../../core_plugins/data/public'; import { leastCommonInterval } from '../vis/lib/least_common_interval'; /** diff --git a/src/legacy/ui/public/courier/search_source/search_source.js b/src/legacy/ui/public/courier/search_source/search_source.js index 729de41ed77db..bc69e862fea48 100644 --- a/src/legacy/ui/public/courier/search_source/search_source.js +++ b/src/legacy/ui/public/courier/search_source/search_source.js @@ -71,13 +71,12 @@ import _ from 'lodash'; import angular from 'angular'; -import { getEsQueryConfig, buildEsQuery } from '@kbn/es-query'; import { normalizeSortRequest } from './_normalize_sort_request'; import { fetchSoon } from '../fetch'; import { fieldWildcardFilter } from '../../field_wildcard'; -import { getHighlightRequest } from '../../../../../plugins/data/public'; +import { getHighlightRequest, esQuery } from '../../../../../plugins/data/public'; import { npSetup } from 'ui/new_platform'; import chrome from '../../chrome'; import { RequestFailure } from '../fetch/errors'; @@ -491,8 +490,8 @@ export class SearchSource { _.set(flatData.body, '_source.includes', remainingFields); } - const esQueryConfigs = getEsQueryConfig(config); - flatData.body.query = buildEsQuery(flatData.index, flatData.query, flatData.filters, esQueryConfigs); + const esQueryConfigs = esQuery.getEsQueryConfig(config); + flatData.body.query = esQuery.buildEsQuery(flatData.index, flatData.query, flatData.filters, esQueryConfigs); if (flatData.highlightAll != null) { if (flatData.highlightAll && flatData.body.query) { diff --git a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js b/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js deleted file mode 100644 index 5b6455bf20847..0000000000000 --- a/src/legacy/ui/public/filter_manager/__tests__/filter_generator.js +++ /dev/null @@ -1,166 +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 MockState from 'fixtures/mock_state'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { getFilterGenerator } from '..'; -import { FilterBarQueryFilterProvider } from '../../filter_manager/query_filter'; -import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; - -let queryFilter; -let filterGen; -let appState; - -function checkAddFilters(length, comps, idx) { - idx = idx || 0; - const filters = queryFilter.addFilters.getCall(idx).args[0]; - - expect(filters.length).to.be(length); - if (!Array.isArray(comps)) return; - comps.forEach(function (comp, i) { - expect(filters[i]).to.eql(comp); - }); -} - -describe('Filter Manager', function () { - beforeEach(ngMock.module( - 'kibana', - 'kibana/global_state', - function ($provide) { - $provide.service('indexPatterns', require('fixtures/mock_index_patterns')); - - appState = new MockState({ filters: [] }); - $provide.service('getAppState', function () { - return function () { return appState; }; - }); - } - )); - - beforeEach(ngMock.inject(function (_$rootScope_, Private) { - - // mock required queryFilter methods, used in the manager - queryFilter = Private(FilterBarQueryFilterProvider); - filterGen = getFilterGenerator(queryFilter); - sinon.stub(queryFilter, 'getAppFilters').callsFake(() => appState.filters); - sinon.stub(queryFilter, 'addFilters').callsFake((filters) => { - if (!Array.isArray(filters)) filters = [filters]; - appState.filters = uniqFilters(appState.filters.concat(filters)); - }); - })); - - it('should have an `add` function', function () { - expect(filterGen.add).to.be.a(Function); - }); - - it('should add a filter', function () { - filterGen.add('myField', 1, '+', 'myIndex'); - expect(queryFilter.addFilters.callCount).to.be(1); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }]); - }); - - it('should add multiple filters if passed an array of values', function () { - filterGen.add('myField', [1, 2, 3], '+', 'myIndex'); - expect(queryFilter.addFilters.callCount).to.be(1); - checkAddFilters(3, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }, { - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 2 } } - }, { - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 3 } } - }]); - }); - - it('should add an exists filter if _exists_ is used as the field', function () { - filterGen.add('_exists_', 'myField', '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - exists: { field: 'myField' } - }]); - }); - - it('should negate existing filter instead of added a conflicting filter', function () { - filterGen.add('myField', 1, '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - query: { match_phrase: { myField: 1 } } - }], 0); - expect(appState.filters).to.have.length(1); - - // NOTE: negating exists filters also forces disabled to false - filterGen.add('myField', 1, '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false }, - query: { match_phrase: { myField: 1 } } - }], 1); - expect(appState.filters).to.have.length(1); - - filterGen.add('_exists_', 'myField', '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false }, - exists: { field: 'myField' } - }], 2); - expect(appState.filters).to.have.length(2); - - filterGen.add('_exists_', 'myField', '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false }, - exists: { field: 'myField' } - }], 3); - expect(appState.filters).to.have.length(2); - - const scriptedField = { name: 'scriptedField', scripted: true, script: 1, lang: 'painless' }; - filterGen.add(scriptedField, 1, '+', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: false, field: 'scriptedField' }, - script: esFilters.getPhraseScript(scriptedField, 1) - }], 4); - expect(appState.filters).to.have.length(3); - - filterGen.add(scriptedField, 1, '-', 'myIndex'); - checkAddFilters(1, [{ - meta: { index: 'myIndex', negate: true, disabled: false, field: 'scriptedField' }, - script: esFilters.getPhraseScript(scriptedField, 1) - }], 5); - expect(appState.filters).to.have.length(3); - }); - - it('should enable matching filters being changed', function () { - _.each([true, false], function (negate) { - appState.filters = [{ - query: { match_phrase: { myField: 1 } }, - meta: { disabled: true, negate: negate } - }]; - expect(appState.filters.length).to.be(1); - expect(appState.filters[0].meta.disabled).to.be(true); - - filterGen.add('myField', 1, '+', 'myIndex'); - expect(appState.filters.length).to.be(1); - expect(appState.filters[0].meta.disabled).to.be(false); - }); - }); -}); diff --git a/src/legacy/ui/public/filter_manager/filter_generator.js b/src/legacy/ui/public/filter_manager/filter_generator.js deleted file mode 100644 index e11e0ff6653a7..0000000000000 --- a/src/legacy/ui/public/filter_manager/filter_generator.js +++ /dev/null @@ -1,98 +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 { esFilters } from '../../../../plugins/data/public'; - -// Adds a filter to a passed state -export function getFilterGenerator(queryFilter) { - const filterGen = {}; - - filterGen.generate = (field, values, operation, index) => { - values = Array.isArray(values) ? values : [values]; - const fieldName = _.isObject(field) ? field.name : field; - const filters = _.flatten([queryFilter.getAppFilters()]); - const newFilters = []; - - const negate = (operation === '-'); - - // TODO: On array fields, negating does not negate the combination, rather all terms - _.each(values, function (value) { - let filter; - const existing = _.find(filters, function (filter) { - if (!filter) return; - - if (fieldName === '_exists_' && filter.exists) { - return filter.exists.field === value; - } - - if (esFilters.isPhraseFilter(filter)) { - return esFilters.getPhraseFilterField(filter) === fieldName && esFilters.getPhraseFilterValue(filter) === value; - } - - if (filter.script) { - return filter.meta.field === fieldName && filter.script.script.params.value === value; - } - }); - - if (existing) { - existing.meta.disabled = false; - if (existing.meta.negate !== negate) { - existing.meta.negate = !existing.meta.negate; - } - newFilters.push(existing); - return; - } - - switch (fieldName) { - case '_exists_': - filter = { - meta: { negate, index }, - exists: { - field: value - } - }; - break; - default: - if (field.scripted) { - filter = { - meta: { negate, index, field: fieldName }, - script: esFilters.getPhraseScript(field, value) - }; - } else { - filter = { meta: { negate, index }, query: { match_phrase: {} } }; - filter.query.match_phrase[fieldName] = value; - } - - break; - } - - newFilters.push(filter); - }); - - return newFilters; - }; - - filterGen.add = function (field, values, operation, index) { - const newFilters = this.generate(field, values, operation, index); - return queryFilter.addFilters(newFilters); - }; - - return filterGen; -} diff --git a/src/legacy/ui/public/filter_manager/index.js b/src/legacy/ui/public/filter_manager/index.js index 6adc4e0965ccd..ce99d4cac3017 100644 --- a/src/legacy/ui/public/filter_manager/index.js +++ b/src/legacy/ui/public/filter_manager/index.js @@ -17,4 +17,3 @@ * under the License. */ -export { getFilterGenerator } from './filter_generator'; diff --git a/src/legacy/ui/public/resize_checker/__tests__/resize_checker.js b/src/legacy/ui/public/resize_checker/__tests__/resize_checker.js deleted file mode 100644 index 5fccf77f8f13d..0000000000000 --- a/src/legacy/ui/public/resize_checker/__tests__/resize_checker.js +++ /dev/null @@ -1,208 +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 'jquery'; -import { delay } from 'bluebird'; -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import ngMock from 'ng_mock'; -import NoDigestPromises from 'test_utils/no_digest_promises'; - -import { ResizeChecker } from '../resize_checker'; -import EventEmitter from 'events'; - -describe('Resize Checker', () => { - NoDigestPromises.activateForSuite(); - - const teardown = []; - let setup; - - beforeEach(ngMock.module('kibana')); - beforeEach(() => { - setup = () => { - - const createEl = () => { - const el = $('
').appendTo('body').get(0); - teardown.push(() => $(el).remove()); - return el; - }; - - const createChecker = el => { - const checker = new ResizeChecker(el); - teardown.push(() => checker.destroy()); - return checker; - }; - - const createListener = () => { - let resolveFirstCallPromise; - const listener = sinon.spy(() => resolveFirstCallPromise()); - listener.firstCallPromise = new Promise(resolve => (resolveFirstCallPromise = resolve)); - return listener; - }; - - return { createEl, createChecker, createListener }; - }; - }); - - afterEach(() => { - teardown.splice(0).forEach(fn => { - fn(); - }); - }); - - describe('construction', () => { - it('accepts a jQuery wrapped element', () => { - const { createChecker } = setup(); - - createChecker($('
')); - }); - }); - - describe('events', () => { - it('is an event emitter', () => { - const { createEl, createChecker } = setup(); - - const checker = createChecker(createEl()); - expect(checker).to.be.a(EventEmitter); - }); - - it('emits a "resize" event asynchronously', async () => { - const { createEl, createChecker, createListener } = setup(); - - const el = createEl(); - const checker = createChecker(el); - const listener = createListener(); - - checker.on('resize', listener); - $(el).height(100); - sinon.assert.notCalled(listener); - await listener.firstCallPromise; - sinon.assert.calledOnce(listener); - }); - }); - - describe('enable/disabled state', () => { - it('should not trigger events while disabled', async () => { - const { createEl, createListener } = setup(); - - const el = createEl(); - const checker = new ResizeChecker(el, { disabled: true }); - const listener = createListener(); - checker.on('resize', listener); - - expect(listener.notCalled).to.be(true); - $(el).height(100); - await delay(1000); - expect(listener.notCalled).to.be(true); - }); - - it('should trigger resize events after calling enable', async () => { - const { createEl, createListener } = setup(); - - const el = createEl(); - const checker = new ResizeChecker(el, { disabled: true }); - const listener = createListener(); - checker.on('resize', listener); - - expect(listener.notCalled).to.be(true); - checker.enable(); - $(el).height(100); - await listener.firstCallPromise; - expect(listener.calledOnce).to.be(true); - }); - - it('should not trigger the first time after enable when the size does not change', async () => { - const { createEl, createListener } = setup(); - - const el = createEl(); - const checker = new ResizeChecker(el, { disabled: true }); - const listener = createListener(); - checker.on('resize', listener); - - expect(listener.notCalled).to.be(true); - $(el).height(250); - checker.enable(); - $(el).height(250); - await delay(1000); - expect(listener.notCalled).to.be(true); - }); - }); - - describe('#modifySizeWithoutTriggeringResize()', () => { - it(`does not emit "resize" events caused by the block`, async () => { - const { createChecker, createEl, createListener } = setup(); - - const el = createEl(); - const checker = createChecker(el); - const listener = createListener(); - - checker.on('resize', listener); - checker.modifySizeWithoutTriggeringResize(() => { - $(el).height(100); - }); - await delay(1000); - sinon.assert.notCalled(listener); - }); - - it('does emit "resize" when modification is made between the block and resize notification', async () => { - const { createChecker, createEl, createListener } = setup(); - - const el = createEl(); - const checker = createChecker(el); - const listener = createListener(); - - checker.on('resize', listener); - checker.modifySizeWithoutTriggeringResize(() => { - $(el).height(100); - }); - sinon.assert.notCalled(listener); - $(el).height(200); - await listener.firstCallPromise; - sinon.assert.calledOnce(listener); - }); - }); - - describe('#destroy()', () => { - it('destroys internal observer instance', () => { - const { createChecker, createEl, createListener } = setup(); - - const checker = createChecker(createEl()); - createListener(); - - checker.destroy(); - expect(!checker._observer).to.be(true); - }); - - it('does not emit future resize events', async () => { - const { createChecker, createEl, createListener } = setup(); - - const el = createEl(); - const checker = createChecker(el); - const listener = createListener(); - - checker.on('resize', listener); - checker.destroy(); - - $(el).height(100); - await delay(1000); - sinon.assert.notCalled(listener); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx b/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx index 071e15f8b97f8..a216ad5d928b6 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/ranges.tsx @@ -29,6 +29,8 @@ import { EuiSpacer, EuiButtonEmpty, EuiFormRow, + EuiToolTip, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -154,15 +156,24 @@ function RangesParamEditor({ [isFromValid, isToValid] = validateRange({ from, to }, index); } - const fromPrepend = i18n.translate( + const gtePrependLabel = i18n.translate( 'common.ui.aggTypes.ranges.greaterThanOrEqualPrepend', { defaultMessage: '\u2265', } ); - const toPrepend = i18n.translate('common.ui.aggTypes.ranges.lessThanPrepend', { + const gteTooltipContent = i18n.translate( + 'common.ui.aggTypes.ranges.greaterThanOrEqualTooltip', + { + defaultMessage: 'Greater than or equal to', + } + ); + const ltPrependLabel = i18n.translate('common.ui.aggTypes.ranges.lessThanPrepend', { defaultMessage: '\u003c', }); + const ltTooltipContent = i18n.translate('common.ui.aggTypes.ranges.lessThanTooltip', { + defaultMessage: 'Less than', + }); return ( @@ -179,7 +190,11 @@ function RangesParamEditor({ fullWidth={true} compressed={true} isInvalid={!isFromValid} - prepend={fromPrepend} + prepend={ + + {gtePrependLabel} + + } /> @@ -197,7 +212,11 @@ function RangesParamEditor({ fullWidth={true} compressed={true} isInvalid={!isToValid} - prepend={toPrepend} + prepend={ + + {ltPrependLabel} + + } /> diff --git a/src/legacy/ui/public/visualize/components/visualization_chart.tsx b/src/legacy/ui/public/visualize/components/visualization_chart.tsx index eb7f130ec1a54..8aec7adeaec9a 100644 --- a/src/legacy/ui/public/visualize/components/visualization_chart.tsx +++ b/src/legacy/ui/public/visualize/components/visualization_chart.tsx @@ -22,7 +22,7 @@ import * as Rx from 'rxjs'; import { debounceTime, filter, share, switchMap } from 'rxjs/operators'; import { PersistedState } from '../../persisted_state'; -import { ResizeChecker } from '../../resize_checker'; +import { ResizeChecker } from '../../../../../plugins/kibana_utils/public'; import { Vis, VisualizationController } from '../../vis'; import { getUpdateStatus } from '../../vis/update_status'; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index c5ebc75973d0c..6598da76f60ba 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -22,7 +22,7 @@ import { identity } from 'lodash'; import { AggConfig, Vis } from 'ui/vis'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; +import { FieldFormat } from '../../../../../../plugins/data/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; import chrome from '../../../chrome'; diff --git a/packages/kbn-es-query/src/es_query/__tests__/build_es_query.js b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts similarity index 61% rename from packages/kbn-es-query/src/es_query/__tests__/build_es_query.js rename to src/plugins/data/common/es_query/es_query/build_es_query.test.ts index fde3d063caaa6..3db23051b6ced 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/build_es_query.js +++ b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts @@ -17,99 +17,100 @@ * under the License. */ -import expect from '@kbn/expect'; -import { buildEsQuery } from '../build_es_query'; -import indexPattern from '../../__fixtures__/index_pattern_response.json'; -import { fromKueryExpression, toElasticsearchQuery } from '../../kuery'; -import { luceneStringToDsl } from '../lucene_string_to_dsl'; -import { decorateQuery } from '../decorate_query'; +import { buildEsQuery } from './build_es_query'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { luceneStringToDsl } from './lucene_string_to_dsl'; +import { decorateQuery } from './decorate_query'; +import { IIndexPattern } from '../../index_patterns'; +import { MatchAllFilter } from '../filters'; +import { fields } from '../../index_patterns/mocks'; +import { Query } from '../../query/types'; -describe('build query', function () { - describe('buildEsQuery', function () { +describe('build query', () => { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; - it('should return the parameters of an Elasticsearch bool query', function () { - const result = buildEsQuery(); + describe('buildEsQuery', () => { + it('should return the parameters of an Elasticsearch bool query', () => { + const result = buildEsQuery(indexPattern, [], []); const expected = { bool: { must: [], filter: [], should: [], must_not: [], - } + }, }; - expect(result).to.eql(expected); + expect(result).toEqual(expected); }); - it('should combine queries and filters from multiple query languages into a single ES bool query', function () { + it('should combine queries and filters from multiple query languages into a single ES bool query', () => { const queries = [ { query: 'extension:jpg', language: 'kuery' }, { query: 'bar:baz', language: 'lucene' }, - ]; + ] as Query[]; const filters = [ { match_all: {}, meta: { type: 'match_all' }, - }, + } as MatchAllFilter, ]; const config = { allowLeadingWildcards: true, queryStringOptions: {}, + ignoreFilterIfFieldNotInIndex: false, }; const expectedResult = { bool: { - must: [ - decorateQuery(luceneStringToDsl('bar:baz'), config.queryStringOptions), - ], + must: [decorateQuery(luceneStringToDsl('bar:baz'), config.queryStringOptions)], filter: [ toElasticsearchQuery(fromKueryExpression('extension:jpg'), indexPattern), { match_all: {} }, ], should: [], must_not: [], - } + }, }; const result = buildEsQuery(indexPattern, queries, filters, config); - expect(result).to.eql(expectedResult); + expect(result).toEqual(expectedResult); }); - it('should accept queries and filters as either single objects or arrays', function () { - const queries = { query: 'extension:jpg', language: 'lucene' }; + it('should accept queries and filters as either single objects or arrays', () => { + const queries = { query: 'extension:jpg', language: 'lucene' } as Query; const filters = { match_all: {}, meta: { type: 'match_all' }, - }; + } as MatchAllFilter; const config = { allowLeadingWildcards: true, queryStringOptions: {}, + ignoreFilterIfFieldNotInIndex: false, }; const expectedResult = { bool: { - must: [ - decorateQuery(luceneStringToDsl('extension:jpg'), config.queryStringOptions), - ], + must: [decorateQuery(luceneStringToDsl('extension:jpg'), config.queryStringOptions)], filter: [{ match_all: {} }], should: [], must_not: [], - } + }, }; const result = buildEsQuery(indexPattern, queries, filters, config); - expect(result).to.eql(expectedResult); + expect(result).toEqual(expectedResult); }); - it('should use the default time zone set in the Advanced Settings in queries and filters', function () { + it('should use the default time zone set in the Advanced Settings in queries and filters', () => { const queries = [ { query: '@timestamp:"2019-03-23T13:18:00"', language: 'kuery' }, - { query: '@timestamp:"2019-03-23T13:18:00"', language: 'lucene' } - ]; - const filters = [ - { match_all: {}, meta: { type: 'match_all' } } - ]; + { query: '@timestamp:"2019-03-23T13:18:00"', language: 'lucene' }, + ] as Query[]; + const filters = [{ match_all: {}, meta: { type: 'match_all' } } as MatchAllFilter]; const config = { allowLeadingWildcards: true, queryStringOptions: {}, @@ -120,20 +121,27 @@ describe('build query', function () { const expectedResult = { bool: { must: [ - decorateQuery(luceneStringToDsl('@timestamp:"2019-03-23T13:18:00"'), config.queryStringOptions, config.dateFormatTZ), + decorateQuery( + luceneStringToDsl('@timestamp:"2019-03-23T13:18:00"'), + config.queryStringOptions, + config.dateFormatTZ + ), ], filter: [ - toElasticsearchQuery(fromKueryExpression('@timestamp:"2019-03-23T13:18:00"'), indexPattern, config), - { match_all: {} } + toElasticsearchQuery( + fromKueryExpression('@timestamp:"2019-03-23T13:18:00"'), + indexPattern, + config + ), + { match_all: {} }, ], should: [], must_not: [], - } + }, }; const result = buildEsQuery(indexPattern, queries, filters, config); - expect(result).to.eql(expectedResult); - }); + expect(result).toEqual(expectedResult); + }); }); - }); diff --git a/packages/kbn-es-query/src/es_query/build_es_query.js b/src/plugins/data/common/es_query/es_query/build_es_query.ts similarity index 58% rename from packages/kbn-es-query/src/es_query/build_es_query.js rename to src/plugins/data/common/es_query/es_query/build_es_query.ts index d17147761d8bc..b754496793660 100644 --- a/packages/kbn-es-query/src/es_query/build_es_query.js +++ b/src/plugins/data/common/es_query/es_query/build_es_query.ts @@ -21,6 +21,16 @@ import { groupBy, has } from 'lodash'; import { buildQueryFromKuery } from './from_kuery'; import { buildQueryFromFilters } from './from_filters'; import { buildQueryFromLucene } from './from_lucene'; +import { IIndexPattern } from '../../index_patterns'; +import { Filter } from '../filters'; +import { Query } from '../../query/types'; + +export interface EsQueryConfig { + allowLeadingWildcards: boolean; + queryStringOptions: Record; + ignoreFilterIfFieldNotInIndex: boolean; + dateFormatTZ?: string; +} /** * @param indexPattern @@ -31,30 +41,43 @@ import { buildQueryFromLucene } from './from_lucene'; * config contains dateformat:tz */ export function buildEsQuery( - indexPattern, - queries = [], - filters = [], - config = { + indexPattern: IIndexPattern | null, + queries: Query | Query[], + filters: Filter | Filter[], + config: EsQueryConfig = { allowLeadingWildcards: false, queryStringOptions: {}, ignoreFilterIfFieldNotInIndex: false, - dateFormatTZ: null, - }) { + } +) { queries = Array.isArray(queries) ? queries : [queries]; filters = Array.isArray(filters) ? filters : [filters]; - const validQueries = queries.filter((query) => has(query, 'query')); + const validQueries = queries.filter(query => has(query, 'query')); const queriesByLanguage = groupBy(validQueries, 'language'); - const kueryQuery = buildQueryFromKuery(indexPattern, queriesByLanguage.kuery, config.allowLeadingWildcards, config.dateFormatTZ); - const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, config.queryStringOptions, config.dateFormatTZ); - const filterQuery = buildQueryFromFilters(filters, indexPattern, config.ignoreFilterIfFieldNotInIndex); + const kueryQuery = buildQueryFromKuery( + indexPattern, + queriesByLanguage.kuery, + config.allowLeadingWildcards, + config.dateFormatTZ + ); + const luceneQuery = buildQueryFromLucene( + queriesByLanguage.lucene, + config.queryStringOptions, + config.dateFormatTZ + ); + const filterQuery = buildQueryFromFilters( + filters, + indexPattern, + config.ignoreFilterIfFieldNotInIndex + ); return { bool: { - must: [].concat(kueryQuery.must, luceneQuery.must, filterQuery.must), - filter: [].concat(kueryQuery.filter, luceneQuery.filter, filterQuery.filter), - should: [].concat(kueryQuery.should, luceneQuery.should, filterQuery.should), - must_not: [].concat(kueryQuery.must_not, luceneQuery.must_not, filterQuery.must_not), - } + must: [...kueryQuery.must, ...luceneQuery.must, ...filterQuery.must], + filter: [...kueryQuery.filter, ...luceneQuery.filter, ...filterQuery.filter], + should: [...kueryQuery.should, ...luceneQuery.should, ...filterQuery.should], + must_not: [...kueryQuery.must_not, ...luceneQuery.must_not, ...filterQuery.must_not], + }, }; } diff --git a/packages/kbn-es-query/src/es_query/__tests__/decorate_query.js b/src/plugins/data/common/es_query/es_query/decorate_query.test.ts similarity index 50% rename from packages/kbn-es-query/src/es_query/__tests__/decorate_query.js rename to src/plugins/data/common/es_query/es_query/decorate_query.test.ts index d5978716dac9e..d7cd82eb7108a 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/decorate_query.js +++ b/src/plugins/data/common/es_query/es_query/decorate_query.test.ts @@ -17,21 +17,30 @@ * under the License. */ -import expect from '@kbn/expect'; -import { decorateQuery } from '../decorate_query'; +import { decorateQuery } from './decorate_query'; -describe('Query decorator', function () { - it('should be a function', function () { - expect(decorateQuery).to.be.a(Function); +describe('Query decorator', () => { + test('should be a function', () => { + expect(typeof decorateQuery).toBe('function'); }); - it('should merge in the query string options', function () { - const decoratedQuery = decorateQuery({ query_string: { query: '*' } }, { analyze_wildcard: true }); - expect(decoratedQuery).to.eql({ query_string: { query: '*', analyze_wildcard: true } }); + test('should merge in the query string options', () => { + const decoratedQuery = decorateQuery( + { query_string: { query: '*' } }, + { analyze_wildcard: true } + ); + + expect(decoratedQuery).toEqual({ query_string: { query: '*', analyze_wildcard: true } }); }); - it('should add a default of a time_zone parameter if one is provided', function () { - const decoratedQuery = decorateQuery({ query_string: { query: '*' } }, { analyze_wildcard: true }, 'America/Phoenix'); - expect(decoratedQuery).to.eql({ query_string: { query: '*', analyze_wildcard: true, time_zone: 'America/Phoenix' } }); + test('should add a default of a time_zone parameter if one is provided', () => { + const decoratedQuery = decorateQuery( + { query_string: { query: '*' } }, + { analyze_wildcard: true }, + 'America/Phoenix' + ); + expect(decoratedQuery).toEqual({ + query_string: { query: '*', analyze_wildcard: true, time_zone: 'America/Phoenix' }, + }); }); }); diff --git a/packages/kbn-es-query/src/es_query/decorate_query.js b/src/plugins/data/common/es_query/es_query/decorate_query.ts similarity index 69% rename from packages/kbn-es-query/src/es_query/decorate_query.js rename to src/plugins/data/common/es_query/es_query/decorate_query.ts index 8104707e0298a..891712d057886 100644 --- a/packages/kbn-es-query/src/es_query/decorate_query.js +++ b/src/plugins/data/common/es_query/es_query/decorate_query.ts @@ -17,8 +17,9 @@ * under the License. */ -import _ from 'lodash'; -import { getTimeZoneFromSettings } from '../utils/get_time_zone_from_settings'; +import { extend, defaults } from 'lodash'; +import { getTimeZoneFromSettings } from '../utils'; +import { DslQuery, isEsQueryString } from './es_query_dsl'; /** * Decorate queries with default parameters @@ -28,11 +29,17 @@ import { getTimeZoneFromSettings } from '../utils/get_time_zone_from_settings'; * @returns {object} */ -export function decorateQuery(query, queryStringOptions, dateFormatTZ = null) { - if (_.has(query, 'query_string.query')) { - _.extend(query.query_string, queryStringOptions); +export function decorateQuery( + query: DslQuery, + queryStringOptions: Record, + dateFormatTZ?: string +) { + if (isEsQueryString(query)) { + extend(query.query_string, queryStringOptions); if (dateFormatTZ) { - _.defaults(query.query_string, { time_zone: getTimeZoneFromSettings(dateFormatTZ) }); + defaults(query.query_string, { + time_zone: getTimeZoneFromSettings(dateFormatTZ), + }); } } diff --git a/src/plugins/data/common/es_query/es_query/es_query_dsl.ts b/src/plugins/data/common/es_query/es_query/es_query_dsl.ts new file mode 100644 index 0000000000000..d906ae5359ec2 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/es_query_dsl.ts @@ -0,0 +1,65 @@ +/* + * 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 { has } from 'lodash'; + +export interface DslRangeQuery { + range: { + [name: string]: { + gte: number; + lte: number; + format: string; + }; + }; +} + +export interface DslMatchQuery { + match: { + [name: string]: { + query: string; + operator: string; + zero_terms_query: string; + }; + }; +} + +export interface DslQueryStringQuery { + query_string: { + query: string; + analyze_wildcard?: boolean; + }; +} + +export interface DslMatchAllQuery { + match_all: Record; +} + +export interface DslTermQuery { + term: Record; +} + +export type DslQuery = + | DslRangeQuery + | DslMatchQuery + | DslQueryStringQuery + | DslMatchAllQuery + | DslTermQuery; + +export const isEsQueryString = (query: any): query is DslQueryStringQuery => + has(query, 'query_string.query'); diff --git a/packages/kbn-es-query/src/es_query/__tests__/filter_matches_index.js b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts similarity index 62% rename from packages/kbn-es-query/src/es_query/__tests__/filter_matches_index.js rename to src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts index 1c43230aeea30..6a5c7bdf8eea3 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/filter_matches_index.js +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts @@ -17,31 +17,36 @@ * under the License. */ -import expect from '@kbn/expect'; -import { filterMatchesIndex } from '../filter_matches_index'; +import { Filter } from '../filters'; +import { filterMatchesIndex } from './filter_matches_index'; +import { IIndexPattern } from '../../index_patterns'; -describe('filterMatchesIndex', function () { +describe('filterMatchesIndex', () => { it('should return true if the filter has no meta', () => { - const filter = {}; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] }; - expect(filterMatchesIndex(filter, indexPattern)).to.be(true); + const filter = {} as Filter; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); it('should return true if no index pattern is passed', () => { - const filter = { meta: { index: 'foo', key: 'bar' } }; - const indexPattern = undefined; - expect(filterMatchesIndex(filter, indexPattern)).to.be(true); + const filter = { meta: { index: 'foo', key: 'bar' } } as Filter; + const indexPattern = null; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); it('should return true if the filter key matches a field name', () => { - const filter = { meta: { index: 'foo', key: 'bar' } }; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] }; - expect(filterMatchesIndex(filter, indexPattern)).to.be(true); + const filter = { meta: { index: 'foo', key: 'bar' } } as Filter; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); it('should return false if the filter key does not match a field name', () => { - const filter = { meta: { index: 'foo', key: 'baz' } }; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] }; - expect(filterMatchesIndex(filter, indexPattern)).to.be(false); + const filter = { meta: { index: 'foo', key: 'baz' } } as Filter; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + + expect(filterMatchesIndex(filter, indexPattern)).toBe(false); }); }); diff --git a/packages/kbn-es-query/src/es_query/filter_matches_index.js b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts similarity index 68% rename from packages/kbn-es-query/src/es_query/filter_matches_index.js rename to src/plugins/data/common/es_query/es_query/filter_matches_index.ts index 602a9bbfbc925..496aab3ea585f 100644 --- a/packages/kbn-es-query/src/es_query/filter_matches_index.js +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts @@ -17,12 +17,17 @@ * under the License. */ -// TODO: We should base this on something better than `filter.meta.key`. We should probably modify -// this to check if `filter.meta.index` matches `indexPattern.id` instead, but that's a breaking -// change. -export function filterMatchesIndex(filter, indexPattern) { +import { IIndexPattern, IFieldType } from '../../index_patterns'; +import { Filter } from '../filters'; + +/* + * TODO: We should base this on something better than `filter.meta.key`. We should probably modify + * this to check if `filter.meta.index` matches `indexPattern.id` instead, but that's a breaking + * change. + */ +export function filterMatchesIndex(filter: Filter, indexPattern: IIndexPattern | null) { if (!filter.meta || !indexPattern) { return true; } - return indexPattern.fields.some(field => field.name === filter.meta.key); + return indexPattern.fields.some((field: IFieldType) => field.name === filter.meta.key); } diff --git a/src/plugins/data/common/es_query/es_query/from_filters.test.ts b/src/plugins/data/common/es_query/es_query/from_filters.test.ts new file mode 100644 index 0000000000000..8c1d990c389b8 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/from_filters.test.ts @@ -0,0 +1,148 @@ +/* + * 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 { buildQueryFromFilters } from './from_filters'; +import { IIndexPattern } from '../../index_patterns'; +import { ExistsFilter, Filter, MatchAllFilter } from '../filters'; +import { fields } from '../../index_patterns/mocks'; + +describe('build query', () => { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('buildQueryFromFilters', () => { + test('should return the parameters of an Elasticsearch bool query', () => { + const result = buildQueryFromFilters([], indexPattern, false); + const expected = { + must: [], + filter: [], + should: [], + must_not: [], + }; + expect(result).toEqual(expected); + }); + + test('should transform an array of kibana filters into ES queries combined in the bool clauses', () => { + const filters = [ + { + match_all: {}, + meta: { type: 'match_all' }, + } as MatchAllFilter, + { + exists: { field: 'foo' }, + meta: { type: 'exists' }, + } as ExistsFilter, + ] as Filter[]; + + const expectedESQueries = [{ match_all: {} }, { exists: { field: 'foo' } }]; + + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.filter).toEqual(expectedESQueries); + }); + + test('should remove disabled filters', () => { + const filters = [ + { + match_all: {}, + meta: { type: 'match_all', negate: true, disabled: true }, + } as MatchAllFilter, + ] as Filter[]; + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.must_not).toEqual([]); + }); + + test('should remove falsy filters', () => { + const filters = ([null, undefined] as unknown) as Filter[]; + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.must_not).toEqual([]); + expect(result.must).toEqual([]); + }); + + test('should place negated filters in the must_not clause', () => { + const filters = [ + { + match_all: {}, + meta: { type: 'match_all', negate: true }, + } as MatchAllFilter, + ] as Filter[]; + + const expectedESQueries = [{ match_all: {} }]; + + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.must_not).toEqual(expectedESQueries); + }); + + test('should translate old ES filter syntax into ES 5+ query objects', () => { + const filters = [ + { + query: { exists: { field: 'foo' } }, + meta: { type: 'exists' }, + }, + ] as Filter[]; + + const expectedESQueries = [ + { + exists: { field: 'foo' }, + }, + ]; + + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.filter).toEqual(expectedESQueries); + }); + + test('should migrate deprecated match syntax', () => { + const filters = [ + { + query: { match: { extension: { query: 'foo', type: 'phrase' } } }, + meta: { type: 'phrase' }, + }, + ] as Filter[]; + + const expectedESQueries = [ + { + match_phrase: { extension: { query: 'foo' } }, + }, + ]; + + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.filter).toEqual(expectedESQueries); + }); + + test('should not add query:queryString:options to query_string filters', () => { + const filters = [ + { + query: { query_string: { query: 'foo' } }, + meta: { type: 'query_string' }, + }, + ] as Filter[]; + + const expectedESQueries = [{ query_string: { query: 'foo' } }]; + const result = buildQueryFromFilters(filters, indexPattern, false); + + expect(result.filter).toEqual(expectedESQueries); + }); + }); +}); diff --git a/packages/kbn-es-query/src/es_query/from_filters.js b/src/plugins/data/common/es_query/es_query/from_filters.ts similarity index 69% rename from packages/kbn-es-query/src/es_query/from_filters.js rename to src/plugins/data/common/es_query/es_query/from_filters.ts index 10f9cf82fc972..1e0957d816590 100644 --- a/packages/kbn-es-query/src/es_query/from_filters.js +++ b/src/plugins/data/common/es_query/es_query/from_filters.ts @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import _ from 'lodash'; +import { isUndefined } from 'lodash'; import { migrateFilter } from './migrate_filter'; import { filterMatchesIndex } from './filter_matches_index'; +import { Filter, cleanFilter, isFilterDisabled } from '../filters'; +import { IIndexPattern } from '../../index_patterns'; /** * Create a filter that can be reversed for filters with negate set @@ -28,11 +29,12 @@ import { filterMatchesIndex } from './filter_matches_index'; * through otherwise it will filter out * @returns {function} */ -const filterNegate = function (reverse) { - return function (filter) { - if (_.isUndefined(filter.meta) || _.isUndefined(filter.meta.negate)) return !reverse; - return filter.meta && filter.meta.negate === reverse; - }; +const filterNegate = (reverse: boolean) => (filter: Filter) => { + if (isUndefined(filter.meta) || isUndefined(filter.meta.negate)) { + return !reverse; + } + + return filter.meta && filter.meta.negate === reverse; }; /** @@ -40,7 +42,7 @@ const filterNegate = function (reverse) { * @param {Object} filter - The filter to translate * @return {Object} the query version of that filter */ -const translateToQuery = function (filter) { +const translateToQuery = (filter: Filter) => { if (!filter) return; if (filter.query) { @@ -50,17 +52,13 @@ const translateToQuery = function (filter) { return filter; }; -/** - * Clean out any invalid attributes from the filters - * @param {object} filter The filter to clean - * @returns {object} - */ -const cleanFilter = function (filter) { - return _.omit(filter, ['meta', '$state']); -}; +export const buildQueryFromFilters = ( + filters: Filter[] = [], + indexPattern: IIndexPattern | null, + ignoreFilterIfFieldNotInIndex: boolean = false +) => { + filters = filters.filter(filter => filter && !isFilterDisabled(filter)); -export function buildQueryFromFilters(filters = [], indexPattern, ignoreFilterIfFieldNotInIndex) { - filters = filters.filter(filter => filter && !_.get(filter, ['meta', 'disabled'])); return { must: [], filter: filters @@ -68,17 +66,13 @@ export function buildQueryFromFilters(filters = [], indexPattern, ignoreFilterIf .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) .map(translateToQuery) .map(cleanFilter) - .map(filter => { - return migrateFilter(filter, indexPattern); - }), + .map(filter => migrateFilter(filter, indexPattern)), should: [], must_not: filters .filter(filterNegate(true)) .filter(filter => !ignoreFilterIfFieldNotInIndex || filterMatchesIndex(filter, indexPattern)) .map(translateToQuery) .map(cleanFilter) - .map(filter => { - return migrateFilter(filter, indexPattern); - }), + .map(filter => migrateFilter(filter, indexPattern)), }; -} +}; diff --git a/packages/kbn-es-query/src/es_query/__tests__/from_kuery.js b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts similarity index 54% rename from packages/kbn-es-query/src/es_query/__tests__/from_kuery.js rename to src/plugins/data/common/es_query/es_query/from_kuery.test.ts index 18738b05ea69d..000815b51f620 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/from_kuery.js +++ b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts @@ -17,14 +17,19 @@ * under the License. */ -import { buildQueryFromKuery } from '../from_kuery'; -import indexPattern from '../../__fixtures__/index_pattern_response.json'; -import expect from '@kbn/expect'; -import { fromKueryExpression, toElasticsearchQuery } from '../../kuery'; - -describe('build query', function () { - describe('buildQueryFromKuery', function () { - it('should return the parameters of an Elasticsearch bool query', function () { +import { buildQueryFromKuery } from './from_kuery'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { IIndexPattern } from '../../index_patterns'; +import { fields } from '../../index_patterns/mocks'; +import { Query } from '../../query/types'; + +describe('build query', () => { + const indexPattern: IIndexPattern = ({ + fields, + } as unknown) as IIndexPattern; + + describe('buildQueryFromKuery', () => { + test('should return the parameters of an Elasticsearch bool query', () => { const result = buildQueryFromKuery(null, [], true); const expected = { must: [], @@ -32,50 +37,48 @@ describe('build query', function () { should: [], must_not: [], }; - expect(result).to.eql(expected); + expect(result).toEqual(expected); }); - it('should transform an array of kuery queries into ES queries combined in the bool\'s filter clause', function () { + test("should transform an array of kuery queries into ES queries combined in the bool's filter clause", () => { const queries = [ { query: 'extension:jpg', language: 'kuery' }, { query: 'machine.os:osx', language: 'kuery' }, - ]; + ] as Query[]; - const expectedESQueries = queries.map( - (query) => { - return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern); - } - ); + const expectedESQueries = queries.map(query => { + return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern); + }); const result = buildQueryFromKuery(indexPattern, queries, true); - expect(result.filter).to.eql(expectedESQueries); + expect(result.filter).toEqual(expectedESQueries); }); - it('should accept a specific date format for a kuery query into an ES query in the bool\'s filter clause', function () { - const queries = [{ query: '@timestamp:"2018-04-03T19:04:17"', language: 'kuery' }]; - + test("should accept a specific date format for a kuery query into an ES query in the bool's filter clause", () => { + const queries = [{ query: '@timestamp:"2018-04-03T19:04:17"', language: 'kuery' }] as Query[]; const expectedESQueries = queries.map(query => { - return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern, { dateFormatTZ: 'America/Phoenix' }); + return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern, { + dateFormatTZ: 'America/Phoenix', + }); }); const result = buildQueryFromKuery(indexPattern, queries, true, 'America/Phoenix'); - expect(result.filter).to.eql(expectedESQueries); + expect(result.filter).toEqual(expectedESQueries); }); - it('should gracefully handle date queries when no date format is provided', function () { - const queries = [{ query: '@timestamp:"2018-04-03T19:04:17Z"', language: 'kuery' }]; - + test('should gracefully handle date queries when no date format is provided', () => { + const queries = [ + { query: '@timestamp:"2018-04-03T19:04:17Z"', language: 'kuery' }, + ] as Query[]; const expectedESQueries = queries.map(query => { return toElasticsearchQuery(fromKueryExpression(query.query), indexPattern); }); const result = buildQueryFromKuery(indexPattern, queries, true); - expect(result.filter).to.eql(expectedESQueries); + expect(result.filter).toEqual(expectedESQueries); }); - }); - }); diff --git a/packages/kbn-es-query/src/es_query/from_kuery.js b/src/plugins/data/common/es_query/es_query/from_kuery.ts similarity index 59% rename from packages/kbn-es-query/src/es_query/from_kuery.js rename to src/plugins/data/common/es_query/es_query/from_kuery.ts index 8e6e64b4c984e..f91c3d97b95b4 100644 --- a/packages/kbn-es-query/src/es_query/from_kuery.js +++ b/src/plugins/data/common/es_query/es_query/from_kuery.ts @@ -17,27 +17,40 @@ * under the License. */ -import { - fromKueryExpression, - toElasticsearchQuery, - nodeTypes, -} from '../kuery'; +import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '@kbn/es-query'; +import { IIndexPattern } from '../../index_patterns'; +import { Query } from '../../query/types'; -export function buildQueryFromKuery(indexPattern, queries = [], allowLeadingWildcards, dateFormatTZ = null) { +export function buildQueryFromKuery( + indexPattern: IIndexPattern | null, + queries: Query[] = [], + allowLeadingWildcards: boolean = false, + dateFormatTZ?: string +) { const queryASTs = queries.map(query => { return fromKueryExpression(query.query, { allowLeadingWildcards }); }); + return buildQuery(indexPattern, queryASTs, { dateFormatTZ }); } -function buildQuery(indexPattern, queryASTs, config = null) { - const compoundQueryAST = nodeTypes.function.buildNode('and', queryASTs); - const kueryQuery = toElasticsearchQuery(compoundQueryAST, indexPattern, config); +function buildQuery( + indexPattern: IIndexPattern | null, + queryASTs: KueryNode[], + config: Record = {} +) { + const compoundQueryAST: KueryNode = nodeTypes.function.buildNode('and', queryASTs); + const kueryQuery: Record = toElasticsearchQuery( + compoundQueryAST, + indexPattern, + config + ); + return { must: [], filter: [], should: [], must_not: [], - ...kueryQuery.bool + ...kueryQuery.bool, }; } diff --git a/src/plugins/data/common/es_query/es_query/from_lucene.test.ts b/src/plugins/data/common/es_query/es_query/from_lucene.test.ts new file mode 100644 index 0000000000000..fc85404a5060c --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/from_lucene.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 { buildQueryFromLucene } from './from_lucene'; +import { decorateQuery } from './decorate_query'; +import { luceneStringToDsl } from './lucene_string_to_dsl'; +import { Query } from '../../query/types'; + +describe('build query', () => { + describe('buildQueryFromLucene', () => { + test('should return the parameters of an Elasticsearch bool query', () => { + const result = buildQueryFromLucene([], {}); + const expected = { + must: [], + filter: [], + should: [], + must_not: [], + }; + + expect(result).toEqual(expected); + }); + + test("should transform an array of lucene queries into ES queries combined in the bool's must clause", () => { + const queries = ([ + { query: 'foo:bar', language: 'lucene' }, + { query: 'bar:baz', language: 'lucene' }, + ] as unknown) as Query[]; + const expectedESQueries = queries.map(query => { + return decorateQuery(luceneStringToDsl(query.query), {}); + }); + + const result = buildQueryFromLucene(queries, {}); + + expect(result.must).toEqual(expectedESQueries); + }); + + test('should also accept queries in ES query DSL format, simply passing them through', () => { + const queries = ([{ query: { match_all: {} }, language: 'lucene' }] as unknown) as Query[]; + const result = buildQueryFromLucene(queries, {}); + + expect(result.must).toEqual([queries[0].query]); + }); + }); + + test("should accept a date format in the decorated queries and combine that into the bool's must clause", () => { + const queries = ([ + { query: 'foo:bar', language: 'lucene' }, + { query: 'bar:baz', language: 'lucene' }, + ] as unknown) as Query[]; + const dateFormatTZ = 'America/Phoenix'; + const expectedESQueries = queries.map(query => { + return decorateQuery(luceneStringToDsl(query.query), {}, dateFormatTZ); + }); + const result = buildQueryFromLucene(queries, {}, dateFormatTZ); + + expect(result.must).toEqual(expectedESQueries); + }); +}); diff --git a/packages/kbn-es-query/src/es_query/from_lucene.js b/src/plugins/data/common/es_query/es_query/from_lucene.ts similarity index 81% rename from packages/kbn-es-query/src/es_query/from_lucene.js rename to src/plugins/data/common/es_query/es_query/from_lucene.ts index 8845fd68efb4d..8babb6df4fba5 100644 --- a/packages/kbn-es-query/src/es_query/from_lucene.js +++ b/src/plugins/data/common/es_query/es_query/from_lucene.ts @@ -16,19 +16,23 @@ * specific language governing permissions and limitations * under the License. */ - -import _ from 'lodash'; import { decorateQuery } from './decorate_query'; import { luceneStringToDsl } from './lucene_string_to_dsl'; +import { Query } from '../../query/types'; -export function buildQueryFromLucene(queries, queryStringOptions, dateFormatTZ = null) { - const combinedQueries = _.map(queries, (query) => { +export function buildQueryFromLucene( + queries: Query[], + queryStringOptions: Record, + dateFormatTZ?: string +) { + const combinedQueries = (queries || []).map(query => { const queryDsl = luceneStringToDsl(query.query); + return decorateQuery(queryDsl, queryStringOptions, dateFormatTZ); }); return { - must: [].concat(combinedQueries), + must: combinedQueries, filter: [], should: [], must_not: [], diff --git a/packages/kbn-es-query/src/es_query/__tests__/get_es_query_config.js b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts similarity index 69% rename from packages/kbn-es-query/src/es_query/__tests__/get_es_query_config.js rename to src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts index 8ccb04dd4b25a..a4ab03687f92e 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/get_es_query_config.js +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.test.ts @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +import { get } from 'lodash'; +import { getEsQueryConfig } from './get_es_query_config'; +import { UiSettingsClientContract } from 'kibana/public'; -import expect from '@kbn/expect'; -import { getEsQueryConfig } from '../get_es_query_config'; - -const config = { - get(item) { - return config[item]; +const config = ({ + get(item: string) { + return get(config, item); }, 'query:allowLeadingWildcards': { allowLeadingWildcards: true, @@ -36,10 +36,10 @@ const config = { 'dateFormat:tz': { dateFormatTZ: 'Browser', }, -}; +} as unknown) as UiSettingsClientContract; -describe('getEsQueryConfig', function () { - it('should return the parameters of an Elasticsearch query config requested', function () { +describe('getEsQueryConfig', () => { + test('should return the parameters of an Elasticsearch query config requested', () => { const result = getEsQueryConfig(config); const expected = { allowLeadingWildcards: { @@ -55,12 +55,12 @@ describe('getEsQueryConfig', function () { queryStringOptions: {}, }, }; - expect(result).to.eql(expected); - expect(result).to.have.keys( - 'allowLeadingWildcards', - 'dateFormatTZ', - 'ignoreFilterIfFieldNotInIndex', - 'queryStringOptions' - ); + + expect(result).toEqual(expected); + + expect(result).toHaveProperty('allowLeadingWildcards'); + expect(result).toHaveProperty('dateFormatTZ'); + expect(result).toHaveProperty('ignoreFilterIfFieldNotInIndex'); + expect(result).toHaveProperty('queryStringOptions'); }); }); diff --git a/packages/kbn-es-query/src/es_query/get_es_query_config.js b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts similarity index 78% rename from packages/kbn-es-query/src/es_query/get_es_query_config.js rename to src/plugins/data/common/es_query/es_query/get_es_query_config.ts index 2518b1077462d..0a82cf03bdb44 100644 --- a/packages/kbn-es-query/src/es_query/get_es_query_config.js +++ b/src/plugins/data/common/es_query/es_query/get_es_query_config.ts @@ -17,10 +17,22 @@ * under the License. */ -export function getEsQueryConfig(config) { +import { EsQueryConfig } from './build_es_query'; + +interface KibanaConfig { + get(key: string): T; +} + +export function getEsQueryConfig(config: KibanaConfig) { const allowLeadingWildcards = config.get('query:allowLeadingWildcards'); const queryStringOptions = config.get('query:queryString:options'); const ignoreFilterIfFieldNotInIndex = config.get('courier:ignoreFilterIfFieldNotInIndex'); const dateFormatTZ = config.get('dateFormat:tz'); - return { allowLeadingWildcards, queryStringOptions, ignoreFilterIfFieldNotInIndex, dateFormatTZ }; + + return { + allowLeadingWildcards, + queryStringOptions, + ignoreFilterIfFieldNotInIndex, + dateFormatTZ, + } as EsQueryConfig; } diff --git a/packages/kbn-es-query/src/es_query/index.js b/src/plugins/data/common/es_query/es_query/index.ts similarity index 94% rename from packages/kbn-es-query/src/es_query/index.js rename to src/plugins/data/common/es_query/es_query/index.ts index 57dc31fd9fb6f..82cbc543e19db 100644 --- a/packages/kbn-es-query/src/es_query/index.js +++ b/src/plugins/data/common/es_query/es_query/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { buildEsQuery } from './build_es_query'; +export { buildEsQuery, EsQueryConfig } from './build_es_query'; export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; export { migrateFilter } from './migrate_filter'; diff --git a/packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js b/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.test.ts similarity index 58% rename from packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js rename to src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.test.ts index 04f6209ce6665..a0e94f2c46dcc 100644 --- a/packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js +++ b/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.test.ts @@ -17,37 +17,34 @@ * under the License. */ -import { luceneStringToDsl } from '../lucene_string_to_dsl'; -import expect from '@kbn/expect'; +import { luceneStringToDsl } from './lucene_string_to_dsl'; -describe('build query', function () { - - describe('luceneStringToDsl', function () { - - it('should wrap strings with an ES query_string query', function () { +describe('build query', () => { + describe('luceneStringToDsl', () => { + test('should wrap strings with an ES query_string query', () => { const result = luceneStringToDsl('foo:bar'); const expectedResult = { - query_string: { query: 'foo:bar' } + query_string: { query: 'foo:bar' }, }; - expect(result).to.eql(expectedResult); + + expect(result).toEqual(expectedResult); }); - it('should return a match_all query for empty strings and whitespace', function () { + test('should return a match_all query for empty strings and whitespace', () => { const expectedResult = { - match_all: {} + match_all: {}, }; - expect(luceneStringToDsl('')).to.eql(expectedResult); - expect(luceneStringToDsl(' ')).to.eql(expectedResult); + expect(luceneStringToDsl('')).toEqual(expectedResult); + expect(luceneStringToDsl(' ')).toEqual(expectedResult); }); - it('should return non-string arguments without modification', function () { + test('should return non-string arguments without modification', () => { const expectedResult = {}; const result = luceneStringToDsl(expectedResult); - expect(result).to.be(expectedResult); - expect(result).to.eql(expectedResult); - }); + expect(result).toBe(expectedResult); + expect(result).toEqual(expectedResult); + }); }); - }); diff --git a/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.ts b/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.ts new file mode 100644 index 0000000000000..6e8d519ec0ce2 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.ts @@ -0,0 +1,33 @@ +/* + * 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 { isString } from 'lodash'; +import { DslQuery } from './es_query_dsl'; + +export function luceneStringToDsl(query: string | any): DslQuery { + if (isString(query)) { + if (query.trim() === '') { + return { match_all: {} }; + } + + return { query_string: { query } }; + } + + return query; +} diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts new file mode 100644 index 0000000000000..4617ee1a1c43d --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { isEqual, clone } from 'lodash'; +import { migrateFilter, DeprecatedMatchPhraseFilter } from './migrate_filter'; +import { PhraseFilter, MatchAllFilter } from '../filters'; + +describe('migrateFilter', function() { + const oldMatchPhraseFilter = ({ + match: { + fieldFoo: { + query: 'foobar', + type: 'phrase', + }, + }, + } as unknown) as DeprecatedMatchPhraseFilter; + + const newMatchPhraseFilter = ({ + match_phrase: { + fieldFoo: { + query: 'foobar', + }, + }, + } as unknown) as PhraseFilter; + + it('should migrate match filters of type phrase', function() { + const migratedFilter = migrateFilter(oldMatchPhraseFilter, null); + + expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true); + }); + + it('should not modify the original filter', function() { + const oldMatchPhraseFilterCopy = clone(oldMatchPhraseFilter, true); + + migrateFilter(oldMatchPhraseFilter, null); + + expect(isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).toBe(true); + }); + + it('should return the original filter if no migration is necessary', function() { + const originalFilter = { + match_all: {}, + } as MatchAllFilter; + const migratedFilter = migrateFilter(originalFilter, null); + + expect(migratedFilter).toBe(originalFilter); + expect(isEqual(migratedFilter, originalFilter)).toBe(true); + }); +}); diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts new file mode 100644 index 0000000000000..258ab9e703131 --- /dev/null +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts @@ -0,0 +1,65 @@ +/* + * 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 { get, omit } from 'lodash'; +import { getConvertedValueForField } from '../filters'; +import { Filter } from '../filters'; +import { IIndexPattern } from '../../index_patterns'; + +/** @deprecated + * see https://github.com/elastic/elasticsearch/pull/17508 + * */ +export interface DeprecatedMatchPhraseFilter extends Filter { + match: { + [field: string]: { + query: any; + type: 'phrase'; + }; + }; +} + +/** @deprecated + * see https://github.com/elastic/elasticsearch/pull/17508 + * */ +function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter { + const fieldName = filter.match && Object.keys(filter.match)[0]; + + return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase'); +} + +export function migrateFilter(filter: Filter, indexPattern: IIndexPattern | null) { + if (isMatchPhraseFilter(filter)) { + const fieldName = Object.keys(filter.match)[0]; + const params: Record = get(filter, ['match', fieldName]); + if (indexPattern) { + const field = indexPattern.fields.find(f => f.name === fieldName); + + if (field) { + params.query = getConvertedValueForField(field, params.query); + } + } + return { + match_phrase: { + [fieldName]: omit(params, 'type'), + }, + }; + } + + return filter; +} diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts index 1a404ca415117..a20a4f0634766 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/src/plugins/data/common/es_query/filters/exists_filter.ts @@ -18,7 +18,7 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern, Field } from '../../types'; +import { IIndexPattern, IFieldType } from '../../index_patterns'; export type ExistsFilterMeta = FilterMeta; @@ -33,7 +33,7 @@ export type ExistsFilter = Filter & { export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists; -export const buildExistsFilter = (field: Field, indexPattern: IndexPattern) => { +export const buildExistsFilter = (field: IFieldType, indexPattern: IIndexPattern) => { return { meta: { index: indexPattern.id, diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts index e28ce9ba74975..c19545eb83a06 100644 --- a/src/plugins/data/common/es_query/filters/index.ts +++ b/src/plugins/data/common/es_query/filters/index.ts @@ -17,6 +17,9 @@ * under the License. */ +import { omit, get } from 'lodash'; +import { Filter } from './meta_filter'; + export * from './custom_filter'; export * from './exists_filter'; export * from './geo_bounding_box_filter'; @@ -30,3 +33,12 @@ export * from './query_string_filter'; export * from './range_filter'; export * from './types'; + +/** + * Clean out any invalid attributes from the filters + * @param {object} filter The filter to clean + * @returns {object} + */ +export const cleanFilter = (filter: Filter): Filter => omit(filter, ['meta', '$state']); + +export const isFilterDisabled = (filter: Filter): boolean => get(filter, 'meta.disabled', false); diff --git a/src/plugins/data/common/es_query/filters/meta_filter.ts b/src/plugins/data/common/es_query/filters/meta_filter.ts index 9adfdc4eedcb3..ff6dff9d8b749 100644 --- a/src/plugins/data/common/es_query/filters/meta_filter.ts +++ b/src/plugins/data/common/es_query/filters/meta_filter.ts @@ -33,15 +33,17 @@ export interface FilterValueFormatter { } export interface FilterMeta { + alias: string | null; + disabled: boolean; + negate: boolean; + // controlledBy is there to identify who owns the filter + controlledBy?: string; // index and type are optional only because when you create a new filter, there are no defaults index?: string; type?: string; - disabled: boolean; - negate: boolean; - alias: string | null; key?: string; - value?: string | ((formatter?: FilterValueFormatter) => string); params?: any; + value?: string | ((formatter?: FilterValueFormatter) => string); } export interface Filter { diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts index 250ec792fbb57..3c7d00a80fecf 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.test.ts @@ -18,16 +18,16 @@ */ import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from './phrase_filter'; -import { IndexPattern } from '../../types'; -import { getField } from '../__tests__/fields_mock'; +import { getField } from '../../index_patterns/mocks'; +import { IIndexPattern } from '../../index_patterns'; describe('Phrase filter builder', () => { - let indexPattern: IndexPattern; + let indexPattern: IIndexPattern; beforeEach(() => { indexPattern = { id: 'id', - }; + } as IIndexPattern; }); it('should be a function', () => { diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts index 35110c924fe61..8b8c5f8915269 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts @@ -19,7 +19,7 @@ import { get, isPlainObject } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern, Field } from '../../types'; +import { IIndexPattern, IFieldType } from '../../index_patterns'; export type PhraseFilterMeta = FilterMeta & { params?: { @@ -51,7 +51,7 @@ export const isPhraseFilter = (filter: any): filter is PhraseFilter => { filter.query.match && Object.values(filter.query.match).find((params: any) => params.type === 'phrase'); - return !!(isMatchPhraseQuery || isDeprecatedMatchPhraseQuery); + return Boolean(isMatchPhraseQuery || isDeprecatedMatchPhraseQuery); }; export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter => @@ -69,9 +69,9 @@ export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue => }; export const buildPhraseFilter = ( - field: Field, + field: IFieldType, value: any, - indexPattern: IndexPattern + indexPattern: IIndexPattern ): PhraseFilter => { const convertedValue = getConvertedValueForField(field, value); @@ -92,7 +92,7 @@ export const buildPhraseFilter = ( } }; -export const getPhraseScript = (field: Field, value: string) => { +export const getPhraseScript = (field: IFieldType, value: string) => { const convertedValue = getConvertedValueForField(field, value); const script = buildInlineScriptForPhraseFilter(field); @@ -110,7 +110,7 @@ export const getPhraseScript = (field: Field, value: string) => { // See https://github.com/elastic/elasticsearch/issues/20941 and https://github.com/elastic/kibana/issues/8677 // and https://github.com/elastic/elasticsearch/pull/22201 // for the reason behind this change. Aggs now return boolean buckets with a key of 1 or 0. -export const getConvertedValueForField = (field: Field, value: any) => { +export const getConvertedValueForField = (field: IFieldType, value: any) => { if (typeof value !== 'boolean' && field.type === 'boolean') { if ([1, 'true'].includes(value)) { return true; diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts index e207a3ff5961b..f7164f0ad3c83 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts @@ -18,8 +18,9 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { Field, IndexPattern } from '../../types'; import { getPhraseScript } from './phrase_filter'; +import { FILTERS } from './index'; +import { IIndexPattern, IFieldType } from '../../index_patterns'; export type PhrasesFilterMeta = FilterMeta & { params: string[]; // The unformatted values @@ -31,16 +32,16 @@ export type PhrasesFilter = Filter & { }; export const isPhrasesFilter = (filter: any): filter is PhrasesFilter => - filter && filter.meta.type === 'phrases'; + filter && filter.meta.type === FILTERS.PHRASES; // Creates a filter where the given field matches one or more of the given values // params should be an array of values -export const buildPhrasesFilter = (field: Field, params: any, indexPattern: IndexPattern) => { +export const buildPhrasesFilter = (field: IFieldType, params: any, indexPattern: IIndexPattern) => { const index = indexPattern.id; - const type = 'phrases'; + const type = FILTERS.PHRASES; const key = field.name; - const format = (f: Field, value: any) => + const format = (f: IFieldType, value: any) => f && f.format && f.format.convert ? f.format.convert(value) : value; const value = params.map((v: any) => format(field, v)).join(', '); diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts index 5a580db0c57b8..4fcb15ccac44a 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.test.ts @@ -18,26 +18,17 @@ */ import { buildQueryFilter } from './query_string_filter'; -import { IndexPattern } from '../../types'; describe('Phrase filter builder', () => { - let indexPattern: IndexPattern; - - beforeEach(() => { - indexPattern = { - id: 'id', - }; - }); - it('should be a function', () => { expect(typeof buildQueryFilter).toBe('function'); }); it('should return a query filter when passed a standard field', () => { - expect(buildQueryFilter({ foo: 'bar' }, indexPattern.id, '')).toEqual({ + expect(buildQueryFilter({ foo: 'bar' }, 'index', '')).toEqual({ meta: { alias: '', - index: 'id', + index: 'index', }, query: { foo: 'bar', diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.ts b/src/plugins/data/common/es_query/filters/query_string_filter.ts index d2374162b195f..a0e563eca6334 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.ts +++ b/src/plugins/data/common/es_query/filters/query_string_filter.ts @@ -18,7 +18,6 @@ */ import { Filter, FilterMeta } from './meta_filter'; -import { IndexPattern } from '../../types'; export type QueryStringFilterMeta = FilterMeta; @@ -35,11 +34,7 @@ export const isQueryStringFilter = (filter: any): filter is QueryStringFilter => filter && filter.query && filter.query.query_string; // Creates a filter corresponding to a raw Elasticsearch query DSL object -export const buildQueryFilter = ( - query: QueryStringFilter['query'], - index: IndexPattern, - alias: string -) => +export const buildQueryFilter = (query: QueryStringFilter['query'], index: string, alias: string) => ({ query, meta: { diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts index 017bb8e9cb7c5..56b63018b5153 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts @@ -19,16 +19,16 @@ import { each } from 'lodash'; import { buildRangeFilter, RangeFilter } from './range_filter'; -import { IndexPattern, Field } from '../../types'; -import { getField } from '../__tests__/fields_mock'; +import { getField } from '../../index_patterns/mocks'; +import { IIndexPattern, IFieldType } from '../../index_patterns'; describe('Range filter builder', () => { - let indexPattern: IndexPattern; + let indexPattern: IIndexPattern; beforeEach(() => { indexPattern = { id: 'id', - }; + } as IIndexPattern; }); it('should be a function', () => { @@ -118,7 +118,7 @@ describe('Range filter builder', () => { }); describe('when given params where one side is infinite', () => { - let field: Field; + let field: IFieldType; let filter: RangeFilter; beforeEach(() => { @@ -148,7 +148,7 @@ describe('Range filter builder', () => { }); describe('when given params where both sides are infinite', () => { - let field: Field; + let field: IFieldType; let filter: RangeFilter; beforeEach(() => { diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts index c2513a9dc0c5e..fa07b3e611fa7 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/src/plugins/data/common/es_query/filters/range_filter.ts @@ -18,7 +18,7 @@ */ import { map, reduce, mapValues, get, keys, pick } from 'lodash'; import { Filter, FilterMeta } from './meta_filter'; -import { Field, IndexPattern } from '../../types'; +import { IIndexPattern, IFieldType } from '../../index_patterns'; const OPERANDS_IN_RANGE = 2; @@ -84,18 +84,18 @@ export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => { return hasRangeKeys(params); }; -const formatValue = (field: Field, params: any[]) => +const formatValue = (field: IFieldType, params: any[]) => map(params, (val: any, key: string) => get(operators, key) + format(field, val)).join(' '); -const format = (field: Field, value: any) => +const format = (field: IFieldType, value: any) => field && field.format && field.format.convert ? field.format.convert(value) : value; // Creates a filter where the value for the given field is in the given range // params should be an object containing `lt`, `lte`, `gt`, and/or `gte` export const buildRangeFilter = ( - field: Field, + field: IFieldType, params: RangeFilterParams, - indexPattern: IndexPattern, + indexPattern: IIndexPattern, formattedValue?: string ): RangeFilter => { const filter: any = { meta: { index: indexPattern.id, params: {} } }; @@ -139,7 +139,7 @@ export const buildRangeFilter = ( return filter as RangeFilter; }; -export const getRangeScript = (field: IndexPattern, params: RangeFilterParams) => { +export const getRangeScript = (field: IFieldType, params: RangeFilterParams) => { const knownParams = pick(params, (val, key: any) => key in operators); let script = map( knownParams, diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts index 88e14a43cfaae..56eb45c4b1dca 100644 --- a/src/plugins/data/common/es_query/index.ts +++ b/src/plugins/data/common/es_query/index.ts @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +import * as esQuery from './es_query'; import * as esFilters from './filters'; +import * as utils from './utils'; -export { esFilters }; +export { esFilters, esQuery, utils }; diff --git a/packages/kbn-es-query/src/es_query/lucene_string_to_dsl.js b/src/plugins/data/common/es_query/utils/get_time_zone_from_settings.ts similarity index 78% rename from packages/kbn-es-query/src/es_query/lucene_string_to_dsl.js rename to src/plugins/data/common/es_query/utils/get_time_zone_from_settings.ts index 36ff621e8a694..303bd3547f2ff 100644 --- a/packages/kbn-es-query/src/es_query/lucene_string_to_dsl.js +++ b/src/plugins/data/common/es_query/utils/get_time_zone_from_settings.ts @@ -17,16 +17,10 @@ * under the License. */ -import _ from 'lodash'; +import moment from 'moment-timezone'; -export function luceneStringToDsl(query) { - if (!_.isString(query)) { - return query; - } +export function getTimeZoneFromSettings(dateFormatTZ: string) { + const detectedTimezone = moment.tz.guess(); - if (query.trim() === '') { - return { match_all: {} }; - } - - return { query_string: { query } }; + return dateFormatTZ === 'Browser' ? detectedTimezone : dateFormatTZ; } diff --git a/src/plugins/data/common/es_query/utils/index.ts b/src/plugins/data/common/es_query/utils/index.ts new file mode 100644 index 0000000000000..27f51c1f44cf2 --- /dev/null +++ b/src/plugins/data/common/es_query/utils/index.ts @@ -0,0 +1,20 @@ +/* + * 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 * from './get_time_zone_from_settings'; diff --git a/src/plugins/data/common/field_formats/converters/color.test.ts b/src/plugins/data/common/field_formats/converters/color.test.ts index d3e1054f3db1f..b7fcbf61227eb 100644 --- a/src/plugins/data/common/field_formats/converters/color.test.ts +++ b/src/plugins/data/common/field_formats/converters/color.test.ts @@ -18,7 +18,7 @@ */ import { ColorFormat } from './color'; -import { HTML_CONTEXT_TYPE } from '../../index'; +import { HTML_CONTEXT_TYPE } from '../content_types'; describe('Color Format', () => { describe('field is a number', () => { diff --git a/src/plugins/data/common/field_formats/converters/url.test.ts b/src/plugins/data/common/field_formats/converters/url.test.ts index a194b499744a9..66307cefe08f7 100644 --- a/src/plugins/data/common/field_formats/converters/url.test.ts +++ b/src/plugins/data/common/field_formats/converters/url.test.ts @@ -18,7 +18,7 @@ */ import { UrlFormat } from './url'; -import { TEXT_CONTEXT_TYPE, HTML_CONTEXT_TYPE } from '../../index'; +import { TEXT_CONTEXT_TYPE, HTML_CONTEXT_TYPE } from '../content_types'; describe('UrlFormat', () => { test('outputs a simple tag by default', () => { diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 42b5a03fcc926..f9bbeb5f4b3f3 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -20,6 +20,7 @@ export * from './query'; export * from './field_formats'; export * from './kbn_field_types'; +export * from './index_patterns'; export * from './es_query'; export * from './types'; diff --git a/src/plugins/data/common/es_query/__tests__/fields_mock.ts b/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts similarity index 98% rename from src/plugins/data/common/es_query/__tests__/fields_mock.ts rename to src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts index 83fdf588af00c..c27ff42b1e9d2 100644 --- a/src/plugins/data/common/es_query/__tests__/fields_mock.ts +++ b/src/plugins/data/common/index_patterns/fields/fields.mocks.ts.ts @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ +import { IFieldType } from './types'; -export const fields = [ +export const fields: IFieldType[] = [ { name: 'bytes', type: 'number', @@ -317,4 +318,4 @@ export const fields = [ }, ]; -export const getField = (name: string) => fields.find(field => field.name === name); +export const getField = (name: string) => fields.find(field => field.name === name) as IFieldType; diff --git a/src/plugins/data/common/index_patterns/fields/index.ts b/src/plugins/data/common/index_patterns/fields/index.ts new file mode 100644 index 0000000000000..d8f7b5091eb8f --- /dev/null +++ b/src/plugins/data/common/index_patterns/fields/index.ts @@ -0,0 +1,20 @@ +/* + * 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 * from './types'; diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts new file mode 100644 index 0000000000000..c336472a1e7d6 --- /dev/null +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -0,0 +1,44 @@ +/* + * 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 IFieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + +export interface IFieldType { + name: string; + type: string; + script?: string; + lang?: string; + count?: number; + // esTypes might be undefined on old index patterns that have not been refreshed since we added + // this prop. It is also undefined on scripted fields. + esTypes?: string[]; + aggregatable?: boolean; + filterable?: boolean; + searchable?: boolean; + sortable?: boolean; + visualizable?: boolean; + readFromDocValues?: boolean; + scripted?: boolean; + subType?: IFieldSubType; + displayName?: string; + format?: any; +} diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts new file mode 100644 index 0000000000000..d26587efccc0f --- /dev/null +++ b/src/plugins/data/common/index_patterns/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './fields'; +export * from './types'; diff --git a/src/plugins/data/common/index_patterns/mocks.ts b/src/plugins/data/common/index_patterns/mocks.ts new file mode 100644 index 0000000000000..6036c08fa2b10 --- /dev/null +++ b/src/plugins/data/common/index_patterns/mocks.ts @@ -0,0 +1,20 @@ +/* + * 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 * from './fields/fields.mocks.ts'; diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts new file mode 100644 index 0000000000000..0a1ba48342244 --- /dev/null +++ b/src/plugins/data/common/index_patterns/types.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IFieldType } from './fields'; + +export interface IIndexPattern { + fields: IFieldType[]; + title: string; + id?: string; + type?: string; + timeFieldName?: string; + fieldFormatMap?: Record< + string, + { + id: string; + params: unknown; + } + >; +} diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index ec8d8b006317f..bc0d0c323bafa 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -21,10 +21,4 @@ export * from './field_formats/types'; export * from './timefilter/types'; export * from './query/types'; export * from './kbn_field_types/types'; - -// We can't import the real types from the data plugin, so need to either duplicate -// them here or figure out another solution, perhaps housing them in this package -// will be replaces after Fieds / IndexPattern will be moved into new platform -export type Field = any; -export type IndexPattern = any; -export type StaticIndexPattern = any; +export * from './index_patterns/types'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts index d838e54e9ead4..3d34b1bc4a2d2 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete_provider/types.ts @@ -18,7 +18,7 @@ */ import { AutocompleteProviderRegister } from '.'; -import { Field, StaticIndexPattern } from '..'; +import { IIndexPattern, IFieldType } from '../../common'; export type AutocompletePublicPluginSetup = Pick< AutocompleteProviderRegister, @@ -31,7 +31,7 @@ export type AutocompleteProvider = (args: { config: { get(configKey: string): any; }; - indexPatterns: StaticIndexPattern[]; + indexPatterns: IIndexPattern[]; boolFilter?: any; }) => GetSuggestions; @@ -67,5 +67,5 @@ interface BasicAutocompleteSuggestion { export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { type: 'field'; - field: Field; + field: IFieldType; }; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 32153df69f367..4477c6defbc81 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -25,11 +25,10 @@ export function plugin(initializerContext: PluginInitializerContext) { } export { DataPublicPlugin as Plugin }; -export { DataPublicPluginSetup, DataPublicPluginStart } from './types'; export * from '../common'; -export * from './autocomplete_provider'; +export * from './autocomplete_provider'; export * from './types'; export { IRequestTypesMap, IResponseTypesMap } from './search'; diff --git a/src/plugins/data/public/index_patterns/field.stub.ts b/src/plugins/data/public/index_patterns/field.stub.ts index 315894cd212c4..2e94f4b45f400 100644 --- a/src/plugins/data/public/index_patterns/field.stub.ts +++ b/src/plugins/data/public/index_patterns/field.stub.ts @@ -17,9 +17,9 @@ * under the License. */ -import { Field } from '../../common'; +import { IFieldType } from '../../../../plugins/data/public'; -export const stubFields: Field[] = [ +export const stubFields: IFieldType[] = [ { name: 'machine.os', esTypes: ['text'], diff --git a/src/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/public/index_patterns/index_pattern.stub.ts index 444e65cd0cd4b..3d5151752a080 100644 --- a/src/plugins/data/public/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/public/index_patterns/index_pattern.stub.ts @@ -17,10 +17,10 @@ * under the License. */ -import { IndexPattern } from '../../common'; +import { IIndexPattern } from '../../common'; import { stubFields } from './field.stub'; -export const stubIndexPattern: IndexPattern = { +export const stubIndexPattern: IIndexPattern = { id: 'logstash-*', fields: stubFields, title: 'logstash-*', diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts index 33f9c4ccd795d..7857e2989bda6 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.test.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.test.ts @@ -24,7 +24,7 @@ import { Subscription } from 'rxjs'; import { FilterManager } from './filter_manager'; import { getFilter } from './test_helpers/get_stub_filter'; import { getFiltersArray } from './test_helpers/get_filters_array'; -import { esFilters } from '../../../common/es_query'; +import { esFilters } from '../../../common'; import { coreMock } from '../../../../../core/public/mocks'; const setupMock = coreMock.createSetup(); diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index 06e2b77dca238..feab75ed7457f 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -27,7 +27,7 @@ import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; import { uniqFilters } from './lib/uniq_filters'; import { onlyDisabledFiltersChanged } from './lib/only_disabled'; import { PartitionedFilters } from './types'; -import { esFilters } from '../../../common/es_query'; +import { esFilters } from '../../../common'; export class FilterManager { private filters: esFilters.Filter[] = []; diff --git a/src/plugins/data/public/query/filter_manager/index.ts b/src/plugins/data/public/query/filter_manager/index.ts index 7955cdd825ee6..ce7a479151797 100644 --- a/src/plugins/data/public/query/filter_manager/index.ts +++ b/src/plugins/data/public/query/filter_manager/index.ts @@ -22,3 +22,4 @@ 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'; +export { generateFilters } from './lib/generate_filters'; diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts index 6bde6b528d07b..34fd662c4ba46 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts @@ -18,7 +18,7 @@ */ import { compareFilters } from './compare_filters'; -import { esFilters } from '../../../../common/es_query'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { describe('compare filters', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts index 2a7cbe6e3303b..9b171ab0aacb2 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts @@ -18,7 +18,7 @@ */ import { defaults, isEqual, omit } from 'lodash'; -import { esFilters } from '../../../../common/es_query'; +import { esFilters } from '../../../../common'; /** * Compare two filters to see if they match diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts index 9b493add0886c..ebad5ad6b02c5 100644 --- a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.test.ts @@ -18,13 +18,26 @@ */ import { dedupFilters } from './dedup_filters'; -import { esFilters } from '../../../../common/es_query'; +import { esFilters, IIndexPattern, IFieldType } from '../../../../common'; describe('filter manager utilities', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'index', + } as IIndexPattern; + }); + describe('dedupFilters(existing, filters)', () => { test('should return only filters which are not in the existing', () => { const existing: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 0, to: 1024 }, + indexPattern, + '' + ), esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, 'index', @@ -32,7 +45,12 @@ describe('filter manager utilities', () => { ), ]; const filters: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 1024, to: 2048 }, + indexPattern, + '' + ), esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, 'index', @@ -47,7 +65,12 @@ describe('filter manager utilities', () => { test('should ignore the disabled attribute when comparing ', () => { const existing: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 0, to: 1024 }, + indexPattern, + '' + ), { ...esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, @@ -58,7 +81,12 @@ describe('filter manager utilities', () => { }, ]; const filters: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 1024, to: 2048 }, + indexPattern, + '' + ), esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, 'index1', @@ -73,7 +101,12 @@ describe('filter manager utilities', () => { test('should ignore $state attribute', () => { const existing: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 0, to: 1024 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 0, to: 1024 }, + indexPattern, + '' + ), { ...esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, @@ -84,7 +117,12 @@ describe('filter manager utilities', () => { }, ]; const filters: esFilters.Filter[] = [ - esFilters.buildRangeFilter({ name: 'bytes' }, { from: 1024, to: 2048 }, 'index', ''), + esFilters.buildRangeFilter( + { name: 'bytes' } as IFieldType, + { from: 1024, to: 2048 }, + indexPattern, + '' + ), { ...esFilters.buildQueryFilter( { match: { _term: { query: 'apache', type: 'phrase' } } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts index 6d6f49cb5e833..6dae14f480b4f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/dedup_filters.ts @@ -19,7 +19,7 @@ import { filter, find } from 'lodash'; import { compareFilters } from './compare_filters'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; /** * Combine 2 filter collections, removing duplicates diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts new file mode 100644 index 0000000000000..b8de08fc3a610 --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filter.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { generateFilters } from './generate_filters'; +import { FilterManager } from '../filter_manager'; + +import { esFilters, IFieldType, IIndexPattern } from '../../../../common'; + +const INDEX_NAME = 'my-index'; +const EXISTS_FIELD_NAME = '_exists_'; +const FIELD = { + name: 'my-field', +} as IFieldType; +const PHRASE_VALUE = 'my-value'; + +describe('Generate filters', () => { + let mockFilterManager: FilterManager; + let filtersArray: esFilters.Filter[]; + + beforeEach(() => { + filtersArray = []; + mockFilterManager = { + getAppFilters: () => { + return filtersArray; + }, + } as FilterManager; + }); + + it('should create exists filter', () => { + const filters = generateFilters( + mockFilterManager, + EXISTS_FIELD_NAME, + FIELD.name, + '', + INDEX_NAME + ); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should create negated exists filter', () => { + const filters = generateFilters( + mockFilterManager, + EXISTS_FIELD_NAME, + FIELD.name, + '-', + INDEX_NAME + ); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should update and re-enable EXISTING exists filter', () => { + const filter = esFilters.buildExistsFilter(FIELD, { id: INDEX_NAME } as IIndexPattern); + filter.meta.disabled = true; + filtersArray.push(filter); + + const filters = generateFilters(mockFilterManager, '_exists_', FIELD.name, '-', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(filters[0].meta.disabled).toBeFalsy(); + expect(esFilters.isExistsFilter(filters[0])).toBeTruthy(); + }); + + it('should create phrase filter', () => { + const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + }); + + it('should create negated phrase filter', () => { + const filters = generateFilters(mockFilterManager, FIELD, PHRASE_VALUE, '-', INDEX_NAME); + expect(filters).toHaveLength(1); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeTruthy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + }); + + it('should create multiple phrase filters', () => { + const ANOTHER_PHRASE = 'another-value'; + const filters = generateFilters( + mockFilterManager, + FIELD, + [PHRASE_VALUE, ANOTHER_PHRASE], + '', + INDEX_NAME + ); + expect(filters).toHaveLength(2); + expect(filters[0].meta.index === INDEX_NAME); + expect(filters[0].meta.negate).toBeFalsy(); + expect(filters[1].meta.index === INDEX_NAME); + expect(filters[1].meta.negate).toBeFalsy(); + expect(esFilters.isPhraseFilter(filters[0])).toBeTruthy(); + expect(esFilters.isPhraseFilter(filters[1])).toBeTruthy(); + expect((filters[0] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: PHRASE_VALUE, + }); + expect((filters[1] as esFilters.PhraseFilter).query.match_phrase).toEqual({ + [FIELD.name]: ANOTHER_PHRASE, + }); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts new file mode 100644 index 0000000000000..42607843df3ba --- /dev/null +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -0,0 +1,114 @@ +/* + * 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 { esFilters, IFieldType, IIndexPattern } from '../../../../common'; +import { FilterManager } from '../filter_manager'; + +function getExistingFilter( + appFilters: esFilters.Filter[], + fieldName: string, + value: any +): esFilters.Filter | undefined { + // TODO: On array fields, negating does not negate the combination, rather all terms + return _.find(appFilters, function(filter) { + if (!filter) return; + + if (fieldName === '_exists_' && esFilters.isExistsFilter(filter)) { + return filter.exists!.field === value; + } + + if (esFilters.isPhraseFilter(filter)) { + return ( + esFilters.getPhraseFilterField(filter) === fieldName && + esFilters.getPhraseFilterValue(filter) === value + ); + } + + if (esFilters.isScriptedPhraseFilter(filter)) { + return filter.meta.field === fieldName && filter.meta.script!.script.params.value === value; + } + }); +} + +function updateExistingFilter(existingFilter: esFilters.Filter, negate: boolean) { + existingFilter.meta.disabled = false; + if (existingFilter.meta.negate !== negate) { + existingFilter.meta.negate = !existingFilter.meta.negate; + } +} + +/** + * Generate filter objects, as a result of triggering a filter action on a + * specific index pattern field. + * + * @param {FilterManager} filterManager - The active filter manager to lookup for existing filters + * @param {Field | string} field - The field for which filters should be generated + * @param {any} values - One or more values to filter for. + * @param {string} operation - "-" to create a negated filter + * @param {string} index - Index string to generate filters for + * + * @returns {object} An array of filters to be added back to filterManager + */ +export function generateFilters( + filterManager: FilterManager, + field: IFieldType | string, + values: any, + operation: string, + index: string +): esFilters.Filter[] { + values = Array.isArray(values) ? values : [values]; + const fieldObj = (_.isObject(field) + ? field + : { + name: field, + }) as IFieldType; + const fieldName = fieldObj.name; + const newFilters: esFilters.Filter[] = []; + const appFilters = filterManager.getAppFilters(); + + const negate = operation === '-'; + let filter; + + _.each(values, function(value) { + const existing = getExistingFilter(appFilters, fieldName, value); + + if (existing) { + updateExistingFilter(existing, negate); + filter = existing; + } else { + const tmpIndexPattern = { id: index } as IIndexPattern; + + switch (fieldName) { + case '_exists_': + filter = esFilters.buildExistsFilter(fieldObj, tmpIndexPattern); + break; + default: + filter = esFilters.buildPhraseFilter(fieldObj, value, tmpIndexPattern); + break; + } + + filter.meta.negate = negate; + } + + newFilters.push(filter); + }); + + return newFilters; +} diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts index dfe3a093c6614..9e386bdc7c80d 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts @@ -19,7 +19,7 @@ import sinon from 'sinon'; import { generateMappingChain } from './generate_mapping_chain'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { let mapping: any; diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts index b6764389e0db9..1af8482a96e0f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; const noop = () => { throw new Error('No mappings have been found for filter.'); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts index 9a0d0d93698f6..3190b6777a9e1 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.test.ts @@ -18,7 +18,7 @@ */ import { mapAndFlattenFilters } from './map_and_flatten_filters'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { describe('mapAndFlattenFilters()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts index 5326d59f3e32b..28b5e8d151ff6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_and_flatten_filters.ts @@ -19,7 +19,7 @@ import { compact, flatten } from 'lodash'; import { mapFilter } from './map_filter'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; export const mapAndFlattenFilters = (filters: esFilters.Filter[]) => { return compact(flatten(filters)).map((item: esFilters.Filter) => mapFilter(item)); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index 0d115125451ee..9df07718d5bcb 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -18,7 +18,7 @@ */ import { mapFilter } from './map_filter'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { function getDisplayName(filter: esFilters.Filter) { diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts index 2dc855caabfd3..a68eafe6bf1c2 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.ts @@ -30,7 +30,7 @@ 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'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; export function mapFilter(filter: esFilters.Filter) { /** Mappers **/ diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts index f10766901e5b7..f6baaa9218d74 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.test.ts @@ -18,7 +18,7 @@ */ import { mapDefault } from './map_default'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapDefault()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts index fd84c5c742589..3fee6a063be5a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_default.ts @@ -18,7 +18,7 @@ */ import { find, keys, get } from 'lodash'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapDefault = (filter: esFilters.Filter) => { const metaProperty = /(^\$|meta)/; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts index ff0ed4f4e4d94..2f0ab136bc59f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts @@ -19,12 +19,20 @@ import { mapExists } from './map_exists'; import { mapQueryString } from './map_query_string'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters, IIndexPattern, IFieldType } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapExists()', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'index', + } as IIndexPattern; + }); + test('should return the key and value for matching filters', async () => { - const filter = esFilters.buildExistsFilter({ name: '_type' }, 'index'); + const filter = esFilters.buildExistsFilter({ name: '_type' } as IFieldType, indexPattern); const result = mapExists(filter); expect(result).toHaveProperty('key', '_type'); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts index 63665bdd88ccb..38f9b1554c5c8 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapExists = (filter: esFilters.Filter) => { if (esFilters.isExistsFilter(filter)) { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts index 5fca4a652bad8..322b086c2cf49 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.test.ts @@ -18,7 +18,7 @@ */ import { mapGeoBoundingBox } from './map_geo_bounding_box'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapGeoBoundingBox()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts index 091e9a3f34000..be63d2de5b0df 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_bounding_box.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; const getFormattedValueFn = (params: any) => { return (formatter?: esFilters.FilterValueFormatter) => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts index 1847296016c73..2713f0fd17734 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.test.ts @@ -18,7 +18,7 @@ */ import { mapGeoPolygon } from './map_geo_polygon'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { let filter: esFilters.GeoPolygonFilter; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts index a7881b4a145a1..8cca92a81cb5f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_geo_polygon.ts @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; const POINTS_SEPARATOR = ', '; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts index 4fc6d0b492414..4d6bba6429b47 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.test.ts @@ -18,7 +18,7 @@ */ import { mapMatchAll } from './map_match_all'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter_manager/lib', () => { describe('mapMatchAll()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts index 4e93b1d41e9a8..9a4ea8430a305 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_match_all.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapMatchAll = (filter: esFilters.Filter) => { if (esFilters.isMatchAllFilter(filter)) { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts index 1847eb37ca42f..faf4b54989e20 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.test.ts @@ -18,7 +18,7 @@ */ import { mapMissing } from './map_missing'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapMissing()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts index 51dee89ad884b..a1b6474365f40 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_missing.ts @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapMissing = (filter: esFilters.Filter) => { if (esFilters.isMissingFilter(filter)) { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index 05372d37264b0..5150b32f118ae 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -17,7 +17,7 @@ * under the License. */ import { mapPhrase } from './map_phrase'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapPhrase()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts index b6e9c2007db97..ae7701bf3a501 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; const getScriptedPhraseValue = (filter: esFilters.PhraseFilter) => get(filter, ['script', 'script', 'params', 'value']); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts index 7240d87d02b5a..f8f2aba1309b7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapPhrases = (filter: esFilters.Filter) => { if (!esFilters.isPhrasesFilter(filter)) { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts index c60e7d3454fe0..c65bc00b7df61 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts @@ -18,7 +18,7 @@ */ import { mapQueryString } from './map_query_string'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapQueryString()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts index 20c3555639a3e..e8e4e68318973 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; export const mapQueryString = (filter: esFilters.Filter) => { if (esFilters.isQueryStringFilter(filter)) { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts index c0d5773d6f2c1..2d312351c0f31 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -18,7 +18,7 @@ */ import { mapRange } from './map_range'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; describe('filter manager utilities', () => { describe('mapRange()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts index 51fb970f5f03e..affc8e6343076 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts @@ -18,7 +18,7 @@ */ import { get, has } from 'lodash'; -import { esFilters } from '../../../../../common/es_query'; +import { esFilters } from '../../../../../common'; const getFormattedValueFn = (left: any, right: any) => { return (formatter?: esFilters.FilterValueFormatter) => { diff --git a/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts index b9731797c9ee3..a9863696d47cd 100644 --- a/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.test.ts @@ -18,7 +18,7 @@ */ import { onlyDisabledFiltersChanged } from './only_disabled'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { describe('onlyDisabledFiltersChanged()', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts index 0fb6894a297a1..c040d2f2960c7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts +++ b/src/plugins/data/public/query/filter_manager/lib/only_disabled.ts @@ -18,7 +18,7 @@ */ import { filter, isEqual } from 'lodash'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; const isEnabled = (f: esFilters.Filter) => f && f.meta && !f.meta.disabled; diff --git a/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts index 08eeabc1497e3..f71ac2940f32b 100644 --- a/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.test.ts @@ -18,7 +18,7 @@ */ import { uniqFilters } from './uniq_filters'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; describe('filter manager utilities', () => { describe('niqFilter', () => { diff --git a/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts index e96c52e6db3de..b6001d698c5f1 100644 --- a/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/uniq_filters.ts @@ -18,7 +18,7 @@ */ import { each, union } from 'lodash'; import { dedupFilters } from './dedup_filters'; -import { esFilters } from '../../../../../data/public'; +import { esFilters } from '../../../../common'; /** * Remove duplicate filters from an array of filters diff --git a/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts b/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts index aa047647c5751..c5f0b11ce13f8 100644 --- a/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts +++ b/src/plugins/data/public/query/filter_manager/test_helpers/get_filters_array.ts @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; export function getFiltersArray(): esFilters.Filter[] { return [ 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 index adc72c961b08b..a531ce7e03984 100644 --- 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 @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; export function getFilter( store: esFilters.FilterStateStore, diff --git a/src/plugins/data/public/query/filter_manager/types.ts b/src/plugins/data/public/query/filter_manager/types.ts index 0b3dbca2d6e0a..0f74a243ca91a 100644 --- a/src/plugins/data/public/query/filter_manager/types.ts +++ b/src/plugins/data/public/query/filter_manager/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { esFilters } from '../../../../../plugins/data/public'; +import { esFilters } from '../../../common'; export interface PartitionedFilters { globalFilters: esFilters.Filter[]; diff --git a/src/plugins/data/public/query/timefilter/get_time.ts b/src/plugins/data/public/query/timefilter/get_time.ts index 55ee6527fbb1a..41ad1a49af0ff 100644 --- a/src/plugins/data/public/query/timefilter/get_time.ts +++ b/src/plugins/data/public/query/timefilter/get_time.ts @@ -18,7 +18,7 @@ */ import dateMath from '@elastic/datemath'; -import { TimeRange } from 'src/plugins/data/public'; +import { TimeRange } from '../../../common'; // TODO: remove this import { IndexPattern, Field } from '../../../../../legacy/core_plugins/data/public/index_patterns'; diff --git a/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts b/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts index df3e33060b01f..62805cde15936 100644 --- a/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/change_time_filter.test.ts @@ -17,9 +17,8 @@ * under the License. */ import { changeTimeFilter } from './change_time_filter'; -import { TimeRange } from 'src/plugins/data/public'; import { timefilterServiceMock } from '../timefilter_service.mock'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { TimeRange, esFilters } from '../../../../common'; const timefilterMock = timefilterServiceMock.createSetupContract(); const timefilter = timefilterMock.timefilter; diff --git a/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts index 7943aab3c151f..cae464f1449bc 100644 --- a/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/change_time_filter.ts @@ -19,8 +19,8 @@ import moment from 'moment'; import { keys } from 'lodash'; -import { TimefilterContract } from '../timefilter'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { TimefilterContract } from '../../timefilter'; +import { esFilters } from '../../../../common'; export function convertRangeFilterToTimeRange(filter: esFilters.RangeFilter) { const key = keys(filter.range)[0]; diff --git a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts index 27da80661dbce..850c87635be9c 100644 --- a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts +++ b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { RefreshInterval } from '../../../../common'; import { InputTimeRange } from '../types'; const valueOf = function(o: any) { diff --git a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts index 981c50844c4f3..d371f4587d2b8 100644 --- a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.test.ts @@ -18,9 +18,17 @@ */ import { extractTimeFilter } from './extract_time_filter'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters, IIndexPattern, IFieldType } from '../../../../common'; describe('filter manager utilities', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = { + id: 'logstash-*', + } as IIndexPattern; + }); + describe('extractTimeFilter()', () => { test('should detect timeFilter', async () => { const filters: esFilters.Filter[] = [ @@ -30,9 +38,9 @@ describe('filter manager utilities', () => { '' ), esFilters.buildRangeFilter( - { name: 'time' }, + { name: 'time' } as IFieldType, { gt: 1388559600000, lt: 1388646000000 }, - 'logstash-*' + indexPattern ), ]; const result = await extractTimeFilter('time', filters); @@ -48,7 +56,12 @@ describe('filter manager utilities', () => { 'logstash-*', '' ), - esFilters.buildRangeFilter({ name: '@timestamp' }, { from: 1, to: 2 }, 'logstash-*', ''), + esFilters.buildRangeFilter( + { name: '@timestamp' } as IFieldType, + { from: 1, to: 2 }, + indexPattern, + '' + ), ]; const result = await extractTimeFilter('time', filters); @@ -63,7 +76,7 @@ describe('filter manager utilities', () => { 'logstash-*', '' ), - esFilters.buildPhraseFilter({ name: 'time' }, 'banana', 'logstash-*'), + esFilters.buildPhraseFilter({ name: 'time' } as IFieldType, 'banana', indexPattern), ]; const result = await extractTimeFilter('time', filters); diff --git a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts index 4281610cb63e4..af2e8be65fb62 100644 --- a/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts +++ b/src/plugins/data/public/query/timefilter/lib/extract_time_filter.ts @@ -18,7 +18,7 @@ */ import { keys, partition } from 'lodash'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../common'; export function extractTimeFilter(timeFieldName: string, filters: esFilters.Filter[]) { const [timeRangeFilter, restOfFilters] = partition(filters, (obj: esFilters.Filter) => { diff --git a/src/plugins/data/public/query/timefilter/time_history.ts b/src/plugins/data/public/query/timefilter/time_history.ts index e14c9ac0bc7ca..4dabbb557e9db 100644 --- a/src/plugins/data/public/query/timefilter/time_history.ts +++ b/src/plugins/data/public/query/timefilter/time_history.ts @@ -18,9 +18,9 @@ */ import moment from 'moment'; -import { TimeRange } from 'src/plugins/data/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { PersistedLog } from '../persisted_log'; +import { TimeRange } from '../../../common'; export class TimeHistory { private history: PersistedLog; diff --git a/src/plugins/data/public/query/timefilter/timefilter.test.ts b/src/plugins/data/public/query/timefilter/timefilter.test.ts index cca646508b539..cd904c76ac4d9 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.test.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.test.ts @@ -34,7 +34,7 @@ import expect from '@kbn/expect'; import moment from 'moment'; import { Timefilter } from './timefilter'; import { Subscription } from 'rxjs'; -import { TimeRange, RefreshInterval } from 'src/plugins/data/public'; +import { TimeRange, RefreshInterval } from '../../../common'; import { timefilterServiceMock } from './timefilter_service.mock'; const timefilterSetupMock = timefilterServiceMock.createSetupContract(); diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 137e5100aa20e..639f3f4a66f18 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -20,12 +20,13 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; -import { RefreshInterval, TimeRange, TimeHistoryContract } from 'src/plugins/data/public'; import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; +import { RefreshInterval, TimeRange } from '../../../common'; +import { TimeHistoryContract } from './time_history'; // TODO: remove! diff --git a/src/plugins/data/public/query/timefilter/types.ts b/src/plugins/data/public/query/timefilter/types.ts index 879776cee178e..8b8deea43f808 100644 --- a/src/plugins/data/public/query/timefilter/types.ts +++ b/src/plugins/data/public/query/timefilter/types.ts @@ -17,7 +17,7 @@ * under the License. */ import { Moment } from 'moment'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; +import { TimeRange, RefreshInterval } from '../../../common'; export interface TimefilterConfig { timeDefaults: TimeRange; diff --git a/src/plugins/data/public/suggestions_provider/types.ts b/src/plugins/data/public/suggestions_provider/types.ts index 988b5fcd43fa8..a13ecfb10143f 100644 --- a/src/plugins/data/public/suggestions_provider/types.ts +++ b/src/plugins/data/public/suggestions_provider/types.ts @@ -16,6 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { Field } from '..'; +import { IFieldType } from '../../common'; -export type IGetSuggestions = (index: string, field: Field, query: string, boolFilter?: any) => any; +export type IGetSuggestions = ( + index: string, + field: IFieldType, + query: string, + boolFilter?: any +) => any; diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/suggestions_provider/value_suggestions.ts index c769f64025b0e..3bc1b45d87395 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/suggestions_provider/value_suggestions.ts @@ -21,14 +21,14 @@ import { memoize } from 'lodash'; import { UiSettingsClientContract, HttpServiceBase } from 'src/core/public'; import { IGetSuggestions } from './types'; -import { Field } from '..'; +import { IFieldType } from '../../common'; export function getSuggestionsProvider( uiSettings: UiSettingsClientContract, http: HttpServiceBase ): IGetSuggestions { const requestSuggestions = memoize( - (index: string, field: Field, query: string, boolFilter: any = []) => { + (index: string, field: IFieldType, query: string, boolFilter: any = []) => { return http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), @@ -37,7 +37,7 @@ export function getSuggestionsProvider( resolver ); - return async (index: string, field: Field, query: string, boolFilter?: any) => { + return async (index: string, field: IFieldType, query: string, boolFilter?: any) => { const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); if (field.type === 'boolean') { return [true, false]; @@ -48,7 +48,7 @@ export function getSuggestionsProvider( }; } -function resolver(index: string, field: Field, query: string, boolFilter: any) { +function resolver(index: string, field: IFieldType, query: string, boolFilter: any) { // Only cache results for a minute const ttl = Math.floor(Date.now() / 1000 / 60); return [ttl, query, index, field.name, JSON.stringify(boolFilter)].join('|'); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index f0b6117b928cd..81906a63bd49d 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -32,5 +32,6 @@ export { } from './index_patterns'; export * from './search'; +export * from '../common'; export { IRequestTypesMap, IResponseTypesMap } from './search'; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index cf4af615b9577..88d2d873521cb 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -24,7 +24,7 @@ import sinon from 'sinon'; import * as shouldReadFieldFromDocValuesNS from './should_read_field_from_doc_values'; import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; -import { getKbnFieldType } from '../../../../../../data/common'; +import { getKbnFieldType } from '../../../../../../data/server'; import { readFieldCapsResponse } from './field_caps_response'; import esResponse from './__fixtures__/es_field_caps_response.json'; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index e160fd4026c58..298a665fd5b2c 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -18,7 +18,6 @@ */ export { ISearchSetup } from './i_search_setup'; -export * from '../../common'; export { ISearchContext } from './i_search_context'; diff --git a/src/plugins/es_ui_shared/public/request/np_ready_request.ts b/src/plugins/es_ui_shared/public/request/np_ready_request.ts new file mode 100644 index 0000000000000..48c7904661e51 --- /dev/null +++ b/src/plugins/es_ui_shared/public/request/np_ready_request.ts @@ -0,0 +1,166 @@ +/* + * 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 { useEffect, useState, useRef } from 'react'; + +import { HttpServiceBase } from '../../../../../src/core/public'; + +export interface SendRequestConfig { + path: string; + method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; + body?: any; +} + +export interface SendRequestResponse { + data: any; + error: Error | null; +} + +export interface UseRequestConfig extends SendRequestConfig { + pollIntervalMs?: number; + initialData?: any; + deserializer?: (data: any) => any; +} + +export interface UseRequestResponse { + isInitialRequest: boolean; + isLoading: boolean; + error: null | unknown; + data: any; + sendRequest: (...args: any[]) => Promise; +} + +export const sendRequest = async ( + httpClient: HttpServiceBase, + { path, method, body }: SendRequestConfig +): Promise => { + try { + const response = await httpClient[method](path, { body }); + + return { + data: response.data ? response.data : response, + error: null, + }; + } catch (e) { + return { + data: null, + error: e.response && e.response.data ? e.response.data : e.body, + }; + } +}; + +export const useRequest = ( + httpClient: HttpServiceBase, + { + path, + method, + body, + pollIntervalMs, + initialData, + deserializer = (data: any): any => data, + }: UseRequestConfig +): UseRequestResponse => { + // Main states for tracking request status and data + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [data, setData] = useState(initialData); + + // Consumers can use isInitialRequest to implement a polling UX. + const [isInitialRequest, setIsInitialRequest] = useState(true); + const pollInterval = useRef(null); + const pollIntervalId = useRef(null); + + // We always want to use the most recently-set interval in scheduleRequest. + pollInterval.current = pollIntervalMs; + + // Tied to every render and bound to each request. + let isOutdatedRequest = false; + + const scheduleRequest = () => { + // Clear current interval + if (pollIntervalId.current) { + clearTimeout(pollIntervalId.current); + } + + // Set new interval + if (pollInterval.current) { + pollIntervalId.current = setTimeout(_sendRequest, pollInterval.current); + } + }; + + const _sendRequest = async () => { + // We don't clear error or data, so it's up to the consumer to decide whether to display the + // "old" error/data or loading state when a new request is in-flight. + setIsLoading(true); + + const requestBody = { + path, + method, + body, + }; + + const response = await sendRequest(httpClient, requestBody); + const { data: serializedResponseData, error: responseError } = response; + const responseData = deserializer(serializedResponseData); + + // If an outdated request has resolved, DON'T update state, but DO allow the processData handler + // to execute side effects like update telemetry. + if (isOutdatedRequest) { + return { data: null, error: null }; + } + + setError(responseError); + setData(responseData); + setIsLoading(false); + setIsInitialRequest(false); + + // If we're on an interval, we need to schedule the next request. This also allows us to reset + // the interval if the user has manually requested the data, to avoid doubled-up requests. + scheduleRequest(); + + return { data: serializedResponseData, error: responseError }; + }; + + useEffect(() => { + _sendRequest(); + // To be functionally correct we'd send a new request if the method, path, or body changes. + // But it doesn't seem likely that the method will change and body is likely to be a new + // object even if its shape hasn't changed, so for now we're just watching the path. + }, [path]); + + useEffect(() => { + scheduleRequest(); + + // Clean up intervals and inflight requests and corresponding state changes + return () => { + isOutdatedRequest = true; + if (pollIntervalId.current) { + clearTimeout(pollIntervalId.current); + } + }; + }, [pollIntervalMs]); + + return { + isInitialRequest, + isLoading, + error, + data, + sendRequest: _sendRequest, // Gives the user the ability to manually request data + }; +}; diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 87ef810682f60..faceef4f90a6f 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -21,8 +21,6 @@ import { ExpressionInterpret } from '../interpreter_provider'; import { TimeRange } from '../../../data/public'; import { Adapters } from '../../../inspector/public'; import { Query } from '../../../data/public'; -import { ExpressionAST } from '../../../expressions/public'; -import { ExpressionArgAST } from '../../../../plugins/expressions/public'; import { esFilters } from '../../../../plugins/data/public'; export { ArgumentType } from './arguments'; diff --git a/src/plugins/inspector/public/adapters/request/request_adapter.ts b/src/plugins/inspector/public/adapters/request/request_adapter.ts index a1a1463b8f2f6..70af6b5b51d18 100644 --- a/src/plugins/inspector/public/adapters/request/request_adapter.ts +++ b/src/plugins/inspector/public/adapters/request/request_adapter.ts @@ -54,7 +54,7 @@ class RequestAdapter extends EventEmitter { name, startTime: Date.now(), status: RequestStatus.PENDING, - id: _.get(params, 'id', uuid()), + id: params.id ?? uuid(), }; this.requests.set(req.id, req); this._onChange(); diff --git a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts index e57699e879a87..ca40685db0ebf 100644 --- a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts +++ b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts @@ -18,7 +18,7 @@ */ import { expandShorthand } from './mapping_setup'; -import { ES_FIELD_TYPES } from '../../../data/common'; +import { ES_FIELD_TYPES } from '../../../data/public'; describe('mapping_setup', () => { it('allows shortcuts for field types by just setting the value to the type name', () => { diff --git a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts index 495338735337c..72f3716147efa 100644 --- a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts +++ b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts @@ -18,26 +18,25 @@ */ import { mapValues, isString } from 'lodash'; -import { ES_FIELD_TYPES } from '../../../../plugins/data/common'; import { FieldMappingSpec, MappingObject } from './types'; +import { ES_FIELD_TYPES } from '../../../data/public'; /** @private */ type ShorthandFieldMapObject = FieldMappingSpec | ES_FIELD_TYPES | 'json'; -const json: FieldMappingSpec = { - type: ES_FIELD_TYPES.TEXT, - _serialize(v) { - if (v) return JSON.stringify(v); - }, - _deserialize(v) { - if (v) return JSON.parse(v); - }, -}; - /** @public */ export const expandShorthand = (sh: Record): MappingObject => { return mapValues>(sh, (val: ShorthandFieldMapObject) => { const fieldMap = isString(val) ? { type: val } : val; + const json: FieldMappingSpec = { + type: ES_FIELD_TYPES.TEXT, + _serialize(v) { + if (v) return JSON.stringify(v); + }, + _deserialize(v) { + if (v) return JSON.parse(v); + }, + }; return fieldMap.type === 'json' ? json : fieldMap; }) as MappingObject; diff --git a/src/plugins/kibana_utils/public/field_mapping/types.ts b/src/plugins/kibana_utils/public/field_mapping/types.ts index 973a58d3baec4..f3fb9b000e45a 100644 --- a/src/plugins/kibana_utils/public/field_mapping/types.ts +++ b/src/plugins/kibana_utils/public/field_mapping/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ES_FIELD_TYPES } from '../../../data/common'; +import { ES_FIELD_TYPES } from '../../../data/public'; /** @public */ export interface FieldMappingSpec { diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 3aaa6d28a9f64..04845c72cb755 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -21,6 +21,7 @@ export * from './core'; export * from './errors'; export * from './store'; export * from './parse'; +export * from './resize_checker'; export * from './render_complete'; export * from './store'; export * from './errors'; diff --git a/src/legacy/ui/public/resize_checker/index.ts b/src/plugins/kibana_utils/public/resize_checker/index.ts similarity index 100% rename from src/legacy/ui/public/resize_checker/index.ts rename to src/plugins/kibana_utils/public/resize_checker/index.ts diff --git a/src/plugins/kibana_utils/public/resize_checker/resize_checker.test.ts b/src/plugins/kibana_utils/public/resize_checker/resize_checker.test.ts new file mode 100644 index 0000000000000..d3013aaf8435d --- /dev/null +++ b/src/plugins/kibana_utils/public/resize_checker/resize_checker.test.ts @@ -0,0 +1,199 @@ +/* + * 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 { ResizeChecker } from './resize_checker'; +import { EventEmitter } from 'events'; + +// If you want to know why these mocks are created, +// please check: https://github.com/elastic/kibana/pull/44750 +jest.mock('resize-observer-polyfill'); +import ResizeObserver from 'resize-observer-polyfill'; + +class MockElement { + public clientWidth: number; + public clientHeight: number; + private onResize: any; + + constructor() { + this.clientHeight = 0; + this.clientWidth = 0; + this.onResize = null; + } + + public addEventListener(name: string, listener: any) { + this.onResize = listener; + } + + public dispatchEvent(name: string) { + if (this.onResize) { + this.onResize(); + } + } + + public removeEventListener(name: string, listener: any) { + this.onResize = null; + } +} + +(ResizeObserver as any).mockImplementation(function(this: any, callback: any) { + this.observe = function(el: MockElement) { + el.addEventListener('resize', callback); + }; + this.disconnect = function() {}; + this.unobserve = function(el: MockElement) { + el.removeEventListener('resize', callback); + }; +}); + +describe('Resize Checker', () => { + describe('events', () => { + it('is an event emitter', () => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any); + + expect(checker).toBeInstanceOf(EventEmitter); + }); + + it('emits a "resize" event', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any); + const listener = jest.fn(); + + checker.on('resize', listener); + el.clientHeight = 100; + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener.mock.calls.length).toBe(1); + done(); + }, 100); + }); + }); + + describe('enable/disabled state', () => { + it('should not trigger events while disabled', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + expect(listener).not.toBeCalled(); + el.clientHeight = 100; + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener).not.toBeCalled(); + done(); + }, 100); + }); + + it('should trigger resize events after calling enable', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + expect(listener).not.toBeCalled(); + checker.enable(); + el.clientHeight = 100; + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener).toBeCalled(); + done(); + }, 100); + }); + + it('should not trigger the first time after enable when the size does not change', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + expect(listener).not.toBeCalled(); + el.clientHeight = 100; + checker.enable(); + el.clientHeight = 100; + setTimeout(() => { + expect(listener).not.toBeCalled(); + done(); + }, 100); + }); + }); + + describe('#modifySizeWithoutTriggeringResize()', () => { + it(`does not emit "resize" events caused by the block`, done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + checker.modifySizeWithoutTriggeringResize(() => { + el.clientHeight = 100; + }); + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener).not.toBeCalled(); + done(); + }, 1000); + }); + + it('does emit "resize" when modification is made between the block and resize notification', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + checker.modifySizeWithoutTriggeringResize(() => { + el.clientHeight = 100; + }); + el.dispatchEvent('resize'); + expect(listener).not.toBeCalled(); + + el.clientHeight = 200; + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener).not.toBeCalled(); + done(); + }, 100); + }); + }); + + describe('#destroy()', () => { + it('destroys internal observer instance', () => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + + checker.destroy(); + expect(!(checker as any).observer).toBe(true); + }); + + it('does not emit future resize events', done => { + const el = new MockElement(); + const checker = new ResizeChecker(el as any, { disabled: true }); + const listener = jest.fn(); + checker.on('resize', listener); + + checker.destroy(); + + el.clientHeight = 100; + el.dispatchEvent('resize'); + setTimeout(() => { + expect(listener).not.toBeCalled(); + done(); + }, 100); + }); + }); +}); diff --git a/src/legacy/ui/public/resize_checker/resize_checker.ts b/src/plugins/kibana_utils/public/resize_checker/resize_checker.ts similarity index 89% rename from src/legacy/ui/public/resize_checker/resize_checker.ts rename to src/plugins/kibana_utils/public/resize_checker/resize_checker.ts index c00603c3b11d6..bc514474e73fb 100644 --- a/src/legacy/ui/public/resize_checker/resize_checker.ts +++ b/src/plugins/kibana_utils/public/resize_checker/resize_checker.ts @@ -18,22 +18,9 @@ */ import { EventEmitter } from 'events'; -import $ from 'jquery'; import { isEqual } from 'lodash'; import ResizeObserver from 'resize-observer-polyfill'; -function validateElArg(el: HTMLElement) { - // the ResizeChecker historically accepted jquery elements, - // so we wrap in jQuery then extract the element - const $el = $(el); - - if ($el.length !== 1) { - throw new TypeError('ResizeChecker must be constructed with a single DOM element.'); - } - - return $el.get(0); -} - function getSize(el: HTMLElement): [number, number] { return [el.clientWidth, el.clientHeight]; } @@ -50,7 +37,7 @@ export class ResizeChecker extends EventEmitter { constructor(el: HTMLElement, args: { disabled?: boolean } = {}) { super(); - this.el = validateElArg(el); + this.el = el; this.observer = new ResizeObserver(() => { if (this.expectedSize) { diff --git a/test/api_integration/apis/general/index.js b/test/api_integration/apis/general/index.js index 86b7565cba6de..f8daff1a6e8a8 100644 --- a/test/api_integration/apis/general/index.js +++ b/test/api_integration/apis/general/index.js @@ -21,5 +21,6 @@ export default function ({ loadTestFile }) { describe('general', () => { loadTestFile(require.resolve('./cookies')); loadTestFile(require.resolve('./csp')); + loadTestFile(require.resolve('./prototype_pollution')); }); } diff --git a/test/api_integration/apis/general/prototype_pollution.ts b/test/api_integration/apis/general/prototype_pollution.ts new file mode 100644 index 0000000000000..1b732dc81afa9 --- /dev/null +++ b/test/api_integration/apis/general/prototype_pollution.ts @@ -0,0 +1,57 @@ +/* + * 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 { FtrProviderContext } from 'test/api_integration/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('prototype pollution smoke test', () => { + it('prevents payloads with the "constructor.prototype" pollution vector from being accepted', async () => { + await supertest + .post('/api/sample_data/some_data_id') + .send([ + { + constructor: { + prototype: 'foo', + }, + }, + ]) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: "'constructor.prototype' is an invalid key", + validation: { source: 'payload', keys: [] }, + }); + }); + + it('prevents payloads with the "__proto__" pollution vector from being accepted', async () => { + await supertest + .post('/api/sample_data/some_data_id') + .send(JSON.parse(`{"foo": { "__proto__": {} } }`)) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: "'__proto__' is an invalid key", + validation: { source: 'payload', keys: [] }, + }); + }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js b/test/api_integration/ftr_provider_context.d.ts similarity index 80% rename from src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js rename to test/api_integration/ftr_provider_context.d.ts index 1a2854ec15412..60f4914a1d27e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js +++ b/test/api_integration/ftr_provider_context.d.ts @@ -17,10 +17,8 @@ * under the License. */ -export function addFilter(field, values = [], operation, index, state, filterGen) { - if (!Array.isArray(values)) { - values = [values]; - } +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; - filterGen.add(field, values, operation, index); -} +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 642314d1fb7f1..456752c0cd6eb 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -35,6 +35,8 @@ GET _search export default function({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); + const find = getService('find'); + const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'console']); describe('console app', function describeIndexTests() { @@ -81,5 +83,14 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.console.getRequestFontSize()).to.be('24px'); }); }); + + it('should resize the editor', async () => { + const editor = await find.byCssSelector('.conApp'); + await browser.setWindowSize(1300, 1100); + const initialSize = await editor.getSize(); + await browser.setWindowSize(1000, 1100); + const afterSize = await editor.getSize(); + expect(initialSize.width).to.be.greaterThan(afterSize.width); + }); }); } diff --git a/test/functional/apps/dashboard/dashboard_snapshots.js b/test/functional/apps/dashboard/dashboard_snapshots.js index e98c4afc443f8..698f4d1de6d57 100644 --- a/test/functional/apps/dashboard/dashboard_snapshots.js +++ b/test/functional/apps/dashboard/dashboard_snapshots.js @@ -53,7 +53,6 @@ export default function ({ getService, getPageObjects, updateBaselines }) { await PageObjects.common.closeToast(); await PageObjects.dashboard.saveDashboard('tsvb'); - await PageObjects.common.closeToast(); await PageObjects.dashboard.clickFullScreenMode(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.clickExpandPanelToggle(); @@ -74,7 +73,6 @@ export default function ({ getService, getPageObjects, updateBaselines }) { await PageObjects.common.closeToast(); await PageObjects.dashboard.saveDashboard('area'); - await PageObjects.common.closeToast(); await PageObjects.dashboard.clickFullScreenMode(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.clickExpandPanelToggle(); diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js index af3a15e9b3015..2460422722835 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.js @@ -305,7 +305,8 @@ export function DashboardPageProvider({ getService, getPageObjects }) { /** * Save the current dashboard with the specified name and options and - * verify that the save was successful + * verify that the save was successful, close the toast and return the + * toast message * * @param dashName {String} * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, needsConfirm: false, waitDialogIsClosed: boolean }} @@ -319,8 +320,11 @@ export function DashboardPageProvider({ getService, getPageObjects }) { // Confirm that the Dashboard has actually been saved await testSubjects.existOrFail('saveDashboardSuccess'); + const message = await PageObjects.common.closeToast(); await PageObjects.header.waitUntilLoadingHasFinished(); await this.waitForSaveModalToClose(); + + return message; } async waitForSaveModalToClose() { diff --git a/test/functional/services/find.ts b/test/functional/services/find.ts index c65821ea73ace..312668b718dc0 100644 --- a/test/functional/services/find.ts +++ b/test/functional/services/find.ts @@ -303,10 +303,22 @@ export async function FindProvider({ getService }: FtrProviderContext) { timeout: number = WAIT_FOR_EXISTS_TIME ): Promise { log.debug(`Find.existsByDisplayedByCssSelector('${selector}') with timeout=${timeout}`); - return await this.exists(async drive => { - const elements = wrapAll(await drive.findElements(By.css(selector))); - return await this.filterElementIsDisplayed(elements); - }, timeout); + try { + await retry.tryForTime(timeout, async () => { + // make sure that the find timeout is not longer than the retry timeout + await this._withTimeout(Math.min(timeout, WAIT_FOR_EXISTS_TIME)); + const elements = await driver.findElements(By.css(selector)); + await this._withTimeout(defaultFindTimeout); + const displayed = await this.filterElementIsDisplayed(wrapAll(elements)); + if (displayed.length === 0) { + throw new Error(`${selector} is not displayed`); + } + }); + } catch (err) { + await this._withTimeout(defaultFindTimeout); + return false; + } + return true; } public async existsByCssSelector( diff --git a/test/plugin_functional/plugins/core_plugin_a/package.json b/test/plugin_functional/plugins/core_plugin_a/package.json index 7dede34f3c0cf..060ae49f43e8a 100644 --- a/test/plugin_functional/plugins/core_plugin_a/package.json +++ b/test/plugin_functional/plugins/core_plugin_a/package.json @@ -12,6 +12,6 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/test/plugin_functional/plugins/core_plugin_b/package.json b/test/plugin_functional/plugins/core_plugin_b/package.json index 18ff8cf7cc5c9..3eb878b9ed5dc 100644 --- a/test/plugin_functional/plugins/core_plugin_b/package.json +++ b/test/plugin_functional/plugins/core_plugin_b/package.json @@ -12,6 +12,6 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/test/plugin_functional/plugins/core_plugin_legacy/package.json b/test/plugin_functional/plugins/core_plugin_legacy/package.json index 2ae83b28f7e85..5f784c7b836a5 100644 --- a/test/plugin_functional/plugins/core_plugin_legacy/package.json +++ b/test/plugin_functional/plugins/core_plugin_legacy/package.json @@ -12,6 +12,6 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/test/plugin_functional/plugins/demo_search/common/index.ts b/test/plugin_functional/plugins/demo_search/common/index.ts index 0339e8fbda8c5..9254412ece291 100644 --- a/test/plugin_functional/plugins/demo_search/common/index.ts +++ b/test/plugin_functional/plugins/demo_search/common/index.ts @@ -20,7 +20,7 @@ import { IKibanaSearchRequest, IKibanaSearchResponse, -} from '../../../../../src/plugins/data/common/search'; +} from '../../../../../src/plugins/data/public'; export const DEMO_SEARCH_STRATEGY = 'DEMO_SEARCH_STRATEGY'; 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 196e64af39985..9df9352f76fc2 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -17,6 +17,6 @@ }, "devDependencies": { "@kbn/plugin-helpers": "9.0.2", - "typescript": "3.5.3" + "typescript": "3.7.2" } } 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 33e60128d0806..054276b620907 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 @@ -17,6 +17,6 @@ }, "devDependencies": { "@kbn/plugin-helpers": "9.0.2", - "typescript": "3.5.3" + "typescript": "3.7.2" } } diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 138e20b987761..c16847dab9dc2 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -91,14 +91,18 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider await testSubjects.existOrFail('fooAppPageA'); }); + it('chromeless applications are not visible in apps list', async () => { + expect(await appsMenu.linkExists('Chromeless')).to.be(false); + }); + it('navigating to chromeless application hides chrome', async () => { - await appsMenu.clickLink('Chromeless'); + await PageObjects.common.navigateToApp('chromeless'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(false); }); it('navigating away from chromeless application shows chrome', async () => { - await browser.goBack(); + await PageObjects.common.navigateToApp('foo'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(true); }); diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index e8d7cc03edad0..90df352e18a00 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -68,7 +68,7 @@ def getOssCiGroupWorker(ciGroup) { "CI_GROUP=${ciGroup}", "JOB=kibana-ciGroup${ciGroup}", ]) { - runbld "./test/scripts/jenkins_ci_group.sh" + runbld("./test/scripts/jenkins_ci_group.sh", "Execute kibana-ciGroup${ciGroup}") } }) } @@ -79,7 +79,7 @@ def getXpackCiGroupWorker(ciGroup) { "CI_GROUP=${ciGroup}", "JOB=xpack-kibana-ciGroup${ciGroup}", ]) { - runbld "./test/scripts/jenkins_xpack_ci_group.sh" + runbld("./test/scripts/jenkins_xpack_ci_group.sh", "Execute xpack-kibana-ciGroup${ciGroup}") } }) } @@ -93,7 +93,7 @@ def legacyJobRunner(name) { ]) { jobRunner('linux && immutable', false) { try { - runbld('.ci/run.sh', true) + runbld('.ci/run.sh', "Execute ${name}", true) } finally { catchError { uploadAllGcsArtifacts(name) @@ -118,16 +118,29 @@ def jobRunner(label, useRamDisk, closure) { // Move to a temporary workspace, so that we can symlink the real workspace into /dev/shm def originalWorkspace = env.WORKSPACE ws('/tmp/workspace') { - sh """ - mkdir -p /dev/shm/workspace - mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist - rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it - ln -s /dev/shm/workspace '${originalWorkspace}' - """ + sh( + script: """ + mkdir -p /dev/shm/workspace + mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist + rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it + ln -s /dev/shm/workspace '${originalWorkspace}' + """, + label: "Move workspace to RAM - /dev/shm/workspace" + ) } } - def scmVars = checkout scm + def scmVars + + // Try to clone from Github up to 8 times, waiting 15 secs between attempts + retry(8) { + try { + scmVars = checkout scm + } catch (ex) { + sleep 15 + throw ex + } + } withEnv([ "CI=true", @@ -225,27 +238,33 @@ def sendKibanaMail() { } } -def bash(script) { - sh "#!/bin/bash\n${script}" +def bash(script, label) { + sh( + script: "#!/bin/bash\n${script}", + label: label + ) } def doSetup() { - runbld "./test/scripts/jenkins_setup.sh" + runbld("./test/scripts/jenkins_setup.sh", "Setup Build Environment and Dependencies") } def buildOss() { - runbld "./test/scripts/jenkins_build_kibana.sh" + runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") } def buildXpack() { - runbld "./test/scripts/jenkins_xpack_build_kibana.sh" + runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") } def runErrorReporter() { - bash """ - source src/dev/ci_setup/setup_env.sh - node scripts/report_failed_tests - """ + bash( + """ + source src/dev/ci_setup/setup_env.sh + node scripts/report_failed_tests + """, + "Report failed tests, if necessary" + ) } return this diff --git a/vars/runbld.groovy b/vars/runbld.groovy index 501e2421ca65b..e52bc244c65cb 100644 --- a/vars/runbld.groovy +++ b/vars/runbld.groovy @@ -1,11 +1,17 @@ -def call(script, enableJunitProcessing = false) { +def call(script, label, enableJunitProcessing = false) { def extraConfig = enableJunitProcessing ? "" : "--config ${env.WORKSPACE}/kibana/.ci/runbld_no_junit.yml" - sh "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}" + sh( + script: "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}", + label: label ?: script + ) } def junit() { - sh "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh" + sh( + script: "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh", + label: "Process JUnit reports with runbld" + ) } return this diff --git a/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx b/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx index 8e5fba31ac5a4..8becf6892ff92 100644 --- a/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx +++ b/x-pack/legacy/common/eui_styled_components/eui_styled_components.tsx @@ -38,6 +38,6 @@ const { injectGlobal, keyframes, withTheme, -} = styledComponents as ThemedStyledComponentsModule; +} = (styledComponents as unknown) as ThemedStyledComponentsModule; export { css, euiStyled, EuiThemeProvider, injectGlobal, keyframes, withTheme }; diff --git a/x-pack/legacy/plugins/apm/cypress/package.json b/x-pack/legacy/plugins/apm/cypress/package.json index 98dcd495b8594..ef8955fcbd1b0 100644 --- a/x-pack/legacy/plugins/apm/cypress/package.json +++ b/x-pack/legacy/plugins/apm/cypress/package.json @@ -11,11 +11,11 @@ "@cypress/snapshot": "^2.1.3", "@cypress/webpack-preprocessor": "^4.1.0", "@types/js-yaml": "^3.12.1", - "cypress": "^3.4.1", + "cypress": "^3.5.0", "js-yaml": "^3.13.1", "p-limit": "^2.2.1", "ts-loader": "^6.1.0", - "typescript": "^3.6.3", + "typescript": "3.7.2", "webpack": "^4.40.2" } } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx index 33b20b0f0f226..ab61ce444232d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; export interface ErrorTab { @@ -39,7 +38,7 @@ export const metadataTab: ErrorTab = { }; export function getTabs(error: APMError) { - const hasLogStacktrace = !isEmpty(idx(error, _ => _.error.log.stacktrace)); + const hasLogStacktrace = !isEmpty(error.error.log?.stacktrace); return [ ...(hasLogStacktrace ? [logStacktraceTab] : []), exceptionStacktraceTab, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx index 13c904a119449..0b91bddf862bc 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { EuiTitle } from '@elastic/eui'; -import { idx } from '@kbn/elastic-idx/target'; import { Exception } from '../../../../../typings/es_schemas/raw/ErrorRaw'; import { Stacktrace } from '../../../shared/Stacktrace'; import { CauseStacktrace } from '../../../shared/Stacktrace/CauseStacktrace'; @@ -20,7 +19,7 @@ export function ExceptionStacktrace({ codeLanguage, exceptions }: ExceptionStacktraceProps) { - const title = idx(exceptions, _ => _[0].message); + const title = exceptions[0]?.message; return ( <> diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap index 39874c11b09bf..b2c503806c385 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -52,6 +52,7 @@ exports[`DetailView should render TabContent 1`] = ` error={ Object { "context": Object {}, + "error": Object {}, "timestamp": Object { "us": 0, }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx index c6b84ddc0722e..af4c129d61b60 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.test.tsx @@ -60,6 +60,7 @@ describe('DetailView', () => { const errorGroup = { occurrencesCount: 10, error: { + error: {}, timestamp: { us: 0 } @@ -85,6 +86,7 @@ describe('DetailView', () => { timestamp: { us: 0 }, + error: {}, service: {}, user: {} } as any @@ -109,6 +111,7 @@ describe('DetailView', () => { timestamp: { us: 0 }, + error: {}, context: {} } as any }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 5c7b2b7d0070e..de7f0e133ba49 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -19,7 +19,6 @@ import { Location } from 'history'; import React from 'react'; import styled from 'styled-components'; import { first } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { ErrorGroupAPIResponse } from '../../../../../server/lib/errors/get_error_group'; import { APMError } from '../../../../../typings/es_schemas/ui/APMError'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; @@ -80,11 +79,12 @@ export function DetailView({ errorGroup, urlParams, location }: Props) { const tabs = getTabs(error); const currentTab = getCurrentTab(tabs, urlParams.detailTab); - const errorUrl = - idx(error, _ => _.error.page.url) || idx(error, _ => _.url.full); + const errorUrl = error.error.page?.url || error.url?.full; - const method = idx(error, _ => _.http.request.method); - const status = idx(error, _ => _.http.response.status_code); + const method = error.http?.request.method; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const status = error.http?.response?.status_code; return ( @@ -188,9 +188,9 @@ function TabContent({ error: APMError; currentTab: ErrorTab; }) { - const codeLanguage = idx(error, _ => _.service.language.name); - const exceptions = idx(error, _ => _.error.exception) || []; - const logStackframes = idx(error, _ => _.error.log.stacktrace); + const codeLanguage = error.service.language?.name; + const exceptions = error.error.exception || []; + const logStackframes = error.error.log?.stacktrace; switch (currentTab.key) { case logStacktraceTab.key: diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 2667d03ef8dde..daba164a4a00c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; // @ts-ignore import Histogram from '../../../shared/charts/Histogram'; import { EmptyMessage } from '../../../shared/EmptyMessage'; +import { asRelativeDateTimeRange } from '../../../../utils/formatters'; interface IBucket { key: number; @@ -51,6 +52,9 @@ interface Props { title: React.ReactNode; } +const tooltipHeader = (bucket: FormattedBucket) => + asRelativeDateTimeRange(bucket.x0, bucket.x); + export function ErrorDistribution({ distribution, title }: Props) { const buckets = getFormattedBuckets( distribution.buckets, @@ -73,6 +77,7 @@ export function ErrorDistribution({ distribution, title }: Props) { {title} bucket.x} xType="time" buckets={buckets} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index df16c082404d5..a247390704e72 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -17,7 +17,6 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; import styled from 'styled-components'; -import { idx } from '@kbn/elastic-idx'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { useFetcher } from '../../../hooks/useFetcher'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; @@ -115,14 +114,11 @@ export function ErrorGroupDetails() { // If there are 0 occurrences, show only distribution chart w. empty message const showDetails = errorGroupData.occurrencesCount !== 0; - const logMessage = idx(errorGroupData, _ => _.error.error.log.message); - const excMessage = idx( - errorGroupData, - _ => _.error.error.exception[0].message - ); - const culprit = idx(errorGroupData, _ => _.error.error.culprit); + const logMessage = errorGroupData.error?.error.log?.message; + const excMessage = errorGroupData.error?.error.exception?.[0].message; + const culprit = errorGroupData.error?.error.culprit; const isUnhandled = - idx(errorGroupData, _ => _.error.error.exception[0].handled) === false; + errorGroupData.error?.error.exception?.[0].handled === false; return (
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index 2f06f1d52de6b..a6c8058158578 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -143,7 +143,7 @@ const ErrorGroupList: React.FC = props => { align: 'right', render: (value?: number) => value ? ( - + ) : ( NOT_AVAILABLE_LABEL ) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 8a5d7ad10f22b..c4cc7dbfd5d1f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -13,7 +13,6 @@ import { import { i18n } from '@kbn/i18n'; import { memoize } from 'lodash'; import React, { Fragment } from 'react'; -import { idx } from '@kbn/elastic-idx'; import { KibanaCoreContext } from '../../../../../../observability/public'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { LicenseContext } from '../../../../context/LicenseContext'; @@ -149,9 +148,9 @@ export class ServiceIntegrations extends React.Component { panels={[ { id: 0, - items: this.getPanelItems( - idx(license, _ => _.features.ml.is_available) - ) + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + items: this.getPanelItems(license.features.ml?.is_available) } ]} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx index f2524ef1c16f4..13e7a5bfd894e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { ServiceListAPIResponse } from '../../../../../server/lib/services/get_services'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { fontSizes, truncate } from '../../../../style/variables'; -import { asDecimal, asMillis } from '../../../../utils/formatters'; +import { asDecimal, convertTo } from '../../../../utils/formatters'; import { ManagedTable } from '../../../shared/ManagedTable'; import { EnvironmentBadge } from '../../../shared/EnvironmentBadge'; import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; @@ -80,7 +80,11 @@ export const SERVICE_COLUMNS = [ }), sortable: true, dataType: 'number', - render: (value: number) => asMillis(value) + render: (time: number) => + convertTo({ + unit: 'milliseconds', + microseconds: time + }).formatted }, { field: 'transactionsPerMinute', diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx index 8e7a6e3b50f67..46d6f80bf4834 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx @@ -19,7 +19,6 @@ import { EuiText, EuiSpacer } from '@elastic/eui'; -import { idx } from '@kbn/elastic-idx'; import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; import { isRight } from 'fp-ts/lib/Either'; @@ -89,18 +88,21 @@ export function AddEditFlyout({ // config settings const [sampleRate, setSampleRate] = useState( + // TODO(TS-3.7-ESLINT) ( - idx(selectedConfig, _ => _.settings.transaction_sample_rate) || + selectedConfig?.settings.transaction_sample_rate || // eslint-disable-line @typescript-eslint/camelcase defaultSettings.TRANSACTION_SAMPLE_RATE ).toString() ); const [captureBody, setCaptureBody] = useState( - idx(selectedConfig, _ => _.settings.capture_body) || - defaultSettings.CAPTURE_BODY + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + selectedConfig?.settings.capture_body || defaultSettings.CAPTURE_BODY ); const [transactionMaxSpans, setTransactionMaxSpans] = useState( + // TODO(TS-3.7-ESLINT) ( - idx(selectedConfig, _ => _.settings.transaction_max_spans) || + selectedConfig?.settings.transaction_max_spans || // eslint-disable-line @typescript-eslint/camelcase defaultSettings.TRANSACTION_MAX_SPANS ).toString() ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationList.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationList.tsx index 161d371148478..c660455e1eed8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationList.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationList.tsx @@ -128,7 +128,7 @@ export function AgentConfigurationList({ ), sortable: true, render: (value: number) => ( - + ) }, { diff --git a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/TraceList.tsx b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/TraceList.tsx index ca10b06c11cbf..9116e02870a80 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/TraceList.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TraceOverview/TraceList.tsx @@ -10,7 +10,7 @@ import React from 'react'; import styled from 'styled-components'; import { ITransactionGroup } from '../../../../server/lib/transaction_groups/transform'; import { fontSizes, truncate } from '../../../style/variables'; -import { asMillis } from '../../../utils/formatters'; +import { convertTo } from '../../../utils/formatters'; import { EmptyMessage } from '../../shared/EmptyMessage'; import { ImpactBar } from '../../shared/ImpactBar'; import { TransactionDetailLink } from '../../shared/Links/apm/TransactionDetailLink'; @@ -66,7 +66,11 @@ const traceListColumns: Array> = [ }), sortable: true, dataType: 'number', - render: (value: number) => asMillis(value) + render: (time: number) => + convertTo({ + unit: 'milliseconds', + microseconds: time + }).formatted }, { field: 'transactionsPerMinute', diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index fc86f4bb78afb..c9e5175a10921 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -11,7 +11,7 @@ import React, { FunctionComponent, useCallback } from 'react'; import { TransactionDistributionAPIResponse } from '../../../../../server/lib/transactions/distribution'; import { IBucket } from '../../../../../server/lib/transactions/distribution/get_buckets/transform'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { getTimeFormatter, timeUnit } from '../../../../utils/formatters'; +import { getDurationFormatter } from '../../../../utils/formatters'; // @ts-ignore import Histogram from '../../../shared/charts/Histogram'; import { EmptyMessage } from '../../../shared/EmptyMessage'; @@ -132,8 +132,7 @@ export const TransactionDistribution: FunctionComponent = ( ); const xMax = d3.max(buckets, d => d.x) || 0; - const timeFormatter = getTimeFormatter(xMax); - const unit = timeUnit(xMax); + const timeFormatter = getDurationFormatter(xMax); const bucketIndex = buckets.findIndex( bucket => @@ -187,18 +186,18 @@ export const TransactionDistribution: FunctionComponent = ( }); } }} - formatX={timeFormatter} + formatX={(time: number) => timeFormatter(time).formatted} formatYShort={formatYShort} formatYLong={formatYLong} verticalLineHover={(bucket: IChartPoint) => bucket.y > 0 && !bucket.sample } backgroundHover={(bucket: IChartPoint) => bucket.y > 0 && bucket.sample} - tooltipHeader={(bucket: IChartPoint) => - `${timeFormatter(bucket.x0, { - withUnit: false - })} - ${timeFormatter(bucket.x, { withUnit: false })} ${unit}` - } + tooltipHeader={(bucket: IChartPoint) => { + const xFormatted = timeFormatter(bucket.x); + const x0Formatted = timeFormatter(bucket.x0); + return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + }} tooltipFooter={(bucket: IChartPoint) => !bucket.sample && i18n.translate( diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx index 167940b357616..f767ce2e9f0d8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/HttpContext.tsx @@ -9,7 +9,6 @@ import styled from 'styled-components'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { idx } from '@kbn/elastic-idx'; import { borderRadius, fontFamilyCode, @@ -34,7 +33,7 @@ interface Props { } export function HttpContext({ httpContext }: Props) { - const url = idx(httpContext, _ => _.url.original); + const url = httpContext?.url?.original; if (!url) { return null; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx index 0f5893772fec8..bb7c03c63f8ab 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx @@ -21,7 +21,6 @@ import { import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; import styled from 'styled-components'; -import { idx } from '@kbn/elastic-idx'; import { px, units } from '../../../../../../../style/variables'; import { Summary } from '../../../../../../shared/Summary'; import { TimestampTooltip } from '../../../../../../shared/TimestampTooltip'; @@ -98,13 +97,15 @@ export function SpanFlyout({ } const stackframes = span.span.stacktrace; - const codeLanguage = idx(parentTransaction, _ => _.service.language.name); - const dbContext = idx(span, _ => _.span.db); - const httpContext = idx(span, _ => _.span.http); + const codeLanguage = parentTransaction?.service.language?.name; + const dbContext = span.span.db; + const httpContext = span.span.http; const spanTypes = getSpanTypes(span); - const spanHttpStatusCode = idx(httpContext, _ => _.response.status_code); - const spanHttpUrl = idx(httpContext, _ => _.url.original); - const spanHttpMethod = idx(httpContext, _ => _.method); + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const spanHttpStatusCode = httpContext?.response.status_code; + const spanHttpUrl = httpContext?.url?.original; + const spanHttpMethod = httpContext?.method; return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx index 5b22c902f6a36..f3e29d7c75717 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/DroppedSpansWarning.tsx @@ -7,7 +7,6 @@ import { EuiCallOut, EuiHorizontalRule } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { idx } from '@kbn/elastic-idx'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction'; import { ElasticDocsLink } from '../../../../../../shared/Links/ElasticDocsLink'; @@ -16,7 +15,7 @@ export function DroppedSpansWarning({ }: { transactionDoc: Transaction; }) { - const dropped = idx(transactionDoc, _ => _.transaction.span_count.dropped); + const dropped = transactionDoc.transaction.span_count?.dropped; if (!dropped) { return null; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx index a5e6eb622e8fb..c64231a6ded86 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx @@ -12,7 +12,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { isRumAgentName } from '../../../../../../../common/agent_name'; import { px, unit, units } from '../../../../../../style/variables'; -import { asTime } from '../../../../../../utils/formatters'; +import { asDuration } from '../../../../../../utils/formatters'; import { ErrorCountBadge } from '../../ErrorCountBadge'; import { IWaterfallItem } from './waterfall_helpers/waterfall_helpers'; import { ErrorOverviewLink } from '../../../../../shared/Links/apm/ErrorOverviewLink'; @@ -133,7 +133,7 @@ const SpanActionToolTip: React.SFC = ({ function Duration({ item }: { item: IWaterfallItem }) { return ( - {asTime(item.duration)} + {asDuration(item.duration)} ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 10f59e237ba7f..2a69c5f51173d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -15,7 +15,6 @@ import { isEmpty, first } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { TraceAPIResponse } from '../../../../../../../../server/lib/traces/get_trace'; import { Span } from '../../../../../../../../typings/es_schemas/ui/Span'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction'; @@ -224,7 +223,7 @@ function createGetTransactionById(itemsById: IWaterfallIndex) { } const item = itemsById[id]; - const isTransaction = idx(item, _ => _.docType) === 'transaction'; + const isTransaction = item?.docType === 'transaction'; if (isTransaction) { return (item as IWaterfallItemTransaction).transaction; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/List/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/List/index.tsx index 062d103bfc448..3d75011f52f19 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/List/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/List/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { ITransactionGroup } from '../../../../../server/lib/transaction_groups/transform'; import { fontFamilyCode, truncate } from '../../../../style/variables'; -import { asDecimal, asMillis } from '../../../../utils/formatters'; +import { asDecimal, convertTo } from '../../../../utils/formatters'; import { ImpactBar } from '../../../shared/ImpactBar'; import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; @@ -28,6 +28,12 @@ interface Props { isLoading: boolean; } +const toMilliseconds = (time: number) => + convertTo({ + unit: 'milliseconds', + microseconds: time + }).formatted; + export function TransactionList({ items, isLoading }: Props) { const columns: Array> = useMemo( () => [ @@ -67,7 +73,7 @@ export function TransactionList({ items, isLoading }: Props) { ), sortable: true, dataType: 'number', - render: (value: number) => asMillis(value) + render: (time: number) => toMilliseconds(time) }, { field: 'p95', @@ -79,7 +85,7 @@ export function TransactionList({ items, isLoading }: Props) { ), sortable: true, dataType: 'number', - render: (value: number) => asMillis(value) + render: (time: number) => toMilliseconds(time) }, { field: 'transactionsPerMinute', diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx index 29a8528295dd7..7f2632dc3d81f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -7,7 +7,6 @@ import { EuiBasicTable } from '@elastic/eui'; import { sortByOrder } from 'lodash'; import React, { useMemo, useCallback, ReactNode } from 'react'; -import { idx } from '@kbn/elastic-idx'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; import { fromQuery, toQuery } from '../Links/url_helpers'; @@ -42,7 +41,7 @@ function UnoptimizedManagedTable(props: Props) { columns, initialPageIndex = 0, initialPageSize = 10, - initialSortField = idx(props, _ => _.columns[0].field) || '', + initialSortField = props.columns[0]?.field || '', initialSortDirection = 'asc', hidePerPageOptions = true, noItemsMessage, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx index 9685ba920a73c..4320156fad003 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -22,7 +22,6 @@ import { registerLanguage } from 'react-syntax-highlighter/dist/light'; // @ts-ignore import { xcode } from 'react-syntax-highlighter/dist/styles'; import styled from 'styled-components'; -import { idx } from '@kbn/elastic-idx'; import { IStackframeWithLineContext } from '../../../../typings/es_schemas/raw/fields/Stackframe'; import { borderRadius, px, unit, units } from '../../../style/variables'; @@ -106,13 +105,13 @@ const Code = styled.code` function getStackframeLines(stackframe: IStackframeWithLineContext) { const line = stackframe.line.context; - const preLines = idx(stackframe, _ => _.context.pre) || []; - const postLines = idx(stackframe, _ => _.context.post) || []; + const preLines = stackframe.context?.pre || []; + const postLines = stackframe.context?.post || []; return [...preLines, line, ...postLines]; } function getStartLineNumber(stackframe: IStackframeWithLineContext) { - const preLines = size(idx(stackframe, _ => _.context.pre) || []); + const preLines = size(stackframe.context?.pre || []); return stackframe.line.number - preLines; } @@ -125,7 +124,7 @@ interface Props { export function Context({ stackframe, codeLanguage, isLibraryFrame }: Props) { const lines = getStackframeLines(stackframe); const startLineNumber = getStartLineNumber(stackframe); - const highlightedLineIndex = size(idx(stackframe, _ => _.context.pre) || []); + const highlightedLineIndex = size(stackframe.context?.pre || []); const language = codeLanguage || 'javascript'; // TODO: Add support for more languages return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx index c9f7057d2fb86..5ec6a2289f9c9 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx @@ -7,7 +7,6 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import React, { Fragment } from 'react'; import styled from 'styled-components'; -import { idx } from '@kbn/elastic-idx'; import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; @@ -35,7 +34,7 @@ const FrameHeading: React.SFC = ({ stackframe, isLibraryFrame }) => { const FileDetail = isLibraryFrame ? LibraryFrameFileDetail : AppFrameFileDetail; - const lineNumber = idx(stackframe, _ => _.line.number) || 0; + const lineNumber = stackframe.line.number; return ( {stackframe.filename} in{' '} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx index c76e62d987aac..a5a677296825c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/DurationSummaryItem.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip, EuiText } from '@elastic/eui'; import { PercentOfParent } from '../../app/TransactionDetails/WaterfallWithSummmary/PercentOfParent'; -import { asTime } from '../../../utils/formatters'; +import { asDuration } from '../../../utils/formatters'; interface Props { duration: number; @@ -29,7 +29,7 @@ const DurationSummaryItem = ({ return ( <> - {asTime(duration)} + {asDuration(duration)}   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 b6e783a00b5d6..4de70895ff32b 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { idx } from '@kbn/elastic-idx'; import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; import { Summary } from './'; import { TimestampTooltip } from '../TimestampTooltip'; @@ -22,15 +21,17 @@ interface Props { } const getTransactionResultSummaryItem = (transaction: Transaction) => { - const result = idx(transaction, _ => _.transaction.result); + const result = transaction.transaction.result; const isRumAgent = isRumAgentName(transaction.agent.name); const url = isRumAgent - ? idx(transaction, _ => _.transaction.page.url) - : idx(transaction, _ => _.url.full); + ? transaction.transaction.page?.url + : transaction.url?.full; if (url) { - const method = idx(transaction, _ => _.http.request.method); - const status = idx(transaction, _ => _.http.response.status_code); + const method = transaction.http?.request.method; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const status = transaction.http?.response?.status_code; return ; } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/index.tsx index c4b750a360efd..ce6935d1858aa 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/index.tsx @@ -8,9 +8,10 @@ import { EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { px, units } from '../../../../public/style/variables'; +import { Maybe } from '../../../../typings/common'; interface Props { - items: Array; + items: Array>; } // TODO: Light/Dark theme (@see https://github.com/elastic/kibana/issues/44840) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx new file mode 100644 index 0000000000000..b4678b287dc16 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/__test__/index.test.tsx @@ -0,0 +1,61 @@ +/* + * 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 { shallow } from 'enzyme'; +import React from 'react'; +import moment from 'moment-timezone'; +import { TimestampTooltip } from '../index'; +import { mockNow } from '../../../../utils/testHelpers'; + +describe('TimestampTooltip', () => { + const timestamp = 1570720000123; // Oct 10, 2019, 08:06:40.123 (UTC-7) + + beforeAll(() => { + // mock Date.now + mockNow(1570737000000); + + moment.tz.setDefault('America/Los_Angeles'); + }); + + afterAll(() => moment.tz.setDefault('')); + + it('should render component with relative time in body and absolute time in tooltip', () => { + expect(shallow()) + .toMatchInlineSnapshot(` + + 5 hours ago + + `); + }); + + it('should format with precision in milliseconds by default', () => { + expect( + shallow() + .find('EuiToolTip') + .prop('content') + ).toBe('Oct 10, 2019, 08:06:40.123 (UTC-7)'); + }); + + it('should format with precision in seconds', () => { + expect( + shallow() + .find('EuiToolTip') + .prop('content') + ).toBe('Oct 10, 2019, 08:06:40 (UTC-7)'); + }); + + it('should format with precision in minutes', () => { + expect( + shallow() + .find('EuiToolTip') + .prop('content') + ).toBe('Oct 10, 2019, 08:06 (UTC-7)'); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx deleted file mode 100644 index a7149c7604695..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; -import moment from 'moment-timezone'; -import { TimestampTooltip, asAbsoluteTime } from './index'; -import { mockNow } from '../../../utils/testHelpers'; - -describe('asAbsoluteTime', () => { - afterAll(() => moment.tz.setDefault('')); - - it('should add a leading plus for timezones with positive UTC offset', () => { - moment.tz.setDefault('Europe/Copenhagen'); - expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( - 'Jun 1, 2019, 14:00 (UTC+2)' - ); - }); - - it('should add a leading minus for timezones with negative UTC offset', () => { - moment.tz.setDefault('America/Los_Angeles'); - expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( - 'Jun 1, 2019, 05:00 (UTC-7)' - ); - }); - - it('should use default UTC offset formatting when offset contains minutes', () => { - moment.tz.setDefault('Canada/Newfoundland'); - expect(asAbsoluteTime({ time: 1559390400000, precision: 'minutes' })).toBe( - 'Jun 1, 2019, 09:30 (UTC-02:30)' - ); - }); - - it('should respect DST', () => { - moment.tz.setDefault('Europe/Copenhagen'); - const timeWithDST = 1559390400000; // Jun 1, 2019 - const timeWithoutDST = 1575201600000; // Dec 1, 2019 - - expect(asAbsoluteTime({ time: timeWithDST })).toBe( - 'Jun 1, 2019, 14:00:00.000 (UTC+2)' - ); - - expect(asAbsoluteTime({ time: timeWithoutDST })).toBe( - 'Dec 1, 2019, 13:00:00.000 (UTC+1)' - ); - }); -}); - -describe('TimestampTooltip', () => { - const timestamp = 1570720000123; // Oct 10, 2019, 08:06:40.123 (UTC-7) - - beforeAll(() => { - // mock Date.now - mockNow(1570737000000); - - moment.tz.setDefault('America/Los_Angeles'); - }); - - afterAll(() => moment.tz.setDefault('')); - - it('should render component with relative time in body and absolute time in tooltip', () => { - expect(shallow()) - .toMatchInlineSnapshot(` - - 5 hours ago - - `); - }); - - it('should format with precision in milliseconds by default', () => { - expect( - shallow() - .find('EuiToolTip') - .prop('content') - ).toBe('Oct 10, 2019, 08:06:40.123 (UTC-7)'); - }); - - it('should format with precision in seconds', () => { - expect( - shallow() - .find('EuiToolTip') - .prop('content') - ).toBe('Oct 10, 2019, 08:06:40 (UTC-7)'); - }); - - it('should format with precision in minutes', () => { - expect( - shallow() - .find('EuiToolTip') - .prop('content') - ).toBe('Oct 10, 2019, 08:06 (UTC-7)'); - }); - - it('should format with precision in days', () => { - expect( - shallow() - .find('EuiToolTip') - .prop('content') - ).toBe('Oct 10, 2019 (UTC-7)'); - }); -}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx index d7ef6517c2fb8..504ff36c078f0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TimestampTooltip/index.tsx @@ -6,48 +6,20 @@ import React from 'react'; import { EuiToolTip } from '@elastic/eui'; import moment from 'moment-timezone'; +import { asAbsoluteDateTime, TimeUnit } from '../../../utils/formatters'; interface Props { /** * timestamp in milliseconds */ time: number; - precision?: 'days' | 'minutes' | 'seconds' | 'milliseconds'; + timeUnit?: TimeUnit; } -function getPreciseTime(precision: Props['precision']) { - switch (precision) { - case 'days': - return ''; - case 'minutes': - return ', HH:mm'; - case 'seconds': - return ', HH:mm:ss'; - default: - return ', HH:mm:ss.SSS'; - } -} - -function withLeadingPlus(value: number) { - return value > 0 ? `+${value}` : value; -} - -export function asAbsoluteTime({ time, precision = 'milliseconds' }: Props) { - const momentTime = moment(time); - const utcOffsetHours = momentTime.utcOffset() / 60; - const utcOffsetFormatted = Number.isInteger(utcOffsetHours) - ? withLeadingPlus(utcOffsetHours) - : 'Z'; - - return momentTime.format( - `MMM D, YYYY${getPreciseTime(precision)} (UTC${utcOffsetFormatted})` - ); -} - -export function TimestampTooltip({ time, precision = 'milliseconds' }: Props) { +export function TimestampTooltip({ time, timeUnit = 'milliseconds' }: Props) { const momentTime = moment(time); const relativeTimeLabel = momentTime.fromNow(); - const absoluteTimeLabel = asAbsoluteTime({ time, precision }); + const absoluteTimeLabel = asAbsoluteDateTime(time, timeUnit); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 533bc845f14f9..4a3b77b699c5f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -17,7 +17,6 @@ import { import url from 'url'; import { i18n } from '@kbn/i18n'; import React, { useState, FunctionComponent } from 'react'; -import { idx } from '@kbn/elastic-idx'; import { pick } from 'lodash'; import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; import { DiscoverTransactionLink } from '../Links/DiscoverLinks/DiscoverTransactionLink'; @@ -72,9 +71,9 @@ export const TransactionActionMenu: FunctionComponent = ( const { urlParams } = useUrlParams(); - const hostName = idx(transaction, _ => _.host.hostname); - const podId = idx(transaction, _ => _.kubernetes.pod.uid); - const containerId = idx(transaction, _ => _.container.id); + const hostName = transaction.host?.hostname; + const podId = transaction.kubernetes?.pod.uid; + const containerId = transaction.container?.id; const time = Math.round(transaction.timestamp.us / 1000); const infraMetricsQuery = getInfraMetricsQuery(transaction); @@ -175,7 +174,7 @@ export const TransactionActionMenu: FunctionComponent = ( { dateRangeStart: urlParams.rangeFrom, dateRangeEnd: urlParams.rangeTo, - search: `url.domain:"${idx(transaction, t => t.url.domain)}"` + search: `url.domain:"${transaction.url?.domain}"` }, (val: string) => !!val ) @@ -209,7 +208,7 @@ export const TransactionActionMenu: FunctionComponent = ( })} ), - condition: idx(transaction, _ => _.url.domain) + condition: transaction.url?.domain } ] .filter(({ condition }) => condition) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx index 1bcf4e08c9144..c4e7ed86df8b7 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionBreakdown/TransactionBreakdownGraph/index.tsx @@ -9,6 +9,7 @@ import numeral from '@elastic/numeral'; import { throttle } from 'lodash'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { Maybe } from '../../../../../typings/common'; import { TransactionLineChart } from '../../charts/TransactionCharts/TransactionLineChart'; import { asPercent } from '../../../../utils/formatters'; import { unit } from '../../../../style/variables'; @@ -19,7 +20,7 @@ interface Props { timeseries: TimeSeries[]; } -const tickFormatY = (y: number | null | undefined) => { +const tickFormatY = (y: Maybe) => { return numeral(y || 0).format('0 %'); }; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js index b511bdc439227..f76a27480137a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/Histogram.test.js @@ -11,9 +11,8 @@ import d3 from 'd3'; import { HistogramInner } from '../index'; import response from './response.json'; import { - getTimeFormatter, asDecimal, - timeUnit + getDurationFormatter } from '../../../../../utils/formatters'; import { toJson } from '../../../../../utils/testHelpers'; import { getFormattedBuckets } from '../../../../app/TransactionDetails/Distribution/index'; @@ -25,8 +24,7 @@ describe('Histogram', () => { beforeEach(() => { const buckets = getFormattedBuckets(response.buckets, response.bucketSize); const xMax = d3.max(buckets, d => d.x); - const timeFormatter = getTimeFormatter(xMax); - const unit = timeUnit(xMax); + const timeFormatter = getDurationFormatter(xMax); wrapper = mount( { bucketSize={response.bucketSize} transactionId="myTransactionId" onClick={onClick} - formatX={timeFormatter} + formatX={time => timeFormatter(time).formatted} formatYShort={t => `${asDecimal(t)} occ.`} formatYLong={t => `${asDecimal(t)} occurrences`} - tooltipHeader={bucket => - `${timeFormatter(bucket.x0, { - withUnit: false - })} - ${timeFormatter(bucket.x, { withUnit: false })} ${unit}` - } + tooltipHeader={bucket => { + const xFormatted = timeFormatter(bucket.x); + const x0Formatted = timeFormatter(bucket.x0); + return `${x0Formatted.value} - ${xFormatted.value} ${xFormatted.unit}`; + }} width={800} /> ); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx index 51aa4a40fb923..30dcc99af31b9 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/MetricsChart/index.tsx @@ -9,20 +9,21 @@ import { GenericMetricsChart } from '../../../../../server/lib/metrics/transform // @ts-ignore import CustomPlot from '../CustomPlot'; import { - asDynamicBytes, + asDecimal, asPercent, + asInteger, + asDynamicBytes, getFixedByteFormatter, - asDecimal, - asTime, - asInteger + asDuration } from '../../../../utils/formatters'; import { Coordinate } from '../../../../../typings/timeseries'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; import { useChartsSync } from '../../../../hooks/useChartsSync'; +import { Maybe } from '../../../../../typings/common'; interface Props { - start: number | string | undefined; - end: number | string | undefined; + start: Maybe; + end: Maybe; chart: GenericMetricsChart; } @@ -64,17 +65,17 @@ function getYTickFormatter(chart: GenericMetricsChart) { return getFixedByteFormatter(max); } case 'percent': { - return (y: number | null | undefined) => asPercent(y || 0, 1); + return (y: Maybe) => asPercent(y || 0, 1); } case 'time': { - return (y: number | null | undefined) => asTime(y); + return (y: Maybe) => asDuration(y); } case 'integer': { - return (y: number | null | undefined) => + return (y: Maybe) => isValidCoordinateValue(y) ? asInteger(y) : y; } default: { - return (y: number | null | undefined) => + return (y: Maybe) => isValidCoordinateValue(y) ? asDecimal(y) : y; } } @@ -89,7 +90,7 @@ function getTooltipFormatter({ yUnit }: GenericMetricsChart) { return (c: Coordinate) => asPercent(c.y || 0, 1); } case 'time': { - return (c: Coordinate) => asTime(c.y); + return (c: Coordinate) => asDuration(c.y); } case 'integer': { return (c: Coordinate) => diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js index 1f8c6db8d20a8..8ee23d61fe0eb 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js @@ -10,7 +10,7 @@ import { EuiToolTip } from '@elastic/eui'; import Legend from '../Legend'; import { units, px } from '../../../../style/variables'; import styled from 'styled-components'; -import { asTime } from '../../../../utils/formatters'; +import { asDuration } from '../../../../utils/formatters'; import theme from '@elastic/eui/dist/eui_theme_light.json'; const NameContainer = styled.div` @@ -39,7 +39,7 @@ export default function AgentMarker({ agentMark, x }) { content={
{agentMark.name} - {asTime(agentMark.us)} + {asDuration(agentMark.us)}
} > diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js index 1648f427edd7d..346aec9fb080a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.js @@ -12,7 +12,7 @@ import { XYPlot, XAxis } from 'react-vis'; import LastTickValue from './LastTickValue'; import AgentMarker from './AgentMarker'; import { px } from '../../../../style/variables'; -import { getTimeFormatter } from '../../../../utils/formatters'; +import { getDurationFormatter } from '../../../../utils/formatters'; import theme from '@elastic/eui/dist/eui_theme_light.json'; // Remove any tick that is too close to topTraceDuration @@ -33,8 +33,9 @@ const getXAxisTickValues = (tickValues, topTraceDuration) => { function TimelineAxis({ plotValues, agentMarks, topTraceDuration }) { const { margins, tickValues, width, xDomain, xMax, xScale } = plotValues; - const tickFormat = getTimeFormatter(xMax); + const tickFormatter = getDurationFormatter(xMax); const xAxisTickValues = getXAxisTickValues(tickValues, topTraceDuration); + const topTraceDurationFormatted = tickFormatter(topTraceDuration).formatted; return ( @@ -66,7 +67,7 @@ function TimelineAxis({ plotValues, agentMarks, topTraceDuration }) { orientation="top" tickSize={0} tickValues={xAxisTickValues} - tickFormat={tickFormat} + tickFormat={time => tickFormatter(time).formatted} tickPadding={20} style={{ text: { fill: theme.euiColorDarkShade } @@ -76,7 +77,7 @@ function TimelineAxis({ plotValues, agentMarks, topTraceDuration }) { {topTraceDuration > 0 && ( )} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js index f5992ac7fc63b..239e46c25904d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Tooltip/index.js @@ -19,7 +19,7 @@ import { } from '../../../../style/variables'; import Legend from '../Legend'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { asAbsoluteTime } from '../../TimestampTooltip'; +import { asAbsoluteDateTime } from '../../../../utils/formatters'; const TooltipElm = styled.div` margin: 0 ${px(unit)}; @@ -87,9 +87,7 @@ export default function Tooltip({ return ( -
- {header || asAbsoluteTime({ time: x, precision: 'seconds' })} -
+
{header || asAbsoluteDateTime(x, 'seconds')}
{showLegends ? ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx index adcce161c7ac1..d2b6970841bdc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/ChoroplethMap/ChoroplethToolTip.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { asTime, asInteger } from '../../../../../utils/formatters'; +import { asDuration, asInteger } from '../../../../../utils/formatters'; import { fontSizes } from '../../../../../style/variables'; export const ChoroplethToolTip: React.SFC<{ @@ -26,7 +26,7 @@ export const ChoroplethToolTip: React.SFC<{ )}
- {asTime(value)} + {asDuration(value)}
( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx index 94f30a8a2325a..bb4d9fa264980 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx @@ -19,7 +19,6 @@ import { Location } from 'history'; import React, { Component } from 'react'; import { isEmpty, flatten } from 'lodash'; import styled from 'styled-components'; -import { idx } from '@kbn/elastic-idx'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; import { ITransactionChartData } from '../../../../selectors/chartSelectors'; @@ -27,13 +26,13 @@ import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { asInteger, tpmUnit, - TimeFormatter + TimeFormatter, + getDurationFormatter } from '../../../../utils/formatters'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; import { LicenseContext } from '../../../../context/LicenseContext'; import { TransactionLineChart } from './TransactionLineChart'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; -import { getTimeFormatter } from '../../../../utils/formatters'; import { DurationByCountryMap } from './DurationByCountryMap'; import { TRANSACTION_PAGE_LOAD, @@ -74,12 +73,14 @@ export class TransactionCharts extends Component { }; public getResponseTimeTickFormatter = (formatter: TimeFormatter) => { - return (t: number) => formatter(t); + return (t: number) => formatter(t).formatted; }; public getResponseTimeTooltipFormatter = (formatter: TimeFormatter) => { return (p: Coordinate) => { - return isValidCoordinateValue(p.y) ? formatter(p.y) : NOT_AVAILABLE_LABEL; + return isValidCoordinateValue(p.y) + ? formatter(p.y).formatted + : NOT_AVAILABLE_LABEL; }; }; @@ -154,7 +155,7 @@ export class TransactionCharts extends Component { const { responseTimeSeries, tpmSeries } = charts; const { transactionType } = urlParams; const maxY = this.getMaxY(responseTimeSeries); - const formatter = getTimeFormatter(maxY); + const formatter = getDurationFormatter(maxY); return ( <> @@ -170,9 +171,9 @@ export class TransactionCharts extends Component { {license => - this.renderMLHeader( - idx(license, _ => _.features.ml.is_available) - ) + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + this.renderMLHeader(license.features.ml?.is_available) } diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx index bc6382841be3f..2d60273c1896a 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx @@ -5,7 +5,6 @@ */ import React, { useContext, useEffect, useState, useMemo } from 'react'; -import { idx } from '@kbn/elastic-idx'; import { i18n } from '@kbn/i18n'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; @@ -100,14 +99,13 @@ export function useFetcher( defaultMessage: `Error` })} - {idx(err.response, r => r.statusText)} ( - {idx(err.response, r => r.status)}) + {err.response?.statusText} ({err.response?.status})
{i18n.translate('xpack.apm.fetcher.error.url', { defaultMessage: `URL` })}
- {idx(err.response, r => r.url)} + {err.response?.url}
) }); diff --git a/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts b/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts index b15231e89365a..75a558ac81a54 100644 --- a/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts +++ b/x-pack/legacy/plugins/apm/public/selectors/chartSelectors.ts @@ -16,7 +16,7 @@ import { RectCoordinate, TimeSeries } from '../../typings/timeseries'; -import { asDecimal, asMillis, tpmUnit } from '../utils/formatters'; +import { asDecimal, tpmUnit, convertTo } from '../utils/formatters'; import { IUrlParams } from '../context/UrlParamsContext/types'; import { getEmptySeries } from '../components/shared/charts/CustomPlot/getEmptySeries'; import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor'; @@ -70,6 +70,10 @@ export function getResponseTimeSeries({ }: TimeSeriesAPIResponse) { const { overallAvgDuration } = apmTimeseries; const { avg, p95, p99 } = apmTimeseries.responseTimes; + const formattedDuration = convertTo({ + unit: 'milliseconds', + microseconds: overallAvgDuration + }).formatted; const series: TimeSeries[] = [ { @@ -77,7 +81,7 @@ export function getResponseTimeSeries({ defaultMessage: 'Avg.' }), data: avg, - legendValue: asMillis(overallAvgDuration), + legendValue: formattedDuration, type: 'linemark', color: theme.euiColorVis1 }, diff --git a/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts index 01a58ac03d0c3..295ea1f9f900f 100644 --- a/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts +++ b/x-pack/legacy/plugins/apm/public/utils/flattenObject.ts @@ -5,6 +5,7 @@ */ import { compact, isObject } from 'lodash'; +import { Maybe } from '../../typings/common'; export interface KeyValuePair { key: string; @@ -12,7 +13,7 @@ export interface KeyValuePair { } export const flattenObject = ( - item: Record | null | undefined, + item: Maybe>, parentKey?: string ): KeyValuePair[] => { if (item) { diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters.ts b/x-pack/legacy/plugins/apm/public/utils/formatters.ts deleted file mode 100644 index 34b552230fa77..0000000000000 --- a/x-pack/legacy/plugins/apm/public/utils/formatters.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; -import { memoize } from 'lodash'; -import { NOT_AVAILABLE_LABEL } from '../../common/i18n'; - -const HOURS_CUT_OFF = 3600000000; // 1 hour (in microseconds) -const MINUTES_CUT_OFF = 60000000; // 1 minute (in microseconds) -const SECONDS_CUT_OFF = 10 * 1000000; // 10 seconds (in microseconds) -const MILLISECONDS_CUT_OFF = 10 * 1000; // 10 milliseconds (in microseconds) -const SPACE = ' '; - -/* - * value: time in microseconds - * withUnit: add unit suffix - * defaultValue: value to use if the specified is null/undefined - */ -type FormatterValue = number | undefined | null; -interface FormatterOptions { - withUnit?: boolean; - defaultValue?: string; -} - -export function asHours( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - const hoursLabel = - SPACE + - i18n.translate('xpack.apm.formatters.hoursTimeUnitLabel', { - defaultMessage: 'h' - }); - const formatted = asDecimal(value / 3600000000); - return `${formatted}${withUnit ? hoursLabel : ''}`; -} - -export function asMinutes( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - const minutesLabel = - SPACE + - i18n.translate('xpack.apm.formatters.minutesTimeUnitLabel', { - defaultMessage: 'min' - }); - const formatted = asDecimal(value / 60000000); - return `${formatted}${withUnit ? minutesLabel : ''}`; -} - -export function asSeconds( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - const secondsLabel = - SPACE + - i18n.translate('xpack.apm.formatters.secondsTimeUnitLabel', { - defaultMessage: 's' - }); - const formatted = asDecimal(value / 1000000); - return `${formatted}${withUnit ? secondsLabel : ''}`; -} - -export function asMillis( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - - const millisLabel = - SPACE + - i18n.translate('xpack.apm.formatters.millisTimeUnitLabel', { - defaultMessage: 'ms' - }); - const formatted = asInteger(value / 1000); - return `${formatted}${withUnit ? millisLabel : ''}`; -} - -export function asMicros( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - - const microsLabel = - SPACE + - i18n.translate('xpack.apm.formatters.microsTimeUnitLabel', { - defaultMessage: 'μs' - }); - const formatted = asInteger(value); - return `${formatted}${withUnit ? microsLabel : ''}`; -} - -export type TimeFormatter = ( - value: FormatterValue, - options?: FormatterOptions -) => string; - -type TimeFormatterBuilder = (max: number) => TimeFormatter; - -export const getTimeFormatter: TimeFormatterBuilder = memoize((max: number) => { - const unit = timeUnit(max); - switch (unit) { - case 'h': - return asHours; - case 'm': - return asMinutes; - case 's': - return asSeconds; - case 'ms': - return asMillis; - case 'us': - return asMicros; - } -}); - -export function timeUnit(max: number) { - if (max > HOURS_CUT_OFF) { - return 'h'; - } else if (max > MINUTES_CUT_OFF) { - return 'm'; - } else if (max > SECONDS_CUT_OFF) { - return 's'; - } else if (max > MILLISECONDS_CUT_OFF) { - return 'ms'; - } else { - return 'us'; - } -} - -export function asTime( - value: FormatterValue, - { withUnit = true, defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} -) { - if (value == null) { - return defaultValue; - } - const formatter = getTimeFormatter(value); - return formatter(value, { withUnit, defaultValue }); -} - -export function asDecimal(value: number) { - return numeral(value).format('0,0.0'); -} - -export function asInteger(value: number) { - return numeral(value).format('0,0'); -} - -export function tpmUnit(type?: string) { - return type === 'request' - ? i18n.translate('xpack.apm.formatters.requestsPerMinLabel', { - defaultMessage: 'rpm' - }) - : i18n.translate('xpack.apm.formatters.transactionsPerMinLabel', { - defaultMessage: 'tpm' - }); -} - -export function asPercent( - numerator: number, - denominator: number | undefined, - fallbackResult = '' -) { - if (!denominator || isNaN(numerator)) { - return fallbackResult; - } - - const decimal = numerator / denominator; - return numeral(decimal).format('0.0%'); -} - -function asKilobytes(value: number) { - return `${asDecimal(value / 1000)} KB`; -} - -function asMegabytes(value: number) { - return `${asDecimal(value / 1e6)} MB`; -} - -function asGigabytes(value: number) { - return `${asDecimal(value / 1e9)} GB`; -} - -function asTerabytes(value: number) { - return `${asDecimal(value / 1e12)} TB`; -} - -function asBytes(value: number) { - return `${asDecimal(value)} B`; -} - -const bailIfNumberInvalid = (cb: (val: number) => string) => { - return (val: number | null | undefined) => { - if (val === null || val === undefined || isNaN(val)) { - return ''; - } - return cb(val); - }; -}; - -export const asDynamicBytes = bailIfNumberInvalid((value: number) => { - return unmemoizedFixedByteFormatter(value)(value); -}); - -const unmemoizedFixedByteFormatter = (max: number) => { - if (max > 1e12) { - return asTerabytes; - } - - if (max > 1e9) { - return asGigabytes; - } - - if (max > 1e6) { - return asMegabytes; - } - - if (max > 1000) { - return asKilobytes; - } - - return asBytes; -}; - -export const getFixedByteFormatter = memoize((max: number) => { - const formatter = unmemoizedFixedByteFormatter(max); - - return bailIfNumberInvalid(formatter); -}); diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/datetime.test.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/datetime.test.ts new file mode 100644 index 0000000000000..bec9cede00a2b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/datetime.test.ts @@ -0,0 +1,146 @@ +/* + * 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 moment from 'moment-timezone'; +import { asRelativeDateTimeRange, asAbsoluteDateTime } from '../datetime'; + +describe('date time formatters', () => { + describe('asRelativeDateTimeRange', () => { + beforeAll(() => { + moment.tz.setDefault('Europe/Amsterdam'); + }); + afterAll(() => moment.tz.setDefault('')); + const formatDateToTimezone = (dateTimeString: string) => + moment(dateTimeString).valueOf(); + + describe('YYYY - YYYY', () => { + it('range: 10 years', () => { + const start = formatDateToTimezone('2000-01-01 10:01:01'); + const end = formatDateToTimezone('2010-01-01 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('2000 - 2010'); + }); + it('range: 5 years', () => { + const start = formatDateToTimezone('2010-01-01 10:01:01'); + const end = formatDateToTimezone('2015-01-01 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('2010 - 2015'); + }); + }); + describe('MMM YYYY - MMM YYYY', () => { + it('range: 4 years ', () => { + const start = formatDateToTimezone('2010-01-01 10:01:01'); + const end = formatDateToTimezone('2014-04-01 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Jan 2010 - Apr 2014'); + }); + it('range: 6 months ', () => { + const start = formatDateToTimezone('2019-01-01 10:01:01'); + const end = formatDateToTimezone('2019-07-01 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Jan 2019 - Jul 2019'); + }); + }); + describe('MMM D, YYYY - MMM D, YYYY', () => { + it('range: 2 days', () => { + const start = formatDateToTimezone('2019-10-01 10:01:01'); + const end = formatDateToTimezone('2019-10-05 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 1, 2019 - Oct 5, 2019'); + }); + it('range: 1 day', () => { + const start = formatDateToTimezone('2019-10-01 10:01:01'); + const end = formatDateToTimezone('2019-10-03 10:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 1, 2019 - Oct 3, 2019'); + }); + }); + describe('MMM D, YYYY, HH:mm - HH:mm (UTC)', () => { + it('range: 9 hours', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01'); + const end = formatDateToTimezone('2019-10-29 19:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 29, 2019, 10:01 - 19:01 (UTC+1)'); + }); + it('range: 5 hours', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01'); + const end = formatDateToTimezone('2019-10-29 15:01:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 29, 2019, 10:01 - 15:01 (UTC+1)'); + }); + }); + describe('MMM D, YYYY, HH:mm:ss - HH:mm:ss (UTC)', () => { + it('range: 14 minutes', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01'); + const end = formatDateToTimezone('2019-10-29 10:15:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 29, 2019, 10:01:01 - 10:15:01 (UTC+1)'); + }); + it('range: 5 minutes', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01'); + const end = formatDateToTimezone('2019-10-29 10:06:01'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual('Oct 29, 2019, 10:01:01 - 10:06:01 (UTC+1)'); + }); + }); + describe('MMM D, YYYY, HH:mm:ss.SSS - HH:mm:ss.SSS (UTC)', () => { + it('range: 9 seconds', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01.001'); + const end = formatDateToTimezone('2019-10-29 10:01:10.002'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual( + 'Oct 29, 2019, 10:01:01.001 - 10:01:10.002 (UTC+1)' + ); + }); + it('range: 1 second', () => { + const start = formatDateToTimezone('2019-10-29 10:01:01.001'); + const end = formatDateToTimezone('2019-10-29 10:01:02.002'); + const dateRange = asRelativeDateTimeRange(start, end); + expect(dateRange).toEqual( + 'Oct 29, 2019, 10:01:01.001 - 10:01:02.002 (UTC+1)' + ); + }); + }); + }); + + describe('asAbsoluteDateTime', () => { + afterAll(() => moment.tz.setDefault('')); + + it('should add a leading plus for timezones with positive UTC offset', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteDateTime(1559390400000, 'minutes')).toBe( + 'Jun 1, 2019, 14:00 (UTC+2)' + ); + }); + + it('should add a leading minus for timezones with negative UTC offset', () => { + moment.tz.setDefault('America/Los_Angeles'); + expect(asAbsoluteDateTime(1559390400000, 'minutes')).toBe( + 'Jun 1, 2019, 05:00 (UTC-7)' + ); + }); + + it('should use default UTC offset formatting when offset contains minutes', () => { + moment.tz.setDefault('Canada/Newfoundland'); + expect(asAbsoluteDateTime(1559390400000, 'minutes')).toBe( + 'Jun 1, 2019, 09:30 (UTC-02:30)' + ); + }); + + it('should respect DST', () => { + moment.tz.setDefault('Europe/Copenhagen'); + const timeWithDST = 1559390400000; // Jun 1, 2019 + const timeWithoutDST = 1575201600000; // Dec 1, 2019 + + expect(asAbsoluteDateTime(timeWithDST)).toBe( + 'Jun 1, 2019, 14:00:00.000 (UTC+2)' + ); + + expect(asAbsoluteDateTime(timeWithoutDST)).toBe( + 'Dec 1, 2019, 13:00:00.000 (UTC+1)' + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/duration.test.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/duration.test.ts new file mode 100644 index 0000000000000..014ecad01d4d7 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/duration.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { asDuration, convertTo, toMicroseconds } from '../duration'; + +describe('duration formatters', () => { + describe('asDuration', () => { + it('formats correctly with defaults', () => { + expect(asDuration(null)).toEqual('N/A'); + expect(asDuration(undefined)).toEqual('N/A'); + expect(asDuration(0)).toEqual('0 μs'); + expect(asDuration(1)).toEqual('1 μs'); + expect(asDuration(toMicroseconds(1, 'milliseconds'))).toEqual('1,000 μs'); + expect(asDuration(toMicroseconds(1000, 'milliseconds'))).toEqual( + '1,000 ms' + ); + expect(asDuration(toMicroseconds(10000, 'milliseconds'))).toEqual( + '10,000 ms' + ); + expect(asDuration(toMicroseconds(20, 'seconds'))).toEqual('20.0 s'); + expect(asDuration(toMicroseconds(10, 'minutes'))).toEqual('10.0 min'); + expect(asDuration(toMicroseconds(1, 'hours'))).toEqual('60.0 min'); + expect(asDuration(toMicroseconds(1.5, 'hours'))).toEqual('1.5 h'); + }); + + it('falls back to default value', () => { + expect(asDuration(undefined, { defaultValue: 'nope' })).toEqual('nope'); + }); + }); + + describe('convertTo', () => { + it('hours', () => { + const unit = 'hours'; + const oneHourAsMicro = toMicroseconds(1, 'hours'); + const twoHourAsMicro = toMicroseconds(2, 'hours'); + expect(convertTo({ unit, microseconds: oneHourAsMicro })).toEqual({ + unit: 'h', + value: '1.0', + formatted: '1.0 h' + }); + expect(convertTo({ unit, microseconds: twoHourAsMicro })).toEqual({ + unit: 'h', + value: '2.0', + formatted: '2.0 h' + }); + expect( + convertTo({ unit, microseconds: null, defaultValue: '1.2' }) + ).toEqual({ value: '1.2', formatted: '1.2' }); + }); + + it('minutes', () => { + const unit = 'minutes'; + const oneHourAsMicro = toMicroseconds(1, 'hours'); + const twoHourAsMicro = toMicroseconds(2, 'hours'); + expect(convertTo({ unit, microseconds: oneHourAsMicro })).toEqual({ + unit: 'min', + value: '60.0', + formatted: '60.0 min' + }); + expect(convertTo({ unit, microseconds: twoHourAsMicro })).toEqual({ + unit: 'min', + value: '120.0', + formatted: '120.0 min' + }); + expect( + convertTo({ unit, microseconds: null, defaultValue: '10' }) + ).toEqual({ value: '10', formatted: '10' }); + }); + + it('seconds', () => { + const unit = 'seconds'; + const twentySecondsAsMicro = toMicroseconds(20, 'seconds'); + const thirtyFiveSecondsAsMicro = toMicroseconds(35, 'seconds'); + expect(convertTo({ unit, microseconds: twentySecondsAsMicro })).toEqual({ + unit: 's', + value: '20.0', + formatted: '20.0 s' + }); + expect( + convertTo({ unit, microseconds: thirtyFiveSecondsAsMicro }) + ).toEqual({ unit: 's', value: '35.0', formatted: '35.0 s' }); + expect( + convertTo({ unit, microseconds: null, defaultValue: '10' }) + ).toEqual({ value: '10', formatted: '10' }); + }); + + it('milliseconds', () => { + const unit = 'milliseconds'; + const twentyMilliAsMicro = toMicroseconds(20, 'milliseconds'); + const thirtyFiveMilliAsMicro = toMicroseconds(35, 'milliseconds'); + expect(convertTo({ unit, microseconds: twentyMilliAsMicro })).toEqual({ + unit: 'ms', + value: '20', + formatted: '20 ms' + }); + expect( + convertTo({ unit, microseconds: thirtyFiveMilliAsMicro }) + ).toEqual({ unit: 'ms', value: '35', formatted: '35 ms' }); + expect( + convertTo({ unit, microseconds: null, defaultValue: '10' }) + ).toEqual({ value: '10', formatted: '10' }); + }); + + it('microseconds', () => { + const unit = 'microseconds'; + expect(convertTo({ unit, microseconds: 20 })).toEqual({ + unit: 'μs', + value: '20', + formatted: '20 μs' + }); + expect(convertTo({ unit, microseconds: 35 })).toEqual({ + unit: 'μs', + value: '35', + formatted: '35 μs' + }); + expect( + convertTo({ unit, microseconds: null, defaultValue: '10' }) + ).toEqual({ value: '10', formatted: '10' }); + }); + }); + describe('toMicroseconds', () => { + it('transformes to microseconds', () => { + expect(toMicroseconds(1, 'hours')).toEqual(3600000000); + expect(toMicroseconds(10, 'minutes')).toEqual(600000000); + expect(toMicroseconds(10, 'seconds')).toEqual(10000000); + expect(toMicroseconds(10, 'milliseconds')).toEqual(10000); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/formatters.test.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/formatters.test.ts new file mode 100644 index 0000000000000..f6ed88a850a5b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/formatters.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { asPercent } from '../formatters'; + +describe('formatters', () => { + describe('asPercent', () => { + it('should divide and format item as percent', () => { + expect(asPercent(3725, 10000, 'n/a')).toEqual('37.3%'); + }); + + it('should format when numerator is 0', () => { + expect(asPercent(0, 1, 'n/a')).toEqual('0.0%'); + }); + + it('should return fallback when denominator is undefined', () => { + expect(asPercent(3725, undefined, 'n/a')).toEqual('n/a'); + }); + + it('should return fallback when denominator is 0 ', () => { + expect(asPercent(3725, 0, 'n/a')).toEqual('n/a'); + }); + + it('should return fallback when numerator or denominator is NaN', () => { + expect(asPercent(3725, NaN, 'n/a')).toEqual('n/a'); + expect(asPercent(NaN, 10000, 'n/a')).toEqual('n/a'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/utils/__test__/formatters.test.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/size.test.ts similarity index 63% rename from x-pack/legacy/plugins/apm/public/utils/__test__/formatters.test.ts rename to x-pack/legacy/plugins/apm/public/utils/formatters/__test__/size.test.ts index 093624240565f..07d3d0c1eb08f 100644 --- a/x-pack/legacy/plugins/apm/public/utils/__test__/formatters.test.ts +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/__test__/size.test.ts @@ -3,61 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { getFixedByteFormatter, asDynamicBytes } from '../size'; -import { - asPercent, - asTime, - getFixedByteFormatter, - asDynamicBytes -} from '../formatters'; - -describe('formatters', () => { - describe('asTime', () => { - it('formats correctly with defaults', () => { - expect(asTime(null)).toEqual('N/A'); - expect(asTime(undefined)).toEqual('N/A'); - expect(asTime(0)).toEqual('0 μs'); - expect(asTime(1)).toEqual('1 μs'); - expect(asTime(1000)).toEqual('1,000 μs'); - expect(asTime(1000 * 1000)).toEqual('1,000 ms'); - expect(asTime(1000 * 1000 * 10)).toEqual('10,000 ms'); - expect(asTime(1000 * 1000 * 20)).toEqual('20.0 s'); - expect(asTime(60000000 * 10)).toEqual('10.0 min'); - expect(asTime(3600000000 * 1.5)).toEqual('1.5 h'); - }); - - it('formats without unit', () => { - expect(asTime(1000, { withUnit: false })).toEqual('1,000'); - }); - - it('falls back to default value', () => { - expect(asTime(undefined, { defaultValue: 'nope' })).toEqual('nope'); - }); - }); - - describe('asPercent', () => { - it('should divide and format item as percent', () => { - expect(asPercent(3725, 10000, 'n/a')).toEqual('37.3%'); - }); - - it('should format when numerator is 0', () => { - expect(asPercent(0, 1, 'n/a')).toEqual('0.0%'); - }); - - it('should return fallback when denominator is undefined', () => { - expect(asPercent(3725, undefined, 'n/a')).toEqual('n/a'); - }); - - it('should return fallback when denominator is 0 ', () => { - expect(asPercent(3725, 0, 'n/a')).toEqual('n/a'); - }); - - it('should return fallback when numerator or denominator is NaN', () => { - expect(asPercent(3725, NaN, 'n/a')).toEqual('n/a'); - expect(asPercent(NaN, 10000, 'n/a')).toEqual('n/a'); - }); - }); - +describe('size formatters', () => { describe('byte formatting', () => { const bytes = 10; const kb = 1000 + 1; diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/datetime.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/datetime.ts new file mode 100644 index 0000000000000..98483a0351f06 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/datetime.ts @@ -0,0 +1,149 @@ +/* + * 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 moment from 'moment-timezone'; + +/** + * Returns the timezone set on momentTime. + * (UTC+offset) when offset if bigger than 0. + * (UTC-offset) when offset if lower than 0. + * @param momentTime Moment + */ +function formatTimezone(momentTime: moment.Moment) { + const DEFAULT_TIMEZONE_FORMAT = 'Z'; + + const utcOffsetHours = momentTime.utcOffset() / 60; + + const customTimezoneFormat = + utcOffsetHours > 0 ? `+${utcOffsetHours}` : utcOffsetHours; + + const utcOffsetFormatted = Number.isInteger(utcOffsetHours) + ? customTimezoneFormat + : DEFAULT_TIMEZONE_FORMAT; + + return momentTime.format(`(UTC${utcOffsetFormatted})`); +} + +export type TimeUnit = 'hours' | 'minutes' | 'seconds' | 'milliseconds'; +function getTimeFormat(timeUnit: TimeUnit) { + switch (timeUnit) { + case 'hours': + return 'HH'; + case 'minutes': + return 'HH:mm'; + case 'seconds': + return 'HH:mm:ss'; + case 'milliseconds': + return 'HH:mm:ss.SSS'; + default: + return ''; + } +} + +type DateUnit = 'days' | 'months' | 'years'; +function getDateFormat(dateUnit: DateUnit) { + switch (dateUnit) { + case 'years': + return 'YYYY'; + case 'months': + return 'MMM YYYY'; + case 'days': + return 'MMM D, YYYY'; + default: + return ''; + } +} + +function getFormatsAccordingToDateDifference( + momentStart: moment.Moment, + momentEnd: moment.Moment +) { + const getDateDifference = (unitOfTime: DateUnit | TimeUnit) => + momentEnd.diff(momentStart, unitOfTime); + + if (getDateDifference('years') >= 5) { + return { dateFormat: getDateFormat('years') }; + } + + if (getDateDifference('months') >= 5) { + return { dateFormat: getDateFormat('months') }; + } + + const dateFormatWithDays = getDateFormat('days'); + if (getDateDifference('days') > 1) { + return { dateFormat: dateFormatWithDays }; + } + + if (getDateDifference('hours') >= 5) { + return { + dateFormat: dateFormatWithDays, + timeFormat: getTimeFormat('minutes') + }; + } + + if (getDateDifference('minutes') >= 5) { + return { + dateFormat: dateFormatWithDays, + timeFormat: getTimeFormat('seconds') + }; + } + + return { + dateFormat: dateFormatWithDays, + timeFormat: getTimeFormat('milliseconds') + }; +} + +export function asAbsoluteDateTime( + time: number, + timeUnit: TimeUnit = 'milliseconds' +) { + const momentTime = moment(time); + const formattedTz = formatTimezone(momentTime); + + return momentTime.format( + `${getDateFormat('days')}, ${getTimeFormat(timeUnit)} ${formattedTz}` + ); +} + +/** + * + * Returns the dates formatted according to the difference between the two dates: + * + * | Difference | Format | + * | -------------- |:----------------------------------------------:| + * | >= 5 years | YYYY - YYYY | + * | >= 5 months | MMM YYYY - MMM YYYY | + * | > 1 day | MMM D, YYYY - MMM D, YYYY | + * | >= 5 hours | MMM D, YYYY, HH:mm - HH:mm (UTC) | + * | >= 5 minutes | MMM D, YYYY, HH:mm:ss - HH:mm:ss (UTC) | + * | default | MMM D, YYYY, HH:mm:ss.SSS - HH:mm:ss.SSS (UTC) | + * + * @param start timestamp + * @param end timestamp + */ +export function asRelativeDateTimeRange(start: number, end: number) { + const momentStartTime = moment(start); + const momentEndTime = moment(end); + + const { dateFormat, timeFormat } = getFormatsAccordingToDateDifference( + momentStartTime, + momentEndTime + ); + + if (timeFormat) { + const startFormatted = momentStartTime.format( + `${dateFormat}, ${timeFormat}` + ); + const endFormatted = momentEndTime.format(timeFormat); + const formattedTz = formatTimezone(momentStartTime); + return `${startFormatted} - ${endFormatted} ${formattedTz}`; + } + + const startFormatted = momentStartTime.format(dateFormat); + const endFormatted = momentEndTime.format(dateFormat); + return `${startFormatted} - ${endFormatted}`; +} diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/duration.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/duration.ts new file mode 100644 index 0000000000000..39341e1ff4443 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/duration.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { memoize } from 'lodash'; +import { NOT_AVAILABLE_LABEL } from '../../../common/i18n'; +import { asDecimal, asInteger } from './formatters'; +import { TimeUnit } from './datetime'; +import { Maybe } from '../../../typings/common'; + +interface FormatterOptions { + defaultValue?: string; +} + +type DurationTimeUnit = TimeUnit | 'microseconds'; + +interface DurationUnit { + [unit: string]: { + label: string; + convert: (value: number) => string; + }; +} + +interface ConvertedDuration { + value: string; + unit?: string; + formatted: string; +} + +export type TimeFormatter = ( + value: Maybe, + options?: FormatterOptions +) => ConvertedDuration; + +type TimeFormatterBuilder = (max: number) => TimeFormatter; + +const durationUnit: DurationUnit = { + hours: { + label: i18n.translate('xpack.apm.formatters.hoursTimeUnitLabel', { + defaultMessage: 'h' + }), + convert: (value: number) => + asDecimal(moment.duration(value / 1000).asHours()) + }, + minutes: { + label: i18n.translate('xpack.apm.formatters.minutesTimeUnitLabel', { + defaultMessage: 'min' + }), + convert: (value: number) => + asDecimal(moment.duration(value / 1000).asMinutes()) + }, + seconds: { + label: i18n.translate('xpack.apm.formatters.secondsTimeUnitLabel', { + defaultMessage: 's' + }), + convert: (value: number) => + asDecimal(moment.duration(value / 1000).asSeconds()) + }, + milliseconds: { + label: i18n.translate('xpack.apm.formatters.millisTimeUnitLabel', { + defaultMessage: 'ms' + }), + convert: (value: number) => + asInteger(moment.duration(value / 1000).asMilliseconds()) + }, + microseconds: { + label: i18n.translate('xpack.apm.formatters.microsTimeUnitLabel', { + defaultMessage: 'μs' + }), + convert: (value: number) => asInteger(value) + } +}; + +/** + * Converts a microseconds value into the unit defined. + * + * @param param0 + * { unit: "milliseconds" | "hours" | "minutes" | "seconds" | "microseconds", microseconds, defaultValue } + * + * @returns object { value, unit, formatted } + */ +export function convertTo({ + unit, + microseconds, + defaultValue = NOT_AVAILABLE_LABEL +}: { + unit: DurationTimeUnit; + microseconds: Maybe; + defaultValue?: string; +}): ConvertedDuration { + const duration = durationUnit[unit]; + if (!duration || microseconds == null) { + return { value: defaultValue, formatted: defaultValue }; + } + + const convertedValue = duration.convert(microseconds); + return { + value: convertedValue, + unit: duration.label, + formatted: `${convertedValue} ${duration.label}` + }; +} + +export const toMicroseconds = (value: number, timeUnit: TimeUnit) => + moment.duration(value, timeUnit).asMilliseconds() * 1000; + +function getDurationUnitKey(max: number): DurationTimeUnit { + if (max > toMicroseconds(1, 'hours')) { + return 'hours'; + } + if (max > toMicroseconds(1, 'minutes')) { + return 'minutes'; + } + if (max > toMicroseconds(10, 'seconds')) { + return 'seconds'; + } + if (max > toMicroseconds(10, 'milliseconds')) { + return 'milliseconds'; + } + return 'microseconds'; +} + +export const getDurationFormatter: TimeFormatterBuilder = memoize( + (max: number) => { + const unit = getDurationUnitKey(max); + return (value, { defaultValue }: FormatterOptions = {}) => { + return convertTo({ unit, microseconds: value, defaultValue }); + }; + } +); + +/** + * Converts value and returns it formatted - 00 unit + * + * @param value + * @param param1 { defaultValue } + * @returns formated value - 00 unit + */ +export function asDuration( + value: Maybe, + { defaultValue = NOT_AVAILABLE_LABEL }: FormatterOptions = {} +) { + if (value == null) { + return defaultValue; + } + + const formatter = getDurationFormatter(value); + return formatter(value, { defaultValue }).formatted; +} diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/formatters.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/formatters.ts new file mode 100644 index 0000000000000..630b6a0a18dbf --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/formatters.ts @@ -0,0 +1,38 @@ +/* + * 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 numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; + +export function asDecimal(value: number) { + return numeral(value).format('0,0.0'); +} + +export function asInteger(value: number) { + return numeral(value).format('0,0'); +} + +export function tpmUnit(type?: string) { + return type === 'request' + ? i18n.translate('xpack.apm.formatters.requestsPerMinLabel', { + defaultMessage: 'rpm' + }) + : i18n.translate('xpack.apm.formatters.transactionsPerMinLabel', { + defaultMessage: 'tpm' + }); +} + +export function asPercent( + numerator: number, + denominator: number | undefined, + fallbackResult = '' +) { + if (!denominator || isNaN(numerator)) { + return fallbackResult; + } + + const decimal = numerator / denominator; + return numeral(decimal).format('0.0%'); +} diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/index.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/index.ts new file mode 100644 index 0000000000000..4fedd55ff1e89 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './formatters'; +export * from './datetime'; +export * from './duration'; +export * from './size'; diff --git a/x-pack/legacy/plugins/apm/public/utils/formatters/size.ts b/x-pack/legacy/plugins/apm/public/utils/formatters/size.ts new file mode 100644 index 0000000000000..2cdf8af1d46de --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/utils/formatters/size.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { memoize } from 'lodash'; +import { asDecimal } from './formatters'; +import { Maybe } from '../../../typings/common'; + +function asKilobytes(value: number) { + return `${asDecimal(value / 1000)} KB`; +} + +function asMegabytes(value: number) { + return `${asDecimal(value / 1e6)} MB`; +} + +function asGigabytes(value: number) { + return `${asDecimal(value / 1e9)} GB`; +} + +function asTerabytes(value: number) { + return `${asDecimal(value / 1e12)} TB`; +} + +function asBytes(value: number) { + return `${asDecimal(value)} B`; +} + +const bailIfNumberInvalid = (cb: (val: number) => string) => { + return (val: Maybe) => { + if (val === null || val === undefined || isNaN(val)) { + return ''; + } + return cb(val); + }; +}; + +export const getFixedByteFormatter = memoize((max: number) => { + const formatter = unmemoizedFixedByteFormatter(max); + + return bailIfNumberInvalid(formatter); +}); + +export const asDynamicBytes = bailIfNumberInvalid((value: number) => { + return unmemoizedFixedByteFormatter(value)(value); +}); + +const unmemoizedFixedByteFormatter = (max: number) => { + if (max > 1e12) { + return asTerabytes; + } + + if (max > 1e9) { + return asGigabytes; + } + + if (max > 1e6) { + return asMegabytes; + } + + if (max > 1000) { + return asKilobytes; + } + + return asBytes; +}; diff --git a/x-pack/legacy/plugins/apm/public/utils/isValidCoordinateValue.ts b/x-pack/legacy/plugins/apm/public/utils/isValidCoordinateValue.ts index 411d03fce349d..c36efc232b782 100644 --- a/x-pack/legacy/plugins/apm/public/utils/isValidCoordinateValue.ts +++ b/x-pack/legacy/plugins/apm/public/utils/isValidCoordinateValue.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Maybe } from '../../typings/common'; -export const isValidCoordinateValue = ( - value: number | null | undefined -): value is number => value !== null && value !== undefined; +export const isValidCoordinateValue = (value: Maybe): value is number => + value !== null && value !== undefined; 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 afb8237178a20..37889e69ad8f2 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { ESFilter } from '../../../../typings/elasticsearch'; import { ERROR_GROUP_ID, @@ -64,12 +63,12 @@ export async function getBuckets({ const resp = await client.search(params); - const buckets = ( - idx(resp.aggregations, _ => _.distribution.buckets) || [] - ).map(bucket => ({ - key: bucket.key, - count: bucket.doc_count - })); + const buckets = (resp.aggregations?.distribution.buckets || []).map( + bucket => ({ + key: bucket.key, + count: bucket.doc_count + }) + ); return { noHits: resp.hits.total.value === 0, 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 caadbfef32698..fd1199d07b95f 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, @@ -55,9 +54,9 @@ export async function getErrorGroup({ }; const resp = await client.search(params); - const error = idx(resp, _ => _.hits.hits[0]._source); - const transactionId = idx(error, _ => _.transaction.id); - const traceId = idx(error, _ => _.trace.id); + const error = resp.hits.hits[0]?._source; + const transactionId = error?.transaction?.id; + const traceId = error?.trace?.id; let transaction; if (transactionId && traceId) { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts index 193ee4c2bbd1c..baaa2d97752e3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { ERROR_CULPRIT, ERROR_EXC_HANDLED, @@ -106,23 +105,22 @@ export async function getErrorGroups({ // aggregations can be undefined when no matching indices are found. // this is an exception rather than the rule so the ES type does not account for this. - const hits = (idx(resp, _ => _.aggregations.error_groups.buckets) || []).map( - bucket => { - const source = bucket.sample.hits.hits[0]._source; - const message = - idx(source, _ => _.error.log.message) || - idx(source, _ => _.error.exception[0].message); + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const hits = (resp.aggregations?.error_groups.buckets || []).map(bucket => { + const source = bucket.sample.hits.hits[0]._source; + const message = + source.error.log?.message || source.error.exception?.[0]?.message; - return { - message, - occurrenceCount: bucket.doc_count, - culprit: source.error.culprit, - groupId: source.error.grouping_key, - latestOccurrenceAt: source['@timestamp'], - handled: idx(source, _ => _.error.exception[0].handled) - }; - } - ); + return { + message, + occurrenceCount: bucket.doc_count, + culprit: source.error.culprit, + groupId: source.error.grouping_key, + latestOccurrenceAt: source['@timestamp'], + handled: source.error.exception?.[0].handled + }; + }); return hits; } 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 9a6aed02e2a84..5074f9315d8ae 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { ERROR_LOG_LEVEL, PROCESSOR_EVENT, @@ -55,7 +54,7 @@ export async function getTraceErrorsPerTransaction( const resp = await client.search(params); - return (idx(resp.aggregations, _ => _.transactions.buckets) || []).reduce( + return (resp.aggregations?.transactions.buckets || []).reduce( (acc, bucket) => ({ ...acc, [bucket.key]: bucket.doc_count diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts index 2543c2b9a8a61..9e5f8bb266054 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_kuery_ui_filter_es.ts @@ -5,7 +5,6 @@ */ import { Server } from 'hapi'; -import { idx } from '@kbn/elastic-idx'; import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { ESFilter } from '../../../../typings/elasticsearch'; import { ISavedObject } from '../../../../public/services/rest/savedObjects'; @@ -31,8 +30,8 @@ export async function getKueryUiFilterES( } // lifted from src/legacy/ui/public/index_patterns/static_utils/index.js -export function getFromSavedObject(apmIndexPattern: ISavedObject) { - if (idx(apmIndexPattern, _ => _.attributes.fields) === undefined) { +export function getFromSavedObject(apmIndexPattern: ISavedObject | undefined) { + if (apmIndexPattern?.attributes.fields === undefined) { return; } 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 dcc034287863a..ab0f47eb04d62 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 @@ -7,10 +7,15 @@ import { Legacy } from 'kibana'; import { Server } from 'hapi'; import moment from 'moment'; +import { KibanaConfig } from 'src/legacy/server/kbn_server'; 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'; +import { + getApmIndices, + ApmIndicesConfig +} from '../settings/apm_indices/get_apm_indices'; +import { ESFilter } from '../../../typings/elasticsearch'; +import { ESClient } from './es_client'; function decodeUiFilters(server: Server, uiFiltersEncoded?: string) { if (!uiFiltersEncoded) { @@ -26,9 +31,20 @@ export interface APMRequestQuery { end?: string; uiFilters?: string; } +// Explicitly type Setup to prevent TS initialization errors +// https://github.com/microsoft/TypeScript/issues/34933 -export type Setup = PromiseReturnType; -export async function setupRequest(req: Legacy.Request) { +export interface Setup { + start: number; + end: number; + uiFiltersES: ESFilter[]; + client: ESClient; + internalClient: ESClient; + config: KibanaConfig; + indices: ApmIndicesConfig; +} + +export async function setupRequest(req: Legacy.Request): Promise { const query = (req.query as unknown) as APMRequestQuery; const { server } = req; const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient( diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts index f785e45062807..180537d68a2a2 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts @@ -9,7 +9,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { sum, round } from 'lodash'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { Setup } from '../../../../helpers/setup_request'; @@ -113,7 +112,7 @@ export async function fetchAndTransformGcMetrics({ const response = await client.search(params); - const aggregations = idx(response, _ => _.aggregations); + const { aggregations } = response; if (!aggregations) { return { @@ -127,11 +126,12 @@ export async function fetchAndTransformGcMetrics({ const label = poolBucket.key as string; const timeseriesData = poolBucket.over_time; - const data = (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { + const data = timeseriesData.buckets.map(bucket => { // derivative/value will be undefined for the first hit and if the `max` value is null + const bucketValue = bucket.value?.value; const y = - 'value' in bucket && bucket.value.value !== null - ? round(bucket.value.value * (60 / bucketSize), 1) + bucketValue !== null && bucketValue !== undefined && bucket.value + ? round(bucketValue * (60 / bucketSize), 1) : null; return { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 594a0d35ed176..1e7f197435a67 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { idx } from '@kbn/elastic-idx'; import { Unionize, Overwrite } from 'utility-types'; import { ChartBase } from './types'; import { @@ -48,11 +47,12 @@ type GenericMetricsRequest = Overwrite< } >; -export function transformDataToMetricsChart< - TRequest extends GenericMetricsRequest ->(result: ESSearchResponse, chartBase: ChartBase) { +export function transformDataToMetricsChart( + result: ESSearchResponse, + chartBase: ChartBase +) { const { aggregations, hits } = result; - const timeseriesData = idx(aggregations, _ => _.timeseriesData); + const timeseriesData = aggregations?.timeseriesData; return { title: chartBase.title, @@ -60,7 +60,7 @@ export function transformDataToMetricsChart< yUnit: chartBase.yUnit, noHits: hits.total.value === 0, series: Object.keys(chartBase.series).map((seriesKey, i) => { - const overallValue = idx(aggregations, _ => _[seriesKey].value); + const overallValue = aggregations?.[seriesKey].value; return { title: chartBase.series[seriesKey].title, @@ -68,14 +68,15 @@ export function transformDataToMetricsChart< type: chartBase.type, color: chartBase.series[seriesKey].color || colors[i], overallValue, - data: (idx(timeseriesData, _ => _.buckets) || []).map(bucket => { - const { value } = bucket[seriesKey] as { value: number | null }; - const y = value === null || isNaN(value) ? null : value; - return { - x: bucket.key, - y - }; - }) + data: + timeseriesData?.buckets.map(bucket => { + const { value } = bucket[seriesKey] as { value: number | null }; + const y = value === null || isNaN(value) ? null : value; + return { + x: bucket.key, + y + }; + }) || [] }; }) }; 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 bbb18eae7eb09..9e070f936a25f 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 @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, SERVICE_AGENT_NAME, @@ -44,8 +43,6 @@ export async function getServiceAgentName(serviceName: string, setup: Setup) { }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agents.buckets[0].key) as - | string - | undefined; + const agentName = aggregations?.agents.buckets[0]?.key as string | undefined; return { agentName }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts index 88e9670f4b444..e93f6b4a1c17c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Setup } from '../helpers/setup_request'; import { HOST_NAME, @@ -55,11 +54,8 @@ export async function getServiceNodeMetadata({ const response = await client.search(query); return { - host: - idx(response, _ => _.aggregations.host.buckets[0].key) || - NOT_AVAILABLE_LABEL, + host: response.aggregations?.host.buckets[0].key || NOT_AVAILABLE_LABEL, containerId: - idx(response, _ => _.aggregations.containerId.buckets[0].key) || - NOT_AVAILABLE_LABEL + response.aggregations?.containerId.buckets[0].key || NOT_AVAILABLE_LABEL }; } 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 00ffa7484bb48..098342bf0221d 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 @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -40,7 +39,7 @@ export async function getServiceTransactionTypes( }; const { aggregations } = await client.search(params); - const buckets = idx(aggregations, _ => _.types.buckets) || []; - const transactionTypes = buckets.map(bucket => bucket.key as string); + const transactionTypes = + aggregations?.types.buckets.map(bucket => bucket.key as string) || []; return { transactionTypes }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index 60f3091567059..240a3bd35cbea 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { mergeProjection } from '../../../../common/projections/util/merge_projection'; import { PROCESSOR_EVENT, @@ -53,16 +52,20 @@ export async function getServicesItems(setup: Setup) { const resp = await client.search(params); const aggs = resp.aggregations; - const serviceBuckets = idx(aggs, _ => _.services.buckets) || []; + const serviceBuckets = aggs?.services.buckets || []; const items = serviceBuckets.map(bucket => { const eventTypes = bucket.events.buckets; const transactions = eventTypes.find(e => e.key === 'transaction'); - const totalTransactions = idx(transactions, _ => _.doc_count) || 0; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const totalTransactions = transactions?.doc_count || 0; const errors = eventTypes.find(e => e.key === 'error'); - const totalErrors = idx(errors, _ => _.doc_count) || 0; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const totalErrors = errors?.doc_count || 0; const deltaAsMinutes = (end - start) / 1000 / 60; const transactionsPerMinute = totalTransactions / deltaAsMinutes; @@ -75,9 +78,7 @@ export async function getServicesItems(setup: Setup) { return { serviceName: bucket.key as string, - agentName: idx(bucket, _ => _.agents.buckets[0].key) as - | string - | undefined, + agentName: bucket.agents.buckets[0]?.key as string | undefined, transactionsPerMinute, errorsPerMinute, avgResponseTime: bucket.avg.value, 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 21663b813f01f..83df9153e557b 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Setup } from '../../helpers/setup_request'; import { PROCESSOR_EVENT, @@ -49,7 +48,9 @@ export async function getAgentNameByService({ }; const { aggregations } = await client.search(params); - const agentName = idx(aggregations, _ => _.agent_names.buckets[0].key) as + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const agentName = aggregations?.agent_names.buckets[0].key as | string | undefined; return { agentName }; 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 8215e2b9fd668..52a3422f8e6b7 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Setup } from '../../../helpers/setup_request'; import { PROCESSOR_EVENT, @@ -57,7 +56,9 @@ export async function getAllEnvironments({ }; const resp = await client.search(params); - const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - const environments = buckets.map(bucket => bucket.key as string); + const environments = + resp.aggregations?.environments.buckets.map( + bucket => bucket.key as string + ) || []; return [ALL_OPTION_VALUE, ...environments]; } 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 52efc2b50305b..fc3b62738f8fe 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Setup } from '../../../helpers/setup_request'; import { SERVICE_NAME, @@ -43,6 +42,9 @@ export async function getExistingEnvironmentsForService({ }; const resp = await internalClient.search(params); - const buckets = idx(resp.aggregations, _ => _.environments.buckets) || []; - return buckets.map(bucket => bucket.key as string); + const existingEnvironments = + resp.aggregations?.environments.buckets.map( + bucket => bucket.key as string + ) || []; + return existingEnvironments; } 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 51a4564f53576..9b9acbb1e0ad5 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { Setup } from '../../helpers/setup_request'; import { PromiseReturnType } from '../../../../typings/common'; import { @@ -46,7 +45,9 @@ export async function getServiceNames({ setup }: { setup: Setup }) { }; const resp = await client.search(params); - const buckets = idx(resp.aggregations, _ => _.services.buckets) || []; - const serviceNames = buckets.map(bucket => bucket.key as string).sort(); + const serviceNames = + resp.aggregations?.services.buckets + .map(bucket => bucket.key as string) + .sort() || []; return [ALL_OPTION_VALUE, ...serviceNames]; } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts index 3ec64be08d117..58a952baa8233 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts @@ -5,7 +5,6 @@ */ import moment from 'moment'; -import { idx } from '@kbn/elastic-idx'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { @@ -54,7 +53,7 @@ export function transactionGroupsTransformer({ start: number; end: number; }): ITransactionGroup[] { - const buckets = idx(response, _ => _.aggregations.transactions.buckets) || []; + const buckets = response.aggregations?.transactions.buckets || []; const duration = moment.duration(end - start); const minutes = duration.asMinutes(); const transactionGroups = buckets.map(bucket => 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 3d425415de832..1c84c68fd8c47 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 @@ -5,7 +5,6 @@ */ import { flatten, sortByOrder, last } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { SERVICE_NAME, SPAN_SUBTYPE, @@ -149,7 +148,9 @@ export async function getTransactionBreakdown({ const kpiNames = kpis.map(kpi => kpi.name); - const bucketsByDate = idx(resp.aggregations, _ => _.by_date.buckets) || []; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const bucketsByDate = resp.aggregations?.by_date.buckets || []; const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => { const formattedValues = formatBucket(bucket); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index 27cff20b7ff37..dfa9e9b70abdb 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { getMlIndex } from '../../../../../common/ml_job_constants'; import { Setup } from '../../../helpers/setup_request'; @@ -50,7 +49,9 @@ export async function getMlBucketSize({ try { const resp = await client.search(params); - return idx(resp, _ => _.hits.hits[0]._source.bucket_span) || 0; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + return resp.hits.hits[0]?._source.bucket_span || 0; } catch (err) { const isHttpError = 'statusCode' in err; if (isHttpError) { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts index eab68a2bda974..83a7ffe1f2412 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { ESResponse } from './fetcher'; import { mlAnomalyResponse } from './mock-responses/mlAnomalyResponse'; import { anomalySeriesTransform, replaceFirstAndLastBucket } from './transform'; @@ -291,10 +290,12 @@ function getESResponse(buckets: any): ESResponse { buckets: buckets.map((bucket: any) => { return { ...bucket, - lower: { value: idx(bucket, _ => _.lower.value) || null }, - upper: { value: idx(bucket, _ => _.upper.value) || null }, + lower: { value: bucket?.lower?.value || null }, + upper: { value: bucket?.upper?.value || null }, anomaly_score: { - value: idx(bucket, _ => _.anomaly_score.value) || null + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + value: bucket?.anomaly_score?.value || null } }; }) diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts index 0cc283fb51d91..2994990067d00 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts @@ -5,7 +5,6 @@ */ import { first, last } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { Coordinate, RectCoordinate } from '../../../../../typings/timeseries'; import { ESResponse } from './fetcher'; @@ -32,9 +31,10 @@ export function anomalySeriesTransform( bucketSize: number, timeSeriesDates: number[] ) { - const buckets = ( - idx(response, _ => _.aggregations.ml_avg_response_times.buckets) || [] - ).map(getBucket); + const buckets = + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + response.aggregations?.ml_avg_response_times.buckets.map(getBucket) || []; const bucketSizeInMillis = Math.max(bucketSize, mlBucketSize) * 1000; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index 42828367f7941..1752258078add 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -5,7 +5,6 @@ */ import { isNumber, round, sortBy } from 'lodash'; -import { idx } from '@kbn/elastic-idx'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate } from '../../../../../typings/timeseries'; import { ESResponse } from './fetcher'; @@ -20,14 +19,16 @@ export function timeseriesTransformer({ bucketSize: number; }) { const aggs = timeseriesResponse.aggregations; - const overallAvgDuration = - idx(aggs, _ => _.overall_avg_duration.value) || null; - const responseTimeBuckets = idx(aggs, _ => _.response_times.buckets); + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const overallAvgDuration = aggs?.overall_avg_duration.value || null; + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const responseTimeBuckets = aggs?.response_times.buckets || []; const { avg, p95, p99 } = getResponseTime(responseTimeBuckets); - const transactionResultBuckets = idx( - aggs, - _ => _.transaction_results.buckets - ); + // TODO(TS-3.7-ESLINT) + // eslint-disable-next-line @typescript-eslint/camelcase + const transactionResultBuckets = aggs?.transaction_results.buckets || []; const tpmBuckets = getTpmBuckets(transactionResultBuckets, bucketSize); return { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts index 827194edd6aa3..a16e08138b87f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { PromiseReturnType } from '../../../../../typings/common'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { bucketFetcher } from './fetcher'; @@ -17,13 +16,14 @@ function getBucket( DistributionBucketResponse >['aggregations']['distribution']['buckets'][0] ) { - const sampleSource = idx(bucket, _ => _.sample.hits.hits[0]._source) as + const sampleSource = bucket.sample.hits.hits[0]?._source as | Transaction | undefined; - const isSampled = idx(sampleSource, _ => _.transaction.sampled); + + const isSampled = sampleSource?.transaction.sampled; const sample = { - traceId: idx(sampleSource, _ => _.trace.id), - transactionId: idx(sampleSource, _ => _.transaction.id) + traceId: sampleSource?.trace.id, + transactionId: sampleSource?.transaction.id }; return { @@ -34,9 +34,8 @@ function getBucket( } export function bucketTransformer(response: DistributionBucketResponse) { - const buckets = ( - idx(response.aggregations, _ => _.distribution.buckets) || [] - ).map(getBucket); + const buckets = + response.aggregations?.distribution.buckets.map(getBucket) || []; return { noHits: response.hits.total.value === 0, 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 20152ecf06480..652acf773e2e5 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, TRACE_ID, @@ -40,5 +39,5 @@ export async function getTransaction( }; const resp = await client.search(params); - return idx(resp, _ => _.hits.hits[0]._source); + return resp.hits.hits[0]?._source; } 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 ade491be32fc7..1b9e2ebd2e757 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 @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, SERVICE_ENVIRONMENT, @@ -55,7 +54,7 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { const resp = await client.search(params); const aggs = resp.aggregations; - const environmentsBuckets = idx(aggs, _ => _.environments.buckets) || []; + const environmentsBuckets = aggs?.environments.buckets || []; const environments = environmentsBuckets.map( environmentBucket => environmentBucket.key as string diff --git a/x-pack/legacy/plugins/apm/typings/common.d.ts b/x-pack/legacy/plugins/apm/typings/common.d.ts index d79b05ed99b49..b9064980bd657 100644 --- a/x-pack/legacy/plugins/apm/typings/common.d.ts +++ b/x-pack/legacy/plugins/apm/typings/common.d.ts @@ -27,3 +27,5 @@ export type PromiseReturnType = Func extends ( ) => Promise ? Value : Func; + +export type Maybe = T | null | undefined; diff --git a/x-pack/legacy/plugins/apm/typings/timeseries.ts b/x-pack/legacy/plugins/apm/typings/timeseries.ts index 9b9f7dcc2c820..d64486d8e71e9 100644 --- a/x-pack/legacy/plugins/apm/typings/timeseries.ts +++ b/x-pack/legacy/plugins/apm/typings/timeseries.ts @@ -3,10 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { Maybe } from '../typings/common'; export interface Coordinate { x: number; - y: number | null | undefined; + y: Maybe; } export interface RectCoordinate { diff --git a/x-pack/legacy/plugins/beats_management/public/utils/typed_react.ts b/x-pack/legacy/plugins/beats_management/public/utils/typed_react.ts index 5557befa9d7e5..dbc0894ab8071 100644 --- a/x-pack/legacy/plugins/beats_management/public/utils/typed_react.ts +++ b/x-pack/legacy/plugins/beats_management/public/utils/typed_react.ts @@ -43,7 +43,9 @@ export const asChildFunctionRenderer = ( } public render() { - return this.props.children(this.getRendererArgs()); + return (this.props.children as ChildFunctionRendererProps['children'])( + this.getRendererArgs() + ); } private getRendererArgs = () => diff --git a/x-pack/legacy/plugins/canvas/.storybook/addons.js b/x-pack/legacy/plugins/canvas/.storybook/addons.js index 41e0fb7f3965f..75bbe620c9e7b 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/addons.js +++ b/x-pack/legacy/plugins/canvas/.storybook/addons.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import '@storybook/addon-options/register'; import '@storybook/addon-actions/register'; import '@storybook/addon-knobs/register'; import '@storybook/addon-console'; diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js index b1310f75462c9..12f2195c067ca 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js @@ -30,8 +30,6 @@ module.exports = { '@storybook/addon-knobs', '@storybook/addon-knobs/react', '@storybook/addon-knobs/register', - '@storybook/addon-options', - '@storybook/addon-options/register', '@storybook/core', '@storybook/core/dist/server/common/polyfills.js', '@storybook/react', diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot index 5efbbe0106505..ef301c08cdfe8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot @@ -11,7 +11,7 @@ exports[`Storyshots arguments/AxisConfig extended 1`] = ` } >
- The axis is disabled +

+ Switch on to view axis settings +

diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx index 0ec722e370b40..832f953974af4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx @@ -71,7 +71,11 @@ export class ExtendedTemplate extends PureComponent { const isDisabled = typeof this.props.argValue === 'boolean' && this.props.argValue === false; if (isDisabled) { - return The axis is disabled; + return ( + +

{strings.getDisabledText()}

+
+ ); } const positions = { @@ -85,7 +89,7 @@ export class ExtendedTemplate extends PureComponent { return ( - + + { onChange={ev => setInputValue(ev.target.value)} /> - + - Set + {strings.getButtonSet()} setAddMode(!addMode)} flush="left"> - Cancel + {strings.getButtonCancel()} ); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index e8c433fb8752d..a3c327da2e4dc 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -130,6 +130,7 @@ class ImageUpload extends React.Component { idSelected={urlType} onChange={this.changeUrlType} isFullWidth + className="canvasSidebar__buttonGroup" /> ); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/string.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/string.js index d8a7188dfab28..dc31497a7da78 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/string.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/string.js @@ -26,7 +26,7 @@ const StringArgInput = ({ updateValue, value, confirm, commit, argId }) => ( /> {confirm && ( - + commit(value)}> {confirm} diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js index 462537e82b164..de19d3e29221b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js @@ -19,8 +19,14 @@ const ToggleArgInput = ({ onValueChange, argValue, argId, renderError }) => { return null; } return ( - - + + ); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/demodata.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/demodata.js index ec492f52747c1..193d99e1c9533 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/demodata.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/demodata.js @@ -5,28 +5,15 @@ */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; -import { ComponentStrings, CANVAS, DataSourceStrings } from '../../../i18n'; +import { DataSourceStrings } from '../../../i18n'; const { DemoData: strings } = DataSourceStrings; const DemodataDatasource = () => ( - -

{strings.getHeading()}

-

- {ComponentStrings.DatasourceDatasourceComponent.getChangeButtonLabel()} - ), - }} - /> -

+ +

{strings.getDescription()}

); @@ -34,7 +21,6 @@ export const demodata = () => ({ name: 'demodata', displayName: strings.getDisplayName(), help: strings.getHelp(), - // Replace this with a better icon when we have time. image: 'logoElasticStack', template: templateFromReactComponent(DemodataDatasource), }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js index 43f2fa63aff70..707f2305e1368 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js @@ -6,10 +6,10 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { EuiFormRow, EuiTextArea } from '@elastic/eui'; +import { EuiFormRow, EuiTextArea, EuiLink, EuiText } from '@elastic/eui'; import { getSimpleArg, setSimpleArg } from '../../../public/lib/arg_helpers'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; -import { DataSourceStrings } from '../../../i18n'; +import { DataSourceStrings, SQL_URL } from '../../../i18n'; const { Essql: strings } = DataSourceStrings; @@ -59,13 +59,24 @@ class EssqlDatasource extends PureComponent { const { isInvalid } = this.props; return ( - + + + {strings.getLabelAppend()} + +
+ } + >
); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/index.js index 107d4d241d2e7..13aa2a06306a0 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/index.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { timelion } from './timelion'; import { demodata } from './demodata'; import { essql } from './essql'; +import { timelion } from './timelion'; -export const datasourceSpecs = [timelion, demodata, essql]; +export const datasourceSpecs = [demodata, essql, timelion]; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js index 06efb6a791a2d..b30e43c1c3c57 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/datasources/timelion.js @@ -12,13 +12,13 @@ import { EuiCallOut, EuiSpacer, EuiCode, - EuiText, EuiTextArea, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { getSimpleArg, setSimpleArg } from '../../../public/lib/arg_helpers'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; import { DataSourceStrings, TIMELION, CANVAS } from '../../../i18n'; +import { TooltipIcon } from '../../../public/components/tooltip_icon'; const { Timelion: strings } = DataSourceStrings; @@ -57,43 +57,12 @@ const TimelionDatasource = ({ args, updateArgs, defaultIndex }) => { return (
- -

{TIMELION}

-

{strings.getAbout()}

-
- - - - - setArg(argName, e.target.value)} - /> - - { - // TODO: Time timelion interval picker should be a drop down - } - - setArg('interval', e.target.value)} - /> - - - - - +
  • {
  • {
+ + + + } + > + setArg(argName, e.target.value)} + rows={15} + /> + + { + // TODO: Time timelion interval picker should be a drop down + } + + setArg('interval', e.target.value)} + /> +
); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js index 213a2e0dd3b81..33cdb5541e172 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js @@ -16,6 +16,13 @@ export const metric = () => ({ modelArgs: [['_', { label: strings.getNumberDisplayName() }]], requiresContext: false, args: [ + { + name: 'metricFormat', + displayName: strings.getMetricFormatDisplayName(), + help: strings.getMetricFormatHelp(), + argType: 'numberFormat', + default: `"${AdvancedSettings.get('format:number:defaultPattern')}"`, + }, { name: '_', displayName: strings.getLabelDisplayName(), @@ -23,13 +30,6 @@ export const metric = () => ({ argType: 'string', default: '""', }, - { - name: 'labelFont', - displayName: strings.getLabelFontDisplayName(), - help: strings.getLabelFontHelp(), - argType: 'font', - default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`, - }, { name: 'metricFont', displayName: strings.getMetricFontDisplayName(), @@ -38,11 +38,11 @@ export const metric = () => ({ default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`, }, { - name: 'metricFormat', - displayName: strings.getMetricFormatDisplayName(), - help: strings.getMetricFormatHelp(), - argType: 'numberFormat', - default: `"${AdvancedSettings.get('format:number:defaultPattern')}"`, + name: 'labelFont', + displayName: strings.getLabelFontDisplayName(), + help: strings.getLabelFontHelp(), + argType: 'font', + default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`, }, ], }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/pie.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/pie.js index 4bb68973e80ea..783140b0c8b9e 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/pie.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/pie.js @@ -23,6 +23,16 @@ export const pie = () => ({ name: 'palette', argType: 'palette', }, + { + name: 'legend', + displayName: strings.getLegendDisplayName(), + help: strings.getLegendHelp(), + argType: 'select', + default: 'ne', + options: { + choices: legendOptions, + }, + }, { name: 'hole', displayName: strings.getHoleDisplayName(), @@ -34,13 +44,6 @@ export const pie = () => ({ max: 100, }, }, - { - name: 'labels', - displayName: strings.getLabelsDisplayName(), - help: strings.getLabelsHelp(), - argType: 'toggle', - default: true, - }, { name: 'labelRadius', displayName: strings.getLabelRadiusDisplayName(), @@ -52,16 +55,6 @@ export const pie = () => ({ max: 100, }, }, - { - name: 'legend', - displayName: strings.getLegendDisplayName(), - help: strings.getLegendHelp(), - argType: 'select', - default: 'ne', - options: { - choices: legendOptions, - }, - }, { name: 'radius', displayName: strings.getRadiusDisplayName(), @@ -69,6 +62,20 @@ export const pie = () => ({ argType: 'percentage', default: 1, }, + { + name: 'tilt', + displayName: strings.getTiltDisplayName(), + help: strings.getTiltHelp(), + argType: 'percentage', + default: 1, + }, + { + name: 'labels', + displayName: strings.getLabelsDisplayName(), + help: strings.getLabelsHelp(), + argType: 'toggle', + default: true, + }, { name: 'seriesStyle', argType: 'seriesStyle', @@ -78,13 +85,6 @@ export const pie = () => ({ name: 'font', argType: 'font', }, - { - name: 'tilt', - displayName: strings.getTiltDisplayName(), - help: strings.getTiltHelp(), - argType: 'percentage', - default: 1, - }, ], resolve({ context }) { if (getState(context) !== 'ready') { diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts index 1e6da888abf58..5b9f6f00940f4 100644 --- a/x-pack/legacy/plugins/canvas/i18n/components.ts +++ b/x-pack/legacy/plugins/canvas/i18n/components.ts @@ -228,11 +228,11 @@ export const ComponentStrings = { DatasourceDatasourceComponent: { getChangeButtonLabel: () => i18n.translate('xpack.canvas.datasourceDatasourceComponent.changeButtonLabel', { - defaultMessage: 'Change your data source', + defaultMessage: 'Change element data source', }), getPreviewButtonLabel: () => i18n.translate('xpack.canvas.datasourceDatasourceComponent.previewButtonLabel', { - defaultMessage: 'Preview', + defaultMessage: 'Preview data', }), getSaveButtonLabel: () => i18n.translate('xpack.canvas.datasourceDatasourceComponent.saveButtonLabel', { @@ -294,7 +294,7 @@ export const ComponentStrings = { }), getTitle: () => i18n.translate('xpack.canvas.elementConfig.title', { - defaultMessage: 'Elements', + defaultMessage: 'Element status', description: '"Elements" refers to the individual text, images, or visualizations that you can add to a Canvas workpad', }), @@ -581,7 +581,7 @@ export const ComponentStrings = { }), getBackgroundColorLabel: () => i18n.translate('xpack.canvas.pageConfig.backgroundColorLabel', { - defaultMessage: 'Background Color', + defaultMessage: 'Background', }), getNoTransitionDropDownOptionLabel: () => i18n.translate('xpack.canvas.pageConfig.transitions.noneDropDownOptionLabel', { @@ -592,7 +592,7 @@ export const ComponentStrings = { }), getTitle: () => i18n.translate('xpack.canvas.pageConfig.title', { - defaultMessage: 'Page', + defaultMessage: 'Page styles', }), getTransitionLabel: () => i18n.translate('xpack.canvas.pageConfig.transitionLabel', { @@ -1002,7 +1002,7 @@ export const ComponentStrings = { }), getTitle: () => i18n.translate('xpack.canvas.workpadConfig.title', { - defaultMessage: 'Workpad', + defaultMessage: 'Workpad settings', }), getUSLetterButtonLabel: () => i18n.translate('xpack.canvas.workpadConfig.USLetterButtonLabel', { diff --git a/x-pack/legacy/plugins/canvas/i18n/constants.ts b/x-pack/legacy/plugins/canvas/i18n/constants.ts index 3659c369ba0b6..8aee6ca148681 100644 --- a/x-pack/legacy/plugins/canvas/i18n/constants.ts +++ b/x-pack/legacy/plugins/canvas/i18n/constants.ts @@ -15,6 +15,7 @@ export const CSV = 'CSV'; export const DATEMATH = '`datemath`'; export const DATATABLE = '`datatable`'; export const ELASTICSEARCH = 'Elasticsearch'; +export const ELASTICSEARCH_SHORT = 'ES'; export const FONT_FAMILY = '`font-family`'; export const FONT_WEIGHT = '`font-weight`'; export const HEX = 'HEX'; @@ -32,6 +33,8 @@ export const PDF = 'PDF'; export const POST = 'POST'; export const RGB = 'RGB'; export const SQL = 'SQL'; +export const SQL_URL = + 'https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-spec.html'; export const SVG = 'SVG'; export const TIMELION = 'Timelion'; export const TINYMATH = '`TinyMath`'; diff --git a/x-pack/legacy/plugins/canvas/i18n/expression_types.ts b/x-pack/legacy/plugins/canvas/i18n/expression_types.ts index 6bc40a2758ab3..bdd190f26c97a 100644 --- a/x-pack/legacy/plugins/canvas/i18n/expression_types.ts +++ b/x-pack/legacy/plugins/canvas/i18n/expression_types.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { LUCENE } from './constants'; +import { LUCENE, ELASTICSEARCH } from './constants'; export const ArgTypesStrings = { Color: { @@ -101,13 +101,17 @@ export const ArgTypesStrings = { i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.colorLabel', { defaultMessage: 'Color', }), + getColorValueDefault: () => + i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.colorValueDefault', { + defaultMessage: 'Auto', + }), getStyleLabel: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.styleLabel', { defaultMessage: 'Style', }), getRemoveAriaLabel: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.removeAriaLabel', { - defaultMessage: 'Remove Series Color', + defaultMessage: 'Remove series color', }), getNoSeriesTooltip: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.noSeriesTooltip', { @@ -115,11 +119,11 @@ export const ArgTypesStrings = { }), getSeriesIdentifierLabel: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.seriesIdentifierLabel', { - defaultMessage: 'Series Identifier', + defaultMessage: 'Series id', }), getSelectSeriesOption: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.selectSeriesDropDown', { - defaultMessage: 'Select Series', + defaultMessage: 'Select series', }), getLineLabel: () => i18n.translate('xpack.canvas.expressionTypes.argTypes.seriesStyle.lineLabel', { @@ -152,15 +156,18 @@ export const ExpressionDataSourceStrings = { }), getWarningTitle: () => i18n.translate('xpack.canvas.expressionTypes.datasources.esdocs.warningTitle', { - defaultMessage: 'Be careful', + defaultMessage: 'Query with caution', }), getWarning: () => i18n.translate('xpack.canvas.expressionTypes.datasources.esdocs.warningDescription', { defaultMessage: ` - The Elasticsearch Docs datasource is used to pull documents directly from Elasticsearch + This datasource pulls directly from {elasticsearch} without the use of aggregations. It is best used with low volume datasets and in situations where you need to view raw documents or plot exact, non-aggregated values on a chart.`, + values: { + elasticsearch: ELASTICSEARCH, + }, }), getIndexTitle: () => i18n.translate('xpack.canvas.expressionTypes.datasources.esdocs.indexTitle', { diff --git a/x-pack/legacy/plugins/canvas/i18n/ui.ts b/x-pack/legacy/plugins/canvas/i18n/ui.ts index b65a666aa8809..323a6c97fd967 100644 --- a/x-pack/legacy/plugins/canvas/i18n/ui.ts +++ b/x-pack/legacy/plugins/canvas/i18n/ui.ts @@ -12,9 +12,9 @@ import { CANVAS, CSS, ELASTICSEARCH, + ELASTICSEARCH_SHORT, HEX, HTML, - KIBANA, LUCENE, MARKDOWN, MOMENTJS, @@ -35,6 +35,10 @@ export const ArgumentStrings = { i18n.translate('xpack.canvas.uis.arguments.axisConfigLabel', { defaultMessage: 'Visualization axis configuration', }), + getDisabledText: () => + i18n.translate('xpack.canvas.uis.arguments.axisConfigDisabledText', { + defaultMessage: 'Switch on to view axis settings', + }), getPositionBottom: () => i18n.translate('xpack.canvas.uis.arguments.axisConfig.position.options.bottomDropDown', { defaultMessage: 'bottom', @@ -124,6 +128,14 @@ export const ArgumentStrings = { i18n.translate('xpack.canvas.uis.arguments.filterGroup.createNewGroupLinkText', { defaultMessage: 'Create new group', }), + getButtonSet: () => + i18n.translate('xpack.canvas.uis.arguments.filterGroup.setValue', { + defaultMessage: 'Set', + }), + getButtonCancel: () => + i18n.translate('xpack.canvas.uis.arguments.filterGroup.cancelValue', { + defaultMessage: 'Cancel', + }), getDisplayName: () => i18n.translate('xpack.canvas.uis.arguments.filterGroupTitle', { defaultMessage: 'Filter Group', @@ -260,7 +272,7 @@ export const ArgumentStrings = { }), getHelp: () => i18n.translate('xpack.canvas.uis.arguments.shapeLabel', { - defaultMessage: 'Shape picker', + defaultMessage: 'Change the shape of the current element', }), }, String: { @@ -303,12 +315,20 @@ export const DataSourceStrings = { }), getHeading: () => i18n.translate('xpack.canvas.uis.dataSources.demoData.headingTitle', { - defaultMessage: 'You are using demo data', + defaultMessage: 'This element is using demo data', }), getHelp: () => i18n.translate('xpack.canvas.uis.dataSources.demoDataLabel', { defaultMessage: 'Mock data set with usernames, prices, projects, countries, and phases', }), + getDescription: () => + i18n.translate('xpack.canvas.uis.dataSources.demoDataDescription', { + defaultMessage: + 'By default, every {canvas} element is connected to the demo data source. Change the data source, above, to connect your own data.', + values: { + canvas: CANVAS, + }, + }), }, Essql: { getDisplayName: () => @@ -329,9 +349,13 @@ export const DataSourceStrings = { }), getLabel: () => i18n.translate('xpack.canvas.uis.dataSources.essql.queryTitle', { - defaultMessage: '{elasticsearch} {sql} query', + defaultMessage: 'Query', + }), + getLabelAppend: () => + i18n.translate('xpack.canvas.uis.dataSources.essql.queryTitleAppend', { + defaultMessage: 'Learn {elasticsearchShort} {sql} syntax', values: { - elasticsearch: ELASTICSEARCH, + elasticsearchShort: ELASTICSEARCH_SHORT, sql: SQL, }, }), @@ -340,10 +364,9 @@ export const DataSourceStrings = { getAbout: () => i18n.translate('xpack.canvas.uis.dataSources.timelion.aboutDetail', { defaultMessage: - '{canvas} integrates with {kibanaTimelion} application to allow you to use {timelion} queries to pull back timeseries data in a tabular format that can be used with {canvas} elements.', + 'Use {timelion} queries to pull back timeseries data that can be used with {canvas} elements.', values: { timelion: TIMELION, - kibanaTimelion: `${KIBANA}'s ${TIMELION}`, canvas: CANVAS, }, }), @@ -357,9 +380,8 @@ export const DataSourceStrings = { getIntervalHelp: () => i18n.translate('xpack.canvas.uis.dataSources.timelion.intervalLabel', { defaultMessage: - 'Accepts {elasticsearch} date math: {weeksExample}, {daysExample}, {secondsExample}, or {auto}', + 'Use date math like {weeksExample}, {daysExample}, {secondsExample}, or {auto}', values: { - elasticsearch: ELASTICSEARCH, secondsExample: '10s', daysExample: '5d', weeksExample: '1w', @@ -383,7 +405,11 @@ export const DataSourceStrings = { }), getTipsHeading: () => i18n.translate('xpack.canvas.uis.dataSources.timelion.tipsTitle', { - defaultMessage: 'Some tips', + defaultMessage: 'Tips for using {timelion} in {canvas}', + values: { + timelion: TIMELION, + canvas: CANVAS, + }, }), }, }; @@ -530,7 +556,7 @@ export const ViewStrings = { }), getValueDisplayName: () => i18n.translate('xpack.canvas.uis.views.dropdownControl.args.valueColumnTitle', { - defaultMessage: 'Values column', + defaultMessage: 'Value column', }), getValueHelp: () => i18n.translate('xpack.canvas.uis.views.dropdownControl.args.valueColumnLabel', { @@ -610,7 +636,7 @@ export const ViewStrings = { }), getNumberDisplayName: () => i18n.translate('xpack.canvas.uis.views.numberArgTitle', { - defaultMessage: 'Number', + defaultMessage: 'Value', }), getLabelDisplayName: () => i18n.translate('xpack.canvas.uis.views.metric.args.labelArgTitle', { @@ -618,7 +644,7 @@ export const ViewStrings = { }), getLabelFontDisplayName: () => i18n.translate('xpack.canvas.uis.views.metric.args.labelFontTitle', { - defaultMessage: 'Label text settings', + defaultMessage: 'Label text', }), getLabelFontHelp: () => i18n.translate('xpack.canvas.uis.views.metric.args.labelFontLabel', { @@ -626,11 +652,11 @@ export const ViewStrings = { }), getLabelHelp: () => i18n.translate('xpack.canvas.uis.views.metric.args.labelArgLabel', { - defaultMessage: 'Describes the metric', + defaultMessage: 'Enter a text label for the metric value', }), getMetricFontDisplayName: () => i18n.translate('xpack.canvas.uis.views.metric.args.metricFontTitle', { - defaultMessage: 'Metric text settings', + defaultMessage: 'Metric text', }), getMetricFontHelp: () => i18n.translate('xpack.canvas.uis.views.metric.args.metricFontLabel', { @@ -638,11 +664,11 @@ export const ViewStrings = { }), getMetricFormatDisplayName: () => i18n.translate('xpack.canvas.uis.views.metric.args.metricFormatTitle', { - defaultMessage: 'Metric Format', + defaultMessage: 'Format', }), getMetricFormatHelp: () => i18n.translate('xpack.canvas.uis.views.metric.args.metricFormatLabel', { - defaultMessage: 'Fonts, alignment and color', + defaultMessage: 'Select a format for the metric value', }), }, Pie: { @@ -676,7 +702,7 @@ export const ViewStrings = { }), getLegendDisplayName: () => i18n.translate('xpack.canvas.uis.views.pie.args.legendTitle', { - defaultMessage: 'Legend position', + defaultMessage: 'Legend', }), getLegendHelp: () => i18n.translate('xpack.canvas.uis.views.pie.args.legendLabel', { @@ -714,7 +740,7 @@ export const ViewStrings = { }), getLegendDisplayName: () => i18n.translate('xpack.canvas.uis.views.plot.args.legendTitle', { - defaultMessage: 'Legend position', + defaultMessage: 'Legend', }), getLegendHelp: () => i18n.translate('xpack.canvas.uis.views.plot.args.legendLabel', { @@ -744,7 +770,7 @@ export const ViewStrings = { }), getBarColorHelp: () => i18n.translate('xpack.canvas.uis.views.progress.args.barColorLabel', { - defaultMessage: 'Accepts HEX, RGB or HTML Color names', + defaultMessage: 'Accepts HEX, RGB or HTML color names', }), getBarWeightDisplayName: () => i18n.translate('xpack.canvas.uis.views.progress.args.barWeightTitle', { @@ -930,7 +956,7 @@ export const ViewStrings = { }), getBorderHelp: () => i18n.translate('xpack.canvas.uis.views.shape.args.borderLabel', { - defaultMessage: 'Accepts HEX, RGB or HTML Color names', + defaultMessage: 'Accepts HEX, RGB or HTML color names', }), getBorderWidthDisplayName: () => i18n.translate('xpack.canvas.uis.views.shape.args.borderWidthTitle', { @@ -950,22 +976,19 @@ export const ViewStrings = { }), getFillHelp: () => i18n.translate('xpack.canvas.uis.views.shape.args.fillLabel', { - defaultMessage: 'Accepts HEX, RGB or HTML Color names', + defaultMessage: 'Accepts HEX, RGB or HTML color names', }), getMaintainAspectDisplayName: () => i18n.translate('xpack.canvas.uis.views.shape.args.maintainAspectTitle', { - defaultMessage: 'Maintain aspect ratio', + defaultMessage: 'Fixed ratio', }), getMaintainAspectHelp: () => i18n.translate('xpack.canvas.uis.views.shape.args.maintainAspectLabel', { - defaultMessage: `Select '{true}' to maintain aspect ratio`, - values: { - true: BOOLEAN_TRUE, - }, + defaultMessage: `Enable to maintain aspect ratio`, }), getShapeDisplayName: () => i18n.translate('xpack.canvas.uis.views.shape.args.shapeTitle', { - defaultMessage: 'Select a shape', + defaultMessage: 'Select shape', }), }, Table: { @@ -988,7 +1011,7 @@ export const ViewStrings = { }), getPerPageDisplayName: () => i18n.translate('xpack.canvas.uis.views.table.args.perPageTitle', { - defaultMessage: 'Rows per page', + defaultMessage: 'Rows', }), getPerPageHelp: () => i18n.translate('xpack.canvas.uis.views.table.args.perPageLabel', { @@ -1022,7 +1045,7 @@ export const ViewStrings = { }), getFilterGroupDisplayName: () => i18n.translate('xpack.canvas.uis.views.timefilter.args.filterGroupTitle', { - defaultMessage: 'Filter group name', + defaultMessage: 'Filter group', }), getFilterGroupHelp: () => i18n.translate('xpack.canvas.uis.views.timefilter.args.filterGroupLabel', { diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss index f2cdb1444ef23..15676d2b02490 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss +++ b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.scss @@ -1,3 +1,7 @@ +.canvasArg__addArg { + margin-right: -$euiSizeS; +} + .canvasArg__addPopover { width: 250px; } diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index e771c978d491b..9cc6f870b9bde 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -33,6 +33,7 @@ export const ArgAddPopover = ({ options }: Props) => { iconType="plusInCircle" aria-label={strings.getAddAriaLabel()} onClick={handleClick} + className="canvasArg__addArg" /> ); diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_form.scss b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_form.scss index bef58d6bb6f5f..3fc220d41d551 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_form.scss +++ b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_form.scss @@ -8,6 +8,14 @@ .canvasSidebar__panel { .canvasArg--expandable:last-child { + .canvasArg__accordion { + margin-bottom: (-$euiSizeS); + } + + .canvasArg__accordion:after { + content: none; + } + .canvasArg__accordion.euiAccordion-isOpen:after { display: none; } @@ -15,12 +23,12 @@ } .canvasArg { - margin-top: $euiSize; - + margin-top: $euiSizeS; +} - .canvasArg--remove { - visibility: hidden; - } +.canvasArg__remove { + min-width: $euiSize; + padding: $euiSizeXS 0; } .canvasArg__content { @@ -29,47 +37,18 @@ .canvasArg__form { position: relative; - -} - -.canvasArg__form, -.canvasArg__accordion { - &:hover { - .canvasArg__remove { - opacity: 1; - visibility: visible; - } - } } .canvasArg__tooltip { margin-left: -$euiSizeXL; } -.canvasArg__remove { - position: absolute; - right: -$euiSizeL; - top: $euiSizeS - 2px; - border-radius: $euiBorderRadius; - border: $euiBorderThin; - background: $euiColorEmptyShade; - opacity: 0; - visibility: hidden; - transition: opacity $euiAnimSpeedNormal $euiAnimSlightResistance; - transition-delay: $euiAnimSpeedSlow; -} - .canvasArg__accordion { - padding: $euiSizeS $euiSize; - margin: 0 (-$euiSize); + padding: $euiSizeS $euiSizeM; + margin: 0 (-$euiSizeM); background: $euiColorLightestShade; position: relative; - // different spacing means leff shift - .canvasArg__remove { - right: -$euiSizeM; - } - // don't let remove button position here if this is nested in an accordion .canvasArg__form { position: static; @@ -97,3 +76,8 @@ bottom: 0; } } + +// this is a workaround since an EuiFormRow label cannot be passed in toggle.js +.canvasArg__switch { + padding-top: calc(#{$euiSizeS} * .75); +} diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_label.js b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_label.js index 143ce670d25f6..4324eed0892a5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_label.js +++ b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_label.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiFormRow, EuiAccordion, EuiText, EuiToolTip } from '@elastic/eui'; +import { EuiFormRow, EuiAccordion, EuiText, EuiToolTip, EuiIcon } from '@elastic/eui'; // This is what is being generated by render() from the Arg class. It is called in FunctionForm export const ArgLabel = props => { @@ -32,7 +32,17 @@ export const ArgLabel = props => { ) : ( simpleArg && ( - + + + {label} + + + } + id={argId} + > {simpleArg} ) diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_simple_form.tsx b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_simple_form.tsx index 5b45772c14373..846f912db6f84 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_simple_form.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/arg_form/arg_simple_form.tsx @@ -6,7 +6,7 @@ import React, { ReactNode, MouseEventHandler } from 'react'; import PropTypes from 'prop-types'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { TooltipIcon, IconType } from '../tooltip_icon'; import { ComponentStrings } from '../../../i18n'; @@ -41,13 +41,16 @@ export const ArgSimpleForm: React.FunctionComponent = ({ )} {!required && ( - + + + )} ); diff --git a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx index 842f80112ecbd..fb82af49e815e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx @@ -10,35 +10,41 @@ import { ColorDot } from '../color_dot'; storiesOf('components/Color/ColorDot', module) .addParameters({ info: { propTablesExclude: [EuiIcon] } }) - .add('color dots', () => [ - , - , - , - , - , - , - , - ]) - .add('invalid dots', () => [ - , - , - , - , - , - , - , - ]) - .add('color dots with children', () => [ - - - , - - - , - - - , - - - , - ]); + .add('color dots', () => ( + <> + + + + + + + + + )) + .add('invalid dots', () => ( + <> + + + + + + + + + )) + .add('color dots with children', () => ( + <> + + + + + + + + + + + + + + )); diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx index b83ffe70dc185..005a2541cbc2e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx @@ -57,48 +57,54 @@ storiesOf('components/Color/ColorManager', module) }, }, }) - .add('default', () => [ - , - , - , - , - , - , - , - ]) - .add('invalid colors', () => [ - , - , - , - , - , - , - , - ]) - .add('with buttons', () => [ - , - , - , - ]) + .add('default', () => ( + <> + + + + + + + + + )) + .add('invalid colors', () => ( + <> + + + + + + + + + )) + .add('with buttons', () => ( + <> + + + + + )) .add('interactive', () => , { info: { inline: true, diff --git a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx index 4c8ef7cb132df..14cd5e6d13b4f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx @@ -29,14 +29,18 @@ class Interactive extends React.Component<{}, { value: string }> { } storiesOf('components/Color/ColorPalette', module) - .add('three colors', () => [ - , - , - ]) - .add('six colors', () => [ - , - , - ]) + .add('three colors', () => ( + <> + + + + )) + .add('six colors', () => ( + <> + + + + )) .add('six colors, wrap at 4', () => ( )) diff --git a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource.scss b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource.scss index ee6c082db1217..2407dcbbce593 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource.scss +++ b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource.scss @@ -6,6 +6,31 @@ padding: 0 $euiSizeS; } +.canvasDataSource__section { + padding: $euiSizeM; +} + +.canvasDataSource__triggerButton { + @include euiTitle('xs'); + line-height: $euiSizeXXL; +} + +.canvasDataSource__triggerButtonIcon { + margin-right: $euiSizeS; +} + +.canvasDataSource__list { + padding: $euiSizeM; +} + +.canvasDataSource__card .euiCard__content { + padding-top: 0 !important; // sass-lint:disable-line no-important +} + .canvasDataSource__card + .canvasDataSource__card { margin-top: $euiSizeS; } + +.canvasDataSource__card--isCurrent { + border-color: $euiColorSecondary; +} diff --git a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_component.js index 5f235d4479171..8b0061e047f33 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_component.js +++ b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_component.js @@ -7,20 +7,23 @@ import React, { Fragment, PureComponent } from 'react'; import PropTypes from 'prop-types'; import { - EuiPanel, EuiFlexGroup, EuiFlexItem, EuiButton, - EuiButtonEmpty, EuiSpacer, + EuiIcon, + EuiCallOut, + EuiButtonEmpty, + EuiHorizontalRule, } from '@elastic/eui'; import { isEqual } from 'lodash'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings, DataSourceStrings } from '../../../i18n'; import { getDefaultIndex } from '../../lib/es_service'; import { DatasourceSelector } from './datasource_selector'; import { DatasourcePreview } from './datasource_preview'; const { DatasourceDatasourceComponent: strings } = ComponentStrings; +const { DemoData: demoDataStrings } = DataSourceStrings; export class DatasourceComponent extends PureComponent { static propTypes = { @@ -113,7 +116,13 @@ export class DatasourceComponent extends PureComponent { const { defaultIndex } = this.state; if (selecting) { - return ; + return ( + + ); } const datasourcePreview = previewing ? ( @@ -124,47 +133,51 @@ export class DatasourceComponent extends PureComponent { /> ) : null; + const datasourceRender = stateDatasource.render({ + args: stateArgs, + updateArgs, + datasourceDef, + isInvalid, + setInvalid, + defaultIndex, + }); + return ( - +
setSelecting(!selecting)} + className="canvasDataSource__triggerButton" + flush="left" + size="s" > - {strings.getChangeButtonLabel()} + + {stateDatasource.displayName} - {stateDatasource.render({ - args: stateArgs, - updateArgs, - datasourceDef, - isInvalid, - setInvalid, - defaultIndex, - })} - + {stateDatasource.name === 'demodata' ? ( + + {datasourceRender} + + ) : ( + datasourceRender + )} + - setPreviewing(true)} icon="check"> + setPreviewing(true)}> {strings.getPreviewButtonLabel()} - + - + {strings.getSaveButtonLabel()} - +
{datasourcePreview}
diff --git a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js index e6d2fe550a935..13cd2c5cd11f7 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js +++ b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js @@ -16,6 +16,7 @@ import { EuiPanel, EuiText, EuiEmptyPrompt, + EuiSpacer, } from '@elastic/eui'; import { Datatable } from '../../datatable'; import { Error } from '../../error'; @@ -31,21 +32,22 @@ export const DatasourcePreview = ({ done, datatable }) => ( {strings.getModalTitle()} - +

{datasourceStrings.getSaveButtonLabel()}, }} />

+ {datatable.type === 'error' ? ( ) : ( - + {datatable.rows.length > 0 ? ( ) : ( diff --git a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_selector.js b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_selector.js index 07df2a7007c4f..92f9b92cb1f06 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_selector.js +++ b/x-pack/legacy/plugins/canvas/public/components/datasource/datasource_selector.js @@ -8,17 +8,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiCard, EuiIcon } from '@elastic/eui'; -export const DatasourceSelector = ({ onSelect, datasources }) => ( -
+export const DatasourceSelector = ({ onSelect, datasources, current }) => ( +
{datasources.map(d => ( } - onClick={() => onSelect(d.name)} + titleElement="h5" + icon={} description={d.help} layout="horizontal" className="canvasDataSource__card" + selectable={{ + isSelected: d.name === current ? true : false, + onClick: () => onSelect(d.name), + }} /> ))}
@@ -27,4 +31,5 @@ export const DatasourceSelector = ({ onSelect, datasources }) => ( DatasourceSelector.propTypes = { onSelect: PropTypes.func.isRequired, datasources: PropTypes.array.isRequired, + current: PropTypes.string.isRequired, }; diff --git a/x-pack/legacy/plugins/canvas/public/components/datasource/no_datasource.js b/x-pack/legacy/plugins/canvas/public/components/datasource/no_datasource.js index caafa068c6b5b..f531ee1668aef 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datasource/no_datasource.js +++ b/x-pack/legacy/plugins/canvas/public/components/datasource/no_datasource.js @@ -6,18 +6,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiPanel, EuiText } from '@elastic/eui'; +import { EuiCallOut } from '@elastic/eui'; import { ComponentStrings } from '../../../i18n'; const { DatasourceNoDatasource: strings } = ComponentStrings; export const NoDatasource = () => ( - - -

{strings.getPanelTitle()}

+
+

{strings.getPanelDescription()}

- - +
+
); NoDatasource.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/datatable/datatable.scss b/x-pack/legacy/plugins/canvas/public/components/datatable/datatable.scss index daccfdff5d34b..bd11bff18e091 100644 --- a/x-pack/legacy/plugins/canvas/public/components/datatable/datatable.scss +++ b/x-pack/legacy/plugins/canvas/public/components/datatable/datatable.scss @@ -4,6 +4,7 @@ display: flex; flex-direction: column; justify-content: space-between; + font-size: $euiFontSizeS; .canvasDataTable__tableWrapper { @include euiScrollBar; @@ -12,7 +13,8 @@ overflow: auto; // removes white square in the scrollbar corner - &::-webkit-scrollbar-corner { // sass-lint:disable-line no-vendor-prefixes + // sass-lint:disable no-vendor-prefixes + &::-webkit-scrollbar-corner { background: transparent; } } @@ -21,6 +23,8 @@ width: 100%; display: flex; justify-content: space-around; + padding: $euiSizeS; + border-top: $euiBorderThin; } .canvasDataTable__table { @@ -30,7 +34,8 @@ .canvasDataTable__th, .canvasDataTable__td { text-align: left; - padding: $euiSizeS; + padding: $euiSizeS $euiSizeXS; + border-bottom: $euiBorderThin; } .canvasDataTable__th { diff --git a/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js b/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js index 76007994e56bf..5d710ef883548 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_config/element_config.js @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiAccordion, EuiText, EuiSpacer } from '@elastic/eui'; import PropTypes from 'prop-types'; -import React, { Fragment } from 'react'; +import React from 'react'; import { ComponentStrings } from '../../../i18n'; const { ElementConfig: strings } = ComponentStrings; @@ -20,26 +20,51 @@ export const ElementConfig = ({ elementStats }) => { const progress = total > 0 ? Math.round(((ready + error) / total) * 100) : 100; return ( - - -

{strings.getTitle()}

-
- - + + {strings.getTitle()} +
+ } + initialIsOpen={false} + > + + - + - + - + - + - + ); }; diff --git a/x-pack/legacy/plugins/canvas/public/components/es_field_select/es_field_select.js b/x-pack/legacy/plugins/canvas/public/components/es_field_select/es_field_select.js index 636d9b0006ac6..11c8ab88a4cba 100644 --- a/x-pack/legacy/plugins/canvas/public/components/es_field_select/es_field_select.js +++ b/x-pack/legacy/plugins/canvas/public/components/es_field_select/es_field_select.js @@ -28,6 +28,7 @@ export const ESFieldSelect = ({ value, fields = [], onChange, onFocus, onBlur }) onBlur={onBlur} singleSelection={{ asPlainText: true }} isClearable={false} + compressed /> ); }; diff --git a/x-pack/legacy/plugins/canvas/public/components/es_fields_select/es_fields_select.js b/x-pack/legacy/plugins/canvas/public/components/es_fields_select/es_fields_select.js index fedb4aba7d3d0..ca2cac5a64793 100644 --- a/x-pack/legacy/plugins/canvas/public/components/es_fields_select/es_fields_select.js +++ b/x-pack/legacy/plugins/canvas/public/components/es_fields_select/es_fields_select.js @@ -25,6 +25,7 @@ export const ESFieldsSelect = ({ selected, fields, onChange, onFocus, onBlur }) className="canvasFieldsSelect" onFocus={onFocus} onBlur={onBlur} + compressed /> ); }; diff --git a/x-pack/legacy/plugins/canvas/public/components/es_index_select/es_index_select.js b/x-pack/legacy/plugins/canvas/public/components/es_index_select/es_index_select.js index edc4506f20bda..8f1a4932a5e6c 100644 --- a/x-pack/legacy/plugins/canvas/public/components/es_index_select/es_index_select.js +++ b/x-pack/legacy/plugins/canvas/public/components/es_index_select/es_index_select.js @@ -32,6 +32,7 @@ export const ESIndexSelect = ({ value, loading, indices, onChange, onFocus, onBl singleSelection={{ asPlainText: true }} isClearable={false} onCreateOption={input => onChange(input || defaultIndex)} + compressed /> ); }; diff --git a/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js b/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js index 2586b4ec61f04..583bf1427aab1 100644 --- a/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/page_config/page_config.js @@ -6,7 +6,15 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { EuiCard, EuiFormRow, EuiTitle, EuiSpacer, EuiSelect } from '@elastic/eui'; +import { + EuiCard, + EuiFormRow, + EuiTitle, + EuiSpacer, + EuiSelect, + EuiToolTip, + EuiIcon, +} from '@elastic/eui'; import { WorkpadColorPicker } from '../workpad_color_picker'; import { ComponentStrings } from '../../../i18n'; @@ -22,14 +30,20 @@ export const PageConfig = ({ }) => { return ( - +

{strings.getTitle()}

- + + + {strings.getBackgroundColorLabel()}{' '} + + + + } > diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot index 426c07dac497e..a23452a43ae1e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot @@ -13,20 +13,24 @@ exports[`Storyshots components/Shapes/ShapePickerPopover default 1`] = `
- + +
`; @@ -44,27 +48,31 @@ exports[`Storyshots components/Shapes/ShapePickerPopover interactive 1`] = `
- + /> + +
`; @@ -82,27 +90,31 @@ exports[`Storyshots components/Shapes/ShapePickerPopover shape selected 1`] = `
- + /> + +
`; diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx index 472a14071208a..970f72da698ba 100644 --- a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx @@ -6,7 +6,7 @@ import React, { MouseEvent } from 'react'; import PropTypes from 'prop-types'; -import { EuiLink } from '@elastic/eui'; +import { EuiLink, EuiPanel } from '@elastic/eui'; import { Popover } from '../popover'; import { ShapePicker } from '../shape_picker'; import { ShapePreview } from '../shape_preview'; @@ -21,9 +21,11 @@ interface Props { export const ShapePickerPopover = ({ shapes, onChange, value }: Props) => { const button = (handleClick: (ev: MouseEvent) => void) => ( - - - + + + + + ); return ( diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot index dc80af01f121f..1655320700f87 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot @@ -2,13 +2,17 @@ exports[`Storyshots components/Sidebar/GroupSettings default 1`] = `
-

- Ungroup (U) to edit individual element settings. -

-

- Save this group as a new element to re-use it throughout your workpad. -

+
+

+ Ungroup (U) to edit individual element settings. +

+

+ Save this group as a new element to re-use it throughout your workpad. +

+
`; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot index b9b13ae36e730..49e804640081d 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot @@ -2,13 +2,17 @@ exports[`Storyshots components/Sidebar/MultiElementSettings default 1`] = `
-

- Multiple elements are currently selected. -

-

- Deselect these elements to edit their individual settings, press (G) to group them, or save this selection as a new element to re-use it throughout your workpad. -

+
+

+ Multiple elements are currently selected. +

+

+ Deselect these elements to edit their individual settings, press (G) to group them, or save this selection as a new element to re-use it throughout your workpad. +

+
`; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx index 6d884c05cd13a..74f4887601d30 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx @@ -6,7 +6,7 @@ import React, { FunctionComponent } from 'react'; import PropTypes from 'prop-types'; -import { EuiSpacer, EuiTabbedContent } from '@elastic/eui'; +import { EuiTabbedContent } from '@elastic/eui'; // @ts-ignore unconverted component import { Datasource } from '../../datasource'; // @ts-ignore unconverted component @@ -30,7 +30,6 @@ export const ElementSettings: FunctionComponent = ({ element }) => { name: strings.getDisplayTabLabel(), content: (
-
@@ -42,7 +41,6 @@ export const ElementSettings: FunctionComponent = ({ element }) => { name: strings.getDataTabLabel(), content: (
-
), diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/global_config.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/global_config.tsx index a5920ee197460..2e241681ccc6a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/global_config.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/global_config.tsx @@ -20,10 +20,10 @@ export const GlobalConfig: FunctionComponent = () => ( - + - + ); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx index b46465d9ec775..95d9035774a6a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx @@ -11,8 +11,10 @@ import { ComponentStrings } from '../../../i18n'; const { GroupSettings: strings } = ComponentStrings; export const GroupSettings: FunctionComponent = () => ( - -

{strings.getUngroupDescription()}

-

{strings.getSaveGroupDescription()}

-
+
+ +

{strings.getUngroupDescription()}

+

{strings.getSaveGroupDescription()}

+
+
); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx index 2de3a805c95e9..999c1c2daaf5b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx @@ -11,8 +11,10 @@ import { ComponentStrings } from '../../../i18n'; const { MultiElementSettings: strings } = ComponentStrings; export const MultiElementSettings: FunctionComponent = () => ( - -

{strings.getMultipleElementsDescription()}

-

{strings.getMultipleElementsActionsDescription()}

-
+
+ +

{strings.getMultipleElementsDescription()}

+

{strings.getMultipleElementsActionsDescription()}

+
+
); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar.scss b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar.scss index f9ce6f3cfb555..338d515165e43 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar.scss +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar.scss @@ -2,7 +2,6 @@ @include euiScrollBar; width: 100%; - padding: $euiSizeM; max-height: 100vh; overflow-y: auto; overflow-x: hidden; @@ -25,6 +24,15 @@ margin-bottom: $euiSizeS; } +.canvasSidebar__panel { + border-bottom: $euiBorderThin; + padding: $euiSizeS $euiSizeM; + + &--isEmpty { + border-bottom: none; + } +} + .canvasSidebar__panel-noMinWidth .euiButton { min-width: 0; } diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section.js b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section.js index 29ca72a9737a1..bf149a6c2acb8 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section.js +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section.js @@ -7,10 +7,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiPanel } from '@elastic/eui'; - export const SidebarSection = ({ children }) => ( - {children} +
{children}
); SidebarSection.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section_title.js b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section_title.js index 192786ae86a45..8e1522eae8dcc 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section_title.js +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/sidebar_section_title.js @@ -10,7 +10,7 @@ import { EuiTitle, EuiFlexItem, EuiFlexGroup, EuiToolTip } from '@elastic/eui'; export const SidebarSectionTitle = ({ title, tip, children }) => { const formattedTitle = ( - +

{title}

); diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss index 24453fcf0411e..92b0c50a6be4f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.scss @@ -1,3 +1,7 @@ .canvasLayout__sidebarHeader { - padding: ($euiSizeXS * .5) 0; + padding: calc(#{$euiSizeM} + 2px) $euiSizeS; } + +.canvasLayout__sidebarHeaderWorkpad { + padding: calc(#{$euiSizeS} * .75) 0; +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js b/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js index 9693540769d50..1a44181475091 100644 --- a/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js +++ b/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js @@ -105,22 +105,30 @@ export const TextStylePicker = ({ return (
+ + doChange('family', value)} /> + doChange('size', Number(e.target.value))} options={fontSizes.map(size => ({ text: String(size), value: size }))} + prepend="Size" /> - - doChange('family', value)} /> - - + - + + + doChange('color', value)} + colors={colors} + /> + @@ -138,13 +147,7 @@ export const TextStylePicker = ({ isIconOnly idSelected={align} onChange={onAlignmentChange} - /> - - - doChange('color', value)} - colors={colors} + className="canvasSidebar__buttonGroup" /> diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js b/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js index ec7386bddace6..7dfc378432b57 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_config/workpad_config.js @@ -67,9 +67,11 @@ export class WorkpadConfig extends PureComponent { return (
- -

{strings.getTitle()}

-
+
+ +

{strings.getTitle()}

+
+
@@ -129,37 +131,38 @@ export class WorkpadConfig extends PureComponent {
- - - - {strings.getGlobalCSSLabel()} - - - } - > -
- this.setState({ css: e.target.value })} - rows={10} - /> - - setWorkpadCSS(css || DEFAULT_WORKPAD_CSS)}> - {strings.getApplyStylesheetButtonLabel()} - - -
-
+
+ + + {strings.getGlobalCSSLabel()} + + + } + > +
+ this.setState({ css: e.target.value })} + rows={10} + /> + + setWorkpadCSS(css || DEFAULT_WORKPAD_CSS)}> + {strings.getApplyStylesheetButtonLabel()} + + +
+
+
); } diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot index 5525df639be01..ffe87129c76fa 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -12,7 +12,7 @@ exports[`Storyshots arguments/SeriesStyle extended 1`] = ` >
- Series Identifier + Series id
- Select Series + Select series