diff --git a/.browserslistrc b/.browserslistrc index 04395b913c9c54..36298c0f8cb93b 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -4,6 +4,8 @@ last 2 Chrome versions last 2 Safari versions > 0.25% not ie 11 +not op_mini all +not samsung 4 [dev] last 1 chrome versions diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 525da9d832b532..c179dbadac533b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,7 +39,6 @@ #CC# /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app #CC# /src/legacy/server/url_shortening/ @elastic/kibana-app #CC# /src/legacy/ui/public/state_management @elastic/kibana-app -#CC# /src/plugins/index_pattern_management/public @elastic/kibana-app # App Architecture /examples/bfetch_explorer/ @elastic/kibana-app-arch @@ -70,23 +69,10 @@ /x-pack/plugins/data_enhanced/ @elastic/kibana-app-arch /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-arch /x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana/public/management/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana/server/routes/api/management/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/status_page/public @elastic/kibana-app-arch -#CC# /src/legacy/server/index_patterns/ @elastic/kibana-app-arch -#CC# /src/legacy/ui/public/field_editor @elastic/kibana-app-arch -#CC# /src/legacy/ui/public/management @elastic/kibana-app-arch -#CC# /src/plugins/advanced_settings/ @elastic/kibana-app-arch #CC# /src/plugins/bfetch/ @elastic/kibana-app-arch -#CC# /src/plugins/charts/ @elastic/kibana-app-arch -#CC# /src/plugins/index_pattern_management/public/service @elastic/kibana-app-arch +#CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-arch #CC# /src/plugins/inspector/ @elastic/kibana-app-arch -#CC# /src/plugins/saved_objects/ @elastic/kibana-app-arch #CC# /src/plugins/share/ @elastic/kibana-app-arch -#CC# /src/plugins/vis_default_editor @elastic/kibana-app-arch #CC# /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch #CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-arch #CC# /packages/kbn-interpreter/ @elastic/kibana-app-arch @@ -171,8 +157,6 @@ /x-pack/test/functional/apps/maps/ @elastic/kibana-gis /x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis /x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis -#CC# /src/legacy/core_plugins/region_map @elastic/kibana-gis -#CC# /src/legacy/core_plugins/tile_map @elastic/kibana-gis #CC# /src/plugins/maps_legacy/ @elastic/kibana-gis #CC# /x-pack/plugins/file_upload @elastic/kibana-gis #CC# /x-pack/plugins/maps_legacy_licensing @elastic/kibana-gis @@ -245,6 +229,7 @@ #CC# /src/legacy/ui/public/documentation_links @elastic/kibana-platform #CC# /src/legacy/ui/public/autoload @elastic/kibana-platform #CC# /src/plugins/legacy_export/ @elastic/kibana-platform +#CC# /src/plugins/saved_objects/ @elastic/kibana-platform #CC# /src/plugins/status_page/ @elastic/kibana-platform #CC# /src/plugins/testbed/server/ @elastic/kibana-platform #CC# /x-pack/legacy/plugins/xpack_main/server/ @elastic/kibana-platform diff --git a/.github/ISSUE_TEMPLATE/Bug_report_security_solution.md b/.github/ISSUE_TEMPLATE/Bug_report_security_solution.md index 059e1d267c2862..7a0514bca621dd 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report_security_solution.md +++ b/.github/ISSUE_TEMPLATE/Bug_report_security_solution.md @@ -1,38 +1,38 @@ ---- -name: Bug report for Security Solution -about: Help us identify bugs in Elastic Security, SIEM, and Endpoint so we can fix them! -title: '[Security Solution]' -labels: 'Team: SecuritySolution' ---- - -**Describe the bug:** - -**Kibana/Elasticsearch Stack version:** - -**Server OS version:** - -**Browser and Browser OS versions:** - -**Elastic Endpoint version:** - -**Original install method (e.g. download page, yum, from source, etc.):** - -**Functional Area (e.g. Endpoint management, timelines, resolver, etc.):** - -**Steps to reproduce:** - -1. -2. -3. - -**Current behavior:** - -**Expected behavior:** - -**Screenshots (if relevant):** - -**Errors in browser console (if relevant):** - -**Provide logs and/or server output (if relevant):** - -**Any additional context (logs, chat logs, magical formulas, etc.):** +--- +name: Bug report for Security Solution +about: Help us identify bugs in Elastic Security, SIEM, and Endpoint so we can fix them! +title: '[Security Solution]' +labels: 'bug, Team: SecuritySolution' +--- + +**Describe the bug:** + +**Kibana/Elasticsearch Stack version:** + +**Server OS version:** + +**Browser and Browser OS versions:** + +**Elastic Endpoint version:** + +**Original install method (e.g. download page, yum, from source, etc.):** + +**Functional Area (e.g. Endpoint management, timelines, resolver, etc.):** + +**Steps to reproduce:** + +1. +2. +3. + +**Current behavior:** + +**Expected behavior:** + +**Screenshots (if relevant):** + +**Errors in browser console (if relevant):** + +**Provide logs and/or server output (if relevant):** + +**Any additional context (logs, chat logs, magical formulas, etc.):** diff --git a/.github/ISSUE_TEMPLATE/v8_breaking_change.md b/.github/ISSUE_TEMPLATE/v8_breaking_change.md index c91b937586a09f..42783808e32ed3 100644 --- a/.github/ISSUE_TEMPLATE/v8_breaking_change.md +++ b/.github/ISSUE_TEMPLATE/v8_breaking_change.md @@ -7,6 +7,16 @@ assignees: '' --- + + ## Change description **Which release will ship the breaking change?** diff --git a/docs/api/saved-objects/rotate_encryption_key.asciidoc b/docs/api/saved-objects/rotate_encryption_key.asciidoc index 0a66ed2b4b3610..efc57ddb4308d6 100644 --- a/docs/api/saved-objects/rotate_encryption_key.asciidoc +++ b/docs/api/saved-objects/rotate_encryption_key.asciidoc @@ -25,7 +25,7 @@ Bulk key rotation can consume a considerable amount of resources and hence only `type`:: (Optional, string) Limits encryption key rotation only to the saved objects with the specified type. By default, {kib} tries to rotate the encryption key for all saved object types that may contain encrypted attributes. -`batchSize`:: +`batch_size`:: (Optional, number) Specifies a maximum number of saved objects that {kib} can process in a single batch. Bulk key rotation is an iterative process since {kib} may not be able to fetch and process all required saved objects in one go and splits processing into consequent batches. By default, the batch size is 10000, which is also a maximum allowed value. [[saved-objects-api-rotate-encryption-key-response-body]] @@ -91,7 +91,7 @@ In this example, key rotation is performed for all saved objects with the `alert [source,sh] -------------------------------------------------- -$ curl -X POST /api/encrypted_saved_objects/_rotate_key?type=alert&batchSize=5000 +$ curl -X POST /api/encrypted_saved_objects/_rotate_key?type=alert&batch_size=5000 -------------------------------------------------- // KIBANA diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 8e08c3806446d0..d0808d73151b0b 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -22,8 +22,10 @@ NOTE: |Description -|{kib-repo}blob/{branch}/src/plugins/advanced_settings[advancedSettings] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/advanced_settings/README.md[advancedSettings] +|This plugin contains the advanced settings management section +allowing users to configure their advanced settings, also known +as uiSettings within the code. |{kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss] @@ -38,8 +40,8 @@ NOTE: |The Charts plugin is a way to create easier integration of shared colors, themes, types and other utilities across all Kibana charts and visualizations. -|{kib-repo}blob/{branch}/src/plugins/console[console] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/console/README.md[console] +|Console provides the user with tools for storing and executing requests against Elasticsearch. |<> @@ -130,12 +132,14 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |WARNING: Missing README. -|{kib-repo}blob/{branch}/src/plugins/management[management] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/management/README.md[management] +|This plugins contains the "Stack Management" page framework. It offers navigation and an API +to link individual managment section into it. This plugin does not contain any individual +management section itself. -|{kib-repo}blob/{branch}/src/plugins/maps_legacy[mapsLegacy] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/maps_legacy/README.md[mapsLegacy] +|Internal objects used by the Coordinate, Region, and Vega visualizations. |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] @@ -147,8 +151,8 @@ It also provides a stateful version of it on the start contract. |WARNING: Missing README. -|{kib-repo}blob/{branch}/src/plugins/region_map[regionMap] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/region_map/README.md[regionMap] +|Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states. |{kib-repo}blob/{branch}/src/plugins/saved_objects[savedObjects] @@ -180,8 +184,8 @@ so they can properly protect the data within their clusters. |This plugin adds the Advanced Settings section for the Usage and Security Data collection (aka Telemetry). -|{kib-repo}blob/{branch}/src/plugins/tile_map[tileMap] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/tile_map/README.md[tileMap] +|Create a coordinate map. Display the results of a geohash_tile aggregation as bubbles, rectangles, or heatmap color blobs. |{kib-repo}blob/{branch}/src/plugins/timelion/README.md[timelion] @@ -307,8 +311,8 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error |WARNING: Missing README. -|{kib-repo}blob/{branch}/x-pack/plugins/console_extensions[consoleExtensions] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/console_extensions/README.md[consoleExtensions] +|This plugin provides autocomplete definitions of licensed APIs to the OSS Console plugin. |{kib-repo}blob/{branch}/x-pack/plugins/cross_cluster_replication/README.md[crossClusterReplication] @@ -323,8 +327,8 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error |The deprecated dashboard only mode. -|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced[dataEnhanced] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced/README.md[dataEnhanced] +|The data_enhanced plugin is the x-pack counterpart to the OSS data plugin. |{kib-repo}blob/{branch}/x-pack/plugins/discover_enhanced/README.md[discoverEnhanced] @@ -355,8 +359,8 @@ and actions. |WARNING: Missing README. -|{kib-repo}blob/{branch}/x-pack/plugins/file_upload[fileUpload] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/file_upload/README.md[fileUpload] +|Backend and core front-end react-components for GeoJson file upload. Only supports the Maps plugin. |{kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] @@ -376,8 +380,9 @@ or dashboards from the Kibana instance, from both server and client-side plugins |This is the main source folder of the Graph plugin. It contains all of the Kibana server and client source code. x-pack/test/functional/apps/graph contains additional functional tests. -|{kib-repo}blob/{branch}/x-pack/plugins/grokdebugger[grokdebugger] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/grokdebugger/README.md[grokdebugger] +|This plugin helps users define Grok patterns, +which are particularly useful for ingesting logs. |{kib-repo}blob/{branch}/x-pack/plugins/index_lifecycle_management/README.md[indexLifecycleManagement] @@ -406,8 +411,8 @@ the infrastructure monitoring use-case within Kibana. |Run all tests from the x-pack root directory -|{kib-repo}blob/{branch}/x-pack/plugins/license_management[licenseManagement] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/license_management/README.md[licenseManagement] +|This plugin enables users to activate a trial license, downgrade to Basic, and upload a new license. |{kib-repo}blob/{branch}/x-pack/plugins/licensing/README.md[licensing] @@ -444,12 +449,12 @@ Elastic. |This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. -|{kib-repo}blob/{branch}/x-pack/plugins/painless_lab[painlessLab] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/painless_lab/README.md[painlessLab] +|This plugin helps users learn how to use the Painless scripting language. -|{kib-repo}blob/{branch}/x-pack/plugins/remote_clusters[remoteClusters] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/remote_clusters/README.md[remoteClusters] +|This plugin helps users manage their remote clusters, which enable cross-cluster search and cross-cluster replication. |{kib-repo}blob/{branch}/x-pack/plugins/reporting/README.md[reporting] @@ -460,8 +465,11 @@ Elastic. |Welcome to the Kibana rollup plugin! This plugin provides Kibana support for Elasticsearch's rollup feature. Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs. -|{kib-repo}blob/{branch}/x-pack/plugins/searchprofiler[searchprofiler] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/searchprofiler/README.md[searchprofiler] +|The search profiler consumes the Profile API +by sending a search API with profile: true enabled in the request body. The response contains +detailed information on how Elasticsearch executed the search request. People use this information +to understand why a search request might be slow. |{kib-repo}blob/{branch}/x-pack/plugins/security/README.md[security] @@ -488,8 +496,8 @@ the alertTypes by the Stack in the alerting plugin, register associated HTTP routes, etc. -|{kib-repo}blob/{branch}/x-pack/plugins/task_manager[taskManager] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/task_manager/README.md[taskManager] +|The task manager is a generic system for running background tasks. |{kib-repo}blob/{branch}/x-pack/plugins/telemetry_collection_xpack/README.md[telemetryCollectionXpack] @@ -513,8 +521,9 @@ As a developer you can reuse and extend built-in alerts and actions UI functiona |Registers commercially licensed generic actions like per panel time range and contains some code that supports drilldown work. -|{kib-repo}blob/{branch}/x-pack/plugins/upgrade_assistant[upgradeAssistant] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/upgrade_assistant/README.md[upgradeAssistant] +|Upgrade Assistant helps users prepare their Stack for being upgraded to the next major. Its primary +purposes are to: |{kib-repo}blob/{branch}/x-pack/plugins/uptime/README.md[uptime] diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md new file mode 100644 index 00000000000000..dfe25c5c9e42d0 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getBreadcrumbsAppendExtension$](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md) + +## ChromeStart.getBreadcrumbsAppendExtension$() method + +Get an observable of the current extension appended to breadcrumbs + +Signature: + +```typescript +getBreadcrumbsAppendExtension$(): Observable; +``` +Returns: + +`Observable` + diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md index 2594848ef0847f..663b326193de56 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md @@ -55,6 +55,7 @@ core.chrome.setHelpExtension(elem => { | [getBadge$()](./kibana-plugin-core-public.chromestart.getbadge_.md) | Get an observable of the current badge | | [getBrand$()](./kibana-plugin-core-public.chromestart.getbrand_.md) | Get an observable of the current brand information. | | [getBreadcrumbs$()](./kibana-plugin-core-public.chromestart.getbreadcrumbs_.md) | Get an observable of the current list of breadcrumbs | +| [getBreadcrumbsAppendExtension$()](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md) | Get an observable of the current extension appended to breadcrumbs | | [getCustomNavLink$()](./kibana-plugin-core-public.chromestart.getcustomnavlink_.md) | Get an observable of the current custom nav link | | [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent | | [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. | @@ -64,6 +65,7 @@ core.chrome.setHelpExtension(elem => { | [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge | | [setBrand(brand)](./kibana-plugin-core-public.chromestart.setbrand.md) | Set the brand configuration. | | [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-core-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs | +| [setBreadcrumbsAppendExtension(breadcrumbsAppendExtension)](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) | Mount an element next to the last breadcrumb | | [setCustomNavLink(newCustomNavLink)](./kibana-plugin-core-public.chromestart.setcustomnavlink.md) | Override the current set of custom nav link | | [setHelpExtension(helpExtension)](./kibana-plugin-core-public.chromestart.sethelpextension.md) | Override the current set of custom help content | | [setHelpSupportUrl(url)](./kibana-plugin-core-public.chromestart.sethelpsupporturl.md) | Override the default support URL shown in the help menu | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md new file mode 100644 index 00000000000000..02adb9b4d325d5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [setBreadcrumbsAppendExtension](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) + +## ChromeStart.setBreadcrumbsAppendExtension() method + +Mount an element next to the last breadcrumb + +Signature: + +```typescript +setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| breadcrumbsAppendExtension | ChromeBreadcrumbsAppendExtension | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md index 1134994faa9bd3..4129662acb2b1d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.md @@ -30,9 +30,9 @@ export declare class KibanaRequestboolean | Whether or not the request is a "system request" rather than an application-level request. Can be set on the client using the HttpFetchOptions#asSystemRequest option. | | [params](./kibana-plugin-core-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-core-server.kibanarequest.query.md) | | Query | | -| [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) | | Url | URL rewritten in onPreRouting request interceptor. | +| [rewrittenUrl](./kibana-plugin-core-server.kibanarequest.rewrittenurl.md) | | URL | URL rewritten in onPreRouting request interceptor. | | [route](./kibana-plugin-core-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-core-server.kibanarequest.socket.md) | | IKibanaSocket | [IKibanaSocket](./kibana-plugin-core-server.ikibanasocket.md) | -| [url](./kibana-plugin-core-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | +| [url](./kibana-plugin-core-server.kibanarequest.url.md) | | URL | a WHATWG URL standard object. | | [uuid](./kibana-plugin-core-server.kibanarequest.uuid.md) | | string | A UUID to identify this request. | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md index 10628bafaf1d41..fb547330ee6ea1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.rewrittenurl.md @@ -9,5 +9,5 @@ URL rewritten in onPreRouting request interceptor. Signature: ```typescript -readonly rewrittenUrl?: Url; +readonly rewrittenUrl?: URL; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md index 31d13481972017..b72760e272bb21 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanarequest.url.md @@ -9,5 +9,5 @@ a WHATWG URL standard object. Signature: ```typescript -readonly url: Url; +readonly url: URL; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md index bbf856480aedd2..b2f8e83d8e6545 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md @@ -17,6 +17,6 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | AggsSetup | | -| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | session management | +| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | SearchUsageCollector | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md index 7f39d9714a3a31..739fdfdeb5fc36 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md @@ -4,7 +4,7 @@ ## ISearchSetup.session property -session management +session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md index 4a69e94dd6f588..dba60c7bdf147d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md @@ -19,6 +19,6 @@ export interface ISearchStart | [aggs](./kibana-plugin-plugins-data-public.isearchstart.aggs.md) | AggsStart | agg config sub service [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | | [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | ISearchGeneric | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | ISearchStartSearchSource | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | -| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | session management | +| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | (e: Error) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md index de25cccd6d27a4..1ad194a9bec868 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md @@ -4,7 +4,7 @@ ## ISearchStart.session property -session management +session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md new file mode 100644 index 00000000000000..fc3d214eb4cad1 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) + +## ISessionService.clear property + +Clears the active session. + +Signature: + +```typescript +clear: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md new file mode 100644 index 00000000000000..e30c89fb1a9fdf --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) + +## ISessionService.getSession$ property + +Returns the observable that emits an update every time the session ID changes + +Signature: + +```typescript +getSession$: () => Observable; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md new file mode 100644 index 00000000000000..838023ff1d8b90 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) + +## ISessionService.getSessionId property + +Returns the active session ID + +Signature: + +```typescript +getSessionId: () => string | undefined; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md new file mode 100644 index 00000000000000..174f9dbe66bf4c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) + +## ISessionService interface + +Signature: + +```typescript +export interface ISessionService +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | +| [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | +| [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => void | Restores existing session | +| [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md new file mode 100644 index 00000000000000..857e85bbd30eb5 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) + +## ISessionService.restore property + +Restores existing session + +Signature: + +```typescript +restore: (sessionId: string) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md new file mode 100644 index 00000000000000..9e14c5ed267658 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) + +## ISessionService.start property + +Starts a new session + +Signature: + +```typescript +start: () => string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 6a3c437305cc83..ac6923fd12f96a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -74,6 +74,7 @@ | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | | [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | search service | | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | +| [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md index d1d97d50f5948a..f36f7b4ee77a4c 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md @@ -19,5 +19,6 @@ export declare type EmbeddableInput = { timeRange?: TimeRange; query?: Query; filters?: Filter[]; + searchSessionId?: string; }; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md new file mode 100644 index 00000000000000..62610624655a14 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) + +## isContextMenuTriggerContext variable + +Signature: + +```typescript +isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index df67eda5074b9f..06f792837e4fec 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -77,6 +77,7 @@ | [contextMenuTrigger](./kibana-plugin-plugins-embeddable-public.contextmenutrigger.md) | | | [defaultEmbeddableFactoryProvider](./kibana-plugin-plugins-embeddable-public.defaultembeddablefactoryprovider.md) | | | [EmbeddableRenderer](./kibana-plugin-plugins-embeddable-public.embeddablerenderer.md) | Helper react component to render an embeddable Can be used if you have an embeddable object or an embeddable factory Supports updating input by passing input prop | +| [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) | | | [isRangeSelectTriggerContext](./kibana-plugin-plugins-embeddable-public.israngeselecttriggercontext.md) | | | [isValueClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isvalueclicktriggercontext.md) | | | [PANEL\_BADGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_badge_trigger.md) | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md index aefd04112dc1ca..3cc38a0cbdc0fd 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md @@ -39,6 +39,8 @@ export declare class Executor = Record + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrate](./kibana-plugin-plugins-expressions-public.executor.migrate.md) + +## Executor.migrate() method + +Signature: + +```typescript +migrate(ast: SerializableState, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | SerializableState | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md new file mode 100644 index 00000000000000..23b7e6035a0aeb --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.executor.migratetolatest.md) + +## Executor.migrateToLatest() method + +Signature: + +```typescript +migrateToLatest(ast: unknown, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | unknown | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md index 1815d63d804b1b..3e75e9ab3ef6f8 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md @@ -29,6 +29,7 @@ export declare class ExpressionFunction implements PersistableStatestring | A short help text. | | [inject](./kibana-plugin-plugins-expressions-public.expressionfunction.inject.md) | | (state: ExpressionAstFunction['arguments'], references: SavedObjectReference[]) => ExpressionAstFunction['arguments'] | | | [inputTypes](./kibana-plugin-plugins-expressions-public.expressionfunction.inputtypes.md) | | string[] | undefined | Type of inputs that this function supports. | +| [migrations](./kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md) | | {
[key: string]: (state: SerializableState) => SerializableState;
} | | | [name](./kibana-plugin-plugins-expressions-public.expressionfunction.name.md) | | string | Name of function | | [telemetry](./kibana-plugin-plugins-expressions-public.expressionfunction.telemetry.md) | | (state: ExpressionAstFunction['arguments'], telemetryData: Record<string, any>) => Record<string, any> | | | [type](./kibana-plugin-plugins-expressions-public.expressionfunction.type.md) | | string | Return type of function. This SHOULD be supplied. We use it for UI and autocomplete hinting. We may also use it for optimizations in the future. | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md new file mode 100644 index 00000000000000..28d521f4b3fe19 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunction](./kibana-plugin-plugins-expressions-public.expressionfunction.md) > [migrations](./kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md) + +## ExpressionFunction.migrations property + +Signature: + +```typescript +migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md new file mode 100644 index 00000000000000..cce7a463d15616 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) + +## ExpressionFunctionDefinitions.derivative property + +Signature: + +```typescript +derivative: ExpressionFunctionDerivative; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md index d1703a1e019e68..0e180d1fabe390 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md @@ -18,6 +18,7 @@ export interface ExpressionFunctionDefinitions | --- | --- | --- | | [clog](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.clog.md) | ExpressionFunctionClog | | | [cumulative\_sum](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | +| [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md index 041d66b22dd50b..307fc73ec6e9c1 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md @@ -39,6 +39,8 @@ export declare class ExpressionsService implements PersistableStateExpressionsServiceStart['getType'] | | | [getTypes](./kibana-plugin-plugins-expressions-public.expressionsservice.gettypes.md) | | () => ReturnType<Executor['getTypes']> | Returns POJO map of all registered expression types, where keys are names of the types and values are ExpressionType instances. | | [inject](./kibana-plugin-plugins-expressions-public.expressionsservice.inject.md) | | (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression | Injects saved object references into expression AST | +| [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) | | (state: SerializableState, version: string) => ExpressionAstExpression | Injects saved object references into expression AST | +| [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) | | (state: unknown, version: string) => ExpressionAstExpression | Injects saved object references into expression AST | | [registerFunction](./kibana-plugin-plugins-expressions-public.expressionsservice.registerfunction.md) | | (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void | Register an expression function, which will be possible to execute as part of the expression pipeline.Below we register a function which simply sleeps for given number of milliseconds to delay the execution and outputs its input as-is. ```ts expressions.registerFunction({ diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md new file mode 100644 index 00000000000000..88a6bda4ee3f58 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) + +## ExpressionsService.migrate property + +Injects saved object references into expression AST + +Signature: + +```typescript +readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md new file mode 100644 index 00000000000000..e6860df19fd3f6 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) + +## ExpressionsService.migrateToLatest property + +Injects saved object references into expression AST + +Signature: + +```typescript +readonly migrateToLatest: (state: unknown, version: string) => ExpressionAstExpression; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md index 66c2e1e3c0c8d0..32a7151578658c 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md @@ -7,5 +7,5 @@ Signature: ```typescript -ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element +ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md new file mode 100644 index 00000000000000..3f7eb12fbb7a87 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ReactExpressionRendererProps](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) > [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) + +## ReactExpressionRendererProps.debounce property + +Signature: + +```typescript +debounce?: number; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md index 5622516530edd8..e4980ce04b9e27 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md @@ -16,6 +16,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams | --- | --- | --- | | [className](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.classname.md) | string | | | [dataAttrs](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.dataattrs.md) | string[] | | +| [debounce](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.debounce.md) | number | | | [expression](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.expression.md) | string | ExpressionAstExpression | | | [onEvent](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.onevent.md) | (event: ExpressionRendererEvent) => void | | | [padding](./kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.padding.md) | 'xs' | 's' | 'm' | 'l' | 'xl' | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md index 97bb3ac895084d..da20ae4aa892ee 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md @@ -39,6 +39,8 @@ export declare class Executor = Record + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrate](./kibana-plugin-plugins-expressions-server.executor.migrate.md) + +## Executor.migrate() method + +Signature: + +```typescript +migrate(ast: SerializableState, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | SerializableState | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md new file mode 100644 index 00000000000000..72e3f8d8b7edc5 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-server.executor.migratetolatest.md) + +## Executor.migrateToLatest() method + +Signature: + +```typescript +migrateToLatest(ast: unknown, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | unknown | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md index 7fcda94968d132..00c8aa63bfbd82 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md @@ -29,6 +29,7 @@ export declare class ExpressionFunction implements PersistableStatestring | A short help text. | | [inject](./kibana-plugin-plugins-expressions-server.expressionfunction.inject.md) | | (state: ExpressionAstFunction['arguments'], references: SavedObjectReference[]) => ExpressionAstFunction['arguments'] | | | [inputTypes](./kibana-plugin-plugins-expressions-server.expressionfunction.inputtypes.md) | | string[] | undefined | Type of inputs that this function supports. | +| [migrations](./kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md) | | {
[key: string]: (state: SerializableState) => SerializableState;
} | | | [name](./kibana-plugin-plugins-expressions-server.expressionfunction.name.md) | | string | Name of function | | [telemetry](./kibana-plugin-plugins-expressions-server.expressionfunction.telemetry.md) | | (state: ExpressionAstFunction['arguments'], telemetryData: Record<string, any>) => Record<string, any> | | | [type](./kibana-plugin-plugins-expressions-server.expressionfunction.type.md) | | string | Return type of function. This SHOULD be supplied. We use it for UI and autocomplete hinting. We may also use it for optimizations in the future. | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md new file mode 100644 index 00000000000000..29031a9306b2f8 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunction](./kibana-plugin-plugins-expressions-server.expressionfunction.md) > [migrations](./kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md) + +## ExpressionFunction.migrations property + +Signature: + +```typescript +migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md new file mode 100644 index 00000000000000..6c51f1eb977509 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) + +## ExpressionFunctionDefinitions.derivative property + +Signature: + +```typescript +derivative: ExpressionFunctionDerivative; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md index 05b4ddce4ccdec..d4b71a36e0de17 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md @@ -18,6 +18,7 @@ export interface ExpressionFunctionDefinitions | --- | --- | --- | | [clog](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.clog.md) | ExpressionFunctionClog | | | [cumulative\_sum](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | +| [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 293597685ecc06..dbac6997ff4333 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -11,6 +11,13 @@ values. . Enter a new value for the setting. . Click *Save changes*. +[float] +=== Required permissions + +The `Advanced Settings` {kib} privilege is required to access *Advanced Settings*. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + [float] [[settings-read-only-access]] diff --git a/docs/management/alerting/alerts-and-actions-intro.asciidoc b/docs/management/alerting/alerts-and-actions-intro.asciidoc index 0c7ca7f1db17db..35a2452e999513 100644 --- a/docs/management/alerting/alerts-and-actions-intro.asciidoc +++ b/docs/management/alerting/alerts-and-actions-intro.asciidoc @@ -24,3 +24,8 @@ The *Alerts and Actions* UI only shows alerts and connectors for the current spa can be managed through the <>. See <> for more information. ============================================================================ + +[float] +=== Required permissions + +Access to alerts and actions is granted based on your privileges to alerting-enabled features. See <> for more information. diff --git a/docs/management/images/management-index-templates-mappings.png b/docs/management/images/management-index-templates-mappings.png deleted file mode 100644 index beb964b348171f..00000000000000 Binary files a/docs/management/images/management-index-templates-mappings.png and /dev/null differ diff --git a/docs/management/images/management-index-templates.png b/docs/management/images/management-index-templates.png deleted file mode 100644 index 07f1fb9a7add11..00000000000000 Binary files a/docs/management/images/management-index-templates.png and /dev/null differ diff --git a/docs/management/images/management_index_component_template.png b/docs/management/images/management_index_component_template.png deleted file mode 100644 index c03029fd172f05..00000000000000 Binary files a/docs/management/images/management_index_component_template.png and /dev/null differ diff --git a/docs/management/images/management_index_create_wizard.png b/docs/management/images/management_index_create_wizard.png deleted file mode 100644 index bff1dd4cd0e7ab..00000000000000 Binary files a/docs/management/images/management_index_create_wizard.png and /dev/null differ diff --git a/docs/management/images/management_index_data_stream_backing_index.png b/docs/management/images/management_index_data_stream_backing_index.png deleted file mode 100644 index a5c577affbbb2d..00000000000000 Binary files a/docs/management/images/management_index_data_stream_backing_index.png and /dev/null differ diff --git a/docs/management/images/management_index_data_stream_stats.png b/docs/management/images/management_index_data_stream_stats.png deleted file mode 100644 index a67ab4a7deb320..00000000000000 Binary files a/docs/management/images/management_index_data_stream_stats.png and /dev/null differ diff --git a/docs/management/images/management_index_details.png b/docs/management/images/management_index_details.png deleted file mode 100644 index b199d13218f5ae..00000000000000 Binary files a/docs/management/images/management_index_details.png and /dev/null differ diff --git a/docs/management/images/management_index_labels.png b/docs/management/images/management_index_labels.png deleted file mode 100644 index a89c32e08beff1..00000000000000 Binary files a/docs/management/images/management_index_labels.png and /dev/null differ diff --git a/docs/management/managing-beats.asciidoc b/docs/management/managing-beats.asciidoc index 10c98cca263458..232efb60cadd3e 100644 --- a/docs/management/managing-beats.asciidoc +++ b/docs/management/managing-beats.asciidoc @@ -29,6 +29,13 @@ more information, see https://www.elastic.co/subscriptions and enrollment and configuration process step by step the first time you use the Central Management UI. +[float] +=== Required permissions + +You must have the `beats_admin` role assigned to use **{beats} Central Management** + +To assign the role, open the menu, then click *Stack Management > Users*. + [float] === Enroll {beats} diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index 441bce43c7cdf3..3734655edd91b3 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -7,6 +7,13 @@ the index patterns that retrieve your data from {es}. [role="screenshot"] image::images/management-index-patterns.png[] +[float] +=== Required permissions + +The `Index Pattern Management` {kib} privilege is required to access the *Index patterns* UI. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + [float] === Create an index pattern diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc deleted file mode 100644 index 8416c164c6c51d..00000000000000 --- a/docs/management/managing-indices.asciidoc +++ /dev/null @@ -1,258 +0,0 @@ -[role="xpack"] -[[managing-indices]] -== Index Management - -*Index Management* features are an easy, convenient way to manage your -{es} cluster's indices, data streams, and index templates. Practicing good index -management ensures your data is stored correctly and in the most cost-effective -way possible. - -[float] -=== What you'll learn - -This page shows you how to use *Index Management* features to: - -To manage your indices, open the main menu, then click *Stack Management > Index Management*. - -[role="screenshot"] -image::images/management_index_labels.png[Index Management UI] - -[float] -=== Before you start - -Before using this feature, you should be familiar with index management -operations. Refer to the {ref}/indices.html[index management APIs], the -{ref}/indices-templates.html[index template APIs], and the -{ref}/data-streams.html[data streams documentation]. - -[float] -=== Required permissions - -The minimum required permissions to access *Index Management* are -the `monitor` cluster privilege and the `view_index_metadata` -and `manage` index privileges to view the data. -For index templates, you must have the `manage_index_templates` cluster privilege. -See {ref}/security-privileges.html[Security privileges] for more -information. - -You can add these privileges in *Stack Management > Security > Roles*. - -[float] -=== View and edit indices - -When you open *Index Management*, you’re presented an overview of your configured indices. -Badges indicate if an index is {ref}/frozen-indices.html[frozen], -a {ref}/ccr-put-follow.html[follower index], -or a {ref}/rollup-get-rollup-index-caps.html[rollup index]. - -Clicking a badge narrows the list to only indices of that type. -You can also filter your indices using the search bar. - -You can drill down into each index to investigate the index -{ref}/index-modules.html#index-modules-settings[settings], {ref}/mapping.html[mapping], and statistics. -From this view, you can also edit the index settings. - -[role="screenshot"] -image::images/management_index_details.png[Index Management UI] - -[float] -=== Perform index-level operations - -Use the *Manage* menu to perform index-level operations. This menu -is available in the index details view, or when you select the checkbox of one or more -indices on the overview page. The menu includes the following actions: - -* *Close index*. Blocks the index from read/write operations. -A closed index exists in the cluster, but doesn't consume resources -other than disk space. If you reopen a closed index, it goes through the -normal recovery process. - -* *Force merge index*. Reduces the number of segments in your shard by -merging smaller files and clearing deleted ones. Only force merge a read-only index. - -* *Refresh index*. Writes the operations in the indexing buffer to the -filesystem cache. This action is automatically performed once per second. Forcing a manual -refresh is useful during testing, but should not be routinely done in -production because it has a performance impact. - -* *Clear index cache*. Clears all caches associated with the index. - -* *Flush index*. Frees memory by syncing the filesystem cache to disk and -clearing the cache. Once the sync is complete, the internal transaction log is reset. - -* *Freeze index*. Makes the index read-only and reduces its memory footprint -by moving shards to disk. Frozen indices remain -searchable, but queries take longer. - -* *Delete index*. Permanently removes the index and all of its documents. - -* *Add lifecycle policy*. Specifies a policy for managing the lifecycle of the -index. - -[float] -[[manage-data-streams]] -=== Manage data streams - -A {ref}/data-streams.html[data stream] lets you store time series data across -multiple backing indices while giving you a single named resource to use in -requests. The *Data Streams* view lists your data streams and lets you examine -or delete them. - -To view more information about a data stream, such as its generation or its -current index lifecycle policy, click the stream's name. - -[role="screenshot"] -image::images/management_index_data_stream_stats.png[Data stream details] - -To view information about the stream's backing indices, click the number in the -*Indices* column. - -[role="screenshot"] -image::images/management_index_data_stream_backing_index.png[Backing index] - -[float] -[[manage-index-templates]] -=== Manage index templates - -An index template defines {ref}/index-modules.html#index-modules-settings[settings], -{ref}/mapping.html[mappings], and {ref}/indices-add-alias.html[aliases] -that you can automatically apply when creating a new index. {es} applies a -template to a new index based on an index pattern that matches the index name. - -The *Index Templates* view lists your templates and enables you to examine, edit, clone, and -delete them. Changes you make to an index template -do not affect existing indices. - -[role="screenshot"] -image::images/management-index-templates.png[Index templates] - -If you don't have any templates, you can create one using the *Create template* wizard. -Index templates are applied during index creation, -so you must create the -template before you create the indices. - -[float] -==== Try it: Create an index template - -In this tutorial, you’ll create an index template for randomly generated log -files. You'll then use the template to configure two new indices. - -*Step 1. Add a name and index pattern* - -. In the *Index Templates* view, open the *Create template* wizard. -+ -[role="screenshot"] -image::images/management_index_create_wizard.png[Create wizard] - -. In the *Name* field, enter `my-index-template`. - -. Set *Index pattern* to `my-index-*` so the template matches any index -with that index pattern. - -. Leave *Data Stream*, *Priority*, *Version*, and *_meta field* as-is or blank. - -. Click *Next*. - -*Step 2. Add settings, mappings, and index aliases* - -. Add component templates to your index template. -+ -{ref}/indices-component-template.html[Component templates] are pre-configured -sets of mappings, index settings, and index aliases you can reuse across -multiple index templates. Badges indicate whether a component template contains -mappings (*M*), index settings (*S*), index aliases (*A*), or a combination of -the three. -+ -Component templates are optional. For this tutorial, do not add any component -templates. -+ -[role="screenshot"] -image::images/management_index_component_template.png[Component templates page] - -. Define index settings. These are optional. For this tutorial, leave this -section blank. - -. Define a mapping that contains an object field named `geo` with a child -geo-point field named `coordinates`: -+ -[role="screenshot"] -image::images/management-index-templates-mappings.png[Mapped fields page] -+ -Alternatively, you can click the *Load JSON* link and define the mapping as JSON: -+ -[source,js] ----- -{ - "properties": { - "geo": { - "properties": { - "coordinates": { - "type": "geo_point" - } - } - } - } -} ----- -+ -You can create additional mapping configurations in the *Dynamic templates* and -*Advanced options* tabs. No additional mappings are required for this tutorial. - -. Define an index alias named `my-index`: -+ -[source,js] ----- -{ - "my-index": {} -} ----- - -. On the review page, check the summary. If everything looks right, click -*Create template*. - -*Step 3. Create new indices* - -You’re now ready to load the logs data and create new indices using your index -template. - -. In the {kib} *Console*, index the following documents: -+ -[source,js] ----- -POST /my-index-000001/_doc -{ - "@timestamp": "2019-05-18T15:57:27.541Z", - "ip": "225.44.217.191", - "extension": "jpg", - "response": "200", - "geo": { - "coordinates": { - "lat": 38.53146222, - "lon": -121.7864906 - } - }, - "url": "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/charles-fullerton.jpg" -} - -POST /my-index-000002/_doc -{ - "@timestamp": "2019-05-20T03:44:20.844Z", - "ip": "198.247.165.49", - "extension": "php", - "response": "200", - "geo": { - "coordinates": { - "lat": 37.13189556, - "lon": -76.4929875 - } - }, - "memory": 241720, - "url": "https://theacademyofperformingartsandscience.org/people/type:astronauts/name:laurel-b-clark/profile" -} ----- -+ -These requests create two indices: `my-index-000001` and `my-index-000002`. - -. Use the {es} {ref}/indices-get-index.html#indices-get-index[get index API] to -view one of the newly created indices. The index's mappings and alias are -configured automatically based on the template. diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index 639be87c540fbf..2e081c09e0e702 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -10,6 +10,16 @@ To get started, open the main menu, then click *Stack Management > Saved Objects [role="screenshot"] image::images/management-saved-objects.png[Saved Objects] +[float] +=== Required permissions + +The `Saved Objects Management` {kib} privilege is required to access the *Saved Objects* UI. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + +NOTE: +Granting access to Saved Objects Management will authorize users to manage all saved objects in {kib}, including objects that are managed by applications they may not otherwise be authorized to access. + [float] [[managing-saved-objects-view]] diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 69c33aa7a1dac1..aded7a45022db1 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -46,7 +46,7 @@ To manage roles, open then main menu, then click *Stack Management > Roles*, or all users with the same role. NOTE: If you are creating a threshold watch, you must also have the `view_index_metadata` index privilege. See -<> for detailed information. +{ref}/index-mgmt.html[Index management] for detailed information. [float] [[watcher-create-threshold-alert]] diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 0cb28ce0fb6e78..ef76121b21d290 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -167,4 +167,14 @@ The `xpack.` prefix has been removed for all telemetry configurations. *Impact:* For any configurations beginning with `xpack.telemetry`, remove the `xpack` prefix. Use {kibana-ref}/telemetry-settings-kbn.html#telemetry-general-settings[`telemetry.enabled`] instead. +[float] +=== SysV init support has been removed + +*Details:* +All supported operating systems support using systemd service files. Any system that doesn't already have service aliased to use kibana.service should use `systemctl start kibana.service` instead of the `service start kibana`. + +*Impact:* +Any installations using `.deb` or `.rpm` packages using SysV will need to migrate to systemd. + + // end::notable-breaking-changes[] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 7fc8fe5114e1e4..833273c7b3ef05 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -143,3 +143,8 @@ This content has moved. See This content has moved. See <>. +[role="exclude",id="managing-indices"] +== Index management + +This content has moved. See {ref}/index-mgmt.html[Index management]. + diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 9054a97c90496c..aa680720fc8ff0 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -43,6 +43,12 @@ Changing these settings may disable features of the APM App. | `xpack.apm.enabled` | Set to `false` to disable the APM app. Defaults to `true`. +| `xpack.apm.serviceMapFingerprintBucketSize` + | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. + +| `xpack.apm.serviceMapFingerprintGlobalBucketSize` + | Maximum number of unique transaction combinations sampled for generating the global service map. Defaults to `100`. + | `xpack.apm.ui.enabled` {ess-icon} | Set to `false` to hide the APM app from the main menu. Defaults to `true`. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index c968ca6f350291..03a728a15351e1 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -1,108 +1,73 @@ [[connect-to-elasticsearch]] -== Add data to {kib} +== Add data +++++ +Add data +++++ -To start working with your data in {kib}, you can: +To start working with your data in {kib}, use one of the many ingest options, +available from the home page. +You can collect data from an app or service +or upload a file that contains your data. If you're not ready to use your own data, +add a sample data set and give {kib} a test drive. -* Upload a CSV, JSON, or log file with the File Data Visualizer. - -* Upload geospatial data with the GeoJSON Upload feature. - -* Index logs, metrics, events, or application data by setting up a Beats module. +[role="screenshot"] +image::images/add-data-home.png[Built-in options for adding data to Kibana: Add data, Add Elastic Agent, Upload a file] -* Connect {kib} with existing {es} indices. +[float] +[[add-data-tutorial-kibana]] +=== Add data -If you're not ready to use your own data, you can add a <> -to see all that you can do in {kib}. +Want to ingest logs, metrics, security, or application data? +Install and configure a Beats data shipper or other module to periodically collect the data +and send it to {es}. You can then use the pre-built dashboards to explore and analyze the data. -[float] -[[upload-data-kibana]] -=== Upload a CSV, JSON, or log file +[role="screenshot"] +image::images/add-data-tutorials.png[Add Data tutorials] -experimental[] +[discrete] +=== Add Elastic Agent -To visualize data in a CSV, JSON, or log file, you can upload it using the File -Data Visualizer. On the home page, click *Upload a file*, and -then drag your file onto the *File Data Visualizer*. Alternatively, you can open -it by navigating to *Machine Learning* from the side navigation and selecting +beta[] *Elastic Agent* is a sneak peek at the next generation of +data integration modules, offering +a centralized way to set up your integrations. +With *Fleet*, you can add +and manage integrations for popular services and platforms, providing +an easy way to collect your data. The integrations +ship with dashboards and visualizations, +so you can quickly get insights into your data. -*Data Visualizer*. +To get started, refer to +{ingest-guide}/ingest-management-getting-started.html[Quick start: Get logs and metrics into the Elastic Stack]. [role="screenshot"] -image::images/ingest-data.png[File Data Visualizer on the home page] +image::images/add-data-fleet.png[Add data using Fleet] + +[discrete] +[[upload-data-kibana]] +=== Upload a file -You can upload a file up to 100 MB. This value is configurable up to 1 GB in -<>. +experimental[] If your data is in a CSV, JSON, or log file, you can upload it using the File +Data Visualizer. You can upload a file up to 100 MB. This value is configurable up to 1 GB in +<>. To upload a file with geospatial data, +refer to <>. [role="screenshot"] image::images/add-data-fv.png[File Data Visualizer] -The File Data Visualizer uses the {ref}/ml-find-file-structure.html[find_file_structure API] to analyze -the uploaded file and to suggest ingest pipelines and mappings for your data. + NOTE: This feature is not intended for use as part of a repeated production process, but rather for the initial exploration of your data. -[float] -[[upload-geoipdata-kibana]] -=== Upload geospatial data - -To visualize geospatial data in a point or shape file, you can upload it using the <> -feature in Maps, and then use that data as a layer in a map. -The data is also available for use in the broader Kibana ecosystem, for example, -in visualizations and Canvas workpads. -With GeoJSON Upload, you can upload a file up to 50 MB. - -[float] -[[add-data-tutorial-kibana]] -=== Index metrics, log, security, and application data -The built-in data tutorials can help you quickly get up and running with -metrics data, log analytics, security events, and application data. -These tutorials walk you through installing and configuring a -Beats data shipper to periodically collect and send data to {es}. -You can then use the pre-built dashboards to explore and analyze the data. +[discrete] +=== Additional options for loading your data -You access the tutorials from the home page. -If a tutorial doesn’t exist for your data, go to the {beats-ref}/beats-reference.html[Beats overview] -to learn about other data shippers in the Beats family. +If the {kib} ingest options don't work for you, you can index your +data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] +or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. +After you add your data, you're required to create an <> to tell +{kib} where to find the data. -[role="screenshot"] -image::images/add-data-tutorials.png[Add Data tutorials] - - -[float] -[[connect-to-es]] -=== Connect with {es} indices - -To visualize data in existing {es} indices, you must -create an index pattern that matches the names of the indices that you want to explore. -When you add data with the File Data Visualizer, GeoJSON Upload feature, -or built-in tutorial, an index pattern is created for you. - -. Open the main menu, then click *Stack Management > Index Patterns*. - -. Click *Create index pattern*. - -. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. -+ -For example, an index pattern can point to your Apache data from yesterday, -`filebeat-apache-4-3-2022`, or any index that matches the pattern, `filebeat-*`. -Using a wildcard is the more popular approach. - - -. Click *Next Step*, and then select the index field that contains the timestamp you want to use to perform time-based -comparisons. -+ -Kibana reads the index mapping and lists all fields that contain a timestamp. If your -index doesn't have time-based data, choose *I don't want to use the time filter*. -+ -You must select a time field to use global time filters on your dashboards. - -. Click *Create index pattern*. -+ -{kib} is now configured to access your {es} indices. -You’ll see a list of fields configured for the matching index. -You can designate your index pattern as the default by clicking the star icon on this page. -+ -When searching in *Discover* and creating visualizations, you choose a pattern -from the index pattern menu to specify the {es} indices that contain the data you want to explore. +* To add data for Elastic Observability, refer to {observability-guide}/add-observability-data.html[Send data to Elasticsearch]. +* To add data for Elastic Security, refer to https://www.elastic.co/guide/en/security/current/ingest-data.html[Ingest data to Elastic Security]. diff --git a/docs/setup/images/add-data-fleet.png b/docs/setup/images/add-data-fleet.png new file mode 100644 index 00000000000000..b6d49cfaf8d3a5 Binary files /dev/null and b/docs/setup/images/add-data-fleet.png differ diff --git a/docs/setup/images/add-data-home-page.png b/docs/setup/images/add-data-home-page.png new file mode 100644 index 00000000000000..a3373757683be3 Binary files /dev/null and b/docs/setup/images/add-data-home-page.png differ diff --git a/docs/setup/images/add-data-home.png b/docs/setup/images/add-data-home.png new file mode 100644 index 00000000000000..3a844b1c40de94 Binary files /dev/null and b/docs/setup/images/add-data-home.png differ diff --git a/docs/setup/install/deb-init.asciidoc b/docs/setup/install/deb-init.asciidoc deleted file mode 100644 index 6e21b8f97cf7e1..00000000000000 --- a/docs/setup/install/deb-init.asciidoc +++ /dev/null @@ -1,20 +0,0 @@ -==== Run {kib} with SysV `init` - -Use the `update-rc.d` command to configure Kibana to start automatically -when the system boots up: - -[source,sh] --------------------------------------------------- -sudo update-rc.d kibana defaults 95 10 --------------------------------------------------- - -You can start and stop Kibana using the `service` command: - -[source,sh] --------------------------------------------- -sudo -i service kibana start -sudo -i service kibana stop --------------------------------------------- - -If Kibana fails to start for any reason, it will print the reason for -failure to `STDOUT`. Log files can be found in `/var/log/kibana/`. diff --git a/docs/setup/install/deb.asciidoc b/docs/setup/install/deb.asciidoc index 234c02cee0be13..c830faf1432d7c 100644 --- a/docs/setup/install/deb.asciidoc +++ b/docs/setup/install/deb.asciidoc @@ -160,15 +160,7 @@ https://artifacts.elastic.co/downloads/kibana/kibana-oss-{version}-amd64.deb endif::[] -==== SysV `init` vs `systemd` - -include::init-systemd.asciidoc[] - -[[deb-running-init]] -include::deb-init.asciidoc[] - [[deb-running-systemd]] - include::systemd.asciidoc[] [[deb-configuring]] diff --git a/docs/setup/install/rpm-init.asciidoc b/docs/setup/install/rpm-init.asciidoc deleted file mode 100644 index 08282635a014f6..00000000000000 --- a/docs/setup/install/rpm-init.asciidoc +++ /dev/null @@ -1,20 +0,0 @@ -==== Run {kib} with SysV `init` - -Use the `chkconfig` command to configure Kibana to start automatically -when the system boots up: - -[source,sh] --------------------------------------------------- -sudo chkconfig --add kibana --------------------------------------------------- - -You can start and stop Kibana using the `service` command: - -[source,sh] --------------------------------------------- -sudo -i service kibana start -sudo -i service kibana stop --------------------------------------------- - -If Kibana fails to start for any reason, it will print the reason for -failure to `STDOUT`. Log files can be found in `/var/log/kibana/`. diff --git a/docs/setup/install/rpm.asciidoc b/docs/setup/install/rpm.asciidoc index 1153353aa9a0fa..0b63684808d7d6 100644 --- a/docs/setup/install/rpm.asciidoc +++ b/docs/setup/install/rpm.asciidoc @@ -153,13 +153,6 @@ https://artifacts.elastic.co/downloads/kibana/kibana-oss-{version}-x86_64.rpm endif::[] -==== SysV `init` vs `systemd` - -include::init-systemd.asciidoc[] - -[[rpm-running-init]] -include::rpm-init.asciidoc[] - [[rpm-running-systemd]] include::systemd.asciidoc[] diff --git a/docs/setup/start-stop.asciidoc b/docs/setup/start-stop.asciidoc index 198bc76bbb4001..8952cd3a23cd31 100644 --- a/docs/setup/start-stop.asciidoc +++ b/docs/setup/start-stop.asciidoc @@ -25,25 +25,8 @@ stop and start {kib} from the command line. include::install/windows-running.asciidoc[] [float] -[[start-stop-deb]] -=== Debian packages - -include::install/init-systemd.asciidoc[] - -[float] -include::install/deb-init.asciidoc[] - -[float] -include::install/systemd.asciidoc[] - -[float] -[[start-stop-rpm]] -=== RPM packages - -include::install/init-systemd.asciidoc[] - -[float] -include::install/rpm-init.asciidoc[] +[[start-stop-deb-rpm]] +=== Debian and RPM packages [float] include::install/systemd.asciidoc[] diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index 6d69b6921b6122..92cd6e9ead5a1e 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -4,29 +4,24 @@ Depending on the {kib} version you're upgrading from, the upgrade process to 7.0 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]] === Before you begin +WARNING: {kib} automatically runs upgrade migrations when required. To roll back to an earlier version in case of an upgrade failure, you **must** have a backup snapshot available. Use <> to back up {kib} data by targeting the `.kibana*` indices. For more information see <>. + Before you upgrade {kib}: * Consult the <>. +* Back up your data with <>. To roll back to an earlier version, you **must** have a snapshot of the `.kibana*` indices. +* Although not a requirement for rollbacks, we recommend taking a snapshot of all {kib} indices created by the plugins you use such as the `.reporting*` indices created by the reporting plugin. * Before you upgrade production servers, test the upgrades in a dev environment. -* 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. +* See <> for common reasons upgrades fail and how to prevent these. * If you are using custom plugins, check that a compatible version is available. -* Shut down all {kib} nodes. Running more than one {kib} version against the - same Elasticseach index is unsupported. If you upgrade while older {kib} nodes are - running, the upgrade can fail. +* Shut down all {kib} instances. Running more than one {kib} version against + the same Elasticseach index is unsupported. Upgrading while older {kib} + instances are running can cause data loss or upgrade failures. To identify the changes you need to make to upgrade, and to enable you to perform an Elasticsearch rolling upgrade with no downtime, you must upgrade to diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index cbe56b9e65894a..74d097164c4a78 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -1,54 +1,127 @@ [[upgrade-migrations]] -=== Migrate saved objects +=== Upgrade migrations -Every time {kib} is upgraded it checks to see if all saved objects, such as dashboards, visualizations, and index patterns, are compatible with the new version. If any objects need to be updated, then the automatic saved object migration process is kicked off. +Every time {kib} is upgraded it checks to see if all saved objects, such as dashboards, visualizations, and index patterns, are compatible with the new version. If any saved objects need to be updated, then the automatic saved object migration process is kicked off. NOTE: 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] to help you prepare for your upgrade to 7.0. To access the assistant, go to *Management > 7.0 Upgrade Assistant*. +WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings were changed these instructions will have to be adapted accordingly. + [float] [[upgrade-migrations-process]] -==== How the process works +==== Background -Saved objects are stored in an index named `.kibana_N`, where `N` is a number that increments over time as {kib} is upgraded. The index alias `.kibana` points to the latest up-to-date index for a given install. +Saved objects are stored in two indices: -NOTE: Prior to 6.5.0, saved objects were stored directly in an index named `.kibana`, so the first time you upgrade to {kib} version 6.5+, {kib} will migrate into `.kibana_1` and set `.kibana` up as an index alias. +* `.kibana_N`, or if set, the `kibana.index` configuration setting +* `.kibana_task_manager_N`, or if set, the `xpack.tasks.index` configuration setting + +For each of these indices, `N` is a number that increments every time {kib} runs an upgrade migration on that index. The index aliases `.kibana` and `.kibana_task_manager` point to the most up-to-date index. While {kib} is starting up and before serving any HTTP traffic, it checks to see if any internal mapping changes or data transformations for existing saved objects are required. -When changes are necessary, a new incremental `.kibana_N` index is created with updated mappings, then the saved objects are loaded in batches from the existing index, transformed to whatever extent necessary, and added to this new index. +When changes are necessary, a new migration is started. To ensure that only one {kib} instance performs the migration, each instance will attempt to obtain a migration lock by creating a new `.kibana_N+1` index. The instance that succeeds in creating the index will then read batches of documents from the existing index, migrate them, and write them to the new index. Once the objects are migrated, the lock is released by pointing the `.kibana` index alias the new upgraded `.kibana_N+1` index. + +Instances that failed to acquire a lock will log `Another Kibana instance appears to be migrating the index. Waiting for that migration to complete`. The instance will then wait until `.kibana` points to an upgraded index before starting up and serving HTTP traffic. -Once the objects are migrated, the `.kibana` index alias is updated to point to the new index, and {kib} finishes starting up and serving HTTP traffic. +NOTE: Prior to 6.5.0, saved objects were stored directly in an index named `.kibana`. After upgrading to version 6.5+, {kib} will migrate this index into `.kibana_N` and set `.kibana` up as an index alias. + +Prior to 7.4.0, task manager tasks were stored directly in an index name `.kibana_task_manager`. After upgrading to version 7.4+, {kib} will migrate this index into `.kibana_task_manager_N` and set `.kibana_task_manager` up as an index alias. [float] -[[upgrade-migrations-old-indices]] -==== Handling old `.kibana` indices +[[preventing-migration-failures]] +==== Preventing migration failures +This section highlights common causes of {kib} upgrade failures and how to prevent them. + +[float] +===== Corrupt saved objects +We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. + +[float] +===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings +Matching index templates which specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. + +Prevention: narrow down the index patterns of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. -After migrations have run, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, etc). {kib} only uses the index that the `.kibana` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. +Note: {kib} < 6.5 creates it's own index template called `kibana_index_template:.kibana` and index pattern `.kibana`. This index template will not interfere and does not need to be changed or removed. + +[float] +===== An unhealthy {es} cluster +Problems with your {es} cluster can prevent {kib} upgrades from succeeding. Ensure that your cluster has: + + * enough free disk space, at least twice the amount of storage taken up by the `.kibana` and `.kibana_task_manager` indices + * sufficient heap size + * a "green" cluster status + +[float] +===== Running different versions of {kib} connected to the same {es} index +Kibana does not support rolling upgrades. Stop all {kib} instances before starting a newer version to prevent upgrade failures and data loss. + +[float] +===== Incompatible `xpack.tasks.index` configuration setting +For {kib} < 7.5.1, if the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, upgrade migrations will fail. {kib} 7.5.1 and later prevents this by refusing to start with an incompatible configuration setting. [float] -[[upgrade-migrations-errors]] -==== Handling errors during saved object migrations +[[resolve-migrations-failures]] +==== Resolving migration failures -If {kib} terminates unexpectedly while migrating a saved object index, some additional work may be required in order to get {kib} to re-attempt the migration. +If {kib} terminates unexpectedly while migrating a saved object index, manual intervention is required before {kib} will attempt to perform the migration again. Follow the advice in (preventing migration failures)[preventing-migration-failures] before retrying a migration upgrade. -For example, if the `.kibana` alias is pointing to `.kibana_4`, and there is a `.kibana_5` index in {es}, the `.kibana_5` index will need to be deleted. {kib} will never attempt to overwrite an existing index. +As mentioned above, {kib} will create a migration lock for each index that requires a migration by creating a new `.kibana_N+1` index. For example: if the `.kibana_task_manager` alias is pointing to `.kibana_task_manager_5` then the first {kib} that succeeds in creating `.kibana_task_manager_6` will obtain the lock to start migrations. + +However, if the instance that obtained the lock fails to migrate the index, all other {kib} instances will be blocked from performing this migration. This includes the instance that originally obtained the lock, it will be blocked from retrying the migration even when restarted. [float] -[[upgrade-migrations-multiple-instances]] -==== Support for multiple {kib} instances +===== Retry a migration by restoring a backup snapshot: + +1. Before proceeding ensure that you have a recent and successful backup snapshot of all `.kibana*` indices. +2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +3. Delete all saved object indices with `DELETE /.kibana*` +4. Restore the `.kibana* indices and their aliases from the backup snapshot. See {es} {ref}/modules-snapshots.html[snapshots] +5. Start up all {kib} instances to retry the upgrade migration. -If you're running multiple {kib} instances for a single index behind a load balancer, it's important that you stop all instances before upgrading, so you do not have multiple different versions of {kib} trying to perform saved object migrations. +[float] +===== (Not recommended) Retry a migration without a backup snapshot: -The first instance that triggers saved object migrations will run the entire process. Any other instances started up while a migration is running will log a message and then wait until saved object migration has completed before they start serving HTTP traffic. +1. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +2. Identify any migration locks by comparing the output of `GET /_cat/aliases` and `GET /_cat/indices`. If e.g. `.kibana` is pointing to `.kibana_4` and there is a `.kibana_5` index, the `.kibana_5` index will act like a migration lock blocking further attempts. Be sure to check both the `.kibana` and `.kibana_task_manager` aliases and their indices. +3. Remove any migration locks e.g. `DELETE /.kibana_5`. +4. Start up all {kib} instances. [float] [[upgrade-migrations-rolling-back]] ==== Rolling back to a previous version of {kib} -When rolling {kib} back to a previous version, point the `.kibana` alias to -the appropriate {kib} index. When you have the previous version running again, -delete the more recent `.kibana_N` index or indices so that future upgrades are -based on the current {kib} index. You must restart {kib} to re-trigger the migration. +If you've followed the advice in (preventing migration failures)[preventing-migration-failures] and (resolving migration failures)[resolve-migrations-failures] and {kib} is still not able to upgrade successfully, you might choose to rollback {kib} until you're able to identify the root cause. + +WARNING: Before rolling back {kib}, ensure that the version you wish to rollback to is compatible with your {es} cluster. If the version you're rolling back to is not compatible, you will have to also rollback {es}. + +Any changes made after an upgrade will be lost when rolling back to a previous version. + +In order to rollback after a failed upgrade migration, the saved object indices might also have to be rolled back to be compatible with the previous {kibana} version. + +[float] +===== Rollback by restoring a backup snapshot: + +1. Before proceeding ensure that you have a recent and successful backup snapshot of all `.kibana*` indices. +2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +3. Delete all saved object indices with `DELETE /.kibana*` +4. Restore the `.kibana* indices and their aliases from the backup snapshot. See {es} {ref}/modules-snapshots.html[snapshots] +5. Start up all {kib} instances on the older version you wish to rollback to. + +[float] +===== (Not recommended) Rollback without a backup snapshot: + +WARNING: {kib} does not run a migration for every saved object index on every upgrade. A {kib} version upgrade can cause no migrations, migrate only the `.kibana` or the `.kibana_task_manager` index or both. Carefully read the logs to ensure that you're only deleting indices created by a later version of {kib} to avoid data loss. + +1. Shutdown all {kib} instances to be 100% sure that there are no {kib} instances currently performing a migration. +2. Create a backup snapshot of the `.kibana*` indices. +3. Use the logs from the upgraded instances to identify which indices {kib} attempted to upgrade. The server logs will contain an entry like `[savedobjects-service] Creating index .kibana_4.` and/or `[savedobjects-service] Creating index .kibana_task_manager_2.` If no indices were created after upgrading {kib} then no further action is required to perform a rollback, skip ahead to step (5). If you're running multiple {kib} instances, be sure to inspect all instances' logs. +4. Delete each of the indices identified in step (2). e.g. `DELETE /.kibana_task_manager_2` +5. Inspect the output of `GET /_cat/aliases`. If either the `.kibana` and/or `.kibana_task_manager` alias is missing, these will have to be created manually. Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. E.g. if the `.kibana` alias was missing and the latest index is `.kibana_3` create a new alias with `POST /.kibana_3/_aliases/.kibana`. +6. Start up {kib} on the older version you wish to rollback to. + +[float] +[[upgrade-migrations-old-indices]] +==== Handling old `.kibana_N` indices -WARNING: Rolling back to a previous {kib} version can result in saved object data loss if you had successfully upgraded and made changes to saved objects before rolling back. +After migrations have completed, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, etc). {kib} only uses the index that the `.kibana` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. \ No newline at end of file diff --git a/docs/setup/upgrade/upgrade-standard.asciidoc b/docs/setup/upgrade/upgrade-standard.asciidoc index df38427881d656..b27bb8867e624c 100644 --- a/docs/setup/upgrade/upgrade-standard.asciidoc +++ b/docs/setup/upgrade/upgrade-standard.asciidoc @@ -12,11 +12,20 @@ If you've saved and/or exported objects in {kib} that rely on the necessary remediation steps as per those instructions. =========================================== +[float] +==== Upgrading multiple {kib} instances + +WARNING: Kibana does not support rolling upgrades. If you're running multiple {kib} instances, all instances should be stopped before upgrading. + +Different versions of {kib} running against the same {es} index, such as during a rolling upgrade, can cause upgrade migration failures and data loss. This is because acknowledged writes from the older instances could be written into the _old_ index while the migration is in progress. To prevent this from happening ensure that all old {kib} instances are shutdown before starting up instances on a newer version. + +The first instance that triggers saved object migrations will run the entire process. Any other instances started up while a migration is running will log a message and then wait until saved object migrations has completed before they start serving HTTP traffic. + [float] ==== Upgrade using a `deb` or `rpm` package . Stop the existing {kib} process using the appropriate command for your - system. + system. If you have multiple {kib} instances connecting to the same {es} cluster ensure that all instances are stopped before proceeding to the next step to avoid data loss. . Use `rpm` or `dpkg` to install the new package. All files should be placed in their proper locations and config files should not be overwritten. + @@ -43,8 +52,7 @@ otherwise {kib} will fail to start. don't overwrite the `config` or `data` directories. + + -- -IMPORTANT: If you use {monitor-features}, you must re-use the data directory when you -upgrade {kib}. Otherwise, the {kib} instance is assigned a new persistent UUID +IMPORTANT: If you use {monitor-features}, you must re-use the data directory when you upgrade {kib}. Otherwise, the {kib} instance is assigned a new persistent UUID and becomes a new instance in the monitoring data. -- @@ -57,5 +65,5 @@ and becomes a new instance in the monitoring data. . Install the appropriate versions of all your plugins for your new installation using the `kibana-plugin` script. Check out the <> documentation for more information. -. Stop the old {kib} process. +. Stop the old {kib} process. If you have multiple {kib} instances connecting to the same {es} cluster ensure that all instances are stopped before proceeding to the next step to avoid data loss. . Start the new {kib} process. diff --git a/docs/spaces/images/edit-space-feature-visibility.png b/docs/spaces/images/edit-space-feature-visibility.png index bc983bde2679ca..3a74c21fe2635f 100644 Binary files a/docs/spaces/images/edit-space-feature-visibility.png and b/docs/spaces/images/edit-space-feature-visibility.png differ diff --git a/docs/spaces/images/edit-space.png b/docs/spaces/images/edit-space.png index 68ffea23c4ac48..c4565a3ba0f4a4 100644 Binary files a/docs/spaces/images/edit-space.png and b/docs/spaces/images/edit-space.png differ diff --git a/docs/spaces/images/space-selector.png b/docs/spaces/images/space-selector.png index 908c5360acd396..b2576cbc9acc4f 100644 Binary files a/docs/spaces/images/space-selector.png and b/docs/spaces/images/space-selector.png differ diff --git a/docs/spaces/images/spaces-roles.png b/docs/spaces/images/spaces-roles.png old mode 100755 new mode 100644 index 2ecbfed6017c4f..407926895daa87 Binary files a/docs/spaces/images/spaces-roles.png and b/docs/spaces/images/spaces-roles.png differ diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 1bc781e1dda491..81f39457795035 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -25,6 +25,11 @@ Kibana supports spaces in several ways. You can: * <> * <> +[float] +==== Required permissions + +The `kibana_admin` role or equivilent is required to manage **Spaces**. + [float] [[spaces-managing]] === View, create, and delete spaces diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index aa5b0ece08db70..9d580187edff45 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -26,30 +26,16 @@ image::images/intro-kibana.png[Kibana home page] [[get-data-into-kibana]] === Ingest data -{kib} is designed to use {es} as a data source. Think of {es} as the engine that stores +{kib} is designed to use {es} as a data source. Think of Elasticsearch as the engine that stores and processes the data, with {kib} sitting on top. -From the home page, {kib} provides these options for ingesting data: - -* Import data using the -https://www.elastic.co/blog/importing-csv-and-log-data-into-elasticsearch-with-file-data-visualizer[File Data visualizer]. -* Set up a data flow to Elasticsearch using our built-in tutorials. -If a tutorial doesn’t exist for your data, go to the -{beats-ref}/beats-reference.html[Beats overview] to learn about other data shippers -in the {beats} family. -* <> and take {kib} for a test drive without loading data yourself. -* Index your data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] - or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. -+ -[role="screenshot"] -image::images/intro-data-tutorial.png[Ways to get data in from the home page] - +To start working with your data in Kibana, use one of the many ingest options, +available from the home page. You can collect data from an app or service or upload a file that contains your data. +If you're not ready to use your own data, you can add a sample data set +to give {kib} a test drive. -{kib} uses an -<> to tell it which {es} indices to explore. -If you add upload a file, run a built-in tutorial, or add sample data, you get an index pattern for free, -and are good to start exploring. If you load your own data, you can create -an index pattern in <>. +[role="screenshot"] +image::setup/images/add-data-home.png[Built-in options for adding data to Kibana: Add data, Add Elastic Agent, Upload a file] [float] [[explore-and-query]] @@ -94,7 +80,7 @@ and dynamic client-side styling. * <> allows you to combine an infinite number of aggregations to display complex data. -With TSVB, you can analyze multiple index patterns and customize +With TSVB, you can customize every aspect of your visualization. Choose your own date format and color gradients, and easily switch your data view between time series, metric, top N, gauge, and markdown. @@ -112,7 +98,7 @@ You can even choose which features to enable within each space. Don’t need Machine learning in your “Executive” space? Simply turn it off. [role="screenshot"] -image::images/intro-spaces.jpg[] +image::images/intro-spaces.png[Space selector screen] You can take this all one step further with Kibana’s security features, and control which users have access to each space. {kib} allows for fine-grained @@ -124,7 +110,7 @@ dashboards in one space, but full access to all of Kibana’s features in anothe === Manage all things Elastic Stack <> provides guided processes for managing all -things Elastic Stack — indices, clusters, licenses, UI settings, index patterns, +things Elastic Stack — indices, clusters, licenses, UI settings, and more. Want to update your {es} indices? Set user roles and privileges? Turn on dark mode? Kibana has UIs for all that. diff --git a/docs/user/introduction/images/intro-spaces.jpg b/docs/user/introduction/images/intro-spaces.jpg deleted file mode 100755 index 7569dfc16b4f79..00000000000000 Binary files a/docs/user/introduction/images/intro-spaces.jpg and /dev/null differ diff --git a/docs/user/introduction/images/intro-spaces.png b/docs/user/introduction/images/intro-spaces.png index 6f3212cbde26e1..b2576cbc9acc4f 100644 Binary files a/docs/user/introduction/images/intro-spaces.png and b/docs/user/introduction/images/intro-spaces.png differ diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index c371aa695c4759..503f97aabd13ff 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -6,6 +6,10 @@ *Stack Management* is home to UIs for managing all things Elastic Stack— indices, clusters, licenses, UI settings, index patterns, spaces, and more. + +Access to individual features is governed by {es} and {kib} privileges. +Consult your administrator if you do not have the appropriate access. + [float] [[manage-ingest]] == Ingest @@ -35,7 +39,7 @@ quickly deploy configuration changes to all Beats running across your enterprise [cols="50, 50"] |=== -a| <> +a| {ref}/index-mgmt.html[Index Management] | View index settings, mappings, and statistics and perform operations, such as refreshing, flushing, and clearing the cache. Practicing good index management ensures that your data is stored cost effectively. @@ -180,8 +184,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/managing-indices.asciidoc[] - include::{kib-repo-dir}/management/ingest-pipelines/ingest-pipelines.asciidoc[] include::{kib-repo-dir}/management/managing-fields.asciidoc[] diff --git a/docs/user/monitoring/beats-details.asciidoc b/docs/user/monitoring/beats-details.asciidoc index 3d7a726d2f8a23..dbd9aecd04768d 100644 --- a/docs/user/monitoring/beats-details.asciidoc +++ b/docs/user/monitoring/beats-details.asciidoc @@ -9,7 +9,7 @@ If you are monitoring Beats, the *Stack Monitoring* page in {kib} contains a panel for Beats in the cluster overview. [role="screenshot"] -image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="images/monitoring-beats.jpg"] +image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="user/monitoring/images/monitoring-beats.jpg"] To view an overview of the Beats data in the cluster, click *Overview*. The overview page has a section for activity in the last day, which is a real-time @@ -24,7 +24,7 @@ cluster. All columns are sortable. Clicking a Beat name takes you to the detail page. For example: [role="screenshot"] -image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="images/monitoring-beats-detail.jpg"] +image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="user/monitoring/images/monitoring-beats-detail.jpg"] The detail page contains a summary bar and charts. There are more charts on this page than the overview page and they are specific to a single Beat instance. diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index 150004b3ad6914..af5fd34b3b3509 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -12,7 +12,12 @@ NOTE: When running multiple tenants of {kib} by changing the `kibana.index` in y [[xpack-kibana-role-management]] === {kib} role management -To create a role that grants {kib} privileges, open the main menu, click *Stack Management > Roles*, then click *Create role*. +To create a role that grants {kib} privileges, open the menu, then click *Stack Management > Roles* and click **Create role**. + +[float] +==== Required permissions + +The `manage_security` cluster privilege is required to access role management. [[adding_kibana_privileges]] ==== Adding {kib} privileges diff --git a/docs/user/security/images/add-space-privileges.png b/docs/user/security/images/add-space-privileges.png old mode 100755 new mode 100644 index 7739332c33b603..d2fcbe76c1a061 Binary files a/docs/user/security/images/add-space-privileges.png and b/docs/user/security/images/add-space-privileges.png differ diff --git a/docs/user/security/images/assign_base_privilege.png b/docs/user/security/images/assign_base_privilege.png index 34e2bcf81d618c..93bed0de05555a 100644 Binary files a/docs/user/security/images/assign_base_privilege.png and b/docs/user/security/images/assign_base_privilege.png differ diff --git a/docs/user/security/images/assign_feature_privilege.png b/docs/user/security/images/assign_feature_privilege.png index c9449f6390253e..d42ec208325a33 100644 Binary files a/docs/user/security/images/assign_feature_privilege.png and b/docs/user/security/images/assign_feature_privilege.png differ diff --git a/docs/user/security/images/privilege-example-1.png b/docs/user/security/images/privilege-example-1.png old mode 100755 new mode 100644 index 68ba716437240e..b8fb4d15b8f77c Binary files a/docs/user/security/images/privilege-example-1.png and b/docs/user/security/images/privilege-example-1.png differ diff --git a/docs/user/security/images/role-space-visualization.png b/docs/user/security/images/role-space-visualization.png index 746af89c66e85d..0de94d81065fe5 100644 Binary files a/docs/user/security/images/role-space-visualization.png and b/docs/user/security/images/role-space-visualization.png differ diff --git a/docs/user/security/images/view-privilege-summary.png b/docs/user/security/images/view-privilege-summary.png old mode 100755 new mode 100644 index d93d55c93fd128..7d2f3018d7de9e Binary files a/docs/user/security/images/view-privilege-summary.png and b/docs/user/security/images/view-privilege-summary.png differ diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index b5ab57d8f525ac..18ace452ce00c1 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -10,6 +10,12 @@ auditing. For more information, see {ref}/secure-cluster.html[Secure a cluster] and <>. +[float] +=== Required permissions + +The `manage_security` cluster privilege is required to access all Security features. + + [float] === Users diff --git a/docs/user/security/role-mappings/index.asciidoc b/docs/user/security/role-mappings/index.asciidoc index 3f9a17e98d77fe..ca3ca9a686892a 100644 --- a/docs/user/security/role-mappings/index.asciidoc +++ b/docs/user/security/role-mappings/index.asciidoc @@ -19,6 +19,11 @@ With *Role mappings*, you can: [role="screenshot"] image:user/security/role-mappings/images/role-mappings-grid.png["Role mappings"] +[float] +==== Required permissions + +The `manage_security` cluster privilege is required to manage Role Mappings. + [float] === Create a role mapping diff --git a/examples/bfetch_explorer/README.md b/examples/bfetch_explorer/README.md new file mode 100644 index 00000000000000..33723e7cabe076 --- /dev/null +++ b/examples/bfetch_explorer/README.md @@ -0,0 +1,11 @@ +## bfetch explorer + +bfetch is a service that allows you to batch HTTP requests and stream responses +back. + +This example app demonstrates: + - How you can create a streaming response route and consume it from the + client + - How you can create a batch processing route and consume it from the client + +To run this example, use the command `yarn start --run-examples`. diff --git a/examples/embeddable_examples/README.md b/examples/embeddable_examples/README.md new file mode 100644 index 00000000000000..d05c315d318172 --- /dev/null +++ b/examples/embeddable_examples/README.md @@ -0,0 +1,4 @@ +## Embeddable examples + +This example plugin exists to support the `embeddable_explorer` app. + diff --git a/examples/embeddable_explorer/README.md b/examples/embeddable_explorer/README.md new file mode 100644 index 00000000000000..0425790f07487c --- /dev/null +++ b/examples/embeddable_explorer/README.md @@ -0,0 +1,10 @@ +## Embeddable explorer + +This example app shows how to: + - Create a basic "hello world" embeddable + - Create embeddables that accept inputs and use an EmbeddableRenderer + - Nest embeddables inside a container + - Dynamically add children to embeddable containers + - Work with the EmbeddablePanel component + +To run this example, use the command `yarn start --run-examples`. diff --git a/examples/embeddable_explorer/tsconfig.json b/examples/embeddable_explorer/tsconfig.json index 86b35c5e4943fd..4baebebcea42ee 100644 --- a/examples/embeddable_explorer/tsconfig.json +++ b/examples/embeddable_explorer/tsconfig.json @@ -9,10 +9,11 @@ "public/**/*.ts", "public/**/*.tsx", "server/**/*.ts", - "../../typings/**/*", + "../../typings/**/*" ], "exclude": [], "references": [ - { "path": "../../src/core/tsconfig.json" } + { "path": "../../src/core/tsconfig.json" }, + { "path": "../../src/plugins/inspector/tsconfig.json" } ] } diff --git a/examples/state_containers_examples/README.md b/examples/state_containers_examples/README.md new file mode 100644 index 00000000000000..c4c6642789bd9d --- /dev/null +++ b/examples/state_containers_examples/README.md @@ -0,0 +1,8 @@ +## State containers examples + +This example app shows how to: + - Use state containers to manage your application state + - Integrate with browser history and hash history routing + - Sync your state container with the URL + +To run this example, use the command `yarn start --run-examples`. diff --git a/package.json b/package.json index 3a2d13fd5ef3bc..1d5c5059cd03e8 100644 --- a/package.json +++ b/package.json @@ -110,14 +110,16 @@ "**/grunt-*/**", "x-pack/typescript", "@elastic/eui/rehype-react", + "@elastic/eui/remark-parse", "@elastic/eui/remark-rehype", - "@elastic/eui/remark-rehype/**" + "@elastic/eui/remark-rehype/**", + "@elastic/eui/unified" ] }, "dependencies": { "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "7.10.0-rc.1", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", "@elastic/request-crypto": "1.1.4", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index c660d372225042..770bb4f510301f 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -14,7 +14,7 @@ pageLoadAssetSize: dashboard: 374194 dashboardEnhanced: 65646 dashboardMode: 22716 - data: 1287839 + data: 1317839 dataEnhanced: 50420 devTools: 38637 discover: 105145 @@ -54,7 +54,7 @@ pageLoadAssetSize: mapsLegacy: 116817 mapsLegacyLicensing: 20214 ml: 82187 - monitoring: 268612 + monitoring: 50000 navigation: 37269 newsfeed: 42228 observability: 89709 diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index cb5bb1e8fc529f..ddb19c8cdc3d75 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -108,4 +108,4 @@ exports[`prepares assets for distribution: baz bundle 1`] = ` exports[`prepares assets for distribution: foo async bundle 1`] = `"(window[\\"foo_bundle_jsonpfunction\\"]=window[\\"foo_bundle_jsonpfunction\\"]||[]).push([[1],{3:function(module,__webpack_exports__,__webpack_require__){\\"use strict\\";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,\\"foo\\",(function(){return foo}));function foo(){}}}]);"`; -exports[`prepares assets for distribution: foo bundle 1`] = `"(function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i({ Any field property in the schema accepts a `type` field. By default the type is `object` which accepts nested properties under it. Currently we accept the following property types: ``` -AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +'long', 'integer', 'short', 'byte', 'double', 'float', 'keyword', 'text', 'boolean', 'date' ``` diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json index 51e5df9bf7dc09..a385cd6798365a 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json @@ -8,16 +8,16 @@ "my_index_signature_prop": { "properties": { "avg": { - "type": "number" + "type": "float" }, "count": { - "type": "number" + "type": "long" }, "max": { - "type": "number" + "type": "long" }, "min": { - "type": "number" + "type": "long" } } }, @@ -27,7 +27,7 @@ "my_objects": { "properties": { "total": { - "type": "number" + "type": "long" }, "type": { "type": "boolean" @@ -39,7 +39,7 @@ "items": { "properties": { "total": { - "type": "number" + "type": "long" }, "type": { "type": "boolean" diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts index 83866a2b6afec0..109fc045b6ee03 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts @@ -28,7 +28,7 @@ export const parsedIndexedInterfaceWithNoMatchingSchema: ParsedUsageCollection = value: { something: { count_1: { - type: 'number', + type: 'long', }, }, }, diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts index 833344fa368b03..4a1a622e23f36f 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts @@ -34,7 +34,7 @@ export const parsedSchemaDefinedWithSpreadsCollector: ParsedUsageCollection = [ }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean', diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts index acf984b7d10eed..ef6227cf35c374 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts @@ -34,21 +34,21 @@ export const parsedWorkingCollector: ParsedUsageCollection = [ }, my_index_signature_prop: { avg: { - type: 'number', + type: 'float', }, count: { - type: 'number', + type: 'long', }, max: { - type: 'number', + type: 'long', }, min: { - type: 'number', + type: 'long', }, }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean', @@ -58,7 +58,7 @@ export const parsedWorkingCollector: ParsedUsageCollection = [ type: 'array', items: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, diff --git a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap index 4725be77533af1..fe589be7993d0f 100644 --- a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap +++ b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap @@ -176,7 +176,7 @@ Array [ }, "my_objects": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", @@ -248,7 +248,7 @@ Array [ "my_array": Object { "items": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", @@ -258,21 +258,21 @@ Array [ }, "my_index_signature_prop": Object { "avg": Object { - "type": "number", + "type": "float", }, "count": Object { - "type": "number", + "type": "long", }, "max": Object { - "type": "number", + "type": "long", }, "min": Object { - "type": "number", + "type": "long", }, }, "my_objects": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", diff --git a/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts index a101210185a638..b6ea9d49cf6d0b 100644 --- a/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts @@ -44,7 +44,7 @@ describe('checkMatchingMapping', () => { it('returns diff on mismatching parsedCollections and stored mapping', async () => { const mockSchema = await parseJsonFile('mock_schema.json'); const malformedParsedCollector = cloneDeep(parsedWorkingCollector); - const fieldMapping = { type: 'number' }; + const fieldMapping = { type: 'long' }; malformedParsedCollector[1].schema.value.flat = fieldMapping; const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); @@ -61,9 +61,9 @@ describe('checkMatchingMapping', () => { const mockSchema = await parseJsonFile('mock_schema.json'); const malformedParsedCollector = cloneDeep(parsedWorkingCollector); const collectorName = 'New Collector in town!'; - const collectorMapping = { some_usage: { type: 'number' } }; + const collectorMapping = { some_usage: { type: 'long' } }; malformedParsedCollector[1].collectorName = collectorName; - malformedParsedCollector[1].schema.value = { some_usage: { type: 'number' } }; + malformedParsedCollector[1].schema.value = { some_usage: { type: 'long' } }; const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); expect(diffs).toEqual({ diff --git a/packages/kbn-telemetry-tools/src/tools/manage_schema.ts b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts index 7721492fdb691a..e2bfca34a64878 100644 --- a/packages/kbn-telemetry-tools/src/tools/manage_schema.ts +++ b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts @@ -19,14 +19,9 @@ import { ParsedUsageCollection } from './ts_parser'; -export type AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float'; + +export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date'; export function compatibleSchemaTypes(type: AllowedSchemaTypes | 'array') { switch (type) { @@ -36,9 +31,12 @@ export function compatibleSchemaTypes(type: AllowedSchemaTypes | 'array') { return 'string'; case 'boolean': return 'boolean'; - case 'number': - case 'float': case 'long': + case 'integer': + case 'short': + case 'byte': + case 'double': + case 'float': return 'number'; case 'array': return 'array'; diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index d954ae0823cafe..b47adb29f1e684 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@babel/core": "^7.11.6", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/babel-preset": "1.0.0", "@kbn/optimizer": "1.0.0", "babel-loader": "^8.0.6", diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index b1b5d6e2b419eb..e6883be307bb24 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "24.0.0", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "@kbn/monaco": "1.0.0", diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index d3fb5bdf36194a..093cd29ba5507d 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -110,3 +110,10 @@ export type MethodKeysOf = { * Returns an object with public methods only. */ export type PublicMethodsOf = Pick>; + +/** + * Makes an object with readonly properties mutable. + */ +export type Writable = { + -readonly [K in keyof T]: T[K]; +}; diff --git a/src/core/typings.ts b/packages/kbn-utility-types/jest/index.ts similarity index 90% rename from src/core/typings.ts rename to packages/kbn-utility-types/jest/index.ts index f271d0b03e0d38..8b3d926b8e8df5 100644 --- a/src/core/typings.ts +++ b/packages/kbn-utility-types/jest/index.ts @@ -16,12 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -type DeeplyMockedKeys = { +export type DeeplyMockedKeys = { [P in keyof T]: T[P] extends (...args: any[]) => any ? jest.MockInstance, Parameters> : DeeplyMockedKeys; } & T; -type MockedKeys = { [P in keyof T]: jest.Mocked }; +export type MockedKeys = { [P in keyof T]: jest.Mocked }; diff --git a/packages/kbn-utility-types/jest/package.json b/packages/kbn-utility-types/jest/package.json new file mode 100644 index 00000000000000..9f92fc5e945280 --- /dev/null +++ b/packages/kbn-utility-types/jest/package.json @@ -0,0 +1,3 @@ +{ + "types": "../target/jest/index.d.ts" +} diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts b/packages/kbn-utility-types/test-d/method_keys_of.ts similarity index 74% rename from src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts rename to packages/kbn-utility-types/test-d/method_keys_of.ts index 925d560761a84c..46cb17122f416b 100644 --- a/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts +++ b/packages/kbn-utility-types/test-d/method_keys_of.ts @@ -16,11 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { ContextErrorMessage } from './context_error_message'; -export function createContextErrorMessageDirective(reactDirective: any) { - return reactDirective(ContextErrorMessage, [ - ['status', { watchDepth: 'reference' }], - ['reason', { watchDepth: 'reference' }], - ]); +import { expectType } from 'tsd'; +import { MethodKeysOf } from '../index'; + +class Test { + public name: string = ''; + getName() { + return this.name; + } + // @ts-ignore + private getDoubleName() { + return this.name.repeat(2); + } } + +expectType>('getName'); diff --git a/packages/kbn-utility-types/test-d/public_methods_of.ts b/packages/kbn-utility-types/test-d/public_methods_of.ts new file mode 100644 index 00000000000000..3f5d442342b15a --- /dev/null +++ b/packages/kbn-utility-types/test-d/public_methods_of.ts @@ -0,0 +1,50 @@ +/* + * 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 { expectAssignable, expectNotAssignable } from 'tsd'; +import { PublicMethodsOf } from '../index'; + +class Test { + public name: string = ''; + getName() { + return this.name; + } + // @ts-ignore + private getDoubleName() { + return this.name.repeat(2); + } +} + +expectAssignable>({ + getName() { + return ''; + }, +}); + +expectNotAssignable>({ + getName() { + return 1; + }, +}); + +expectNotAssignable>({ + getDoubleName() { + return 1; + }, +}); diff --git a/packages/kbn-utility-types/test-d/writable.ts b/packages/kbn-utility-types/test-d/writable.ts new file mode 100644 index 00000000000000..0771fa926c4ec2 --- /dev/null +++ b/packages/kbn-utility-types/test-d/writable.ts @@ -0,0 +1,29 @@ +/* + * 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 { expectAssignable } from 'tsd'; +import { Writable } from '../index'; + +type WritableArray = Writable; +expectAssignable(['1']); + +type WritableObject = Writable<{ + readonly name: string; +}>; +expectAssignable({ name: '1' }); diff --git a/packages/kbn-utility-types/tsconfig.json b/packages/kbn-utility-types/tsconfig.json index 79cf423fe78ace..c2d206526e6f43 100644 --- a/packages/kbn-utility-types/tsconfig.json +++ b/packages/kbn-utility-types/tsconfig.json @@ -7,10 +7,11 @@ "stripInternal": true, "declarationMap": true, "types": [ - "node" + "node", + "jest" ] }, - "include": ["index.ts", "test-d/**/*"], + "include": ["index.ts", "jest/**/*", "test-d/**/*"], "exclude": [ "target" ] diff --git a/src/core/public/apm_system.test.ts b/src/core/public/apm_system.test.ts index f88cdd899ef81c..be1cb6dc682080 100644 --- a/src/core/public/apm_system.test.ts +++ b/src/core/public/apm_system.test.ts @@ -18,6 +18,7 @@ */ jest.mock('@elastic/apm-rum'); +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { init, apm } from '@elastic/apm-rum'; import { ApmSystem } from './apm_system'; diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 9cd96763d2e791..6df8d57c8c5747 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { ChromeBadge, ChromeBrand, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; const createStartContractMock = () => { @@ -62,6 +63,8 @@ const createStartContractMock = () => { setBadge: jest.fn(), getBreadcrumbs$: jest.fn(), setBreadcrumbs: jest.fn(), + getBreadcrumbsAppendExtension$: jest.fn(), + setBreadcrumbsAppendExtension: jest.fn(), getHelpExtension$: jest.fn(), setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), @@ -75,6 +78,7 @@ const createStartContractMock = () => { startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name'])); startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge)); startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb])); + startContract.getBreadcrumbsAppendExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 0150554a609065..3783f3bd9b65e6 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -363,6 +363,25 @@ describe('start', () => { }); }); + describe('breadcrumbsAppendExtension$', () => { + it('updates the breadcrumbsAppendExtension$', async () => { + const { chrome, service } = await start(); + const promise = chrome.getBreadcrumbsAppendExtension$().pipe(toArray()).toPromise(); + + chrome.setBreadcrumbsAppendExtension({ content: (element) => () => {} }); + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + undefined, + Object { + "content": [Function], + }, + ] + `); + }); + }); + describe('custom nav link', () => { it('updates/emits the current custom nav link', async () => { const { chrome, service } = await start(); @@ -429,33 +448,33 @@ describe('start', () => { expect(docTitleResetSpy).toBeCalledTimes(1); await expect(promises).resolves.toMatchInlineSnapshot(` - Array [ - Array [ - undefined, - Object { - "appName": "App name", - }, - undefined, - ], - Array [ - Array [], - Array [ - Object { - "text": "App breadcrumb", - }, - ], - Array [], - ], - Array [ - undefined, - Object { - "text": "App badge", - "tooltip": "App tooltip", - }, - undefined, - ], - ] - `); + Array [ + Array [ + undefined, + Object { + "appName": "App name", + }, + undefined, + ], + Array [ + Array [], + Array [ + Object { + "text": "App breadcrumb", + }, + ], + Array [], + ], + Array [ + undefined, + Object { + "text": "App badge", + "tooltip": "App tooltip", + }, + undefined, + ], + ] + `); }); }); }); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index b01f120b81305d..b39c83498859ce 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -24,6 +24,7 @@ import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject } import { flatMap, map, takeUntil } from 'rxjs/operators'; import { parse } from 'url'; import { EuiLink } from '@elastic/eui'; +import { MountPoint } from '../types'; import { mountReactNode } from '../utils/mount'; import { InternalApplicationStart } from '../application'; import { DocLinksStart } from '../doc_links'; @@ -58,6 +59,11 @@ export interface ChromeBrand { /** @public */ export type ChromeBreadcrumb = EuiBreadcrumb; +/** @public */ +export interface ChromeBreadcrumbsAppendExtension { + content: MountPoint; +} + /** @public */ export interface ChromeHelpExtension { /** @@ -146,6 +152,9 @@ export class ChromeService { const applicationClasses$ = new BehaviorSubject>(new Set()); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); + const breadcrumbsAppendExtension$ = new BehaviorSubject< + ChromeBreadcrumbsAppendExtension | undefined + >(undefined); const badge$ = new BehaviorSubject(undefined); const customNavLink$ = new BehaviorSubject(undefined); const helpSupportUrl$ = new BehaviorSubject(KIBANA_ASK_ELASTIC_LINK); @@ -225,6 +234,7 @@ export class ChromeService { badge$={badge$.pipe(takeUntil(this.stop$))} basePath={http.basePath} breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))} + breadcrumbsAppendExtension$={breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$))} customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} kibanaDocLink={docLinks.links.kibana} forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()} @@ -290,6 +300,14 @@ export class ChromeService { breadcrumbs$.next(newBreadcrumbs); }, + getBreadcrumbsAppendExtension$: () => breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$)), + + setBreadcrumbsAppendExtension: ( + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension + ) => { + breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension); + }, + getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)), setHelpExtension: (helpExtension?: ChromeHelpExtension) => { @@ -431,6 +449,18 @@ export interface ChromeStart { */ setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + /** + * Get an observable of the current extension appended to breadcrumbs + */ + getBreadcrumbsAppendExtension$(): Observable; + + /** + * Mount an element next to the last breadcrumb + */ + setBreadcrumbsAppendExtension( + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension + ): void; + /** * Get an observable of the current custom nav link */ diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 34c5f213a7221c..ee2fcbd5078af1 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -300,6 +300,55 @@ exports[`Header renders 1`] = ` "thrownError": null, } } + breadcrumbsAppendExtension$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -5029,6 +5078,55 @@ exports[`Header renders 1`] = ` "thrownError": null, } } + breadcrumbsAppendExtension$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } > ; badge$: Observable; breadcrumbs$: Observable; + breadcrumbsAppendExtension$: Observable; customNavLink$: Observable; homeHref: string; isVisible$: Observable; @@ -169,6 +170,7 @@ export function Header({ diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 7fe2c910870903..64401171d142a7 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -22,12 +22,17 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); const wrapper = mount( - + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); @@ -39,4 +44,29 @@ describe('HeaderBreadcrumbs', () => { wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); + + it('renders breadcrumbs extension', () => { + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const breadcrumbsAppendExtension$ = new BehaviorSubject< + undefined | ChromeBreadcrumbsAppendExtension + >({ + content: (root: HTMLDivElement) => { + root.innerHTML = '
__render__
'; + return () => (root.innerHTML = ''); + }, + }); + + const wrapper = mount( + + ); + + expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeDefined(); + act(() => breadcrumbsAppendExtension$.next(undefined)); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeNull(); + }); }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index 52412f8990c7a9..d52faa87cfecd2 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -22,16 +22,19 @@ import classNames from 'classnames'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { ChromeBreadcrumb } from '../../chrome_service'; +import { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; +import { HeaderExtension } from './header_extension'; interface Props { appTitle$: Observable; breadcrumbs$: Observable; + breadcrumbsAppendExtension$: Observable; } -export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { +export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$, breadcrumbsAppendExtension$ }: Props) { const appTitle = useObservable(appTitle$, 'Kibana'); const breadcrumbs = useObservable(breadcrumbs$, []); + const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); let crumbs = breadcrumbs; if (breadcrumbs.length === 0 && appTitle) { @@ -48,5 +51,15 @@ export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { ), })); + if (breadcrumbsAppendExtension) { + const lastCrumb = crumbs[crumbs.length - 1]; + lastCrumb.text = ( + <> + {lastCrumb.text} + + + ); + } + return ; } diff --git a/src/core/public/chrome/ui/header/header_extension.test.tsx b/src/core/public/chrome/ui/header/header_extension.test.tsx index 3d5678b8bb7efc..ba00c74b81cfad 100644 --- a/src/core/public/chrome/ui/header/header_extension.test.tsx +++ b/src/core/public/chrome/ui/header/header_extension.test.tsx @@ -32,6 +32,14 @@ describe('HeaderExtension', () => { expect(divNode).toBeInstanceOf(HTMLElement); }); + it('calls navControl.render with div node as inlineBlock', () => { + const renderSpy = jest.fn(); + mount(); + + const [divNode] = renderSpy.mock.calls[0]; + expect(divNode).toHaveAttribute('style', 'display: inline-block;'); + }); + it('calls unrender callback when unmounted', () => { const unrenderSpy = jest.fn(); const render = () => unrenderSpy; diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx index 76413a0ea03175..97cf38f44c3f10 100644 --- a/src/core/public/chrome/ui/header/header_extension.tsx +++ b/src/core/public/chrome/ui/header/header_extension.tsx @@ -22,6 +22,7 @@ import { MountPoint } from '../../../types'; interface Props { extension?: MountPoint; + display?: 'block' | 'inlineBlock'; } export class HeaderExtension extends React.Component { @@ -46,7 +47,12 @@ export class HeaderExtension extends React.Component { } public render() { - return
; + return ( +
+ ); } private renderExtension() { diff --git a/src/core/public/chrome/ui/header/recent_links.tsx b/src/core/public/chrome/ui/header/recent_links.tsx deleted file mode 100644 index 0e068a3465b0e2..00000000000000 --- a/src/core/public/chrome/ui/header/recent_links.tsx +++ /dev/null @@ -1,52 +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 React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiNavDrawerGroup } from '@elastic/eui'; -import { RecentNavLink } from './nav_link'; - -interface Props { - recentNavLinks: RecentNavLink[]; -} - -export function RecentLinks({ recentNavLinks }: Props) { - return ( - - ); -} diff --git a/src/core/public/notifications/notifications_service.mock.ts b/src/core/public/notifications/notifications_service.mock.ts index 990ab479d35c3c..b69b4604d0788e 100644 --- a/src/core/public/notifications/notifications_service.mock.ts +++ b/src/core/public/notifications/notifications_service.mock.ts @@ -17,6 +17,7 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { MockedKeys } from '@kbn/utility-types/jest'; import { NotificationsService, NotificationsSetup, diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts index 66ba36b20b45cd..595813972b8bc4 100644 --- a/src/core/public/overlays/overlay_service.mock.ts +++ b/src/core/public/overlays/overlay_service.mock.ts @@ -17,6 +17,7 @@ * under the License. */ import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { OverlayService, OverlayStart } from './overlay_service'; import { overlayBannersServiceMock } from './banners/banners_service.mock'; import { overlayFlyoutServiceMock } from './flyout/flyout_service.mock'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f52d5b6fbd6a5d..4babec38a936e1 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -343,6 +343,8 @@ export interface ChromeStart { getBadge$(): Observable; getBrand$(): Observable; getBreadcrumbs$(): Observable; + // Warning: (ae-forgotten-export) The symbol "ChromeBreadcrumbsAppendExtension" needs to be exported by the entry point index.d.ts + getBreadcrumbsAppendExtension$(): Observable; getCustomNavLink$(): Observable | undefined>; getHelpExtension$(): Observable; getIsNavDrawerLocked$(): Observable; @@ -355,6 +357,7 @@ export interface ChromeStart { setBadge(badge?: ChromeBadge): void; setBrand(brand: ChromeBrand): void; setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void; setCustomNavLink(newCustomNavLink?: Partial): void; setHelpExtension(helpExtension?: ChromeHelpExtension): void; setHelpSupportUrl(url: string): void; diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index bbf04783278f7e..4df0b2dfb9d8a7 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -18,6 +18,7 @@ */ import { REPO_ROOT } from '@kbn/dev-utils'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { CoreContext } from './core_context'; import { Env, IConfigService } from './config'; import { configServiceMock, getEnvOptions } from './config/mocks'; diff --git a/src/core/server/elasticsearch/client/mocks.ts b/src/core/server/elasticsearch/client/mocks.ts index fb2826c7877180..bedd0e65c5a832 100644 --- a/src/core/server/elasticsearch/client/mocks.ts +++ b/src/core/server/elasticsearch/client/mocks.ts @@ -18,6 +18,7 @@ */ import { Client, ApiResponse } from '@elastic/elasticsearch'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { ElasticsearchClient } from './types'; import { ICustomClusterClient } from './cluster_client'; diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 6aad232cf42b64..d615e799f383f0 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { parse as parseUrl } from 'url'; import { Request } from 'hapi'; import { merge } from 'lodash'; import { Socket } from 'net'; @@ -72,6 +73,7 @@ function createKibanaRequestMock

({ auth = { isAuthenticated: true }, }: RequestFixtureOptions = {}) { const queryString = stringify(query, { sort: false }); + const url = parseUrl(`${path}${queryString ? `?${queryString}` : ''}`); return KibanaRequest.from( createRawRequestMock({ @@ -83,12 +85,7 @@ function createKibanaRequestMock

({ payload: body, path, method, - url: { - path, - pathname: path, - query: queryString, - search: queryString ? `?${queryString}` : queryString, - }, + url, route: { settings: { tags: routeTags, auth: routeAuthRequired, app: kibanaRouteOptions }, }, @@ -121,6 +118,11 @@ interface DeepPartialArray extends Array> {} type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; function createRawRequestMock(customization: DeepPartial = {}) { + const pathname = customization.url?.pathname || '/'; + const path = `${pathname}${customization.url?.search || ''}`; + const url = Object.assign({ pathname, path, href: path }, customization.url); + + // @ts-expect-error _core isn't supposed to be accessed - remove once we upgrade to hapi v18 return merge( {}, { @@ -129,17 +131,21 @@ function createRawRequestMock(customization: DeepPartial = {}) { isAuthenticated: true, }, headers: {}, - path: '/', + path, route: { settings: {} }, - url: { - href: '/', - }, + url, raw: { req: { - url: '/', + url: path, socket: {}, }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, customization ) as Request; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 2440f2b1da0bd2..d94bce12fb4397 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -271,7 +271,7 @@ export class HttpServer { } this.registerOnPreRouting((request, response, toolkit) => { - const oldUrl = request.url.href!; + const oldUrl = request.url.pathname + request.url.search; const newURL = basePathService.remove(oldUrl); const shouldRedirect = newURL !== oldUrl; if (shouldRedirect) { diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 01817b29de8ac1..37401a2c24ccd1 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -124,7 +124,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/login', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.rewriteUrl('/login')); @@ -143,7 +149,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/reroute-2', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.rewriteUrl('/reroute-1')); @@ -163,7 +175,13 @@ describe('OnPreRouting', () => { const router = createRouter('/'); router.get({ path: '/login', validate: false }, (context, req, res) => { - return res.ok({ body: { rewrittenUrl: req.rewrittenUrl?.path } }); + return res.ok({ + body: { + rewrittenUrl: req.rewrittenUrl + ? `${req.rewrittenUrl.pathname}${req.rewrittenUrl.search}` + : undefined, + }, + }); }); registerOnPreRouting((req, res, t) => t.next()); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index e19c348511f1aa..ad228d9b0bb9d2 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -384,7 +384,7 @@ describe('Options', () => { }); describe('idleSocket', () => { - it('should timeout if payload sending has too long of an idle period', async () => { + it.skip('should timeout if payload sending has too long of an idle period', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); diff --git a/src/core/server/http/lifecycle/on_pre_routing.ts b/src/core/server/http/lifecycle/on_pre_routing.ts index 92ae1f0b7bbdfe..e553f113a7cf8d 100644 --- a/src/core/server/http/lifecycle/on_pre_routing.ts +++ b/src/core/server/http/lifecycle/on_pre_routing.ts @@ -17,6 +17,7 @@ * under the License. */ +import { URL } from 'url'; import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; import { Logger } from '../../logging'; import { @@ -110,10 +111,30 @@ export function adoptToHapiOnRequest(fn: OnPreRoutingHandler, log: Logger) { if (preRoutingResult.isRewriteUrl(result)) { const appState = request.app as KibanaRequestState; - appState.rewrittenUrl = appState.rewrittenUrl ?? request.url; + appState.rewrittenUrl = + // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 + appState.rewrittenUrl ?? new URL(request.url.href!, request._core.info.uri); const { url } = result; - request.setUrl(url); + + // TODO: Remove once we upgrade to Node.js 12! + // + // Warning: The following for-loop took 10 days to write, and is a hack + // to force V8 to make a copy of the string in memory. + // + // The reason why we need this is because of what appears to be a bug + // in V8 that caused some URL paths to not be routed correctly once + // `request.setUrl` was called with the path. + // + // The details can be seen in this discussion on Twitter: + // https://twitter.com/wa7son/status/1319992632366518277 + let urlCopy = ''; + for (let i = 0; i < url.length; i++) { + urlCopy += url[i]; + } + + request.setUrl(urlCopy); + // We should update raw request as well since it can be proxied to the old platform request.raw.req.url = url; return responseToolkit.continue; diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 2d0e8d6c1a6ad9..561bf742050c34 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Url } from 'url'; +import { URL } from 'url'; import uuid from 'uuid'; import { Request, RouteOptionsApp, ApplicationState } from 'hapi'; import { Observable, fromEvent, merge } from 'rxjs'; @@ -45,7 +45,7 @@ export interface KibanaRouteOptions extends RouteOptionsApp { export interface KibanaRequestState extends ApplicationState { requestId: string; requestUuid: string; - rewrittenUrl?: Url; + rewrittenUrl?: URL; } /** @@ -163,7 +163,7 @@ export class KibanaRequest< */ public readonly uuid: string; /** a WHATWG URL standard object. */ - public readonly url: Url; + public readonly url: URL; /** matched route details */ public readonly route: RecursiveReadonly>; /** @@ -190,7 +190,7 @@ export class KibanaRequest< /** * URL rewritten in onPreRouting request interceptor. */ - public readonly rewrittenUrl?: Url; + public readonly rewrittenUrl?: URL; /** @internal */ protected readonly [requestSymbol]: Request; @@ -212,7 +212,8 @@ export class KibanaRequest< this.uuid = appState?.requestUuid ?? uuid.v4(); this.rewrittenUrl = appState?.rewrittenUrl; - this.url = request.url; + // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 + this.url = new URL(request.url.href!, request._core.info.uri); this.headers = deepFreeze({ ...request.headers }); this.isSystemRequest = request.headers['kbn-system-request'] === 'true' || @@ -304,8 +305,8 @@ export class KibanaRequest< if (authOptions === false) return false; throw new Error( `unexpected authentication options: ${JSON.stringify(authOptions)} for route: ${ - this.url.href - }` + this.url.pathname + }${this.url.search}` ); } } diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index e47d06409894e1..7551f53ab27de1 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -19,6 +19,7 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { ByteSizeValue } from '@kbn/config-schema'; +import type { MockedKeys } from '@kbn/utility-types/jest'; import { PluginInitializerContext, CoreSetup, CoreStart, StartServicesAccessor } from '.'; import { loggingSystemMock } from './logging/logging_system.mock'; import { loggingServiceMock } from './logging/logging_service.mock'; diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts index 1d5ce5625bf48e..862d11cfa663aa 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts @@ -78,7 +78,7 @@ describe('getObjectReferencesToFetch()', () => { `); }); - test(`doesn't deal with circular dependencies`, () => { + test('does not fail on circular dependencies', () => { const map = new Map(); map.set('index-pattern:1', { id: '1', @@ -527,7 +527,7 @@ describe('injectNestedDependencies', () => { `); }); - test(`doesn't deal with circular dependencies`, async () => { + test('does not fail on circular dependencies', async () => { const savedObjects = [ { id: '2', diff --git a/src/core/server/saved_objects/export/sort_objects.test.ts b/src/core/server/saved_objects/export/sort_objects.test.ts index 7b6698dfaf8877..cd116d767b0c32 100644 --- a/src/core/server/saved_objects/export/sort_objects.test.ts +++ b/src/core/server/saved_objects/export/sort_objects.test.ts @@ -46,27 +46,27 @@ describe('sortObjects()', () => { }, ]; expect(sortObjects(docs)).toMatchInlineSnapshot(` -Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref1", - "type": "index-pattern", - }, - ], - "type": "search", - }, -] -`); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "index-pattern", + }, + ], + "type": "search", + }, + ] + `); }); test('should not mutate parameter', () => { @@ -91,49 +91,49 @@ Array [ }, ]; expect(sortObjects(docs)).toMatchInlineSnapshot(` -Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref1", - "type": "index-pattern", - }, - ], - "type": "search", - }, -] -`); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "index-pattern", + }, + ], + "type": "search", + }, + ] + `); expect(docs).toMatchInlineSnapshot(` -Array [ - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref1", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, -] -`); + Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + ] + `); }); test('should sort unordered array', () => { @@ -199,71 +199,71 @@ Array [ }, ]; expect(sortObjects(docs)).toMatchInlineSnapshot(` -Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref1", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "attributes": Object {}, - "id": "3", - "references": Array [ - Object { - "id": "2", - "name": "ref1", - "type": "search", - }, - ], - "type": "visualization", - }, - Object { - "attributes": Object {}, - "id": "4", - "references": Array [ - Object { - "id": "1", - "name": "ref1", - "type": "index-pattern", - }, - ], - "type": "visualization", - }, - Object { - "attributes": Object {}, - "id": "5", - "references": Array [ - Object { - "id": "3", - "name": "ref1", - "type": "visualization", - }, - Object { - "id": "4", - "name": "ref2", - "type": "visualization", - }, - ], - "type": "dashboard", - }, -] -`); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "3", + "references": Array [ + Object { + "id": "2", + "name": "ref1", + "type": "search", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object {}, + "id": "4", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "index-pattern", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object {}, + "id": "5", + "references": Array [ + Object { + "id": "3", + "name": "ref1", + "type": "visualization", + }, + Object { + "id": "4", + "name": "ref2", + "type": "visualization", + }, + ], + "type": "dashboard", + }, + ] + `); }); - test('detects circular dependencies', () => { + test('should not fail on circular dependencies', () => { const docs = [ { id: '1', @@ -290,8 +290,149 @@ Array [ ], }, ]; - expect(() => sortObjects(docs)).toThrowErrorMatchingInlineSnapshot( - `"circular reference: [foo:1] ref-> [foo:2] ref-> [foo:1]"` - ); + + expect(sortObjects(docs)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref1", + "type": "foo", + }, + ], + "type": "foo", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [ + Object { + "id": "2", + "name": "ref1", + "type": "foo", + }, + ], + "type": "foo", + }, + ] + `); + }); + test('should not fail on complex circular dependencies', () => { + const docs = [ + { + id: '1', + type: 'foo', + attributes: {}, + references: [ + { + name: 'ref12', + type: 'foo', + id: '2', + }, + { + name: 'ref13', + type: 'baz', + id: '3', + }, + ], + }, + { + id: '2', + type: 'foo', + attributes: {}, + references: [ + { + name: 'ref13', + type: 'foo', + id: '3', + }, + ], + }, + { + id: '3', + type: 'baz', + attributes: {}, + references: [ + { + name: 'ref13', + type: 'xyz', + id: '4', + }, + ], + }, + { + id: '4', + type: 'xyz', + attributes: {}, + references: [ + { + name: 'ref14', + type: 'foo', + id: '1', + }, + ], + }, + ]; + + expect(sortObjects(docs)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "3", + "name": "ref13", + "type": "foo", + }, + ], + "type": "foo", + }, + Object { + "attributes": Object {}, + "id": "4", + "references": Array [ + Object { + "id": "1", + "name": "ref14", + "type": "foo", + }, + ], + "type": "xyz", + }, + Object { + "attributes": Object {}, + "id": "3", + "references": Array [ + Object { + "id": "4", + "name": "ref13", + "type": "xyz", + }, + ], + "type": "baz", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [ + Object { + "id": "2", + "name": "ref12", + "type": "foo", + }, + Object { + "id": "3", + "name": "ref13", + "type": "baz", + }, + ], + "type": "foo", + }, + ] + `); }); }); diff --git a/src/core/server/saved_objects/export/sort_objects.ts b/src/core/server/saved_objects/export/sort_objects.ts index 64bab9f43bf147..ec83b687527fc5 100644 --- a/src/core/server/saved_objects/export/sort_objects.ts +++ b/src/core/server/saved_objects/export/sort_objects.ts @@ -17,7 +17,6 @@ * under the License. */ -import Boom from 'boom'; import { SavedObject } from '../types'; export function sortObjects(savedObjects: SavedObject[]): SavedObject[] { @@ -30,11 +29,7 @@ export function sortObjects(savedObjects: SavedObject[]): SavedObject[] { function includeObjects(objects: SavedObject[]) { for (const object of objects) { if (path.has(object)) { - throw Boom.badRequest( - `circular reference: ${[...path, object] - .map((obj) => `[${obj.type}:${obj.id}]`) - .join(' ref-> ')}` - ); + continue; } const refdObjects = object.references diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d9dc46d2cad997..914b5fbdb5196f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -162,7 +162,7 @@ import { Type } from '@kbn/config-schema'; import { TypeOf } from '@kbn/config-schema'; import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; -import { Url } from 'url'; +import { URL } from 'url'; // @public export interface AppCategory { @@ -1007,11 +1007,11 @@ export class KibanaRequest>; // (undocumented) readonly socket: IKibanaSocket; - readonly url: Url; + readonly url: URL; readonly uuid: string; } diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index a3531057767d4e..4281559c9aa140 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -13,8 +13,7 @@ "types/**/*", "test_helpers/**/*", "utils/**/*", - "index.ts", - "typings.ts" + "index.ts" ], "references": [ { "path": "../test_utils/" } diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh index 1c679bdb40b59c..939226b565f791 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_install.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_install.sh @@ -7,14 +7,17 @@ set_chmod() { chmod -f 660 ${KBN_PATH_CONF}/kibana.yml || true chmod -f 2750 <%= dataDir %> || true chmod -f 2750 ${KBN_PATH_CONF} || true + chmod -f 2750 <%= logDir %> || true } set_chown() { + chown <%= user %>:<%= group %> <%= logDir %> chown -R <%= user %>:<%= group %> <%= dataDir %> chown -R root:<%= group %> ${KBN_PATH_CONF} } -set_access() { +setup() { + [ ! -d "<%= logDir %>" ] && mkdir "<%= logDir %>" set_chmod set_chown } @@ -35,7 +38,7 @@ case $1 in IS_UPGRADE=true fi - set_access + setup ;; abort-deconfigure|abort-upgrade|abort-remove) ;; @@ -55,7 +58,7 @@ case $1 in IS_UPGRADE=true fi - set_access + setup ;; *) diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts index b5169ec3d43b69..e5de760ea11d06 100644 --- a/src/dev/build/tasks/os_packages/run_fpm.ts +++ b/src/dev/build/tasks/os_packages/run_fpm.ts @@ -109,6 +109,8 @@ export async function runFpm( `pluginsDir=/usr/share/kibana/plugins`, '--template-value', `dataDir=/var/lib/kibana`, + '--template-value', + `logDir=/var/log/kibana`, // config and data directories are copied to /usr/share and /var/lib // below, so exclude them from the main package source located in @@ -132,7 +134,6 @@ export async function runFpm( `${resolveWithTrailingSlash(fromBuild('data'))}=/var/lib/kibana/`, // copy package configurations - `${resolveWithTrailingSlash(__dirname, 'service_templates/sysv/')}=/`, `${resolveWithTrailingSlash(__dirname, 'service_templates/systemd/')}=/`, ]; diff --git a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana similarity index 100% rename from src/dev/build/tasks/os_packages/service_templates/sysv/etc/default/kibana rename to src/dev/build/tasks/os_packages/service_templates/systemd/etc/default/kibana diff --git a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service index e66e0e7c8dfb50..05724db8799f32 100644 --- a/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service +++ b/src/dev/build/tasks/os_packages/service_templates/systemd/etc/systemd/system/kibana.service @@ -1,21 +1,32 @@ [Unit] Description=Kibana +Documentation=https://www.elastic.co +Wants=network-online.target +After=network-online.target [Service] Type=simple User=kibana Group=kibana -# Load env vars from /etc/default/ and /etc/sysconfig/ if they exist. -# Prefixing the path with '-' makes it try to load, but if the file doesn't -# exist, it continues onward. + +Environment=KBN_HOME=/usr/share/kibana +Environment=KBN_PATH_CONF=/etc/kibana + EnvironmentFile=-/etc/default/kibana EnvironmentFile=-/etc/sysconfig/kibana -ExecStart=/usr/share/kibana/bin/kibana + +ExecStart=/usr/share/kibana/bin/kibana --logging.dest="/var/log/kibana/kibana.log" + Restart=on-failure RestartSec=3 + StartLimitBurst=3 StartLimitInterval=60 -WorkingDirectory=/ + +WorkingDirectory=/usr/share/kibana + +StandardOutput=journal +StandardError=inherit [Install] WantedBy=multi-user.target diff --git a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana b/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana deleted file mode 100755 index c13676ef031b0a..00000000000000 --- a/src/dev/build/tasks/os_packages/service_templates/sysv/etc/init.d/kibana +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/sh -# Init script for kibana -# Maintained by -# Generated by pleaserun. -# Implemented based on LSB Core 3.1: -# * Sections: 20.2, 20.3 -# -### BEGIN INIT INFO -# Provides: kibana -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: -# Description: Kibana -### END INIT INFO - -# -# Source function libraries if present. -# (It improves integration with systemd) -# -# Red Hat -if [ -f /etc/rc.d/init.d/functions ]; then - . /etc/rc.d/init.d/functions - -# Debian -elif [ -f /lib/lsb/init-functions ]; then - . /lib/lsb/init-functions - -# SUSE -elif [ -f /etc/rc.status ]; then - . /etc/rc.status - rc_reset -fi - -name=kibana -program=/usr/share/kibana/bin/kibana -pidfile="/var/run/kibana/$name.pid" - -[ -r /etc/default/$name ] && . /etc/default/$name -[ -r /etc/sysconfig/$name ] && . /etc/sysconfig/$name - -export KBN_PATH_CONF -export NODE_OPTIONS - -[ -z "$nice" ] && nice=0 - -trace() { - logger -t "/etc/init.d/kibana" "$@" -} - -emit() { - trace "$@" - echo "$@" -} - -start() { - [ ! -d "/var/log/kibana/" ] && mkdir "/var/log/kibana/" - chown "$user":"$group" "/var/log/kibana/" - chmod 2750 "/var/log/kibana/" - - [ ! -d "/var/run/kibana/" ] && mkdir "/var/run/kibana/" - chown "$user":"$group" "/var/run/kibana/" - chmod 755 "/var/run/kibana/" - - chroot --userspec "$user":"$group" "$chroot" sh -c " - - cd \"$chdir\" - exec \"$program\" - " >> /var/log/kibana/kibana.stdout 2>> /var/log/kibana/kibana.stderr & - - # Generate the pidfile from here. If we instead made the forked process - # generate it there will be a race condition between the pidfile writing - # and a process possibly asking for status. - echo $! > $pidfile - - emit "$name started" - return 0 -} - -stop() { - # Try a few times to kill TERM the program - if status ; then - pid=$(cat "$pidfile") - trace "Killing $name (pid $pid) with SIGTERM" - kill -TERM $pid - # Wait for it to exit. - for i in 1 2 3 4 5 ; do - trace "Waiting $name (pid $pid) to die..." - status || break - sleep 1 - done - if status ; then - if [ "$KILL_ON_STOP_TIMEOUT" -eq 1 ] ; then - trace "Timeout reached. Killing $name (pid $pid) with SIGKILL. This may result in data loss." - kill -KILL $pid - emit "$name killed with SIGKILL." - else - emit "$name stop failed; still running." - fi - else - emit "$name stopped." - fi - fi -} - -status() { - if [ -f "$pidfile" ] ; then - pid=$(cat "$pidfile") - if ps -p $pid > /dev/null 2> /dev/null ; then - # process by this pid is running. - # It may not be our pid, but that's what you get with just pidfiles. - # TODO(sissel): Check if this process seems to be the same as the one we - # expect. It'd be nice to use flock here, but flock uses fork, not exec, - # so it makes it quite awkward to use in this case. - return 0 - else - return 2 # program is dead but pid file exists - fi - else - return 3 # program is not running - fi -} - -force_stop() { - if status ; then - stop - status && kill -KILL $(cat "$pidfile") - fi -} - - -case "$1" in - force-start|start|stop|force-stop|restart) - trace "Attempting '$1' on kibana" - ;; -esac - -case "$1" in - force-start) - PRESTART=no - exec "$0" start - ;; - start) - status - code=$? - if [ $code -eq 0 ]; then - emit "$name is already running" - exit $code - else - start - exit $? - fi - ;; - stop) stop ;; - force-stop) force_stop ;; - status) - status - code=$? - if [ $code -eq 0 ] ; then - emit "$name is running" - else - emit "$name is not running" - fi - exit $code - ;; - restart) - - stop && start - ;; - *) - echo "Usage: $SCRIPTNAME {start|force-start|stop|force-start|force-stop|status|restart}" >&2 - exit 3 - ;; -esac - -exit $? diff --git a/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts b/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts index af9fef0bbd2970..791c366199d74c 100644 --- a/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts +++ b/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts @@ -49,7 +49,7 @@ const someSchema: MakeSchemaFrom> = { const someOtherSchema: MakeSchemaFrom> = { my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, diff --git a/src/fixtures/telemetry_collectors/working_collector.ts b/src/fixtures/telemetry_collectors/working_collector.ts index 0a3bf49638a7b3..f9cb3bb568673e 100644 --- a/src/fixtures/telemetry_collectors/working_collector.ts +++ b/src/fixtures/telemetry_collectors/working_collector.ts @@ -85,7 +85,7 @@ export const myCollector = makeUsageCollector({ }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, @@ -93,17 +93,17 @@ export const myCollector = makeUsageCollector({ type: 'array', items: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, }, my_str_array: { type: 'array', items: { type: 'keyword' } }, my_index_signature_prop: { - count: { type: 'number' }, - avg: { type: 'number' }, - max: { type: 'number' }, - min: { type: 'number' }, + count: { type: 'long' }, + avg: { type: 'float' }, + max: { type: 'long' }, + min: { type: 'long' }, }, }, }); diff --git a/src/legacy/server/i18n/get_kibana_translation_paths.test.ts b/src/legacy/server/i18n/get_kibana_translation_paths.test.ts new file mode 100644 index 00000000000000..0f202c4d433c0c --- /dev/null +++ b/src/legacy/server/i18n/get_kibana_translation_paths.test.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { I18N_RC } from './constants'; +import { fromRoot } from '../../../core/server/utils'; + +jest.mock('./get_translation_paths', () => ({ getTranslationPaths: jest.fn() })); +import { getKibanaTranslationPaths } from './get_kibana_translation_paths'; +import { getTranslationPaths as mockGetTranslationPaths } from './get_translation_paths'; + +describe('getKibanaTranslationPaths', () => { + const mockConfig = { get: jest.fn() }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('calls getTranslationPaths against kibana root and kibana-extra', async () => { + mockConfig.get.mockReturnValue([]); + await getKibanaTranslationPaths(mockConfig); + expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(1, { + cwd: fromRoot('.'), + glob: `*/${I18N_RC}`, + }); + + expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(2, { + cwd: fromRoot('../kibana-extra'), + glob: `*/${I18N_RC}`, + }); + }); + + it('calls getTranslationPaths for each config returned in plugin.paths and plugins.scanDirs', async () => { + mockConfig.get.mockReturnValueOnce(['a', 'b']).mockReturnValueOnce(['c']); + await getKibanaTranslationPaths(mockConfig); + expect(mockConfig.get).toHaveBeenNthCalledWith(1, 'plugins.paths'); + expect(mockConfig.get).toHaveBeenNthCalledWith(2, 'plugins.scanDirs'); + + expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(2, { cwd: 'a', glob: I18N_RC }); + expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(3, { cwd: 'b', glob: I18N_RC }); + expect(mockGetTranslationPaths).toHaveBeenNthCalledWith(4, { cwd: 'c', glob: `*/${I18N_RC}` }); + }); +}); diff --git a/src/legacy/server/i18n/get_kibana_translation_paths.ts b/src/legacy/server/i18n/get_kibana_translation_paths.ts new file mode 100644 index 00000000000000..d7f77d3185ba40 --- /dev/null +++ b/src/legacy/server/i18n/get_kibana_translation_paths.ts @@ -0,0 +1,42 @@ +/* + * 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 { KibanaConfig } from '../kbn_server'; +import { fromRoot } from '../../../core/server/utils'; +import { I18N_RC } from './constants'; +import { getTranslationPaths } from './get_translation_paths'; + +export async function getKibanaTranslationPaths(config: Pick) { + return await Promise.all([ + getTranslationPaths({ + cwd: fromRoot('.'), + glob: `*/${I18N_RC}`, + }), + ...(config.get('plugins.paths') as string[]).map((cwd) => + getTranslationPaths({ cwd, glob: I18N_RC }) + ), + ...(config.get('plugins.scanDirs') as string[]).map((cwd) => + getTranslationPaths({ cwd, glob: `*/${I18N_RC}` }) + ), + getTranslationPaths({ + cwd: fromRoot('../kibana-extra'), + glob: `*/${I18N_RC}`, + }), + ]); +} diff --git a/src/legacy/server/i18n/get_translations_path.ts b/src/legacy/server/i18n/get_translation_paths.ts similarity index 100% rename from src/legacy/server/i18n/get_translations_path.ts rename to src/legacy/server/i18n/get_translation_paths.ts diff --git a/src/legacy/server/i18n/i18n_mixin.ts b/src/legacy/server/i18n/i18n_mixin.ts new file mode 100644 index 00000000000000..4f77fa8df96cd9 --- /dev/null +++ b/src/legacy/server/i18n/i18n_mixin.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n, i18nLoader } from '@kbn/i18n'; +import { basename } from 'path'; +import { Server } from 'hapi'; +import type { UsageCollectionSetup } from '../../../plugins/usage_collection/server'; +import { getKibanaTranslationPaths } from './get_kibana_translation_paths'; +import KbnServer, { KibanaConfig } from '../kbn_server'; +import { registerLocalizationUsageCollector } from './localization'; + +export async function i18nMixin( + kbnServer: KbnServer, + server: Server, + config: Pick +) { + const locale = config.get('i18n.locale') as string; + + const translationPaths = await getKibanaTranslationPaths(config); + + const currentTranslationPaths = ([] as string[]) + .concat(...translationPaths) + .filter((translationPath) => basename(translationPath, '.json') === locale); + i18nLoader.registerTranslationFiles(currentTranslationPaths); + + const translations = await i18nLoader.getTranslationsByLocale(locale); + i18n.init( + Object.freeze({ + locale, + ...translations, + }) + ); + + const getTranslationsFilePaths = () => currentTranslationPaths; + + server.decorate('server', 'getTranslationsFilePaths', getTranslationsFilePaths); + + if (kbnServer.newPlatform.setup.plugins.usageCollection) { + const { usageCollection } = kbnServer.newPlatform.setup.plugins as { + usageCollection: UsageCollectionSetup; + }; + registerLocalizationUsageCollector(usageCollection, { + getLocale: () => config.get('i18n.locale') as string, + getTranslationsFilePaths, + }); + } +} diff --git a/src/legacy/server/i18n/index.ts b/src/legacy/server/i18n/index.ts index 61caefb2fb5996..a7ef49f44532c7 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/legacy/server/i18n/index.ts @@ -17,60 +17,4 @@ * under the License. */ -import { i18n, i18nLoader } from '@kbn/i18n'; -import { basename } from 'path'; -import { Server } from 'hapi'; -import { fromRoot } from '../../../core/server/utils'; -import type { UsageCollectionSetup } from '../../../plugins/usage_collection/server'; -import { getTranslationPaths } from './get_translations_path'; -import { I18N_RC } from './constants'; -import KbnServer, { KibanaConfig } from '../kbn_server'; -import { registerLocalizationUsageCollector } from './localization'; - -export async function i18nMixin(kbnServer: KbnServer, server: Server, config: KibanaConfig) { - const locale = config.get('i18n.locale') as string; - - const translationPaths = await Promise.all([ - getTranslationPaths({ - cwd: fromRoot('.'), - glob: `*/${I18N_RC}`, - }), - ...(config.get('plugins.paths') as string[]).map((cwd) => - getTranslationPaths({ cwd, glob: I18N_RC }) - ), - ...(config.get('plugins.scanDirs') as string[]).map((cwd) => - getTranslationPaths({ cwd, glob: `*/${I18N_RC}` }) - ), - getTranslationPaths({ - cwd: fromRoot('../kibana-extra'), - glob: `*/${I18N_RC}`, - }), - ]); - - const currentTranslationPaths = ([] as string[]) - .concat(...translationPaths) - .filter((translationPath) => basename(translationPath, '.json') === locale); - i18nLoader.registerTranslationFiles(currentTranslationPaths); - - const translations = await i18nLoader.getTranslationsByLocale(locale); - i18n.init( - Object.freeze({ - locale, - ...translations, - }) - ); - - const getTranslationsFilePaths = () => currentTranslationPaths; - - server.decorate('server', 'getTranslationsFilePaths', getTranslationsFilePaths); - - if (kbnServer.newPlatform.setup.plugins.usageCollection) { - const { usageCollection } = kbnServer.newPlatform.setup.plugins as { - usageCollection: UsageCollectionSetup; - }; - registerLocalizationUsageCollector(usageCollection, { - getLocale: () => config.get('i18n.locale') as string, - getTranslationsFilePaths, - }); - } -} +export { i18nMixin } from './i18n_mixin'; diff --git a/src/plugins/advanced_settings/README.md b/src/plugins/advanced_settings/README.md new file mode 100644 index 00000000000000..a364348a8e99ac --- /dev/null +++ b/src/plugins/advanced_settings/README.md @@ -0,0 +1,5 @@ +# Advanced Settings + +This plugin contains the advanced settings management section +allowing users to configure their advanced settings, also known +as uiSettings within the code. \ No newline at end of file diff --git a/src/plugins/charts/public/static/components/number_input.tsx b/src/plugins/charts/public/static/components/number_input.tsx index 8c2874f522902b..68e292861c32e1 100644 --- a/src/plugins/charts/public/static/components/number_input.tsx +++ b/src/plugins/charts/public/static/components/number_input.tsx @@ -54,7 +54,7 @@ function NumberInputOption({ 'data-test-subj': dataTestSubj, }: NumberInputOptionProps) { return ( - + ({ } }; return ( - + ({ ); return ( - + ({ setValue, }: SwitchOptionProps) { return ( - + ({ setValue, }: TextInputOptionProps) { return ( - + {i18n.translate('dashboard.panel.libraryNotification.toolTip', { defaultMessage: - 'This panel is linked to a library item. Editing the panel might affect other dashboards.', + 'Editing this panel might affect other dashboards. To change to this panel only, unlink it from the library.', })}

diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index fa45e433050abf..e5947b73b305be 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -139,7 +139,7 @@ export class DashboardAppController { dashboardCapabilities, scopedHistory, embeddableCapabilities: { visualizeCapabilities, mapsCapabilities }, - data: { query: queryService }, + data: { query: queryService, search: searchService }, core: { notifications, overlays, @@ -412,8 +412,9 @@ export class DashboardAppController { >(DASHBOARD_CONTAINER_TYPE); if (dashboardFactory) { + const searchSessionId = searchService.session.start(); dashboardFactory - .create(getDashboardInput()) + .create({ ...getDashboardInput(), searchSessionId }) .then((container: DashboardContainer | ErrorEmbeddable | undefined) => { if (container && !isErrorEmbeddable(container)) { dashboardContainer = container; @@ -572,7 +573,7 @@ export class DashboardAppController { differences.filters = appStateDashboardInput.filters; } - Object.keys(_.omit(containerInput, ['filters'])).forEach((key) => { + Object.keys(_.omit(containerInput, ['filters', 'searchSessionId'])).forEach((key) => { const containerValue = (containerInput as { [key: string]: unknown })[key]; const appStateValue = ((appStateDashboardInput as unknown) as { [key: string]: unknown })[ key @@ -590,7 +591,8 @@ export class DashboardAppController { const refreshDashboardContainer = () => { const changes = getChangesFromAppStateForContainerState(); if (changes && dashboardContainer) { - dashboardContainer.updateInput(changes); + const searchSessionId = searchService.session.start(); + dashboardContainer.updateInput({ ...changes, searchSessionId }); } }; @@ -1109,12 +1111,6 @@ export class DashboardAppController { $scope.model.filters = filterManager.getFilters(); $scope.model.query = queryStringManager.getQuery(); dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters); - if (dashboardContainer) { - dashboardContainer.updateInput({ - filters: $scope.model.filters, - query: $scope.model.query, - }); - } }, }); @@ -1159,6 +1155,7 @@ export class DashboardAppController { if (dashboardContainer) { dashboardContainer.destroy(); } + searchService.session.clear(); }); } } diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index a7226082d3dce3..89aacf2a84029c 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -134,3 +134,25 @@ test('Container view mode change propagates to new children', async () => { expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); }); + +test('searchSessionId propagates to children', async () => { + const searchSessionId1 = 'searchSessionId1'; + const container = new DashboardContainer( + getSampleDashboardInput({ searchSessionId: searchSessionId1 }), + options + ); + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Bob', + }); + + expect(embeddable.getInput().searchSessionId).toBe(searchSessionId1); + + const searchSessionId2 = 'searchSessionId2'; + container.updateInput({ searchSessionId: searchSessionId2 }); + + expect(embeddable.getInput().searchSessionId).toBe(searchSessionId2); +}); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 036880a1d088ba..757488185fe8e5 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -78,6 +78,7 @@ export interface InheritedChildInput extends IndexSignature { viewMode: ViewMode; hidePanelTitles?: boolean; id: string; + searchSessionId?: string; } export interface DashboardContainerOptions { @@ -228,7 +229,15 @@ export class DashboardContainer extends Container; diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index af956a20c0dc51..5a830586b8d05b 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { PublicMethodsOf } from '@kbn/utility-types'; import { GetConfigFn } from '../types'; import { FieldFormat } from './field_format'; import { FieldFormatsRegistry } from './field_formats_registry'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index fd3d7a1d138fda..aae9b89cdc61fe 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -18,6 +18,7 @@ */ import { i18n } from '@kbn/i18n'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsClientCommon } from '../..'; import { createIndexPatternCache } from '.'; diff --git a/src/plugins/data/common/search/aggs/aggs_service.test.ts b/src/plugins/data/common/search/aggs/aggs_service.test.ts index bcf2101704c805..160860bcce5913 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.test.ts @@ -44,6 +44,8 @@ describe('Aggs service', () => { }; startDeps = { getConfig: jest.fn(), + getIndexPattern: jest.fn(), + isDefaultTimezone: jest.fn(), }; }); @@ -201,8 +203,9 @@ describe('Aggs service', () => { describe('start()', () => { test('exposes proper contract', () => { const start = service.start(startDeps); - expect(Object.keys(start).length).toBe(3); + expect(Object.keys(start).length).toBe(4); expect(start).toHaveProperty('calculateAutoTimeExpression'); + expect(start).toHaveProperty('getDateMetaByDatatableColumn'); expect(start).toHaveProperty('createAggConfigs'); expect(start).toHaveProperty('types'); }); diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts index 6f3e3904dbbd58..b6afa708f9e6f3 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.ts @@ -18,7 +18,7 @@ */ import { ExpressionsServiceSetup } from 'src/plugins/expressions/common'; -import { UI_SETTINGS } from '../../../common'; +import { IndexPattern, UI_SETTINGS } from '../../../common'; import { GetConfigFn } from '../../types'; import { AggConfigs, @@ -28,6 +28,7 @@ import { getCalculateAutoTimeExpression, } from './'; import { AggsCommonSetup, AggsCommonStart } from './types'; +import { getDateMetaByDatatableColumn } from './utils/time_column_meta'; /** @internal */ export const aggsRequiredUiSettings = [ @@ -50,6 +51,8 @@ export interface AggsCommonSetupDependencies { /** @internal */ export interface AggsCommonStartDependencies { getConfig: GetConfigFn; + getIndexPattern(id: string): Promise; + isDefaultTimezone: () => boolean; } /** @@ -77,11 +80,22 @@ export class AggsCommonService { }; } - public start({ getConfig }: AggsCommonStartDependencies): AggsCommonStart { + public start({ + getConfig, + getIndexPattern, + isDefaultTimezone, + }: AggsCommonStartDependencies): AggsCommonStart { const aggTypesStart = this.aggTypesRegistry.start(); + const calculateAutoTimeExpression = getCalculateAutoTimeExpression(getConfig); return { - calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig), + calculateAutoTimeExpression, + getDateMetaByDatatableColumn: getDateMetaByDatatableColumn({ + calculateAutoTimeExpression, + getIndexPattern, + getConfig, + isDefaultTimezone, + }), createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: aggTypesStart, diff --git a/src/plugins/data/common/search/aggs/buckets/date_histogram.ts b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts index c273ca53a5fed4..694b03f6604528 100644 --- a/src/plugins/data/common/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/common/search/aggs/buckets/date_histogram.ts @@ -34,6 +34,7 @@ import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { BaseAggParams } from '../types'; import { dateHistogramInterval } from '../utils'; +import { inferTimeZone } from '../utils'; /** @internal */ export type CalculateBoundsFn = (timeRange: TimeRange) => TimeRangeBounds; @@ -235,25 +236,7 @@ export const getDateHistogramBucketAgg = ({ // time_zones being persisted into saved_objects serialize: noop, write(agg, output) { - // If a time_zone has been set explicitly always prefer this. - let tz = agg.params.time_zone; - if (!tz && agg.params.field) { - // If a field has been configured check the index pattern's typeMeta if a date_histogram on that - // field requires a specific time_zone - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_histogram', - agg.params.field.name, - 'time_zone', - ]); - } - if (!tz) { - // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz - const detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - tz = isDefaultTimezone() ? detectedTimezone || tzOffset : getConfig('dateFormat:tz'); - } + const tz = inferTimeZone(agg.params, agg.getIndexPattern(), isDefaultTimezone, getConfig); output.params.time_zone = tz; }, }, diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index aec3dcc9d068c2..09a13762d4d704 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -18,7 +18,9 @@ */ import { Assign } from '@kbn/utility-types'; +import { DatatableColumn } from 'src/plugins/expressions'; import { IndexPattern } from '../../index_patterns/index_patterns/index_pattern'; +import { TimeRange } from '../../query'; import { AggConfigSerialized, AggConfigs, @@ -80,6 +82,19 @@ export interface AggsCommonSetup { /** @internal */ export interface AggsCommonStart { calculateAutoTimeExpression: ReturnType; + /** + * Helper function returning meta data about use date intervals for a data table column. + * If the column is not a column created by a date histogram aggregation of the esaggs data source, + * this function will return undefined. + * + * Otherwise, it will return the following attributes in an object: + * * `timeZone` time zone used to create the buckets (important e.g. for DST), + * * `timeRange` total time range of the fetch data (to infer partial buckets at the beginning and end of the data) + * * `interval` Interval used on elasticsearch (`auto` resolved to the actual interval) + */ + getDateMetaByDatatableColumn: ( + column: DatatableColumn + ) => Promise; createAggConfigs: ( indexPattern: IndexPattern, configStates?: CreateAggConfigParams[], diff --git a/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts index 28646c092c01c8..826e402b146822 100644 --- a/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts +++ b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.test.ts @@ -79,6 +79,33 @@ describe('getFormatWithAggs', () => { expect(getFormat).toHaveBeenCalledTimes(1); }); + test('creates alternative format for range using the template parameter', () => { + const mapping = { id: 'range', params: { template: 'arrow_right' } }; + const getFieldFormat = getFormatWithAggs(getFormat); + const format = getFieldFormat(mapping); + + expect(format.convert({ gte: 1, lt: 20 })).toBe('1 → 20'); + expect(getFormat).toHaveBeenCalledTimes(1); + }); + + test('handles Infinity values internally when no nestedFormatter is passed', () => { + const mapping = { id: 'range', params: { replaceInfinity: true } }; + const getFieldFormat = getFormatWithAggs(getFormat); + const format = getFieldFormat(mapping); + + expect(format.convert({ gte: -Infinity, lt: Infinity })).toBe('≥ −∞ and < +∞'); + expect(getFormat).toHaveBeenCalledTimes(1); + }); + + test('lets Infinity values handling to nestedFormatter even when flag is on', () => { + const mapping = { id: 'range', params: { replaceInfinity: true, id: 'any' } }; + const getFieldFormat = getFormatWithAggs(getFormat); + const format = getFieldFormat(mapping); + + expect(format.convert({ gte: -Infinity, lt: Infinity })).toBe('≥ -Infinity and < Infinity'); + expect(getFormat).toHaveBeenCalledTimes(1); + }); + test('returns custom label for range if provided', () => { const mapping = { id: 'range', params: {} }; const getFieldFormat = getFormatWithAggs(getFormat); diff --git a/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts index a8134619fec0d0..6b03dc5f70edc3 100644 --- a/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts +++ b/src/plugins/data/common/search/aggs/utils/get_format_with_aggs.ts @@ -56,15 +56,35 @@ export function getFormatWithAggs(getFieldFormat: GetFieldFormat): GetFieldForma id: nestedFormatter.id, params: nestedFormatter.params, }); + const gte = '\u2265'; const lt = '\u003c'; + let fromValue = format.convert(range.gte); + let toValue = format.convert(range.lt); + // In case of identity formatter and a specific flag, replace Infinity values by specific strings + if (params.replaceInfinity && nestedFormatter.id == null) { + const FROM_PLACEHOLDER = '\u2212\u221E'; + const TO_PLACEHOLDER = '+\u221E'; + fromValue = isFinite(range.gte) ? fromValue : FROM_PLACEHOLDER; + toValue = isFinite(range.lt) ? toValue : TO_PLACEHOLDER; + } + + if (params.template === 'arrow_right') { + return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessageArrowRight', { + defaultMessage: '{from} → {to}', + values: { + from: fromValue, + to: toValue, + }, + }); + } return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', { defaultMessage: '{gte} {from} and {lt} {to}', values: { gte, - from: format.convert(range.gte), + from: fromValue, lt, - to: format.convert(range.lt), + to: toValue, }, }); }); diff --git a/src/plugins/data/common/search/aggs/utils/index.ts b/src/plugins/data/common/search/aggs/utils/index.ts index 99ce44207d80d2..7d6cb1c7ef33fa 100644 --- a/src/plugins/data/common/search/aggs/utils/index.ts +++ b/src/plugins/data/common/search/aggs/utils/index.ts @@ -23,3 +23,4 @@ export * from './get_format_with_aggs'; export * from './ipv4_address'; export * from './prop_filter'; export * from './to_angular_json'; +export * from './infer_time_zone'; diff --git a/src/plugins/data/common/search/aggs/utils/infer_time_zone.test.ts b/src/plugins/data/common/search/aggs/utils/infer_time_zone.test.ts new file mode 100644 index 00000000000000..8fc3726ee1b32d --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/infer_time_zone.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. + */ + +jest.mock('moment', () => { + const moment: any = jest.fn(() => { + return { + format: jest.fn(() => '-1;00'), + }; + }); + moment.tz = { + guess: jest.fn(() => 'CET'), + }; + return moment; +}); + +import { IndexPattern } from '../../../index_patterns'; +import { AggParamsDateHistogram } from '../buckets'; +import { inferTimeZone } from './infer_time_zone'; + +describe('inferTimeZone', () => { + it('reads time zone from agg params', () => { + const params: AggParamsDateHistogram = { + time_zone: 'CEST', + }; + expect(inferTimeZone(params, {} as IndexPattern, () => false, jest.fn())).toEqual('CEST'); + }); + + it('reads time zone from index pattern type meta if available', () => { + expect( + inferTimeZone( + { field: 'mydatefield' }, + ({ + typeMeta: { + aggs: { + date_histogram: { + mydatefield: { + time_zone: 'UTC', + }, + }, + }, + }, + } as unknown) as IndexPattern, + () => false, + jest.fn() + ) + ).toEqual('UTC'); + }); + + it('reads time zone from moment if set to default', () => { + expect(inferTimeZone({}, {} as IndexPattern, () => true, jest.fn())).toEqual('CET'); + }); + + it('reads time zone from config if not set to default', () => { + expect( + inferTimeZone( + {}, + {} as IndexPattern, + () => false, + () => 'CET' as any + ) + ).toEqual('CET'); + }); +}); diff --git a/src/plugins/data/common/search/aggs/utils/infer_time_zone.ts b/src/plugins/data/common/search/aggs/utils/infer_time_zone.ts new file mode 100644 index 00000000000000..282238c5a04598 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/infer_time_zone.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; +import { IndexPattern } from '../../../index_patterns'; +import { AggParamsDateHistogram } from '../buckets'; + +export function inferTimeZone( + params: AggParamsDateHistogram, + indexPattern: IndexPattern, + isDefaultTimezone: () => boolean, + getConfig: (key: string) => T +) { + let tz = params.time_zone; + if (!tz && params.field) { + // If a field has been configured check the index pattern's typeMeta if a date_histogram on that + // field requires a specific time_zone + tz = indexPattern.typeMeta?.aggs?.date_histogram?.[params.field]?.time_zone; + } + if (!tz) { + // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); + tz = isDefaultTimezone() + ? detectedTimezone || tzOffset + : // if timezone is not the default, this will always return a string + (getConfig('dateFormat:tz') as string); + } + return tz; +} diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts new file mode 100644 index 00000000000000..e56d6227345547 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.test.ts @@ -0,0 +1,121 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BUCKET_TYPES } from '../buckets'; +import { DateMetaByColumnDeps, getDateMetaByDatatableColumn } from './time_column_meta'; + +describe('getDateMetaByDatatableColumn', () => { + let params: DateMetaByColumnDeps; + beforeEach(() => { + params = { + calculateAutoTimeExpression: jest.fn().mockReturnValue('5m'), + getIndexPattern: jest.fn().mockResolvedValue({}), + isDefaultTimezone: jest.fn().mockReturnValue(true), + getConfig: jest.fn(), + }; + }); + + it('returns nothing on column from other data source', async () => { + expect( + await getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'essql', + }, + }) + ).toEqual(undefined); + }); + + it('returns nothing on non date histogram column', async () => { + expect( + await getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.TERMS, + }, + }, + }) + ).toEqual(undefined); + }); + + it('returns time range, time zone and interval', async () => { + expect( + await getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + interval: '1h', + }, + appliedTimeRange: { + from: 'now-5d', + to: 'now', + }, + }, + }, + }) + ).toEqual({ + timeZone: 'UTC', + timeRange: { + from: 'now-5d', + to: 'now', + }, + interval: '1h', + }); + }); + + it('returns resolved auto interval', async () => { + expect( + await getDateMetaByDatatableColumn(params)({ + id: 'test', + name: 'test', + meta: { + type: 'date', + source: 'esaggs', + sourceParams: { + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + time_zone: 'UTC', + interval: 'auto', + }, + appliedTimeRange: { + from: 'now-5d', + to: 'now', + }, + }, + }, + }) + ).toEqual( + expect.objectContaining({ + interval: '5m', + }) + ); + }); +}); diff --git a/src/plugins/data/common/search/aggs/utils/time_column_meta.ts b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts new file mode 100644 index 00000000000000..1bea716c6a0490 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/time_column_meta.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DatatableColumn } from 'src/plugins/expressions/common'; +import { IndexPattern } from '../../../index_patterns'; + +import { TimeRange } from '../../../types'; +import { AggParamsDateHistogram, BUCKET_TYPES } from '../buckets'; +import { inferTimeZone } from './infer_time_zone'; + +export interface DateMetaByColumnDeps { + calculateAutoTimeExpression: (range: TimeRange) => string | undefined; + getIndexPattern: (id: string) => Promise; + isDefaultTimezone: () => boolean; + getConfig: (key: string) => T; +} + +export const getDateMetaByDatatableColumn = ({ + calculateAutoTimeExpression, + getIndexPattern, + isDefaultTimezone, + getConfig, +}: DateMetaByColumnDeps) => async ( + column: DatatableColumn +): Promise => { + if (column.meta.source !== 'esaggs') return; + if (column.meta.sourceParams?.type !== BUCKET_TYPES.DATE_HISTOGRAM) return; + const params = column.meta.sourceParams.params as AggParamsDateHistogram; + const appliedTimeRange = column.meta.sourceParams.appliedTimeRange as TimeRange; + + const tz = inferTimeZone( + params, + await getIndexPattern(column.meta.sourceParams.indexPatternId as string), + isDefaultTimezone, + getConfig + ); + + const interval = + params.interval === 'auto' ? calculateAutoTimeExpression(appliedTimeRange) : params.interval; + + if (!interval) { + throw new Error('time interval could not be determined'); + } + + return { + timeZone: tz, + timeRange: appliedTimeRange, + interval, + }; +}; diff --git a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts index 3badd456bd72a8..b13f6ad2665461 100644 --- a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts +++ b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { defaultSearchStrategy } from './default_search_strategy'; import { LegacyFetchHandlers, SearchStrategySearchParams } from './types'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/plugins/data/common/search/search_source/mocks.ts b/src/plugins/data/common/search/search_source/mocks.ts index d4c0707f950bb8..ea7d6b4441ccfe 100644 --- a/src/plugins/data/common/search/search_source/mocks.ts +++ b/src/plugins/data/common/search/search_source/mocks.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import type { MockedKeys } from '@kbn/utility-types/jest'; import { uiSettingsServiceMock } from '../../../../../core/public/mocks'; import { SearchSource } from './search_source'; diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/common/search/session/mocks.ts index 7d5cd75b575346..2b64bbbd275650 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/common/search/session/mocks.ts @@ -23,6 +23,7 @@ export function getSessionServiceMock(): jest.Mocked { return { clear: jest.fn(), start: jest.fn(), + restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(), }; diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 80ab74f1aa14dd..6660b8395547ff 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -34,6 +34,12 @@ export interface ISessionService { * Starts a new session */ start: () => string; + + /** + * Restores existing session + */ + restore: (sessionId: string) => void; + /** * Clears the active session. */ diff --git a/src/plugins/data/common/utils/abort_utils.test.ts b/src/plugins/data/common/utils/abort_utils.test.ts index ca187e95f528ba..358f00e5e82bd9 100644 --- a/src/plugins/data/common/utils/abort_utils.test.ts +++ b/src/plugins/data/common/utils/abort_utils.test.ts @@ -41,7 +41,7 @@ describe('AbortUtils', () => { describe('rejects', () => { test('should not reject if the signal does not abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal); + const promise = toPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); await flushPromises(); @@ -50,7 +50,7 @@ describe('AbortUtils', () => { test('should reject if the signal does abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal); + const promise = toPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); controller.abort(); @@ -58,13 +58,30 @@ describe('AbortUtils', () => { expect(whenRejected).toBeCalled(); expect(whenRejected.mock.calls[0][0]).toBeInstanceOf(AbortError); }); + + test('should expose cleanup handler', () => { + const controller = new AbortController(); + const promise = toPromise(controller.signal); + expect(promise.cleanup).toBeDefined(); + }); + + test('calling clean up handler prevents rejects', async () => { + const controller = new AbortController(); + const { promise, cleanup } = toPromise(controller.signal); + const whenRejected = jest.fn(); + promise.catch(whenRejected); + cleanup(); + controller.abort(); + await flushPromises(); + expect(whenRejected).not.toBeCalled(); + }); }); }); describe('getCombinedSignal', () => { test('should return an AbortSignal', () => { - const signal = getCombinedSignal([]); - expect(signal instanceof AbortSignal).toBe(true); + const signal = getCombinedSignal([]).signal; + expect(signal).toBeInstanceOf(AbortSignal); }); test('should not abort if none of the signals abort', async () => { @@ -72,7 +89,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(500); await flushPromises(); @@ -84,7 +101,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(1000); await flushPromises(); @@ -95,8 +112,56 @@ describe('AbortUtils', () => { const controller1 = new AbortController(); const controller2 = new AbortController(); controller1.abort(); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(true); }); + + describe('cleanup listener', () => { + const createMockController = () => { + const controller = new AbortController(); + const spyAddListener = jest.spyOn(controller.signal, 'addEventListener'); + const spyRemoveListener = jest.spyOn(controller.signal, 'removeEventListener'); + return { + controller, + getTotalListeners: () => + Math.max(spyAddListener.mock.calls.length - spyRemoveListener.mock.calls.length, 0), + }; + }; + + test('cleanup should cleanup inner listeners', () => { + const controller1 = createMockController(); + const controller2 = createMockController(); + + const { cleanup } = getCombinedSignal([ + controller1.controller.signal, + controller2.controller.signal, + ]); + + expect(controller1.getTotalListeners()).toBe(1); + expect(controller2.getTotalListeners()).toBe(1); + + cleanup(); + + expect(controller1.getTotalListeners()).toBe(0); + expect(controller2.getTotalListeners()).toBe(0); + }); + + test('abort should cleanup inner listeners', async () => { + const controller1 = createMockController(); + const controller2 = createMockController(); + + getCombinedSignal([controller1.controller.signal, controller2.controller.signal]); + + expect(controller1.getTotalListeners()).toBe(1); + expect(controller2.getTotalListeners()).toBe(1); + + controller1.controller.abort(); + + await flushPromises(); + + expect(controller1.getTotalListeners()).toBe(0); + expect(controller2.getTotalListeners()).toBe(0); + }); + }); }); }); diff --git a/src/plugins/data/common/utils/abort_utils.ts b/src/plugins/data/common/utils/abort_utils.ts index a26fec9423f832..81f30b7454c7b4 100644 --- a/src/plugins/data/common/utils/abort_utils.ts +++ b/src/plugins/data/common/utils/abort_utils.ts @@ -36,15 +36,23 @@ export class AbortError extends Error { * * @param signal The `AbortSignal` to generate the `Promise` from */ -export function toPromise(signal: AbortSignal): Promise { - return new Promise((resolve, reject) => { - if (signal.aborted) reject(new AbortError()); - const abortHandler = () => { +export function toPromise(signal: AbortSignal): { promise: Promise; cleanup: () => void } { + let abortHandler: () => void; + const cleanup = () => { + if (abortHandler) { signal.removeEventListener('abort', abortHandler); + } + }; + const promise = new Promise((resolve, reject) => { + if (signal.aborted) reject(new AbortError()); + abortHandler = () => { + cleanup(); reject(new AbortError()); }; signal.addEventListener('abort', abortHandler); }); + + return { promise, cleanup }; } /** @@ -52,13 +60,26 @@ export function toPromise(signal: AbortSignal): Promise { * * @param signals */ -export function getCombinedSignal(signals: AbortSignal[]) { +export function getCombinedSignal( + signals: AbortSignal[] +): { signal: AbortSignal; cleanup: () => void } { const controller = new AbortController(); + let cleanup = () => {}; + if (signals.some((signal) => signal.aborted)) { controller.abort(); } else { const promises = signals.map((signal) => toPromise(signal)); - Promise.race(promises).catch(() => controller.abort()); + cleanup = () => { + promises.forEach((p) => p.cleanup()); + controller.signal.removeEventListener('abort', cleanup); + }; + controller.signal.addEventListener('abort', cleanup); + Promise.race(promises.map((p) => p.promise)).catch(() => { + cleanup(); + controller.abort(); + }); } - return controller.signal; + + return { signal: controller.signal, cleanup }; } diff --git a/src/plugins/data/public/field_formats/mocks.ts b/src/plugins/data/public/field_formats/mocks.ts index ec1233a085bce3..ba1e9de71bab10 100644 --- a/src/plugins/data/public/field_formats/mocks.ts +++ b/src/plugins/data/public/field_formats/mocks.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { FieldFormatsStart, FieldFormatsSetup, FieldFormatsService } from '.'; import { fieldFormatsMock } from '../../common/field_formats/mocks'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index c041511745be21..c54cb36142cbd1 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -380,7 +380,7 @@ export { PainlessError, } from './search'; -export type { SearchSource } from './search'; +export type { SearchSource, ISessionService } from './search'; export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 81fa6d4ba20db7..b072407a5fe102 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -16,6 +16,7 @@ import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; +import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; import { ErrorToastOptions } from 'src/core/public/notifications'; @@ -1414,8 +1415,6 @@ export interface ISearchSetup { // // (undocumented) aggs: AggsSetup; - // Warning: (ae-forgotten-export) The symbol "ISessionService" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ISessionService" session: ISessionService; // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts // @@ -1431,7 +1430,6 @@ export interface ISearchStart { aggs: AggsStart; search: ISearchGeneric; searchSource: ISearchStartSearchSource; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ISessionService" session: ISessionService; // (undocumented) showError: (e: Error) => void; @@ -1448,6 +1446,17 @@ export interface ISearchStartSearchSource { // @public (undocumented) export const isErrorResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; +// Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISessionService { + clear: () => void; + getSession$: () => Observable; + getSessionId: () => string | undefined; + restore: (sessionId: string) => void; + start: () => string; +} + // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2285,7 +2294,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:62:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/search/aggs/types.ts:98:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/search/aggs/types.ts:113:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:66:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/mocks.ts b/src/plugins/data/public/query/mocks.ts index 53c177de0fa394..f8376d78348737 100644 --- a/src/plugins/data/public/query/mocks.ts +++ b/src/plugins/data/public/query/mocks.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { Observable } from 'rxjs'; import { QueryService, QuerySetup, QueryStart } from '.'; import { timefilterServiceMock } from './timefilter/timefilter_service.mock'; diff --git a/src/plugins/data/public/query/query_string/query_string_manager.ts b/src/plugins/data/public/query/query_string/query_string_manager.ts index 50732c99a62d95..90a622fada95bd 100644 --- a/src/plugins/data/public/query/query_string/query_string_manager.ts +++ b/src/plugins/data/public/query/query_string/query_string_manager.ts @@ -19,6 +19,7 @@ import { BehaviorSubject } from 'rxjs'; import { skip } from 'rxjs/operators'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { Query, UI_SETTINGS } from '../../../common'; diff --git a/src/plugins/data/public/query/timefilter/time_history.ts b/src/plugins/data/public/query/timefilter/time_history.ts index fe73fd85b164d9..24786c5eed6237 100644 --- a/src/plugins/data/public/query/timefilter/time_history.ts +++ b/src/plugins/data/public/query/timefilter/time_history.ts @@ -18,6 +18,7 @@ */ import moment from 'moment'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { PersistedLog } from '../persisted_log'; import { TimeRange } from '../../../common'; diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 01b82087cf3544..49a8c68f6916f6 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -20,6 +20,7 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { getForceNow } from './lib/get_force_now'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts index 060257a880528f..9f1c64a1739a5f 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { TimefilterService, TimeHistoryContract, TimefilterContract } from '.'; import { Observable } from 'rxjs'; diff --git a/src/plugins/data/public/search/aggs/aggs_service.test.ts b/src/plugins/data/public/search/aggs/aggs_service.test.ts index db25dfb300d117..de747d234b441b 100644 --- a/src/plugins/data/public/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/public/search/aggs/aggs_service.test.ts @@ -23,6 +23,7 @@ import { coreMock } from '../../../../../core/public/mocks'; import { expressionsPluginMock } from '../../../../../plugins/expressions/public/mocks'; import { BucketAggType, getAggTypes, MetricAggType } from '../../../common'; import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { dataPluginMock } from '../../mocks'; import { AggsService, @@ -46,6 +47,7 @@ describe('AggsService - public', () => { }; startDeps = { fieldFormats: fieldFormatsServiceMock.createStartContract(), + indexPatterns: dataPluginMock.createStartContract().indexPatterns, uiSettings, }; }); @@ -86,8 +88,9 @@ describe('AggsService - public', () => { describe('start()', () => { test('exposes proper contract', () => { const start = service.start(startDeps); - expect(Object.keys(start).length).toBe(3); + expect(Object.keys(start).length).toBe(4); expect(start).toHaveProperty('calculateAutoTimeExpression'); + expect(start).toHaveProperty('getDateMetaByDatatableColumn'); expect(start).toHaveProperty('createAggConfigs'); expect(start).toHaveProperty('types'); }); diff --git a/src/plugins/data/public/search/aggs/aggs_service.ts b/src/plugins/data/public/search/aggs/aggs_service.ts index 4b088ddfe314f6..85e0f604bb8b5f 100644 --- a/src/plugins/data/public/search/aggs/aggs_service.ts +++ b/src/plugins/data/public/search/aggs/aggs_service.ts @@ -32,6 +32,7 @@ import { AggTypesDependencies, } from '../../../common/search/aggs'; import { AggsSetup, AggsStart } from './types'; +import { IndexPatternsContract } from '../../index_patterns'; /** * Aggs needs synchronous access to specific uiSettings. Since settings can change @@ -68,6 +69,7 @@ export interface AggsSetupDependencies { export interface AggsStartDependencies { fieldFormats: FieldFormatsStart; uiSettings: IUiSettingsClient; + indexPatterns: IndexPatternsContract; } /** @@ -94,9 +96,17 @@ export class AggsService { return this.aggsCommonService.setup({ registerFunction }); } - public start({ fieldFormats, uiSettings }: AggsStartDependencies): AggsStart { - const { calculateAutoTimeExpression, types } = this.aggsCommonService.start({ + public start({ fieldFormats, uiSettings, indexPatterns }: AggsStartDependencies): AggsStart { + const isDefaultTimezone = () => uiSettings.isDefault('dateFormat:tz'); + + const { + calculateAutoTimeExpression, + getDateMetaByDatatableColumn, + types, + } = this.aggsCommonService.start({ getConfig: this.getConfig!, + getIndexPattern: indexPatterns.get, + isDefaultTimezone, }); const aggTypesDependencies: AggTypesDependencies = { @@ -106,7 +116,7 @@ export class AggsService { deserialize: fieldFormats.deserialize, getDefaultInstance: fieldFormats.getDefaultInstance, }), - isDefaultTimezone: () => uiSettings.isDefault('dateFormat:tz'), + isDefaultTimezone, }; // initialize each agg type and store in memory @@ -137,6 +147,7 @@ export class AggsService { return { calculateAutoTimeExpression, + getDateMetaByDatatableColumn, createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry }); }, diff --git a/src/plugins/data/public/search/aggs/mocks.ts b/src/plugins/data/public/search/aggs/mocks.ts index ca13343777e63f..abc930f00b5945 100644 --- a/src/plugins/data/public/search/aggs/mocks.ts +++ b/src/plugins/data/public/search/aggs/mocks.ts @@ -67,6 +67,7 @@ export const searchAggsSetupMock = (): AggsSetup => ({ export const searchAggsStartMock = (): AggsStart => ({ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig), + getDateMetaByDatatableColumn: jest.fn(), createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts index 9cadb1e796ad68..b87ac11e810c9f 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup, CoreStart } from '../../../../../core/public'; import { coreMock } from '../../../../../core/public/mocks'; import { usageCollectionPluginMock, Setup } from '../../../../usage_collection/public/mocks'; diff --git a/src/plugins/data/public/search/errors/es_error.test.tsx b/src/plugins/data/public/search/errors/es_error.test.tsx new file mode 100644 index 00000000000000..db719eb6a70c96 --- /dev/null +++ b/src/plugins/data/public/search/errors/es_error.test.tsx @@ -0,0 +1,40 @@ +/* + * 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 { EsError } from './es_error'; +import { IEsError } from './types'; + +describe('EsError', () => { + it('contains the same body as the wrapped error', () => { + const error = { + body: { + attributes: { + error: { + type: 'top_level_exception_type', + reason: 'top-level reason', + }, + }, + }, + } as IEsError; + const esError = new EsError(error); + + expect(typeof esError.body).toEqual('object'); + expect(esError.body).toEqual(error.body); + }); +}); diff --git a/src/plugins/data/public/search/errors/es_error.tsx b/src/plugins/data/public/search/errors/es_error.tsx index 53d00159b836b3..f1e7eaa707a064 100644 --- a/src/plugins/data/public/search/errors/es_error.tsx +++ b/src/plugins/data/public/search/errors/es_error.tsx @@ -22,22 +22,27 @@ import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { ApplicationStart } from 'kibana/public'; import { KbnError } from '../../../../kibana_utils/common'; import { IEsError } from './types'; -import { getRootCause } from './utils'; +import { getRootCause, getTopLevelCause } from './utils'; export class EsError extends KbnError { + readonly body: IEsError['body']; + constructor(protected readonly err: IEsError) { super('EsError'); + this.body = err.body; } public getErrorMessage(application: ApplicationStart) { const rootCause = getRootCause(this.err)?.reason; + const topLevelCause = getTopLevelCause(this.err)?.reason; + const cause = rootCause ?? topLevelCause; return ( <> - {rootCause ? ( + {cause ? ( - {rootCause} + {cause} ) : null} diff --git a/src/plugins/data/public/search/errors/utils.ts b/src/plugins/data/public/search/errors/utils.ts index d07d9b05e91e9d..45a318904665f5 100644 --- a/src/plugins/data/public/search/errors/utils.ts +++ b/src/plugins/data/public/search/errors/utils.ts @@ -26,6 +26,10 @@ export function getFailedShards(err: IEsError) { return failedShards ? failedShards[0] : undefined; } +export function getTopLevelCause(err: IEsError) { + return err.body?.attributes?.error; +} + export function getRootCause(err: IEsError) { return getFailedShards(err)?.reason; } diff --git a/src/plugins/data/public/search/es_search/get_es_preference.test.ts b/src/plugins/data/public/search/es_search/get_es_preference.test.ts index 05a74b3e6205ac..8b3d335b38d17d 100644 --- a/src/plugins/data/public/search/es_search/get_es_preference.test.ts +++ b/src/plugins/data/public/search/es_search/get_es_preference.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { getEsPreference } from './get_es_preference'; import { CoreStart } from '../../../../../core/public'; import { coreMock } from '../../../../../core/public/mocks'; diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index de7a4ffce8bd4b..dba77d398c8b67 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -65,6 +65,7 @@ export interface RequestHandlerParams { metricsAtAllLevels?: boolean; visParams?: any; abortSignal?: AbortSignal; + searchSessionId?: string; } const name = 'esaggs'; @@ -82,6 +83,7 @@ const handleCourierRequest = async ({ inspectorAdapters, filterManager, abortSignal, + searchSessionId, }: RequestHandlerParams) => { // Create a new search source that inherits the original search source // but has the appropriate timeRange applied via a filter. @@ -143,6 +145,7 @@ const handleCourierRequest = async ({ defaultMessage: 'This request queries Elasticsearch to fetch the data for the visualization.', }), + searchSessionId, } ); request.stats(getRequestInspectorStats(requestSearchSource)); @@ -150,6 +153,7 @@ const handleCourierRequest = async ({ try { const response = await requestSearchSource.fetch({ abortSignal, + sessionId: searchSessionId, }); request.stats(getResponseInspectorStats(response, searchSource)).ok({ json: response }); @@ -248,7 +252,7 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ multi: true, }, }, - async fn(input, args, { inspectorAdapters, abortSignal }) { + async fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId }) { const indexPatterns = getIndexPatterns(); const { filterManager } = getQueryService(); const searchService = getSearchService(); @@ -276,6 +280,7 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ inspectorAdapters: inspectorAdapters as Adapters, filterManager, abortSignal: (abortSignal as unknown) as AbortSignal, + searchSessionId: getSearchSessionId(), }); const table: Datatable = { @@ -293,6 +298,13 @@ export const esaggs = (): EsaggsExpressionFunctionDefinition => ({ source: 'esaggs', sourceParams: { indexPatternId: indexPattern.id, + appliedTimeRange: + column.aggConfig.params.field?.name && + input?.timeRange && + args.timeFields && + args.timeFields.includes(column.aggConfig.params.field?.name) + ? { from: input.timeRange.from, to: input.timeRange.to } + : undefined, ...column.aggConfig.serialize(), }, }, diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 86804a819cb0e5..1abf3192a4846e 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -41,6 +41,7 @@ export { SearchSourceDependencies, SearchSourceFields, SortDirection, + ISessionService, } from '../../common/search'; export { getEsPreference } from './es_search'; diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index e8a728bb9cec3d..472caa5e4f45ff 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup, CoreStart } from '../../../../core/public'; import { coreMock } from '../../../../core/public/mocks'; import { IEsSearchRequest } from '../../common/search'; diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 1afcf4615ab5a1..0f069d54ee9c8b 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -20,16 +20,17 @@ import { get, memoize, trimEnd } from 'lodash'; import { BehaviorSubject, throwError, timer, defer, from, Observable, NEVER } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { - getCombinedSignal, AbortError, IKibanaSearchRequest, IKibanaSearchResponse, ISearchOptions, ES_SEARCH_STRATEGY, ISessionService, + getCombinedSignal, } from '../../common'; import { SearchUsageCollector } from './collectors'; import { @@ -170,11 +171,12 @@ export class SearchInterceptor { ...(abortSignal ? [abortSignal] : []), ]; - const combinedSignal = getCombinedSignal(signals); + const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = getCombinedSignal(signals); const cleanup = () => { subscription.unsubscribe(); + combinedSignal.removeEventListener('abort', cleanup); + cleanupCombinedSignal(); }; - combinedSignal.addEventListener('abort', cleanup); return { diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index b59fa6fa16bf61..20041a02067d9f 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { coreMock } from '../../../../core/public/mocks'; import { CoreSetup, CoreStart } from '../../../../core/public'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index f955dc5b6ebd51..3dbabfc68fdbc8 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -143,7 +143,7 @@ export class SearchService implements Plugin { }; return { - aggs: this.aggsService.start({ fieldFormats, uiSettings }), + aggs: this.aggsService.start({ fieldFormats, uiSettings, indexPatterns }), search, showError: (e: Error) => { this.searchInterceptor.showError(e); diff --git a/src/plugins/data/public/search/session_service.test.ts b/src/plugins/data/public/search/session_service.test.ts index dd64d187f47d6c..bcfd06944d9831 100644 --- a/src/plugins/data/public/search/session_service.test.ts +++ b/src/plugins/data/public/search/session_service.test.ts @@ -20,6 +20,7 @@ import { SessionService } from './session_service'; import { ISessionService } from '../../common'; import { coreMock } from '../../../../core/public/mocks'; +import { take, toArray } from 'rxjs/operators'; describe('Session service', () => { let sessionService: ISessionService; @@ -39,5 +40,20 @@ describe('Session service', () => { sessionService.clear(); expect(sessionService.getSessionId()).toBeUndefined(); }); + + it('Restores a session', async () => { + const sessionId = 'sessionId'; + sessionService.restore(sessionId); + expect(sessionService.getSessionId()).toBe(sessionId); + }); + + it('sessionId$ observable emits current value', async () => { + sessionService.restore('1'); + const emittedValues = sessionService.getSession$().pipe(take(3), toArray()).toPromise(); + sessionService.restore('2'); + sessionService.clear(); + + expect(await emittedValues).toEqual(['1', '2', undefined]); + }); }); }); diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 31524434af3022..a172738812937b 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -18,14 +18,16 @@ */ import uuid from 'uuid'; -import { Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; -import { ISessionService } from '../../common/search'; import { ConfigSchema } from '../../config'; +import { ISessionService } from '../../common/search'; export class SessionService implements ISessionService { - private sessionId?: string; - private session$: Subject = new Subject(); + private session$ = new BehaviorSubject(undefined); + private get sessionId() { + return this.session$.getValue(); + } private appChangeSubscription$?: Subscription; private curApp?: string; @@ -68,13 +70,15 @@ export class SessionService implements ISessionService { } public start() { - this.sessionId = uuid.v4(); - this.session$.next(this.sessionId); - return this.sessionId; + this.session$.next(uuid.v4()); + return this.sessionId!; + } + + public restore(sessionId: string) { + this.session$.next(sessionId); } public clear() { - this.sessionId = undefined; - this.session$.next(this.sessionId); + this.session$.next(undefined); } } diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 0d544ac9ad16a0..8c009576ff2805 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -105,7 +105,6 @@ function FilterBarUI(props: Props) { isOpen={isAddFilterPopoverOpen} closePopover={() => setIsAddFilterPopoverOpen(false)} anchorPosition="downLeft" - withTitle panelPaddingSize="none" ownFocus={true} initialFocus=".filterEditor__hiddenItem" diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 018f41ab82bfc9..48dbfea634256b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -358,7 +358,6 @@ export function FilterItem(props: Props) { }} button={badge} anchorPosition="downLeft" - withTitle={true} panelPaddingSize="none" > diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index b97e0e33f2400b..46dda2382a5cac 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -166,7 +166,6 @@ class FilterOptionsUI extends Component { } anchorPosition="rightUp" panelPaddingSize="none" - withTitle repositionOnScroll > diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 4d51b173f67439..3957e59388acf5 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -76,7 +76,6 @@ export function QueryLanguageSwitcher(props: Props) { button={button} isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(false)} - withTitle repositionOnScroll > diff --git a/src/plugins/data/server/index_patterns/mocks.ts b/src/plugins/data/server/index_patterns/mocks.ts index 8f95afe3b3c9db..52d8aa1e350930 100644 --- a/src/plugins/data/server/index_patterns/mocks.ts +++ b/src/plugins/data/server/index_patterns/mocks.ts @@ -19,6 +19,6 @@ export function createIndexPatternsStartMock() { return { - indexPatternsServiceFactory: jest.fn(), + indexPatternsServiceFactory: jest.fn().mockResolvedValue({ get: jest.fn() }), }; } diff --git a/src/plugins/data/server/search/aggs/aggs_service.test.ts b/src/plugins/data/server/search/aggs/aggs_service.test.ts index d9a945a15fb67d..cb4239cc339c45 100644 --- a/src/plugins/data/server/search/aggs/aggs_service.test.ts +++ b/src/plugins/data/server/search/aggs/aggs_service.test.ts @@ -23,6 +23,7 @@ import { coreMock } from '../../../../../core/server/mocks'; import { expressionsPluginMock } from '../../../../../plugins/expressions/server/mocks'; import { BucketAggType, getAggTypes, MetricAggType } from '../../../common'; import { createFieldFormatsStartMock } from '../../field_formats/mocks'; +import { createIndexPatternsStartMock } from '../../index_patterns/mocks'; import { AggsService, AggsSetupDependencies, AggsStartDependencies } from './aggs_service'; @@ -40,6 +41,7 @@ describe('AggsService - server', () => { }; startDeps = { fieldFormats: createFieldFormatsStartMock(), + indexPatterns: createIndexPatternsStartMock(), uiSettings, }; }); diff --git a/src/plugins/data/server/search/aggs/aggs_service.ts b/src/plugins/data/server/search/aggs/aggs_service.ts index 3e5cd8adb44a66..c805c8af6694cf 100644 --- a/src/plugins/data/server/search/aggs/aggs_service.ts +++ b/src/plugins/data/server/search/aggs/aggs_service.ts @@ -30,6 +30,7 @@ import { TimeRange, } from '../../../common'; import { FieldFormatsStart } from '../../field_formats'; +import { IndexPatternsServiceStart } from '../../index_patterns'; import { AggsSetup, AggsStart } from './types'; /** @internal */ @@ -41,6 +42,7 @@ export interface AggsSetupDependencies { export interface AggsStartDependencies { fieldFormats: FieldFormatsStart; uiSettings: UiSettingsServiceStart; + indexPatterns: IndexPatternsServiceStart; } /** @@ -61,7 +63,7 @@ export class AggsService { return this.aggsCommonService.setup({ registerFunction }); } - public start({ fieldFormats, uiSettings }: AggsStartDependencies): AggsStart { + public start({ fieldFormats, uiSettings, indexPatterns }: AggsStartDependencies): AggsStart { return { asScopedToClient: async (savedObjectsClient: SavedObjectsClientContract) => { const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); @@ -72,8 +74,18 @@ export class AggsService { const getConfig = (key: string): T => { return uiSettingsCache[key]; }; + const isDefaultTimezone = () => getConfig('dateFormat:tz') === 'Browser'; - const { calculateAutoTimeExpression, types } = this.aggsCommonService.start({ getConfig }); + const { + calculateAutoTimeExpression, + getDateMetaByDatatableColumn, + types, + } = this.aggsCommonService.start({ + getConfig, + getIndexPattern: (await indexPatterns.indexPatternsServiceFactory(savedObjectsClient)) + .get, + isDefaultTimezone, + }); const aggTypesDependencies: AggTypesDependencies = { calculateBounds: this.calculateBounds, @@ -87,7 +99,7 @@ export class AggsService { * default timezone, but `isDefault` is not currently offered on the * server, so we need to manually check for the default value. */ - isDefaultTimezone: () => getConfig('dateFormat:tz') === 'Browser', + isDefaultTimezone, }; const typesRegistry = { @@ -109,6 +121,7 @@ export class AggsService { return { calculateAutoTimeExpression, + getDateMetaByDatatableColumn, createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry }); }, diff --git a/src/plugins/data/server/search/aggs/mocks.ts b/src/plugins/data/server/search/aggs/mocks.ts index b50e22fe87b7ce..be060de73b9ff9 100644 --- a/src/plugins/data/server/search/aggs/mocks.ts +++ b/src/plugins/data/server/search/aggs/mocks.ts @@ -68,6 +68,7 @@ export const searchAggsSetupMock = (): AggsSetup => ({ const commonStartMock = (): AggsCommonStart => ({ calculateAutoTimeExpression: getCalculateAutoTimeExpression(getConfig), + getDateMetaByDatatableColumn: jest.fn(), createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), diff --git a/src/plugins/data/server/search/collectors/register.ts b/src/plugins/data/server/search/collectors/register.ts index ab0ea93edd49e2..5db4f52169350d 100644 --- a/src/plugins/data/server/search/collectors/register.ts +++ b/src/plugins/data/server/search/collectors/register.ts @@ -37,9 +37,9 @@ export async function registerUsageCollector( isReady: () => true, fetch: fetchProvider(context.config.legacy.globalConfig$), schema: { - successCount: { type: 'number' }, - errorCount: { type: 'number' }, - averageDuration: { type: 'long' }, + successCount: { type: 'long' }, + errorCount: { type: 'long' }, + averageDuration: { type: 'float' }, }, }); usageCollection.registerCollector(collector); diff --git a/src/plugins/data/server/search/routes/call_msearch.test.ts b/src/plugins/data/server/search/routes/call_msearch.test.ts index 3d409e22aaa88e..183c2334b4e32a 100644 --- a/src/plugins/data/server/search/routes/call_msearch.test.ts +++ b/src/plugins/data/server/search/routes/call_msearch.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { Observable } from 'rxjs'; import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server'; diff --git a/src/plugins/data/server/search/routes/msearch.test.ts b/src/plugins/data/server/search/routes/msearch.test.ts index 2f414fe0b49202..e2e5818cf9a721 100644 --- a/src/plugins/data/server/search/routes/msearch.test.ts +++ b/src/plugins/data/server/search/routes/msearch.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { Observable } from 'rxjs'; import { diff --git a/src/plugins/data/server/search/routes/search.test.ts b/src/plugins/data/server/search/routes/search.test.ts index 834e5de5c31214..845ab3bbe4eb16 100644 --- a/src/plugins/data/server/search/routes/search.test.ts +++ b/src/plugins/data/server/search/routes/search.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { Observable, from } from 'rxjs'; import { diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index a001d56b365149..2b513be147e9d2 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup, CoreStart } from '../../../../core/server'; import { coreMock } from '../../../../core/server/mocks'; diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 0130d3aacc91fb..04ee0e95c7f08f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -149,7 +149,7 @@ export class SearchService implements Plugin { { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { return { - aggs: this.aggsService.start({ fieldFormats, uiSettings }), + aggs: this.aggsService.start({ fieldFormats, uiSettings, indexPatterns }), getSearchStrategy: this.getSearchStrategy, search: this.search.bind(this), searchSource: { diff --git a/src/plugins/data/server/search/search_source/mocks.ts b/src/plugins/data/server/search/search_source/mocks.ts index 7e9cc8f2ff42cc..73b39b416316ee 100644 --- a/src/plugins/data/server/search/search_source/mocks.ts +++ b/src/plugins/data/server/search/search_source/mocks.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { KibanaRequest } from 'src/core/server'; import { searchSourceCommonMock } from '../../../common/search/search_source/mocks'; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 97cbb40c13db09..a3edbbd3844b35 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -13,6 +13,7 @@ import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; import { CoreStart } from 'src/core/server'; import { CoreStart as CoreStart_2 } from 'kibana/server'; +import { DatatableColumn } from 'src/plugins/expressions'; import { Duration } from 'moment'; import { ElasticsearchClient } from 'kibana/server'; import { Ensure } from '@kbn/utility-types'; @@ -37,6 +38,7 @@ import { PathConfigType } from '@kbn/utils'; import { Plugin as Plugin_2 } from 'src/core/server'; import { Plugin as Plugin_3 } from 'kibana/server'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { RecursiveReadonly } from '@kbn/utility-types'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestHandlerContext } from 'src/core/server'; diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx index ac88d2aa366960..d294ffca863414 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { @@ -86,7 +86,11 @@ export function ActionBar({ onChangeCount(newDocCount); } }; - + useEffect(() => { + if (newDocCount !== docCount && newDocCount === 0) { + setNewDocCount(docCount); + } + }, [docCount, newDocCount]); return (
@@ -145,7 +149,7 @@ export function ActionBar({ - + {isSuccessor ? ( - - - + - - -
- - - - - - - - -
+ reason="contextApp.state.loadingStatus.anchor.reason" + default-step-size="contextApp.state.queryParameters.defaultStepSize" + predecessor-count="contextApp.state.queryParameters.predecessorCount" + predecessor-available="contextApp.state.rows.predecessors.length" + predecessor-status="contextApp.state.loadingStatus.predecessors.status" + on-change-predecessor-count="contextApp.actions.fetchGivenPredecessorRows" + successor-count="contextApp.state.queryParameters.successorCount" + successor-available="contextApp.state.rows.successors.length" + successor-status="contextApp.state.loadingStatus.successors.status" + on-change-successor-count="contextApp.actions.fetchGivenSuccessorRows" +> diff --git a/src/plugins/discover/public/application/components/context_app/__snapshots__/context_app_legacy.test.tsx.snap b/src/plugins/discover/public/application/components/context_app/__snapshots__/context_app_legacy.test.tsx.snap deleted file mode 100644 index 58305ee23cb210..00000000000000 --- a/src/plugins/discover/public/application/components/context_app/__snapshots__/context_app_legacy.test.tsx.snap +++ /dev/null @@ -1,741 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ContextAppLegacy test renders correctly 1`] = ` - - - - - -
-
- -
- -
-
- - - - - -`; - -exports[`ContextAppLegacy test renders loading indicator 1`] = ` - - - - - -
- -
- -
- - Loading... - -
-
-
-
-
-
-
-
-
-
-`; diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss new file mode 100644 index 00000000000000..87194d834827b2 --- /dev/null +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss @@ -0,0 +1,5 @@ +.dscCxtAppContent { + border: none; + background-color: transparent; + box-shadow: none; +} diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx index 16d8cd78004f92..77dd0a6d647c68 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx @@ -23,6 +23,8 @@ import { IIndexPattern } from '../../../../../data/common/index_patterns'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DocTableLegacy } from '../../angular/doc_table/create_doc_table_react'; import { findTestSubject } from '@elastic/eui/lib/test'; +import { ActionBar } from '../../angular/context/components/action_bar/action_bar'; +import { ContextErrorMessage } from '../context_error_message'; describe('ContextAppLegacy test', () => { const hit = { @@ -48,28 +50,47 @@ describe('ContextAppLegacy test', () => { columns: ['_source'], filter: () => {}, hits: [hit], - infiniteScroll: true, sorting: ['order_date', 'desc'], minimumVisibleRows: 5, indexPattern, status: 'loaded', + reason: 'no reason', + defaultStepSize: 5, + predecessorCount: 10, + successorCount: 10, + predecessorAvailable: 10, + successorAvailable: 10, + onChangePredecessorCount: jest.fn(), + onChangeSuccessorCount: jest.fn(), + predecessorStatus: 'loaded', + successorStatus: 'loaded', }; it('renders correctly', () => { const component = mountWithIntl(); - expect(component).toMatchSnapshot(); expect(component.find(DocTableLegacy).length).toBe(1); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(loadingIndicator.length).toBe(0); + expect(component.find(ActionBar).length).toBe(2); }); it('renders loading indicator', () => { const props = { ...defaultProps }; props.status = 'loading'; const component = mountWithIntl(); - expect(component).toMatchSnapshot(); - expect(component.find('DocTableLegacy').length).toBe(0); + expect(component.find(DocTableLegacy).length).toBe(0); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(loadingIndicator.length).toBe(1); + expect(component.find(ActionBar).length).toBe(2); + }); + + it('renders error message', () => { + const props = { ...defaultProps }; + props.status = 'failed'; + props.reason = 'something went wrong'; + const component = mountWithIntl(); + expect(component.find(DocTableLegacy).length).toBe(0); + const errorMessage = component.find(ContextErrorMessage); + expect(errorMessage.length).toBe(1); }); }); diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx index ee8b2f590f71c0..b5387ec51db81b 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx @@ -16,16 +16,18 @@ * specific language governing permissions and limitations * under the License. */ +import './context_app_legacy.scss'; import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiPanel, EuiText } from '@elastic/eui'; -import { I18nProvider } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { EuiPanel, EuiText, EuiPageContent, EuiPage } from '@elastic/eui'; +import { ContextErrorMessage } from '../context_error_message'; import { DocTableLegacy, DocTableLegacyProps, } from '../../angular/doc_table/create_doc_table_react'; import { IIndexPattern, IndexPatternField } from '../../../../../data/common/index_patterns'; import { LOADING_STATUS } from './constants'; +import { ActionBar, ActionBarProps } from '../../angular/context/components/action_bar/action_bar'; export interface ContextAppProps { columns: string[]; @@ -35,15 +37,66 @@ export interface ContextAppProps { minimumVisibleRows: number; sorting: string[]; status: string; + reason: string; + defaultStepSize: number; + predecessorCount: number; + successorCount: number; + predecessorAvailable: number; + successorAvailable: number; + onChangePredecessorCount: (count: number) => void; + onChangeSuccessorCount: (count: number) => void; + predecessorStatus: string; + successorStatus: string; +} + +const PREDECESSOR_TYPE = 'predecessors'; +const SUCCESSOR_TYPE = 'successors'; + +function isLoading(status: string) { + return status !== LOADING_STATUS.LOADED && status !== LOADING_STATUS.FAILED; } export function ContextAppLegacy(renderProps: ContextAppProps) { - const { hits, filter, sorting, status } = renderProps; - const props = ({ ...renderProps } as unknown) as DocTableLegacyProps; - props.rows = hits; - props.onFilter = filter; - props.sort = sorting.map((el) => [el]); + const status = renderProps.status; const isLoaded = status === LOADING_STATUS.LOADED; + const isFailed = status === LOADING_STATUS.FAILED; + + const actionBarProps = (type: string) => { + const { + defaultStepSize, + successorCount, + predecessorCount, + predecessorAvailable, + successorAvailable, + predecessorStatus, + successorStatus, + onChangePredecessorCount, + onChangeSuccessorCount, + } = renderProps; + const isPredecessorType = type === PREDECESSOR_TYPE; + return { + defaultStepSize, + docCount: isPredecessorType ? predecessorCount : successorCount, + docCountAvailable: isPredecessorType ? predecessorAvailable : successorAvailable, + onChangeCount: isPredecessorType ? onChangePredecessorCount : onChangeSuccessorCount, + isLoading: isPredecessorType ? isLoading(predecessorStatus) : isLoading(successorStatus), + type, + isDisabled: !isLoaded, + } as ActionBarProps; + }; + + const docTableProps = () => { + const { hits, filter, sorting, columns, indexPattern, minimumVisibleRows } = renderProps; + return { + columns, + indexPattern, + minimumVisibleRows, + rows: hits, + onFilter: filter, + sort: sorting.map((el) => [el]), + } as DocTableLegacyProps; + }; + const loadingFeedback = () => { if (status === LOADING_STATUS.UNINITIALIZED || status === LOADING_STATUS.LOADING) { return ( @@ -59,18 +112,27 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { } return null; }; + return ( - - {loadingFeedback()} - {isLoaded ? ( - -
- -
-
- ) : null} -
+ {isFailed ? ( + + ) : ( + + + + {loadingFeedback()} + {isLoaded ? ( + +
+ +
+
+ ) : null} + +
+
+ )}
); } diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts index af94c5537da288..bc4b7c4babd21e 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts @@ -25,8 +25,17 @@ export function createContextAppLegacy(reactDirective: any) { ['indexPattern', { watchDepth: 'reference' }], ['sorting', { watchDepth: 'reference' }], ['columns', { watchDepth: 'collection' }], - ['infiniteScroll', { watchDepth: 'reference' }], ['minimumVisibleRows', { watchDepth: 'reference' }], ['status', { watchDepth: 'reference' }], + ['reason', { watchDepth: 'reference' }], + ['defaultStepSize', { watchDepth: 'reference' }], + ['predecessorCount', { watchDepth: 'reference' }], + ['predecessorAvailable', { watchDepth: 'reference' }], + ['predecessorStatus', { watchDepth: 'reference' }], + ['onChangePredecessorCount', { watchDepth: 'reference' }], + ['successorCount', { watchDepth: 'reference' }], + ['successorAvailable', { watchDepth: 'reference' }], + ['successorStatus', { watchDepth: 'reference' }], + ['onChangeSuccessorCount', { watchDepth: 'reference' }], ]); } diff --git a/src/plugins/discover/public/application/components/context_error_message/index.ts b/src/plugins/discover/public/application/components/context_error_message/index.ts index f20f2ccf8afa0a..dfa9602408f8f2 100644 --- a/src/plugins/discover/public/application/components/context_error_message/index.ts +++ b/src/plugins/discover/public/application/components/context_error_message/index.ts @@ -18,4 +18,3 @@ */ export { ContextErrorMessage } from './context_error_message'; -export { createContextErrorMessageDirective } from './context_error_message_directive'; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 99dad418c04bdd..a42e2412ae9288 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -224,7 +224,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { legend={legend} options={toggleButtons(id)} idSelected={`${id}-${values[id]}`} - onChange={(optionId) => handleValueChange(id, optionId.replace(`${id}-`, ''))} + onChange={(optionId: string) => handleValueChange(id, optionId.replace(`${id}-`, ''))} buttonSize="compressed" isFullWidth data-test-subj={`${id}ButtonGroup`} diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index af88cacfcf9923..170078076ec6f4 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -266,6 +266,8 @@ export class SearchEmbeddable } private fetch = async () => { + const searchSessionId = this.input.searchSessionId; + if (!this.searchScope) return; const { searchSource } = this.savedSearch; @@ -292,7 +294,11 @@ export class SearchEmbeddable const description = i18n.translate('discover.embeddable.inspectorRequestDescription', { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); - const inspectorRequest = this.inspectorAdaptors.requests.start(title, { description }); + + const inspectorRequest = this.inspectorAdaptors.requests.start(title, { + description, + searchSessionId, + }); inspectorRequest.stats(getRequestInspectorStats(searchSource)); searchSource.getSearchRequestBody().then((body: Record) => { inspectorRequest.json(body); @@ -303,6 +309,7 @@ export class SearchEmbeddable // Make the request const resp = await searchSource.fetch({ abortSignal: this.abortController.signal, + sessionId: searchSessionId, }); this.updateOutput({ loading: false, error: undefined }); diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index 55a75240909bf5..651a26cad755db 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -52,7 +52,6 @@ import { createTopNavDirective, createTopNavHelper, } from '../../kibana_legacy/public'; -import { createContextErrorMessageDirective } from './application/components/context_error_message'; import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive'; @@ -137,8 +136,7 @@ export function initializeInnerAngularModule( .config(watchMultiDecorator) .run(registerListenEventListener) .directive('renderComplete', createRenderCompleteDirective) - .directive('discoverLegacy', createDiscoverLegacyDirective) - .directive('contextErrorMessage', createContextErrorMessageDirective); + .directive('discoverLegacy', createDiscoverLegacyDirective); } function createLocalPromiseModule() { diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index 68b842c934de89..2737f2678ff32c 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -67,4 +67,9 @@ export type EmbeddableInput = { * Visualization filters used to narrow down results. */ filters?: Filter[]; + + /** + * Search session id to group searches + */ + searchSessionId?: string; }; diff --git a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx index 7790646a88a68c..9b9d95628f19d9 100644 --- a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx +++ b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx @@ -83,7 +83,6 @@ export const PanelOptionsMenu: React.FC = ({ panelPaddingSize="none" anchorPosition="downRight" data-test-subj={open ? 'embeddablePanelContextMenuOpen' : 'embeddablePanelContextMenuClosed'} - withTitle > diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 789353ca4abd78..0d2dcf208f2ef1 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -70,6 +70,7 @@ export { isSavedObjectEmbeddableInput, isRangeSelectTriggerContext, isValueClickTriggerContext, + isContextMenuTriggerContext, EmbeddableStateTransfer, EmbeddableEditorState, EmbeddablePackageState, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index 340d851f3eedfc..b020006c0c2bb0 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -25,6 +25,7 @@ import { EmbeddableOutput, EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; import { FilterableEmbeddable } from '../test_samples/embeddables/filterable_embeddable'; +import type { Filter } from '../../../../data/public'; class TestClass { constructor() {} @@ -79,6 +80,20 @@ test('Embeddable reload is called if lastReloadRequest input time changes', asyn expect(hello.reload).toBeCalledTimes(1); }); +test('Embeddable reload is called if lastReloadRequest input time changed and new input is used', async () => { + const hello = new FilterableEmbeddable({ id: '123', filters: [], lastReloadRequestTime: 0 }); + + const aFilter = ({} as unknown) as Filter; + hello.reload = jest.fn(() => { + // when reload is called embeddable already has new input + expect(hello.getInput().filters).toEqual([aFilter]); + }); + + hello.updateInput({ lastReloadRequestTime: 1, filters: [aFilter] }); + + expect(hello.reload).toBeCalledTimes(1); +}); + test('Embeddable reload is not called if lastReloadRequest input time does not change', async () => { const hello = new FilterableEmbeddable({ id: '123', filters: [], lastReloadRequestTime: 1 }); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 9267d600360cfc..c7afc157c14523 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -195,14 +195,15 @@ export abstract class Embeddable< private onResetInput(newInput: TEmbeddableInput) { if (!isEqual(this.input, newInput)) { - if (this.input.lastReloadRequestTime !== newInput.lastReloadRequestTime) { - this.reload(); - } + const oldLastReloadRequestTime = this.input.lastReloadRequestTime; this.input = newInput; this.input$.next(newInput); this.updateOutput({ title: getPanelTitle(this.input, this.output), } as Partial); + if (oldLastReloadRequestTime !== newInput.lastReloadRequestTime) { + this.reload(); + } } } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx index 629a5f8c880e89..82bfb9b47e1a7c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx @@ -113,7 +113,6 @@ export class PanelOptionsMenu extends React.Component = | ValueClickContext | RangeSelectContext; -export const isValueClickTriggerContext = ( - context: ChartActionContext -): context is ValueClickContext => context.data && 'data' in context.data; - -export const isRangeSelectTriggerContext = ( - context: ChartActionContext -): context is RangeSelectContext => context.data && 'range' in context.data; - export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { id: CONTEXT_MENU_TRIGGER, - title: 'Context menu', - description: 'Triggered on top-right corner context-menu select.', + title: i18n.translate('embeddableApi.contextMenuTrigger.title', { + defaultMessage: 'Context menu', + }), + description: i18n.translate('embeddableApi.contextMenuTrigger.description', { + defaultMessage: 'A panel top-right corner context menu click.', + }), }; export const PANEL_BADGE_TRIGGER = 'PANEL_BADGE_TRIGGER'; export const panelBadgeTrigger: Trigger<'PANEL_BADGE_TRIGGER'> = { id: PANEL_BADGE_TRIGGER, - title: 'Panel badges', - description: 'Actions appear in title bar when an embeddable loads in a panel.', + title: i18n.translate('embeddableApi.panelBadgeTrigger.title', { + defaultMessage: 'Panel badges', + }), + description: i18n.translate('embeddableApi.panelBadgeTrigger.description', { + defaultMessage: 'Actions appear in title bar when an embeddable loads in a panel.', + }), }; export const PANEL_NOTIFICATION_TRIGGER = 'PANEL_NOTIFICATION_TRIGGER'; export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { id: PANEL_NOTIFICATION_TRIGGER, - title: 'Panel notifications', - description: 'Actions appear in top-right corner of a panel.', + title: i18n.translate('embeddableApi.panelNotificationTrigger.title', { + defaultMessage: 'Panel notifications', + }), + description: i18n.translate('embeddableApi.panelNotificationTrigger.description', { + defaultMessage: 'Actions appear in top-right corner of a panel.', + }), }; + +export const isValueClickTriggerContext = ( + context: ChartActionContext +): context is ValueClickContext => context.data && 'data' in context.data; + +export const isRangeSelectTriggerContext = ( + context: ChartActionContext +): context is RangeSelectContext => context.data && 'range' in context.data; + +export const isContextMenuTriggerContext = (context: unknown): context is EmbeddableContext => + !!context && + typeof context === 'object' && + !!(context as EmbeddableContext).embeddable && + typeof (context as EmbeddableContext).embeddable === 'object'; diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 84dd97c8288fce..e84dff1172c2e3 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -17,6 +17,7 @@ import { CoreSetup as CoreSetup_2 } from 'src/core/public'; import { CoreSetup as CoreSetup_3 } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'kibana/public'; import * as CSS from 'csstype'; +import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; @@ -425,6 +426,7 @@ export type EmbeddableInput = { timeRange?: TimeRange; query?: Query; filters?: Filter[]; + searchSessionId?: string; }; // Warning: (ae-missing-release-tag) "EmbeddableInstanceConfiguration" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -693,6 +695,11 @@ export interface IEmbeddable): void; } +// Warning: (ae-missing-release-tag) "isContextMenuTriggerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext; + // Warning: (ae-missing-release-tag) "isErrorEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -882,7 +889,7 @@ export const withEmbeddableSubscription: { + const encodedString = 'test%3F'; + expect(attemptToURIDecode(encodedString)).toBe('test?'); +}); + +// react router partially decodes %25 sequence to % in match params +// https://github.com/elastic/kibana/pull/81664 +test('ignores the error if a string is already decoded', () => { + const decodedString = 'test%'; + expect(attemptToURIDecode(decodedString)).toBe(decodedString); +}); diff --git a/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts new file mode 100644 index 00000000000000..65444b83f77bb2 --- /dev/null +++ b/src/plugins/es_ui_shared/public/url/attempt_to_uri_decode.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Use this function with any match params coming from react router to safely decode values. + * https://github.com/elastic/kibana/pull/81664 + */ +export const attemptToURIDecode = (value: string) => { + let result = value; + try { + result = decodeURIComponent(value); + } catch (e) { + // do nothing + } + return result; +}; diff --git a/src/plugins/es_ui_shared/public/url/index.ts b/src/plugins/es_ui_shared/public/url/index.ts index 692e094f9eda49..a40885545ca0cf 100644 --- a/src/plugins/es_ui_shared/public/url/index.ts +++ b/src/plugins/es_ui_shared/public/url/index.ts @@ -18,3 +18,4 @@ */ export { extractQueryParams } from './extract_query_params'; +export { attemptToURIDecode } from './attempt_to_uri_decode'; diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 2bcf441b14203e..ba115a7538604d 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -99,7 +99,7 @@ export class Execution< * Races a given promise against the "abort" event of `abortController`. */ private race(promise: Promise): Promise { - return Promise.race([this.abortRejection, promise]); + return Promise.race([this.abortRejection.promise, promise]); } /** @@ -189,14 +189,18 @@ export class Execution< else reject(error); }); - this.firstResultFuture.promise.then( - (result) => { - this.state.transitions.setResult(result); - }, - (error) => { - this.state.transitions.setError(error); - } - ); + this.firstResultFuture.promise + .then( + (result) => { + this.state.transitions.setResult(result); + }, + (error) => { + this.state.transitions.setError(error); + } + ) + .finally(() => { + this.abortRejection.cleanup(); + }); } async invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise { diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index 856b22470d782b..eaf7e6ea862eb4 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -59,7 +59,7 @@ describe('ExecutionContract', () => { test('can cancel execution', () => { const execution = createExecution('foo bar=123'); - const spy = jest.spyOn(execution, 'cancel'); + const spy = jest.spyOn(execution, 'cancel').mockImplementation(() => {}); const contract = new ExecutionContract(execution); expect(spy).toHaveBeenCalledTimes(0); diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index a658d3457407c2..308d6f7e71814e 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -22,6 +22,7 @@ import * as expressionTypes from '../expression_types'; import * as expressionFunctions from '../expression_functions'; import { Execution } from '../execution'; import { ExpressionAstFunction, parseExpression } from '../ast'; +import { MigrateFunction } from '../../../kibana_utils/common/persistable_state'; describe('Executor', () => { test('can instantiate', () => { @@ -158,6 +159,7 @@ describe('Executor', () => { const injectFn = jest.fn().mockImplementation((args, references) => args); const extractFn = jest.fn().mockReturnValue({ args: {}, references: [] }); + const migrateFn = jest.fn().mockImplementation((args) => args); const fooFn = { name: 'foo', @@ -174,6 +176,14 @@ describe('Executor', () => { inject: (state: ExpressionAstFunction['arguments']) => { return injectFn(state); }, + migrations: { + '7.10.0': (((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { + return migrateFn(state, version); + }) as any) as MigrateFunction, + '7.10.1': (((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { + return migrateFn(state, version); + }) as any) as MigrateFunction, + }, fn: jest.fn(), }; executor.registerFunction(fooFn); @@ -194,5 +204,26 @@ describe('Executor', () => { expect(extractFn).toBeCalledTimes(5); }); }); + + describe('.migrate', () => { + test('calls migrate function for every expression function in expression', () => { + executor.migrate( + parseExpression('foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}'), + '7.10.0' + ); + expect(migrateFn).toBeCalledTimes(5); + }); + }); + + describe('.migrateToLatest', () => { + test('calls extract function for every expression function in expression', () => { + migrateFn.mockClear(); + executor.migrateToLatest( + parseExpression('foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}'), + '7.10.0' + ); + expect(migrateFn).toBeCalledTimes(10); + }); + }); }); }); diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 85b5589b593af0..19fc4cf5a14a2a 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -32,7 +32,7 @@ import { typeSpecs } from '../expression_types/specs'; import { functionSpecs } from '../expression_functions/specs'; import { getByAlias } from '../util'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; import { ExpressionExecutionParams } from '../service'; export interface ExpressionExecOptions { @@ -88,6 +88,20 @@ export class FunctionsRegistry implements IRegistry { } } +const semverGte = (semver1: string, semver2: string) => { + const regex = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/; + const matches1 = regex.exec(semver1) as RegExpMatchArray; + const matches2 = regex.exec(semver2) as RegExpMatchArray; + + const [, major1, minor1, patch1] = matches1; + const [, major2, minor2, patch2] = matches2; + + return ( + major1 > major2 || + (major1 === major2 && (minor1 > minor2 || (minor1 === minor2 && patch1 >= patch2))) + ); +}; + export class Executor = Record> implements PersistableState { static createWithDefaults = Record>( @@ -249,6 +263,27 @@ export class Executor = Record { + if (!fn.migrations[version]) return link; + const updatedAst = fn.migrations[version](link) as ExpressionAstFunction; + link.arguments = updatedAst.arguments; + link.type = updatedAst.type; + }); + } + + public migrateToLatest(ast: unknown, version: string) { + return this.walkAst(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => { + for (const key of Object.keys(fn.migrations)) { + if (semverGte(key, version)) { + const updatedAst = fn.migrations[key](link) as ExpressionAstFunction; + link.arguments = updatedAst.arguments; + link.type = updatedAst.type; + } + } + }); + } + public fork(): Executor { const initialState = this.state.get(); const fork = new Executor(initialState); diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts index 0b56d3c169ff4e..2879cc8e3632c2 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -24,7 +24,7 @@ import { ExpressionValue } from '../expression_types/types'; import { ExecutionContext } from '../execution'; import { ExpressionAstFunction } from '../ast'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; export class ExpressionFunction implements PersistableState { /** @@ -76,6 +76,9 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; constructor(functionDefinition: AnyExpressionFunctionDefinition) { const { @@ -91,6 +94,7 @@ export class ExpressionFunction implements PersistableState c); this.inject = inject || identity; this.extract = extract || ((s) => ({ state: s, references: [] })); + this.migrations = migrations || {}; for (const [key, arg] of Object.entries(args || {})) { this.args[key] = new ExpressionFunctionParameter(key, arg); diff --git a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts index 970015638794f1..672abadd3c0160 100644 --- a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts +++ b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts @@ -19,7 +19,8 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; -import { Datatable, DatatableRow } from '../../expression_types'; +import { Datatable } from '../../expression_types'; +import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; export interface CumulativeSumArgs { by?: string[]; @@ -35,15 +36,6 @@ export type ExpressionFunctionCumulativeSum = ExpressionFunctionDefinition< Datatable >; -/** - * Returns a string identifying the group of a row by a list of columns to group by - */ -function getBucketIdentifier(row: DatatableRow, groupColumns?: string[]) { - return (groupColumns || []) - .map((groupColumnId) => (row[groupColumnId] == null ? '' : String(row[groupColumnId]))) - .join('|'); -} - /** * Calculates the cumulative sum of a specified column in the data table. * @@ -114,38 +106,17 @@ export const cumulativeSum: ExpressionFunctionCumulativeSum = { }, fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { - if (input.columns.some((column) => column.id === outputColumnId)) { - throw new Error( - i18n.translate('expressions.functions.cumulativeSum.columnConflictMessage', { - defaultMessage: - 'Specified outputColumnId {columnId} already exists. Please pick another column id.', - values: { - columnId: outputColumnId, - }, - }) - ); - } - - const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); - if (!inputColumnDefinition) { + if (!resultColumns) { return input; } - const outputColumnDefinition = { - ...inputColumnDefinition, - id: outputColumnId, - name: outputColumnName || outputColumnId, - }; - - const resultColumns = [...input.columns]; - // add output column after input column in the table - resultColumns.splice( - resultColumns.indexOf(inputColumnDefinition) + 1, - 0, - outputColumnDefinition - ); - const accumulators: Partial> = {}; return { ...input, diff --git a/src/plugins/expressions/common/expression_functions/specs/derivative.ts b/src/plugins/expressions/common/expression_functions/specs/derivative.ts new file mode 100644 index 00000000000000..44ac198e2d17c1 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/derivative.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 { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../types'; +import { Datatable } from '../../expression_types'; +import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; + +export interface DerivativeArgs { + by?: string[]; + inputColumnId: string; + outputColumnId: string; + outputColumnName?: string; +} + +export type ExpressionFunctionDerivative = ExpressionFunctionDefinition< + 'derivative', + Datatable, + DerivativeArgs, + Datatable +>; + +/** + * Calculates the derivative of a specified column in the data table. + * + * Also supports multiple series in a single data table - use the `by` argument + * to specify the columns to split the calculation by. + * For each unique combination of all `by` columns a separate derivative will be calculated. + * The order of rows won't be changed - this function is not modifying any existing columns, it's only + * adding the specified `outputColumnId` column to every row of the table without adding or removing rows. + * + * Behavior: + * * Will write the derivative of `inputColumnId` into `outputColumnId` + * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId` + * * Derivative always start with an undefined value for the first row of a series, a cell will contain its own value minus the + * value of the previous cell of the same series. + * + * Edge cases: + * * Will return the input table if `inputColumnId` does not exist + * * Will throw an error if `outputColumnId` exists already in provided data table + * * If there is no previous row of the current series with a non `null` or `undefined` value, the output cell of the current row + * will be set to `undefined`. + * * If the row value contains `null` or `undefined`, it will be ignored and the output cell will be set to `undefined` + * * If the value of the previous row of the same series contains `null` or `undefined`, the output cell of the current row will be set to `undefined` as well + * * For all values besides `null` and `undefined`, the value will be cast to a number before it's used in the + * calculation of the current series even if this results in `NaN` (like in case of objects). + * * To determine separate series defined by the `by` columns, the values of these columns will be cast to strings + * before comparison. If the values are objects, the return value of their `toString` method will be used for comparison. + * Missing values (`null` and `undefined`) will be treated as empty strings. + */ +export const derivative: ExpressionFunctionDerivative = { + name: 'derivative', + type: 'datatable', + + inputTypes: ['datatable'], + + help: i18n.translate('expressions.functions.derivative.help', { + defaultMessage: 'Calculates the derivative of a column in a data table', + }), + + args: { + by: { + help: i18n.translate('expressions.functions.derivative.args.byHelpText', { + defaultMessage: 'Column to split the derivative calculation by', + }), + multi: true, + types: ['string'], + required: false, + }, + inputColumnId: { + help: i18n.translate('expressions.functions.derivative.args.inputColumnIdHelpText', { + defaultMessage: 'Column to calculate the derivative of', + }), + types: ['string'], + required: true, + }, + outputColumnId: { + help: i18n.translate('expressions.functions.derivative.args.outputColumnIdHelpText', { + defaultMessage: 'Column to store the resulting derivative in', + }), + types: ['string'], + required: true, + }, + outputColumnName: { + help: i18n.translate('expressions.functions.derivative.args.outputColumnNameHelpText', { + defaultMessage: 'Name of the column to store the resulting derivative in', + }), + types: ['string'], + required: false, + }, + }, + + fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); + + if (!resultColumns) { + return input; + } + + const previousValues: Partial> = {}; + return { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + const bucketIdentifier = getBucketIdentifier(row, by); + const previousValue = previousValues[bucketIdentifier]; + const currentValue = newRow[inputColumnId]; + + if (currentValue != null && previousValue != null) { + newRow[outputColumnId] = Number(currentValue) - previousValue; + } else { + newRow[outputColumnId] = undefined; + } + + if (currentValue != null) { + previousValues[bucketIdentifier] = Number(currentValue); + } else { + previousValues[bucketIdentifier] = undefined; + } + + return newRow; + }), + }; + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index aadea5882b9c05..d414057598f123 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -26,6 +26,7 @@ import { variable } from './var'; import { AnyExpressionFunctionDefinition } from '../types'; import { theme } from './theme'; import { cumulativeSum } from './cumulative_sum'; +import { derivative } from './derivative'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, @@ -36,6 +37,7 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ variable, theme, cumulativeSum, + derivative, ]; export * from './clog'; @@ -46,3 +48,4 @@ export * from './var_set'; export * from './var'; export * from './theme'; export * from './cumulative_sum'; +export * from './derivative'; diff --git a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts b/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts new file mode 100644 index 00000000000000..8ba9d527d4c598 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Datatable, DatatableRow } from '../../expression_types'; + +/** + * Returns a string identifying the group of a row by a list of columns to group by + */ +export function getBucketIdentifier(row: DatatableRow, groupColumns?: string[]) { + return (groupColumns || []) + .map((groupColumnId) => (row[groupColumnId] == null ? '' : String(row[groupColumnId]))) + .join('|'); +} + +/** + * Checks whether input and output columns are defined properly + * and builds column array of the output table if that's the case. + * + * * Throws an error if the output column exists already. + * * Returns undefined if the input column doesn't exist. + * @param input Input datatable + * @param outputColumnId Id of the output column + * @param inputColumnId Id of the input column + * @param outputColumnName Optional name of the output column + */ +export function buildResultColumns( + input: Datatable, + outputColumnId: string, + inputColumnId: string, + outputColumnName: string | undefined +) { + if (input.columns.some((column) => column.id === outputColumnId)) { + throw new Error( + i18n.translate('expressions.functions.seriesCalculations.columnConflictMessage', { + defaultMessage: + 'Specified outputColumnId {columnId} already exists. Please pick another column id.', + values: { + columnId: outputColumnId, + }, + }) + ); + } + + const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + + if (!inputColumnDefinition) { + return; + } + + const outputColumnDefinition = { + ...inputColumnDefinition, + id: outputColumnId, + name: outputColumnName || outputColumnId, + }; + + const resultColumns = [...input.columns]; + // add output column after input column in the table + resultColumns.splice(resultColumns.indexOf(inputColumnDefinition) + 1, 0, outputColumnDefinition); + return resultColumns; +} diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts new file mode 100644 index 00000000000000..63b2df5382557d --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts @@ -0,0 +1,406 @@ +/* + * 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 { functionWrapper } from './utils'; +import { derivative, DerivativeArgs } from '../derivative'; +import { ExecutionContext } from '../../../execution/types'; +import { Datatable } from '../../../expression_types/specs/datatable'; + +describe('interpreter/functions#derivative', () => { + const fn = functionWrapper(derivative); + const runFn = (input: Datatable, args: DerivativeArgs) => + fn(input, args, {} as ExecutionContext) as Datatable; + + it('calculates derivative', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2, -4, -1]); + }); + + it('skips null or undefined values until there is real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: undefined }, + { val: undefined }, + { val: 4 }, + { val: 8 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + undefined, + undefined, + undefined, + 8 - 4, + ]); + }); + + it('treats 0 as real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: 8 }, + { val: 0 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + 0 - 2, + undefined, + undefined, + undefined, + undefined, + 8 - 0, + 0 - 8, + ]); + }); + + it('calculates derivative for multiple series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3, split: 'B' }, + { val: 4, split: 'A' }, + { val: 5, split: 'A' }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + 3 - 2, + 4 - 1, + 5 - 4, + 6 - 5, + 7 - 3, + 8 - 7, + ]); + }); + + it('treats missing split column as separate series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 2, + 8 - 7, + ]); + }); + + it('treats null like undefined and empty string for split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: null }, + { val: 8, split: 'B' }, + { val: 9, split: '' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 5, + 8 - 2, + 9 - 7, + ]); + }); + + it('calculates derivative for multiple series by multiple split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + { id: 'split2', name: 'split2', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A', split2: 'C' }, + { val: 2, split: 'B', split2: 'C' }, + { val: 3, split2: 'C' }, + { val: 4, split: 'A', split2: 'C' }, + { val: 5 }, + { val: 6, split: 'A', split2: 'D' }, + { val: 7, split: 'B', split2: 'D' }, + { val: 8, split: 'B', split2: 'D' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split', 'split2'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + undefined, + undefined, + undefined, + 8 - 7, + ]); + }); + + it('splits separate series by the string representation of the cell values', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: { anObj: 3 } }, + { val: 2, split: { anotherObj: 5 } }, + { val: 10, split: 5 }, + { val: 11, split: '5' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2 - 1, undefined, 11 - 10]); + }); + + it('casts values to number before calculating derivative', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, 3 - 7, 2 - 3]); + }); + + it('casts values to number before calculating derivative for NaN like values', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 2 }, { val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, NaN, NaN, 5 - 2]); + }); + + it('copies over meta information from the source column', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }); + }); + + it('sets output name on output column if specified', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output', outputColumnName: 'Output name' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'Output name', + meta: { type: 'number' }, + }); + }); + + it('returns source table if input column does not exist', () => { + const input: Datatable = { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }; + expect(runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe(input); + }); + + it('throws an error if output column exists already', () => { + expect(() => + runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'val' } + ) + ).toThrow(); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index fb1823e85b3917..134e9e3a635021 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -30,6 +30,7 @@ import { ExpressionFunctionVar, ExpressionFunctionTheme, ExpressionFunctionCumulativeSum, + ExpressionFunctionDerivative, } from './specs'; import { ExpressionAstFunction } from '../ast'; import { PersistableStateDefinition } from '../../../kibana_utils/common'; @@ -133,4 +134,5 @@ export interface ExpressionFunctionDefinitions { var: ExpressionFunctionVar; theme: ExpressionFunctionTheme; cumulative_sum: ExpressionFunctionCumulativeSum; + derivative: ExpressionFunctionDerivative; } diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index abbba433ab3caa..0f898563c3d0e0 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -24,7 +24,7 @@ import { ExecutionContract } from '../execution/execution_contract'; import { AnyExpressionTypeDefinition } from '../expression_types'; import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; import { Adapters } from '../../../inspector/common/adapters'; import { ExecutionContextSearch } from '../execution'; @@ -303,6 +303,26 @@ export class ExpressionsService implements PersistableState { + return this.executor.migrate(state, version); + }; + + /** + * Migrates expression to the latest version + * @param state expression AST to update + * @param version the version of kibana in which expression was created + * @returns migrated expression AST + */ + public readonly migrateToLatest = (state: unknown, version: string) => { + return this.executor.migrateToLatest(state, version); + }; + /** * Returns Kibana Platform *setup* life-cycle contract. Useful to return the * same contract on server-side and browser-side. diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index fe95cf5eb0cdac..06b7bc21424478 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -222,6 +222,12 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -342,6 +348,10 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; inputTypes: string[] | undefined; + // (undocumented) + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; name: string; // (undocumented) telemetry: (state: ExpressionAstFunction['arguments'], telemetryData: Record) => Record; @@ -379,6 +389,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) cumulative_sum: ExpressionFunctionCumulativeSum; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDerivative" needs to be exported by the entry point index.d.ts + // + // (undocumented) + derivative: ExpressionFunctionDerivative; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionFont" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -586,6 +600,8 @@ export class ExpressionsService implements PersistableState ReturnType; readonly inject: (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression; + readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; + readonly migrateToLatest: (state: unknown, version: string) => ExpressionAstExpression; readonly registerFunction: (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void; // (undocumented) readonly registerRenderer: (definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)) => void; @@ -1039,7 +1055,7 @@ export interface Range { // Warning: (ae-missing-release-tag) "ReactExpressionRenderer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; +export const ReactExpressionRenderer: ({ className, dataAttrs, padding, renderError, expression, onEvent, reload$, debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => JSX.Element; // Warning: (ae-missing-release-tag) "ReactExpressionRendererProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1050,6 +1066,8 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { // (undocumented) dataAttrs?: string[]; // (undocumented) + debounce?: number; + // (undocumented) expression: string | ExpressionAstExpression; // (undocumented) onEvent?: (event: ExpressionRendererEvent) => void; @@ -1147,7 +1165,6 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // // src/plugins/expressions/common/ast/types.ts:40:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:31:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts -// src/plugins/expressions/common/expression_types/specs/error.ts:32:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index 7c1711f056d69f..052c2a9f6a24a1 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -113,6 +113,39 @@ describe('ExpressionRenderer', () => { instance.unmount(); }); + it('waits for debounce period if specified', () => { + jest.useFakeTimers(); + + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount( + + ); + + instance.setProps({ expression: 'abc' }); + + expect(loaderUpdate).toHaveBeenCalledTimes(1); + + act(() => { + jest.runAllTimers(); + }); + + expect(loaderUpdate).toHaveBeenCalledTimes(2); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 99d170c96666d8..fecebf36ab7e6a 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -45,6 +45,7 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { * An observable which can be used to re-run the expression without destroying the component */ reload$?: Observable; + debounce?: number; } export type ReactExpressionRendererType = React.ComponentType; @@ -71,6 +72,7 @@ export const ReactExpressionRenderer = ({ expression, onEvent, reload$, + debounce, ...expressionLoaderOptions }: ReactExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); @@ -85,12 +87,28 @@ export const ReactExpressionRenderer = ({ const errorRenderHandlerRef: React.MutableRefObject = useRef( null ); + const [debouncedExpression, setDebouncedExpression] = useState(expression); + useEffect(() => { + if (debounce === undefined) { + return; + } + const handler = setTimeout(() => { + setDebouncedExpression(expression); + }, debounce); + + return () => { + clearTimeout(handler); + }; + }, [expression, debounce]); + + const activeExpression = debounce !== undefined ? debouncedExpression : expression; + const waitingForDebounceToComplete = debounce !== undefined && expression !== debouncedExpression; /* eslint-disable react-hooks/exhaustive-deps */ // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() useEffect(() => { const subs: Subscription[] = []; - expressionLoaderRef.current = new ExpressionLoader(mountpoint.current!, expression, { + expressionLoaderRef.current = new ExpressionLoader(mountpoint.current!, activeExpression, { ...expressionLoaderOptions, // react component wrapper provides different // error handling api which is easier to work with from react @@ -146,21 +164,21 @@ export const ReactExpressionRenderer = ({ useEffect(() => { const subscription = reload$?.subscribe(() => { if (expressionLoaderRef.current) { - expressionLoaderRef.current.update(expression, expressionLoaderOptions); + expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); } }); return () => subscription?.unsubscribe(); - }, [reload$, expression, ...Object.values(expressionLoaderOptions)]); + }, [reload$, activeExpression, ...Object.values(expressionLoaderOptions)]); // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { if (expressionLoaderRef.current) { - expressionLoaderRef.current.update(expression, expressionLoaderOptions); + expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); } }, // when expression is changed by reference and when any other loaderOption is changed by reference - [{ expression, ...expressionLoaderOptions }] + [{ activeExpression, ...expressionLoaderOptions }] ); /* eslint-enable react-hooks/exhaustive-deps */ @@ -188,7 +206,9 @@ export const ReactExpressionRenderer = ({ return (
{state.isEmpty && } - {state.isLoading && } + {(state.isLoading || waitingForDebounceToComplete) && ( + + )} {!state.isLoading && state.error && renderError && diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index d6925a027358c8..cacd61a8638a65 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -204,6 +204,12 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -314,6 +320,10 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; inputTypes: string[] | undefined; + // (undocumented) + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; name: string; // (undocumented) telemetry: (state: ExpressionAstFunction['arguments'], telemetryData: Record) => Record; @@ -351,6 +361,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) cumulative_sum: ExpressionFunctionCumulativeSum; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDerivative" needs to be exported by the entry point index.d.ts + // + // (undocumented) + derivative: ExpressionFunctionDerivative; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionFont" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -939,7 +953,6 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // // src/plugins/expressions/common/ast/types.ts:40:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:31:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts -// src/plugins/expressions/common/expression_types/specs/error.ts:32:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap index 9992fe2c8cf5f1..2a4fb9e48a92b2 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap @@ -175,29 +175,25 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
- - -
- - + + + Step 1 is incomplete + + + + - - -
- - - - - -
-
-
+ + + + diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.test.js b/src/plugins/home/public/application/components/tutorial/tutorial.test.js index 9944ac4848bc61..65e11170e0e429 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.test.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.test.js @@ -133,7 +133,7 @@ describe('isCloudEnabled is false', () => { ); await loadTutorialPromise; component.update(); - component.find('button#onPremElasticCloud').closest('div').find('input').simulate('change'); + component.find('#onPremElasticCloud').first().simulate('click'); component.update(); expect(component.state('visibleInstructions')).toBe('onPremElasticCloud'); }); diff --git a/src/plugins/home/public/services/environment/environment.mock.ts b/src/plugins/home/public/services/environment/environment.mock.ts index d8be02bf6552cb..7b1fb0a85e0d9c 100644 --- a/src/plugins/home/public/services/environment/environment.mock.ts +++ b/src/plugins/home/public/services/environment/environment.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { EnvironmentService, EnvironmentServiceSetup } from './environment'; const createSetupMock = (): jest.Mocked => { diff --git a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts index e1a415ba2d5714..6cb4bb1db90a79 100644 --- a/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts +++ b/src/plugins/home/public/services/feature_catalogue/feature_catalogue_registry.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { FeatureCatalogueRegistrySetup, FeatureCatalogueRegistry, diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts b/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts index 667730e25a2e3e..7aa1aec0898fe1 100644 --- a/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts +++ b/src/plugins/home/public/services/tutorials/tutorial_service.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { TutorialService, TutorialServiceSetup } from './tutorial_service'; const createSetupMock = (): jest.Mocked => { diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts index 4d0fb4f96023a4..717f21576def9b 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { SampleDataRegistrySetup, SampleDataRegistryStart, diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts index 5ff0152062f4b1..162216161342cd 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { TutorialsRegistrySetup, TutorialsRegistryStart, diff --git a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts index b91a265da7d18f..96dab89bb17025 100644 --- a/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts +++ b/src/plugins/home/server/services/tutorials/tutorials_registry.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { TutorialsRegistry } from './tutorials_registry'; import { coreMock } from '../../../../../core/server/mocks'; import { CoreSetup } from '../../../../../core/server'; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap index 6631a9bbd1d02b..db527ea81b2cbc 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap @@ -63,7 +63,6 @@ exports[`IndicesList should change pages 1`] = ` isOpen={false} ownFocus={false} panelPaddingSize="none" - withTitle={true} > diff --git a/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts b/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts index 2c2c68b8ead2d6..5ddbca67d9fec9 100644 --- a/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts +++ b/src/plugins/index_pattern_management/public/service/environment/environment.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { EnvironmentService, EnvironmentServiceSetup } from './environment'; import { MlCardState } from '../../types'; diff --git a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts index 5de6ddf351c021..9d4c1bf005e63e 100644 --- a/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts +++ b/src/plugins/index_pattern_management/server/routes/preview_scripted_field.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { CoreSetup, RequestHandlerContext } from 'src/core/server'; import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks'; import { registerPreviewScriptedFieldRoute } from './preview_scripted_field'; diff --git a/src/plugins/inspector/common/adapters/request/types.ts b/src/plugins/inspector/common/adapters/request/types.ts index 0a62df7c8d10c9..c08c2ef6f706fe 100644 --- a/src/plugins/inspector/common/adapters/request/types.ts +++ b/src/plugins/inspector/common/adapters/request/types.ts @@ -49,6 +49,7 @@ export interface Request extends RequestParams { export interface RequestParams { id?: string; description?: string; + searchSessionId?: string; } export interface RequestStatistics { diff --git a/src/plugins/inspector/public/views/requests/components/requests_view.tsx b/src/plugins/inspector/public/views/requests/components/requests_view.tsx index a433ea70dc35c3..13575de0c5064f 100644 --- a/src/plugins/inspector/public/views/requests/components/requests_view.tsx +++ b/src/plugins/inspector/public/views/requests/components/requests_view.tsx @@ -153,6 +153,21 @@ export class RequestsViewComponent extends Component )} + {this.state.request && this.state.request.searchSessionId && ( + +

+ +

+
+ )} + {this.state.request && } diff --git a/src/plugins/inspector/tsconfig.json b/src/plugins/inspector/tsconfig.json new file mode 100644 index 00000000000000..2a9c41464532c3 --- /dev/null +++ b/src/plugins/inspector/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "index.ts"], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../kibana_react/tsconfig.json" } + ] +} diff --git a/src/plugins/kibana_legacy/public/angular/kbn_top_nav.d.ts b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.d.ts new file mode 100644 index 00000000000000..3d1dcdbef3f4b4 --- /dev/null +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.d.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Injectable, IDirectiveFactory, IScope, IAttributes, IController } from 'angular'; + +export const createTopNavDirective: Injectable>; +export const createTopNavHelper: ( + options: unknown +) => Injectable>; +export function loadKbnTopNavDirectives(navUi: unknown): void; diff --git a/src/plugins/vis_type_vislib/public/components/options/index.ts b/src/plugins/kibana_legacy/public/angular/promises.d.ts similarity index 77% rename from src/plugins/vis_type_vislib/public/components/options/index.ts rename to src/plugins/kibana_legacy/public/angular/promises.d.ts index 57afbd4818ae44..1a2ce66834d7bd 100644 --- a/src/plugins/vis_type_vislib/public/components/options/index.ts +++ b/src/plugins/kibana_legacy/public/angular/promises.d.ts @@ -17,8 +17,4 @@ * under the License. */ -export { GaugeOptions } from './gauge'; -export { PieOptions } from './pie'; -export { PointSeriesOptions } from './point_series'; -export { HeatmapOptions } from './heatmap'; -export { MetricsAxisOptions } from './metrics_axes'; +export function PromiseServiceCreator($q: unknown, $timeout: unknown): (fn: unknown) => unknown; diff --git a/src/plugins/vis_type_vega/public/components/index.ts b/src/plugins/kibana_legacy/public/angular/watch_multi.d.ts similarity index 92% rename from src/plugins/vis_type_vega/public/components/index.ts rename to src/plugins/kibana_legacy/public/angular/watch_multi.d.ts index 90f067c778fd2d..7c73abf2f9aa22 100644 --- a/src/plugins/vis_type_vega/public/components/index.ts +++ b/src/plugins/kibana_legacy/public/angular/watch_multi.d.ts @@ -17,4 +17,4 @@ * under the License. */ -export { VegaVisEditor } from './vega_vis_editor'; +export function watchMultiDecorator($provide: unknown): void; diff --git a/src/plugins/kibana_legacy/public/utils/kbn_accessible_click.d.ts b/src/plugins/kibana_legacy/public/utils/kbn_accessible_click.d.ts new file mode 100644 index 00000000000000..e4ef43fe8d4432 --- /dev/null +++ b/src/plugins/kibana_legacy/public/utils/kbn_accessible_click.d.ts @@ -0,0 +1,27 @@ +/* + * 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 { Injectable, IDirectiveFactory, IScope, IAttributes, IController } from 'angular'; + +export const KbnAccessibleClickProvider: Injectable>; diff --git a/src/plugins/kibana_legacy/public/utils/private.d.ts b/src/plugins/kibana_legacy/public/utils/private.d.ts index 3efc9cd5308f73..00b0220316ead2 100644 --- a/src/plugins/kibana_legacy/public/utils/private.d.ts +++ b/src/plugins/kibana_legacy/public/utils/private.d.ts @@ -17,4 +17,8 @@ * under the License. */ +import { IServiceProvider } from 'angular'; + export type IPrivate = (provider: (...injectable: any[]) => T) => T; + +export function PrivateProvider(): IServiceProvider; diff --git a/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.d.ts b/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.d.ts new file mode 100644 index 00000000000000..eff9b4b871f56c --- /dev/null +++ b/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.d.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 function registerListenEventListener($rootScope: unknown): void; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index e0e295723a69d2..b6efa40c5e40b9 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -427,6 +427,7 @@ class TableListView extends React.Component !error, onClick: this.props.editItem, }, ]; diff --git a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx index 832ea70f0460e0..5d26925d340880 100644 --- a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx +++ b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx @@ -105,15 +105,19 @@ export class ValidatedDualRange extends Component { allowEmptyRange, ...rest // TODO: Consider alternatives for spread operator in component } = this.props; + // Ensure the form row is display as compressed if compressed is true + let evaluatedDisplay = formRowDisplay; + if (!evaluatedDisplay) { + evaluatedDisplay = compressed ? 'rowCompressed' : 'row'; + } return ( = (state: FromVersion) => ToVersion; + export interface PersistableState

{ /** * function to extract telemetry information @@ -47,8 +52,29 @@ export interface PersistableState

{ state: P; references: SavedObjectReference[] }; + + /** + * migrateToLatest function receives state of older version and should migrate to the latest version + * @param state + * @param version + */ + migrateToLatest?: (state: SerializableState, version: string) => P; + + /** + * migrate function runs the specified migration + * @param state + * @param version + */ + migrate?: (state: SerializableState, version: string) => SerializableState; } export type PersistableStateDefinition

= Partial< - PersistableState

->; + Omit, 'migrate'> +> & { + /** + * list of all migrations per semver + */ + migrations?: { + [key: string]: MigrateFunction; + }; +}; diff --git a/src/plugins/kibana_utils/public/storage/storage.test.ts b/src/plugins/kibana_utils/public/storage/storage.test.ts index 8c5d3d11a21fe7..293194cae55a63 100644 --- a/src/plugins/kibana_utils/public/storage/storage.test.ts +++ b/src/plugins/kibana_utils/public/storage/storage.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { MockedKeys } from '@kbn/utility-types/jest'; import { Storage } from './storage'; import { IStorage, IStorageWrapper } from './types'; diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md new file mode 100644 index 00000000000000..097ecd95ea8fac --- /dev/null +++ b/src/plugins/management/README.md @@ -0,0 +1,5 @@ +# Management Plugin + +This plugins contains the "Stack Management" page framework. It offers navigation and an API +to link individual managment section into it. This plugin does not contain any individual +management section itself. \ No newline at end of file diff --git a/src/plugins/maps_legacy/README.md b/src/plugins/maps_legacy/README.md new file mode 100644 index 00000000000000..4a870e4f7492d8 --- /dev/null +++ b/src/plugins/maps_legacy/README.md @@ -0,0 +1,7 @@ +# Maps legacy + +Internal objects used by the Coordinate, Region, and Vega visualizations. + +It exports the default Leaflet-based map and exposes the connection to the Elastic Maps service. + +This plugin is targeted for removal in 8.0. \ No newline at end of file diff --git a/src/plugins/region_map/README.md b/src/plugins/region_map/README.md new file mode 100644 index 00000000000000..540ab47c102d3e --- /dev/null +++ b/src/plugins/region_map/README.md @@ -0,0 +1,5 @@ +# Region map visualization + +Create choropleth maps. Display the results of a term-aggregation as e.g. countries, zip-codes, states. + +This plugin is targeted for removal in 8.0. \ No newline at end of file diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index b86a1aa966dc67..328ef0e95f7448 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -59,6 +59,7 @@ exports[`Intro component renders correctly 1`] = ` size="s" > { conflictedSavedObjectsLinkedToSavedSearches: undefined, conflictedSearchDocs: undefined, unmatchedReferences: undefined, + unmatchedReferencesTablePagination: { + pageIndex: 0, + pageSize: 5, + }, conflictingRecord: undefined, error: undefined, file: undefined, @@ -467,7 +472,7 @@ export class Flyout extends Component { }; renderUnmatchedReferences() { - const { unmatchedReferences } = this.state; + const { unmatchedReferences, unmatchedReferencesTablePagination: tablePagination } = this.state; if (!unmatchedReferences) { return null; @@ -527,22 +532,28 @@ export class Flyout extends Component { { defaultMessage: 'New index pattern' } ), render: (id: string) => { - const options = this.state.indexPatterns!.map( - (indexPattern) => - ({ - text: indexPattern.title, - value: indexPattern.id, - 'data-test-subj': `indexPatternOption-${indexPattern.title}`, - } as { text: string; value: string; 'data-test-subj'?: string }) - ); - - options.unshift({ - text: '-- Skip Import --', - value: '', - }); + const options = [ + { + text: '-- Skip Import --', + value: '', + }, + ...this.state.indexPatterns!.map( + (indexPattern) => + ({ + text: indexPattern.title, + value: indexPattern.id, + 'data-test-subj': `indexPatternOption-${indexPattern.title}`, + } as { text: string; value: string; 'data-test-subj'?: string }) + ), + ]; + + const selectedValue = + unmatchedReferences?.find((unmatchedRef) => unmatchedRef.existingIndexPatternId === id) + ?.newIndexPatternId ?? ''; return ( this.onIndexChanged(id, e)} options={options} @@ -553,6 +564,7 @@ export class Flyout extends Component { ]; const pagination = { + ...tablePagination, pageSizeOptions: [5, 10, 25], }; @@ -561,6 +573,16 @@ export class Flyout extends Component { items={unmatchedReferences as any[]} columns={columns} pagination={pagination} + onTableChange={({ page }) => { + if (page) { + this.setState({ + unmatchedReferencesTablePagination: { + pageSize: page.size, + pageIndex: page.index, + }, + }); + } + }} /> ); } diff --git a/src/plugins/saved_objects_management/public/services/action_service.mock.ts b/src/plugins/saved_objects_management/public/services/action_service.mock.ts index 97c95a589b9250..dfa5f47325eb63 100644 --- a/src/plugins/saved_objects_management/public/services/action_service.mock.ts +++ b/src/plugins/saved_objects_management/public/services/action_service.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsManagementActionService, SavedObjectsManagementActionServiceSetup, diff --git a/src/plugins/saved_objects_management/public/services/column_service.mock.ts b/src/plugins/saved_objects_management/public/services/column_service.mock.ts index 977b2099771baa..9dfa130d463c59 100644 --- a/src/plugins/saved_objects_management/public/services/column_service.mock.ts +++ b/src/plugins/saved_objects_management/public/services/column_service.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsManagementColumnService, SavedObjectsManagementColumnServiceSetup, diff --git a/src/plugins/saved_objects_management/public/services/service_registry.ts b/src/plugins/saved_objects_management/public/services/service_registry.ts index 2d6ec0b92047af..d4dc6d6166e461 100644 --- a/src/plugins/saved_objects_management/public/services/service_registry.ts +++ b/src/plugins/saved_objects_management/public/services/service_registry.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectLoader } from '../../../saved_objects/public'; export interface SavedObjectsManagementServiceRegistryEntry { diff --git a/src/plugins/saved_objects_management/server/services/management.mock.ts b/src/plugins/saved_objects_management/server/services/management.mock.ts index 85c2d3e4b08d97..d7656073165913 100644 --- a/src/plugins/saved_objects_management/server/services/management.mock.ts +++ b/src/plugins/saved_objects_management/server/services/management.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { SavedObjectsManagement } from './management'; type Management = PublicMethodsOf; diff --git a/src/plugins/saved_objects_management/server/services/management.ts b/src/plugins/saved_objects_management/server/services/management.ts index 499f37990c346f..f24226d798fae6 100644 --- a/src/plugins/saved_objects_management/server/services/management.ts +++ b/src/plugins/saved_objects_management/server/services/management.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { ISavedObjectTypeRegistry, SavedObject } from 'src/core/server'; export type ISavedObjectsManagement = PublicMethodsOf; diff --git a/src/plugins/security_oss/public/plugin.mock.ts b/src/plugins/security_oss/public/plugin.mock.ts index c513d241dccbb0..bb6e1c6bc65456 100644 --- a/src/plugins/security_oss/public/plugin.mock.ts +++ b/src/plugins/security_oss/public/plugin.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { mockInsecureClusterService } from './insecure_cluster_service/insecure_cluster_service.mock'; import { SecurityOssPluginSetup, SecurityOssPluginStart } from './plugin'; diff --git a/src/plugins/share/public/services/share_menu_manager.mock.ts b/src/plugins/share/public/services/share_menu_manager.mock.ts index 7104abeb26090d..c20543fea16db9 100644 --- a/src/plugins/share/public/services/share_menu_manager.mock.ts +++ b/src/plugins/share/public/services/share_menu_manager.mock.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import type { PublicMethodsOf } from '@kbn/utility-types'; import { ShareMenuManager, ShareMenuManagerStart } from './share_menu_manager'; const createStartMock = (): jest.Mocked => { diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 3325c5503fe894..c1f8d55dfc76fe 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -89,7 +89,6 @@ export class ShareMenuManager { isOpen={true} closePopover={this.onClose} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > ({ }, some_obj: { total: { - type: 'number', + type: 'long', }, }, some_array: { @@ -182,7 +182,7 @@ export const myCollector = makeUsageCollector({ type: 'array', items: { total: { - type: 'number', + type: 'long', }, }, }, diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index 951418d448cbdd..73febc0183fc55 100644 --- a/src/plugins/usage_collection/server/collector/collector.ts +++ b/src/plugins/usage_collection/server/collector/collector.ts @@ -27,14 +27,9 @@ import { export type CollectorFormatForBulkUpload = (result: T) => { type: string; payload: U }; -export type AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float'; + +export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date'; export interface SchemaField { type: string; diff --git a/src/plugins/usage_collection/server/collector/index.ts b/src/plugins/usage_collection/server/collector/index.ts index da85f9ab181c98..2f8be884a8a7b0 100644 --- a/src/plugins/usage_collection/server/collector/index.ts +++ b/src/plugins/usage_collection/server/collector/index.ts @@ -21,6 +21,7 @@ export { CollectorSet } from './collector_set'; export { Collector, AllowedSchemaTypes, + AllowedSchemaNumberTypes, SchemaField, MakeSchemaFrom, CollectorOptions, diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts index 834ad8b70ad0db..a56155db02f6b2 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts @@ -33,6 +33,7 @@ import { FieldParamEditor, OrderByParamEditor } from './controls'; import { EditorConfig } from './utils'; import { Schema } from '../schemas'; import { EditorVisState } from './sidebar/state/reducers'; +import { groupAndSortBy } from '../utils'; jest.mock('../utils', () => ({ groupAndSortBy: jest.fn(() => ['indexedFields']), @@ -169,6 +170,9 @@ describe('DefaultEditorAggParams helpers', () => { ], advanced: [], }); + + // Should be grouped using displayName as label + expect(groupAndSortBy).toHaveBeenCalledWith(expect.anything(), 'type', 'displayName', 'name'); }); }); diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts index b13ca32601aa95..271fc75a0853ee 100644 --- a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts @@ -93,7 +93,7 @@ function getAggParamsToRender({ } } fields = filterAggTypeFields(availableFields, agg); - indexedFields = groupAndSortBy(fields, 'type', 'name'); + indexedFields = groupAndSortBy(fields, 'type', 'displayName', 'name'); if (fields && !indexedFields.length && index > 0) { // don't draw the rest of the options if there are no indexed fields and it's an extra param (index > 0). diff --git a/src/plugins/vis_default_editor/public/components/agg_select.tsx b/src/plugins/vis_default_editor/public/components/agg_select.tsx index cccfb043cf0afa..9d45b72d35cc04 100644 --- a/src/plugins/vis_default_editor/public/components/agg_select.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_select.tsx @@ -141,7 +141,7 @@ function DefaultEditorAggSelect({ error={errors} isInvalid={showValidation ? !isValid : false} fullWidth={true} - compressed + display="rowCompressed" > + <> {numbers.map((number, arrayIndex) => ( diff --git a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx index 4afc85ba3b5c27..785ef1b83a23d9 100644 --- a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx @@ -125,7 +125,7 @@ function DateRangesParamEditor({ ); return ( - + <> diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx index bfc4f881f84588..9529adfe127200 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -52,7 +52,7 @@ function FieldParamEditor({ }: FieldParamEditorProps) { const [isDirty, setIsDirty] = useState(false); const selectedOptions: ComboBoxGroupedOptions = value - ? [{ label: value.displayName || value.name, target: value }] + ? [{ label: value.displayName, target: value, key: value.name }] : []; const onChange = (options: EuiComboBoxOptionOption[]) => { @@ -108,7 +108,7 @@ function FieldParamEditor({ isInvalid={showErrorMessage} fullWidth={true} error={errors} - compressed + display="rowCompressed" > + {agg.params.ipRangeType === IpRangeTypes.MASK ? ( setValue(ev.target.value), [setValue]); return ( - + + + - + + <> {ranges.map(({ from, to, id }, index) => { const deleteBtnTitle = i18n.translate( diff --git a/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx b/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx index b433b704330a79..714bb3f3294ab1 100644 --- a/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx @@ -82,7 +82,7 @@ function RawJsonParamEditor({ label={label} isInvalid={showValidation ? !isFieldValid : false} fullWidth={true} - compressed + display="rowCompressed" > <> - + + setAutoApplyEnabled(e.target.checked), []); + const toggleAutoApply = useCallback( + (nextAutoApplyEnabled) => setAutoApplyEnabled(nextAutoApplyEnabled), + [] + ); const onClickDiscard = useCallback(() => dispatch(discardChanges(vis)), [dispatch, vis]); useDebounce( @@ -131,19 +127,33 @@ function DefaultEditorControls({ defaultMessage: 'Auto updates the visualization on every change.', })} > - toggleAutoApply(!autoApplyEnabled)} size="s" - isIconOnly - /> + minWidth={80} + aria-label={ + autoApplyEnabled + ? i18n.translate('visDefaultEditor.sidebar.autoApplyChangesLabelOn', { + defaultMessage: 'Auto apply is on', + }) + : i18n.translate('visDefaultEditor.sidebar.autoApplyChangesLabelOff', { + defaultMessage: 'Auto apply is off', + }) + } + > + {autoApplyEnabled + ? i18n.translate('visDefaultEditor.sidebar.autoApplyChangesOn', { + defaultMessage: 'On', + }) + : i18n.translate('visDefaultEditor.sidebar.autoApplyChangesOff', { + defaultMessage: 'Off', + })} + )}

diff --git a/src/plugins/vis_default_editor/public/utils.ts b/src/plugins/vis_default_editor/public/utils.ts index d0a9c067e9da28..11b7c07acc2c29 100644 --- a/src/plugins/vis_default_editor/public/utils.ts +++ b/src/plugins/vis_default_editor/public/utils.ts @@ -18,10 +18,12 @@ */ interface ComboBoxOption { + key?: string; label: string; target: T; } interface ComboBoxGroupedOption { + key?: string; label: string; options: Array>; } @@ -40,15 +42,22 @@ export type ComboBoxGroupedOptions = Array>; * @returns An array of grouped and sorted alphabetically `objects` that are compatible with EuiComboBox options. */ export function groupAndSortBy< - T extends Record, + T extends Record, TGroupBy extends string = 'type', - TLabelName extends string = 'title' ->(objects: T[], groupBy: TGroupBy, labelName: TLabelName): ComboBoxGroupedOptions { + TLabelName extends string = 'title', + TKeyName extends string = never +>( + objects: T[], + groupBy: TGroupBy, + labelName: TLabelName, + keyName?: TKeyName +): ComboBoxGroupedOptions { const groupedOptions = objects.reduce((array, obj) => { const group = array.find((element) => element.label === obj[groupBy]); const option = { label: obj[labelName], target: obj, + ...(keyName ? { key: obj[keyName] } : {}), }; if (group && group.options) { diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx index ac6ab9e9e69a8d..d0a7412238871a 100644 --- a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -86,7 +86,7 @@ function MetricVisOptions({ ); const setColorMode: EuiButtonGroupProps['onChange'] = useCallback( - (id) => setMetricValue('metricColorMode', id as ColorModes), + (id: string) => setMetricValue('metricColorMode', id as ColorModes), [setMetricValue] ); diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx index cb0daa6d293825..a14328ac994f0a 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx @@ -45,7 +45,9 @@ export const TagCloudChart = ({ const visController = useRef(null); useEffect(() => { - visController.current = new TagCloudVisualization(chartDiv.current, colors, fireEvent); + if (chartDiv.current) { + visController.current = new TagCloudVisualization(chartDiv.current, colors, fireEvent); + } return () => { visController.current.destroy(); visController.current = null; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx b/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx index 7ed98f0fb802eb..e0a76316669f7c 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -108,7 +108,7 @@ function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProp return ( diff --git a/src/plugins/vis_type_timeseries/common/agg_lookup.js b/src/plugins/vis_type_timeseries/common/agg_lookup.js index 432da03e3d45dc..0a71ab34082f80 100644 --- a/src/plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/plugins/vis_type_timeseries/common/agg_lookup.js @@ -98,7 +98,7 @@ export const lookup = { }), top_hit: i18n.translate('visTypeTimeseries.aggLookup.topHitLabel', { defaultMessage: 'Top Hit' }), positive_rate: i18n.translate('visTypeTimeseries.aggLookup.positiveRateLabel', { - defaultMessage: 'Positive Rate', + defaultMessage: 'Counter Rate', }), }; diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.js b/src/plugins/vis_type_timeseries/common/calculate_label.js index 9f3030eeb6eae2..96e9fa0825b25b 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.js +++ b/src/plugins/vis_type_timeseries/common/calculate_label.js @@ -72,7 +72,7 @@ export function calculateLabel(metric, metrics) { } if (metric.type === 'positive_rate') { return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', { - defaultMessage: 'Positive Rate of {field}', + defaultMessage: 'Counter Rate of {field}', values: { field: metric.field }, }); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx index 968fa5384e1d87..6c75e081429dee 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx @@ -63,7 +63,7 @@ describe('TSVB AggSelect', () => { "value": "count", }, Object { - "label": "Positive Rate", + "label": "Counter Rate", "value": "positive_rate", }, Object { @@ -131,7 +131,7 @@ describe('TSVB AggSelect', () => { "value": "filter_ratio", }, Object { - "label": "Positive Rate", + "label": "Counter Rate", "value": "positive_rate", }, Object { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx index 7701d351e54786..5c8049e3636946 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx @@ -54,7 +54,7 @@ const metricAggs: AggSelectOption[] = [ }, { label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel', { - defaultMessage: 'Positive Rate', + defaultMessage: 'Counter Rate', }), value: 'positive_rate', }, diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss index c445d456a1703b..9585711c73dd2a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/_vis_types.scss @@ -3,29 +3,23 @@ flex-direction: column; flex: 1 1 100%; - // border used in lieu of padding to prevent overlapping background-color - border-width: $euiSizeS; - border-style: solid; - border-color: transparent; - .tvbVisTimeSeries { overflow: hidden; } .tvbVisTimeSeriesDark { .echReactiveChart_unavailable { - color: #DFE5EF; + color: #dfe5ef; } - .echLegendItem { - color: #DFE5EF; + .echLegendItem { + color: #dfe5ef; } } .tvbVisTimeSeriesLight { .echReactiveChart_unavailable { color: #343741; } - .echLegendItem { + .echLegendItem { color: #343741; } } } - diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js index 2434285bd94c6a..c12e518a9dcd3a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/vis.js @@ -19,7 +19,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import reactCSS from 'reactcss'; import { startsWith, get, cloneDeep, map } from 'lodash'; import { htmlIdGenerator } from '@elastic/eui'; @@ -150,13 +149,6 @@ export class TimeseriesVisualization extends Component { render() { const { model, visData, onBrush } = this.props; - const styles = reactCSS({ - default: { - tvbVis: { - borderColor: get(model, 'background_color'), - }, - }, - }); const series = get(visData, `${model.id}.series`, []); const interval = getInterval(visData, model); const yAxisIdGenerator = htmlIdGenerator('yaxis'); @@ -231,7 +223,7 @@ export class TimeseriesVisualization extends Component { }); return ( -
+
void; + visData: VegaParser; +} + +type VegaVisController = InstanceType>; + +const VegaVisComponent = ({ visData, fireEvent, renderComplete, deps }: VegaVisComponentProps) => { + const chartDiv = useRef(null); + const visController = useRef(null); + + useEffect(() => { + if (chartDiv.current) { + const VegaVis = createVegaVisualization(deps); + visController.current = new VegaVis(chartDiv.current, fireEvent); + } + + return () => { + visController.current?.destroy(); + visController.current = null; + }; + }, [deps, fireEvent]); + + useEffect(() => { + if (visController.current) { + visController.current.render(visData).then(renderComplete); + } + }, [visData, renderComplete]); + + const updateChartSize = useMemo( + () => + throttle(() => { + if (visController.current) { + visController.current.render(visData).then(renderComplete); + } + }, 300), + [renderComplete, visData] + ); + + return ( + + {(resizeRef) => ( +
+
+
+ )} + + ); +}; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaVisComponent as default }; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx index 5e770fcff556de..7d669235c36b8d 100644 --- a/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx +++ b/src/plugins/vis_type_vega/public/components/vega_vis_editor.tsx @@ -30,6 +30,8 @@ import { VisParams } from '../vega_fn'; import { VegaHelpMenu } from './vega_help_menu'; import { VegaActionsMenu } from './vega_actions_menu'; +import './vega_editor.scss'; + const aceOptions = { maxLines: Infinity, highlightActiveLine: false, @@ -102,4 +104,6 @@ function VegaVisEditor({ stateParams, setValue }: VisOptionsProps) { ); } -export { VegaVisEditor }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaVisEditor as default }; diff --git a/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx b/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx new file mode 100644 index 00000000000000..d6c78972410e09 --- /dev/null +++ b/src/plugins/vis_type_vega/public/components/vega_vis_editor_lazy.tsx @@ -0,0 +1,29 @@ +/* + * 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 React, { lazy } from 'react'; + +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { VisParams } from '../vega_fn'; + +const VegaVisEditor = lazy(() => import('./vega_vis_editor')); + +export const VegaVisEditorComponent = (props: VisOptionsProps) => ( + +); diff --git a/src/plugins/vis_type_vega/public/index.scss b/src/plugins/vis_type_vega/public/index.scss deleted file mode 100644 index 78d9eb61999f7d..00000000000000 --- a/src/plugins/vis_type_vega/public/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "vga" to avoid conflicts. -// Examples -// vgaChart -// vgaChart__legend -// vgaChart__legend--small -// vgaChart__legend-isLoading - -@import './vega_vis'; -@import './vega_editor'; diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts index ce5c5130961c6b..04481685c841ba 100644 --- a/src/plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -35,10 +35,10 @@ import { import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; import { IServiceSettings } from '../../maps_legacy/public'; -import './index.scss'; import { ConfigSchema } from '../config'; import { getVegaInspectorView } from './vega_inspector'; +import { getVegaVisRenderer } from './vega_vis_renderer'; /** @internal */ export interface VegaVisualizationDependencies { @@ -93,6 +93,7 @@ export class VegaPlugin implements Plugin, void> { inspector.registerView(getVegaInspectorView({ uiSettings: core.uiSettings })); expressions.registerFunction(() => createVegaFn(visualizationDependencies)); + expressions.registerRenderer(getVegaVisRenderer(visualizationDependencies)); visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies)); } diff --git a/src/plugins/vis_type_vega/public/to_ast.ts b/src/plugins/vis_type_vega/public/to_ast.ts new file mode 100644 index 00000000000000..a5fe8f13c3daf0 --- /dev/null +++ b/src/plugins/vis_type_vega/public/to_ast.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { VegaExpressionFunctionDefinition, VisParams } from './vega_fn'; + +export const toExpressionAst = (vis: Vis) => { + const vega = buildExpressionFunction('vega', { + spec: vis.params.spec, + }); + + const ast = buildExpression([vega]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_vega/public/vega_fn.ts b/src/plugins/vis_type_vega/public/vega_fn.ts index c88b78948133c1..25d4e76c336b3a 100644 --- a/src/plugins/vis_type_vega/public/vega_fn.ts +++ b/src/plugins/vis_type_vega/public/vega_fn.ts @@ -40,21 +40,23 @@ interface Arguments { export type VisParams = Required; -interface RenderValue { +export interface RenderValue { visData: VegaParser; visType: 'vega'; visConfig: VisParams; } -export const createVegaFn = ( - dependencies: VegaVisualizationDependencies -): ExpressionFunctionDefinition< +export type VegaExpressionFunctionDefinition = ExpressionFunctionDefinition< 'vega', Input, Arguments, Output, ExecutionContext -> => ({ +>; + +export const createVegaFn = ( + dependencies: VegaVisualizationDependencies +): VegaExpressionFunctionDefinition => ({ name: 'vega', type: 'render', inputTypes: ['kibana_context', 'null'], @@ -80,7 +82,7 @@ export const createVegaFn = ( return { type: 'render', - as: 'visualization', + as: 'vega_vis', value: { visData: response, visType: 'vega', diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx index 6dfa7a23c4fe82..350e781dc70761 100644 --- a/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx +++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_data_inspector.tsx @@ -41,7 +41,7 @@ const specLabel = i18n.translate('visTypeVega.inspector.specLabel', { defaultMessage: 'Spec', }); -export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { +const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { const tabs = [ { id: 'data-viewer--id', @@ -75,3 +75,7 @@ export const VegaDataInspector = ({ adapters }: VegaDataInspectorProps) => { /> ); }; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VegaDataInspector as default }; diff --git a/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx index 83d9e467646a6c..4b3e48d6a37a00 100644 --- a/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx +++ b/src/plugins/vis_type_vega/public/vega_inspector/vega_inspector.tsx @@ -16,14 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'kibana/public'; -import { VegaAdapter } from './vega_adapter'; -import { VegaDataInspector, VegaDataInspectorProps } from './vega_data_inspector'; import { KibanaContextProvider } from '../../../kibana_react/public'; import { Adapters, RequestAdapter, InspectorViewDescription } from '../../../inspector/public'; +import { VegaAdapter } from './vega_adapter'; +import type { VegaDataInspectorProps } from './vega_data_inspector'; + +const VegaDataInspector = lazy(() => import('./vega_data_inspector')); export interface VegaInspectorAdapters extends Adapters { requests: RequestAdapter; @@ -46,7 +49,9 @@ export const getVegaInspectorView = (dependencies: VegaInspectorViewDependencies }, component: (props) => ( - + }> + + ), } as InspectorViewDescription); diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index 0496f765e5e99b..17f35b75f00167 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -21,22 +21,20 @@ import { i18n } from '@kbn/i18n'; import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; -import { VegaVisEditor } from './components'; import { createVegaRequestHandler } from './vega_request_handler'; -// @ts-expect-error -import { createVegaVisualization } from './vega_visualization'; import { getDefaultSpec } from './default_spec'; import { createInspectorAdapters } from './vega_inspector'; import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; - +import { toExpressionAst } from './to_ast'; +import { VisParams } from './vega_fn'; import { getInfoMessage } from './components/experimental_map_vis_info'; +import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy'; export const createVegaTypeDefinition = ( dependencies: VegaVisualizationDependencies -): BaseVisTypeOptions => { +): BaseVisTypeOptions => { const requestHandler = createVegaRequestHandler(dependencies); - const visualization = createVegaVisualization(dependencies); return { name: 'vega', @@ -49,13 +47,12 @@ export const createVegaTypeDefinition = ( icon: 'visVega', visConfig: { defaults: { spec: getDefaultSpec() } }, editorConfig: { - optionsTemplate: VegaVisEditor, + optionsTemplate: VegaVisEditorComponent, enableAutoApply: true, defaultSize: DefaultEditorSize.MEDIUM, }, - visualization, requestHandler, - responseHandler: 'none', + toExpressionAst, options: { showIndexSelection: false, showQueryBar: true, diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts new file mode 100644 index 00000000000000..54b96813769bac --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.d.ts @@ -0,0 +1,40 @@ +/* + * 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 { DataPublicPluginStart } from 'src/plugins/data/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { IServiceSettings } from 'src/plugins/maps_legacy/public'; +import { VegaParser } from '../data_model/vega_parser'; + +interface VegaViewParams { + parentEl: HTMLDivElement; + fireEvent: IInterpreterRenderHandlers['event']; + vegaParser: VegaParser; + serviceSettings: IServiceSettings; + filterManager: DataPublicPluginStart['query']['filterManager']; + timefilter: DataPublicPluginStart['query']['timefilter']['timefilter']; + // findIndex: (index: string) => Promise<...>; +} + +export class VegaBaseView { + constructor(params: VegaViewParams); + init(): Promise; + onError(error: any): void; + destroy(): Promise; +} diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index 979432b2aed2a4..25ea77ddbccb42 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -63,7 +63,7 @@ export class VegaBaseView { this._parser = opts.vegaParser; this._serviceSettings = opts.serviceSettings; this._filterManager = opts.filterManager; - this._applyFilter = opts.applyFilter; + this._fireEvent = opts.fireEvent; this._timefilter = opts.timefilter; this._findIndex = opts.findIndex; this._view = null; @@ -264,7 +264,7 @@ export class VegaBaseView { const indexId = await this._findIndex(index); const filter = esFilters.buildQueryFilter(query, indexId); - this._applyFilter({ filters: [filter] }); + this._fireEvent({ name: 'applyFilter', data: { filters: [filter] } }); } /** @@ -301,19 +301,22 @@ export class VegaBaseView { setTimeFilterHandler(start, end) { const { from, to, mode } = VegaBaseView._parseTimeRange(start, end); - this._applyFilter({ - timeFieldName: '*', - filters: [ - { - range: { - '*': { - mode, - gte: from, - lte: to, + this._fireEvent({ + name: 'applyFilter', + data: { + timeFieldName: '*', + filters: [ + { + range: { + '*': { + mode, + gte: from, + lte: to, + }, }, }, - }, - ], + ], + }, }); } diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts new file mode 100644 index 00000000000000..a1210e05f4507d --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.d.ts @@ -0,0 +1,22 @@ +/* + * 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 { VegaBaseView } from './vega_base_view'; + +export class VegaMapView extends VegaBaseView {} diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts b/src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts new file mode 100644 index 00000000000000..c137d8222750c4 --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_view/vega_view.d.ts @@ -0,0 +1,22 @@ +/* + * 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 { VegaBaseView } from './vega_base_view'; + +export class VegaView extends VegaBaseView {} diff --git a/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx b/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx new file mode 100644 index 00000000000000..542f59b3dfff95 --- /dev/null +++ b/src/plugins/vis_type_vega/public/vega_vis_renderer.tsx @@ -0,0 +1,51 @@ +/* + * 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 React, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { VisualizationContainer } from '../../visualizations/public'; +import { VegaVisualizationDependencies } from './plugin'; +import { RenderValue } from './vega_fn'; +const VegaVisComponent = lazy(() => import('./components/vega_vis_component')); + +export const getVegaVisRenderer: ( + deps: VegaVisualizationDependencies +) => ExpressionRenderDefinition = (deps) => ({ + name: 'vega_vis', + reuseDomNode: true, + render: (domNode, { visData }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/vis_type_vega/public/vega_visualization.test.js b/src/plugins/vis_type_vega/public/vega_visualization.test.js index dcf1722768075a..837fdf2a9aea33 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.test.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.test.js @@ -30,8 +30,6 @@ import vegaMapGraph from './test_utils/vega_map_test.json'; import { VegaParser } from './data_model/vega_parser'; import { SearchAPI } from './data_model/search_api'; -import { createVegaTypeDefinition } from './vega_type'; - import { setInjectedVars, setData, setSavedObjects, setNotifications } from './services'; import { coreMock } from '../../../core/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; @@ -49,9 +47,7 @@ jest.mock('./lib/vega', () => ({ describe('VegaVisualizations', () => { let domNode; let VegaVisualization; - let vis; let vegaVisualizationDependencies; - let vegaVisType; let mockWidth; let mockedWidthValue; @@ -91,22 +87,12 @@ describe('VegaVisualizations', () => { getServiceSettings: mockGetServiceSettings, }; - vegaVisType = createVegaTypeDefinition(vegaVisualizationDependencies); VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); }); describe('VegaVisualization - basics', () => { beforeEach(async () => { setupDOM(); - - vis = { - type: vegaVisType, - API: { - events: { - applyFilter: jest.fn(), - }, - }, - }; }); afterEach(() => { @@ -117,7 +103,7 @@ describe('VegaVisualizations', () => { test('should show vegalite graph and update on resize (may fail in dev env)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaliteGraph), @@ -137,7 +123,7 @@ describe('VegaVisualizations', () => { mockedWidthValue = 256; mockedHeightValue = 256; - await vegaVis._vegaView.resize(); + await vegaVis.vegaView.resize(); expect(domNode.innerHTML).toMatchSnapshot(); } finally { @@ -148,7 +134,7 @@ describe('VegaVisualizations', () => { test('should show vega graph (may fail in dev env)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaGraph), new SearchAPI({ @@ -172,7 +158,7 @@ describe('VegaVisualizations', () => { test('should show vega blank rectangle on top of a map (vegamap)', async () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); + vegaVis = new VegaVisualization(domNode, jest.fn()); const vegaParser = new VegaParser( JSON.stringify(vegaMapGraph), new SearchAPI({ diff --git a/src/plugins/vis_type_vega/public/vega_visualization.js b/src/plugins/vis_type_vega/public/vega_visualization.ts similarity index 70% rename from src/plugins/vis_type_vega/public/vega_visualization.js rename to src/plugins/vis_type_vega/public/vega_visualization.ts index 2d58e9cda60cdc..58c436bcd4be4c 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.ts @@ -17,28 +17,34 @@ * under the License. */ import { i18n } from '@kbn/i18n'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { VegaParser } from './data_model/vega_parser'; +import { VegaVisualizationDependencies } from './plugin'; import { getNotifications, getData, getSavedObjects } from './services'; +import type { VegaView } from './vega_view/vega_view'; -export const createVegaVisualization = ({ getServiceSettings }) => +export const createVegaVisualization = ({ getServiceSettings }: VegaVisualizationDependencies) => class VegaVisualization { - constructor(el, vis) { - this._el = el; - this._vis = vis; + private readonly dataPlugin = getData(); + private readonly savedObjectsClient = getSavedObjects(); + private vegaView: InstanceType | null = null; - this.savedObjectsClient = getSavedObjects(); - this.dataPlugin = getData(); - } + constructor( + private el: HTMLDivElement, + private fireEvent: IInterpreterRenderHandlers['event'] + ) {} /** * Find index pattern by its title, of if not given, gets default * @param {string} [index] * @returns {Promise} index id */ - async findIndex(index) { + async findIndex(index: string) { const { indexPatterns } = this.dataPlugin; let idxObj; if (index) { + // @ts-expect-error idxObj = indexPatterns.findByTitle(this.savedObjectsClient, index); if (!idxObj) { throw new Error( @@ -61,16 +67,10 @@ export const createVegaVisualization = ({ getServiceSettings }) => return idxObj.id; } - /** - * - * @param {VegaParser} visData - * @param {*} status - * @returns {Promise} - */ - async render(visData) { + async render(visData: VegaParser) { const { toasts } = getNotifications(); - if (!visData && !this._vegaView) { + if (!visData && !this.vegaView) { toasts.addWarning( i18n.translate('visTypeVega.visualization.unableToRenderWithoutDataWarningMessage', { defaultMessage: 'Unable to render without data', @@ -82,8 +82,8 @@ export const createVegaVisualization = ({ getServiceSettings }) => try { await this._render(visData); } catch (error) { - if (this._vegaView) { - this._vegaView.onError(error); + if (this.vegaView) { + this.vegaView.onError(error); } else { toasts.addError(error, { title: i18n.translate('visTypeVega.visualization.renderErrorTitle', { @@ -94,20 +94,20 @@ export const createVegaVisualization = ({ getServiceSettings }) => } } - async _render(vegaParser) { + async _render(vegaParser: VegaParser) { if (vegaParser) { // New data received, rebuild the graph - if (this._vegaView) { - await this._vegaView.destroy(); - this._vegaView = null; + if (this.vegaView) { + await this.vegaView.destroy(); + this.vegaView = null; } const serviceSettings = await getServiceSettings(); const { filterManager } = this.dataPlugin.query; const { timefilter } = this.dataPlugin.query.timefilter; const vegaViewParams = { - parentEl: this._el, - applyFilter: this._vis.API.events.applyFilter, + parentEl: this.el, + fireEvent: this.fireEvent, vegaParser, serviceSettings, filterManager, @@ -116,18 +116,17 @@ export const createVegaVisualization = ({ getServiceSettings }) => }; if (vegaParser.useMap) { - const services = { toastService: getNotifications().toasts }; const { VegaMapView } = await import('./vega_view/vega_map_view'); - this._vegaView = new VegaMapView(vegaViewParams, services); + this.vegaView = new VegaMapView(vegaViewParams); } else { - const { VegaView } = await import('./vega_view/vega_view'); - this._vegaView = new VegaView(vegaViewParams); + const { VegaView: VegaViewClass } = await import('./vega_view/vega_view'); + this.vegaView = new VegaViewClass(vegaViewParams); } - await this._vegaView.init(); + await this.vegaView?.init(); } } destroy() { - return this._vegaView && this._vegaView.destroy(); + this.vegaView?.destroy(); } }; diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap index 2cee55e4751c2a..b64366c1ce0f36 100644 --- a/src/plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap +++ b/src/plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap @@ -2,12 +2,9 @@ exports[`interpreter/functions#pie returns an object with the correct structure 1`] = ` Object { - "as": "visualization", + "as": "vislib_vis", "type": "render", "value": Object { - "params": Object { - "listenOnChange": true, - }, "visConfig": Object { "addLegend": true, "addTooltip": true, diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap new file mode 100644 index 00000000000000..c3ffc0dd08412d --- /dev/null +++ b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`vislib vis toExpressionAst function should match basic snapshot 1`] = ` +Object { + "addArgument": [Function], + "arguments": Object { + "type": Array [ + "area", + ], + "visConfig": Array [ + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + ], + }, + "getArgument": [Function], + "name": "vislib_vis", + "removeArgument": [Function], + "replaceArgument": [Function], + "toAst": [Function], + "toString": [Function], + "type": "expression_function_builder", +} +`; diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast_pie.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast_pie.test.ts.snap new file mode 100644 index 00000000000000..b8dc4b31747c4b --- /dev/null +++ b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast_pie.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`vislib pie vis toExpressionAst function should match basic snapshot 1`] = ` +Object { + "addArgument": [Function], + "arguments": Object { + "visConfig": Array [ + "{\\"type\\":\\"pie\\",\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"right\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + ], + }, + "getArgument": [Function], + "name": "vislib_pie_vis", + "removeArgument": [Function], + "replaceArgument": [Function], + "toAst": [Function], + "toString": [Function], + "type": "expression_function_builder", +} +`; diff --git a/src/plugins/vis_type_vislib/public/area.ts b/src/plugins/vis_type_vislib/public/area.ts index ec90fbd1746a15..531958d6b3db3a 100644 --- a/src/plugins/vis_type_vislib/public/area.ts +++ b/src/plugins/vis_type_vislib/public/area.ts @@ -37,22 +37,20 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { createVislibVisController } from './vis_controller'; -import { VisTypeVislibDependencies } from './plugin'; import { Rotates } from '../../charts/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { toExpressionAst } from './to_ast'; +import { BasicVislibParams } from './types'; -export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const areaVisTypeDefinition: BaseVisTypeOptions = { name: 'area', title: i18n.translate('visTypeVislib.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', description: i18n.translate('visTypeVislib.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart', }), - visualization: createVislibVisController(deps), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; - }, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + toExpressionAst, visConfig: { defaults: { type: 'area', @@ -131,9 +129,6 @@ export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => labels: {}, }, }, - events: { - brush: { disabled: false }, - }, editorConfig: { collections: getConfigCollections(), optionTabs: getAreaOptionTabs(), @@ -190,4 +185,4 @@ export const createAreaVisTypeDefinition = (deps: VisTypeVislibDependencies) => }, ]), }, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx b/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx index 2f0cb701848d0c..6f55f6f070d004 100644 --- a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx +++ b/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx @@ -37,7 +37,7 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe defaultMessage: 'Truncate', })} fullWidth - compressed + display="rowCompressed" > ) { ); } -export { GaugeOptions }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { GaugeOptions as default }; diff --git a/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx b/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 7a89496d9441e8..312cf60fda6b02 100644 --- a/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx +++ b/src/plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -185,4 +185,6 @@ function HeatmapOptions(props: VisOptionsProps) { ); } -export { HeatmapOptions }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { HeatmapOptions as default }; diff --git a/src/plugins/vis_type_vislib/public/components/options/index.tsx b/src/plugins/vis_type_vislib/public/components/options/index.tsx new file mode 100644 index 00000000000000..18c41bf289b116 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/components/options/index.tsx @@ -0,0 +1,51 @@ +/* + * 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 React, { lazy } from 'react'; + +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { ValidationVisOptionsProps } from '../common'; +import { GaugeVisParams } from '../../gauge'; +import { PieVisParams } from '../../pie'; +import { BasicVislibParams } from '../../types'; +import { HeatmapVisParams } from '../../heatmap'; + +const GaugeOptionsLazy = lazy(() => import('./gauge')); +const PieOptionsLazy = lazy(() => import('./pie')); +const PointSeriesOptionsLazy = lazy(() => import('./point_series')); +const HeatmapOptionsLazy = lazy(() => import('./heatmap')); +const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes')); + +export const GaugeOptions = (props: VisOptionsProps) => ( + +); + +export const PieOptions = (props: VisOptionsProps) => ; + +export const PointSeriesOptions = (props: ValidationVisOptionsProps) => ( + +); + +export const HeatmapOptions = (props: VisOptionsProps) => ( + +); + +export const MetricsAxisOptions = (props: ValidationVisOptionsProps) => ( + +); diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap index de2b7f339ea2f8..6d902f55f6720a 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap +++ b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap @@ -2,9 +2,8 @@ exports[`YExtents component should init with the default set of props 1`] = ` ) ) : null; } -export { MetricsAxisOptions }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { MetricsAxisOptions as default }; diff --git a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx index c2aa917dd3a6f6..4e23ee5a41554c 100644 --- a/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx +++ b/src/plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx @@ -79,7 +79,7 @@ function YExtents({ scale, setScale, setMultipleValidity }: YExtentsProps) { }, [isValid, setMultipleValidity]); return ( - + <> diff --git a/src/plugins/vis_type_vislib/public/components/options/pie.tsx b/src/plugins/vis_type_vislib/public/components/options/pie.tsx index 54ba307982967e..30828bfc6a3ea7 100644 --- a/src/plugins/vis_type_vislib/public/components/options/pie.tsx +++ b/src/plugins/vis_type_vislib/public/components/options/pie.tsx @@ -99,4 +99,6 @@ function PieOptions(props: VisOptionsProps) { ); } -export { PieOptions }; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { PieOptions as default }; diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/index.ts b/src/plugins/vis_type_vislib/public/components/options/point_series/index.ts index fb94ec6743faa1..937b92c9504302 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/index.ts +++ b/src/plugins/vis_type_vislib/public/components/options/point_series/index.ts @@ -17,4 +17,6 @@ * under the License. */ -export { PointSeriesOptions } from './point_series'; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { PointSeriesOptions as default } from './point_series'; diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx b/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx index 08231803007563..964bb7d569b087 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx +++ b/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx @@ -114,7 +114,7 @@ function ThresholdPanel({ defaultMessage: 'Line color', })} fullWidth - compressed + display="rowCompressed" > ({ +export const gaugeVisTypeDefinition: BaseVisTypeOptions = { name: 'gauge', title: i18n.translate('visTypeVislib.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', @@ -63,6 +64,7 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) => defaultMessage: "Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.", }), + toExpressionAst, visConfig: { defaults: { type: 'gauge', @@ -109,7 +111,6 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) => }, }, }, - visualization: createVislibVisController(deps), editorConfig: { collections: getGaugeCollections(), optionsTemplate: GaugeOptions, @@ -145,4 +146,4 @@ export const createGaugeVisTypeDefinition = (deps: VisTypeVislibDependencies) => ]), }, useCustomNoDataScreen: true, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/goal.ts b/src/plugins/vis_type_vislib/public/goal.ts index 5f74698938a0b1..32574fb5b0a9cb 100644 --- a/src/plugins/vis_type_vislib/public/goal.ts +++ b/src/plugins/vis_type_vislib/public/goal.ts @@ -21,20 +21,21 @@ import { i18n } from '@kbn/i18n'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, GaugeTypes } from './utils/collections'; -import { createVislibVisController } from './vis_controller'; -import { VisTypeVislibDependencies } from './plugin'; import { ColorModes, ColorSchemas } from '../../charts/public'; import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; +import { toExpressionAst } from './to_ast'; +import { BaseVisTypeOptions } from '../../visualizations/public'; +import { BasicVislibParams } from './types'; -export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const goalVisTypeDefinition: BaseVisTypeOptions = { name: 'goal', title: i18n.translate('visTypeVislib.goal.goalTitle', { defaultMessage: 'Goal' }), icon: 'visGoal', description: i18n.translate('visTypeVislib.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.', }), - visualization: createVislibVisController(deps), + toExpressionAst, visConfig: { defaults: { addTooltip: true, @@ -110,4 +111,4 @@ export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) => ]), }, useCustomNoDataScreen: true, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/heatmap.ts b/src/plugins/vis_type_vislib/public/heatmap.ts index bd3d02029cb23a..f970eddd645f5c 100644 --- a/src/plugins/vis_type_vislib/public/heatmap.ts +++ b/src/plugins/vis_type_vislib/public/heatmap.ts @@ -23,12 +23,11 @@ import { RangeValues, Schemas } from '../../vis_default_editor/public'; import { AggGroupNames } from '../../data/public'; import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections'; import { HeatmapOptions } from './components/options'; -import { createVislibVisController } from './vis_controller'; import { TimeMarker } from './vislib/visualizations/time_marker'; -import { CommonVislibParams, ValueAxis } from './types'; -import { VisTypeVislibDependencies } from './plugin'; +import { BasicVislibParams, CommonVislibParams, ValueAxis } from './types'; import { ColorSchemas, ColorSchemaParams } from '../../charts/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { toExpressionAst } from './to_ast'; export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams { type: 'heatmap'; @@ -42,17 +41,15 @@ export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaParams times: TimeMarker[]; } -export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const heatmapVisTypeDefinition: BaseVisTypeOptions = { name: 'heatmap', title: i18n.translate('visTypeVislib.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'heatmap', description: i18n.translate('visTypeVislib.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix', }), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter]; - }, - visualization: createVislibVisController(deps), + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter], + toExpressionAst, visConfig: { defaults: { type: 'heatmap', @@ -86,9 +83,6 @@ export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies) ], }, }, - events: { - brush: { disabled: false }, - }, editorConfig: { collections: getHeatmapCollections(), optionsTemplate: HeatmapOptions, @@ -142,4 +136,4 @@ export const createHeatmapVisTypeDefinition = (deps: VisTypeVislibDependencies) }, ]), }, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/histogram.ts b/src/plugins/vis_type_vislib/public/histogram.ts index 8aeeb4ec533abc..d5fb92f5c6a0c2 100644 --- a/src/plugins/vis_type_vislib/public/histogram.ts +++ b/src/plugins/vis_type_vislib/public/histogram.ts @@ -36,12 +36,12 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { createVislibVisController } from './vis_controller'; -import { VisTypeVislibDependencies } from './plugin'; import { Rotates } from '../../charts/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BasicVislibParams } from './types'; +import { toExpressionAst } from './to_ast'; -export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const histogramVisTypeDefinition: BaseVisTypeOptions = { name: 'histogram', title: i18n.translate('visTypeVislib.histogram.histogramTitle', { defaultMessage: 'Vertical Bar', @@ -50,10 +50,8 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies description: i18n.translate('visTypeVislib.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: createVislibVisController(deps), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; - }, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + toExpressionAst, visConfig: { defaults: { type: 'histogram', @@ -133,9 +131,6 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies }, }, }, - events: { - brush: { disabled: false }, - }, editorConfig: { collections: getConfigCollections(), optionTabs: getAreaOptionTabs(), @@ -192,4 +187,4 @@ export const createHistogramVisTypeDefinition = (deps: VisTypeVislibDependencies }, ]), }, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/horizontal_bar.ts b/src/plugins/vis_type_vislib/public/horizontal_bar.ts index 702581828e60d0..f1a5365e5ae743 100644 --- a/src/plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/plugins/vis_type_vislib/public/horizontal_bar.ts @@ -34,12 +34,12 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { createVislibVisController } from './vis_controller'; -import { VisTypeVislibDependencies } from './plugin'; import { Rotates } from '../../charts/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BasicVislibParams } from './types'; +import { toExpressionAst } from './to_ast'; -export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const horizontalBarVisTypeDefinition: BaseVisTypeOptions = { name: 'horizontal_bar', title: i18n.translate('visTypeVislib.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar', @@ -48,10 +48,8 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen description: i18n.translate('visTypeVislib.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: createVislibVisController(deps), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; - }, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + toExpressionAst, visConfig: { defaults: { type: 'histogram', @@ -130,9 +128,6 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen }, }, }, - events: { - brush: { disabled: false }, - }, editorConfig: { collections: getConfigCollections(), optionTabs: getAreaOptionTabs(), @@ -189,4 +184,4 @@ export const createHorizontalBarVisTypeDefinition = (deps: VisTypeVislibDependen }, ]), }, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/index.scss b/src/plugins/vis_type_vislib/public/index.scss index 64445648ba84a8..3c347aebde225a 100644 --- a/src/plugins/vis_type_vislib/public/index.scss +++ b/src/plugins/vis_type_vislib/public/index.scss @@ -1 +1 @@ -@import './vislib/index' +@import './vislib/index'; diff --git a/src/plugins/vis_type_vislib/public/line.ts b/src/plugins/vis_type_vislib/public/line.ts index 6e9190229114b5..a65b0bcf7e2bb7 100644 --- a/src/plugins/vis_type_vislib/public/line.ts +++ b/src/plugins/vis_type_vislib/public/line.ts @@ -35,22 +35,20 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { createVislibVisController } from './vis_controller'; -import { VisTypeVislibDependencies } from './plugin'; import { Rotates } from '../../charts/public'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { toExpressionAst } from './to_ast'; +import { BasicVislibParams } from './types'; -export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const lineVisTypeDefinition: BaseVisTypeOptions = { name: 'line', title: i18n.translate('visTypeVislib.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', description: i18n.translate('visTypeVislib.line.lineDescription', { defaultMessage: 'Emphasize trends', }), - visualization: createVislibVisController(deps), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; - }, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + toExpressionAst, visConfig: { defaults: { type: 'line', @@ -129,9 +127,6 @@ export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => }, }, }, - events: { - brush: { disabled: false }, - }, editorConfig: { collections: getConfigCollections(), optionTabs: getAreaOptionTabs(), @@ -182,4 +177,4 @@ export const createLineVisTypeDefinition = (deps: VisTypeVislibDependencies) => }, ]), }, -}); +}; diff --git a/src/plugins/vis_type_vislib/public/pie.ts b/src/plugins/vis_type_vislib/public/pie.ts index 1e81dbdde3f685..58f7dd0df89e86 100644 --- a/src/plugins/vis_type_vislib/public/pie.ts +++ b/src/plugins/vis_type_vislib/public/pie.ts @@ -23,14 +23,12 @@ import { AggGroupNames } from '../../data/public'; import { Schemas } from '../../vis_default_editor/public'; import { PieOptions } from './components/options'; import { getPositions, Positions } from './utils/collections'; -import { createVislibVisController } from './vis_controller'; import { CommonVislibParams } from './types'; -import { VisTypeVislibDependencies } from './plugin'; -import { VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { BaseVisTypeOptions, VIS_EVENT_TO_TRIGGER } from '../../../plugins/visualizations/public'; +import { toExpressionAst } from './to_ast_pie'; export interface PieVisParams extends CommonVislibParams { type: 'pie'; - addLegend: boolean; isDonut: boolean; labels: { show: boolean; @@ -40,17 +38,15 @@ export interface PieVisParams extends CommonVislibParams { }; } -export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ +export const pieVisTypeDefinition: BaseVisTypeOptions = { name: 'pie', title: i18n.translate('visTypeVislib.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', description: i18n.translate('visTypeVislib.pie.pieDescription', { defaultMessage: 'Compare parts of a whole', }), - visualization: createVislibVisController(deps), - getSupportedTriggers: () => { - return [VIS_EVENT_TO_TRIGGER.filter]; - }, + getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter], + toExpressionAst, visConfig: { defaults: { type: 'pie', @@ -108,4 +104,4 @@ export const createPieVisTypeDefinition = (deps: VisTypeVislibDependencies) => ( }, hierarchicalData: true, responseHandler: 'vislib_slices', -}); +}; diff --git a/src/plugins/vis_type_vislib/public/pie_fn.ts b/src/plugins/vis_type_vislib/public/pie_fn.ts index bee200cbe30ee3..c9da9e9bd9fabd 100644 --- a/src/plugins/vis_type_vislib/public/pie_fn.ts +++ b/src/plugins/vis_type_vislib/public/pie_fn.ts @@ -18,27 +18,35 @@ */ import { i18n } from '@kbn/i18n'; + import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; + // @ts-ignore import { vislibSlicesResponseHandler } from './vislib/response_handler'; +import { PieVisParams } from './pie'; +import { vislibVisName } from './vis_type_vislib_vis_fn'; + +export const vislibPieName = 'vislib_pie_vis'; interface Arguments { visConfig: string; } -type VisParams = Required; - interface RenderValue { - visConfig: VisParams; + visData: unknown; + visType: string; + visConfig: PieVisParams; } -export const createPieVisFn = (): ExpressionFunctionDefinition< - 'kibana_pie', +export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof vislibPieName, Datatable, Arguments, Render -> => ({ - name: 'kibana_pie', +>; + +export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({ + name: vislibPieName, type: 'render', inputTypes: ['datatable'], help: i18n.translate('visTypeVislib.functions.pie.help', { @@ -48,23 +56,20 @@ export const createPieVisFn = (): ExpressionFunctionDefinition< visConfig: { types: ['string'], default: '"{}"', - help: '', + help: 'vislib pie vis config', }, }, fn(input, args) { - const visConfig = JSON.parse(args.visConfig); - const convertedData = vislibSlicesResponseHandler(input, visConfig.dimensions); + const visConfig = JSON.parse(args.visConfig) as PieVisParams; + const visData = vislibSlicesResponseHandler(input, visConfig.dimensions); return { type: 'render', - as: 'visualization', + as: vislibVisName, value: { - visData: convertedData, - visType: 'pie', + visData, visConfig, - params: { - listenOnChange: true, - }, + visType: 'pie', }, }; }, diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index c6a6b6f82592b5..f183042fd52012 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -17,40 +17,20 @@ * under the License. */ -import './index.scss'; - -import { - CoreSetup, - CoreStart, - Plugin, - IUiSettingsClient, - PluginInitializerContext, -} from 'kibana/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; import { VisTypeXyPluginSetup } from 'src/plugins/vis_type_xy/public'; import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; -import { VisualizationsSetup } from '../../visualizations/public'; +import { BaseVisTypeOptions, VisualizationsSetup } from '../../visualizations/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; import { createPieVisFn } from './pie_fn'; -import { - createHistogramVisTypeDefinition, - createLineVisTypeDefinition, - createPieVisTypeDefinition, - createAreaVisTypeDefinition, - createHeatmapVisTypeDefinition, - createHorizontalBarVisTypeDefinition, - createGaugeVisTypeDefinition, - createGoalVisTypeDefinition, -} from './vis_type_vislib_vis_types'; +import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService, setDataActions, setKibanaLegacy } from './services'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; - -export interface VisTypeVislibDependencies { - uiSettings: IUiSettingsClient; - charts: ChartsPluginSetup; -} +import { setFormatService, setDataActions } from './services'; +import { getVislibVisRenderer } from './vis_renderer'; +import { BasicVislibParams } from './types'; /** @internal */ export interface VisTypeVislibPluginSetupDependencies { @@ -66,54 +46,37 @@ export interface VisTypeVislibPluginStartDependencies { kibanaLegacy: KibanaLegacyStart; } -type VisTypeVislibCoreSetup = CoreSetup; +export type VisTypeVislibCoreSetup = CoreSetup; /** @internal */ -export class VisTypeVislibPlugin implements Plugin { +export class VisTypeVislibPlugin + implements + Plugin { constructor(public initializerContext: PluginInitializerContext) {} public async setup( core: VisTypeVislibCoreSetup, { expressions, visualizations, charts, visTypeXy }: VisTypeVislibPluginSetupDependencies ) { - const visualizationDependencies: Readonly = { - uiSettings: core.uiSettings, - charts, - }; - const vislibTypes = [ - createHistogramVisTypeDefinition, - createLineVisTypeDefinition, - createPieVisTypeDefinition, - createAreaVisTypeDefinition, - createHeatmapVisTypeDefinition, - createHorizontalBarVisTypeDefinition, - createGaugeVisTypeDefinition, - createGoalVisTypeDefinition, - ]; - const vislibFns = [createVisTypeVislibVisFn(), createPieVisFn()]; - // if visTypeXy plugin is disabled it's config will be undefined if (!visTypeXy) { - const convertedTypes: any[] = []; + const convertedTypes: Array> = []; const convertedFns: any[] = []; // Register legacy vislib types that have been converted convertedFns.forEach(expressions.registerFunction); - convertedTypes.forEach((vis) => - visualizations.createBaseVisualization(vis(visualizationDependencies)) - ); + convertedTypes.forEach(visualizations.createBaseVisualization); + expressions.registerRenderer(getVislibVisRenderer(core, charts)); } - // Register non-converted types - vislibFns.forEach(expressions.registerFunction); - vislibTypes.forEach((vis) => - visualizations.createBaseVisualization(vis(visualizationDependencies)) - ); + visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization); + visualizations.createBaseVisualization(pieVisTypeDefinition); + expressions.registerRenderer(getVislibVisRenderer(core, charts)); + [createVisTypeVislibVisFn(), createPieVisFn()].forEach(expressions.registerFunction); } - public start(core: CoreStart, { data, kibanaLegacy }: VisTypeVislibPluginStartDependencies) { + public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) { setFormatService(data.fieldFormats); setDataActions(data.actions); - setKibanaLegacy(kibanaLegacy); } } diff --git a/src/plugins/vis_type_vislib/public/sample_vis.test.mocks.ts b/src/plugins/vis_type_vislib/public/sample_vis.test.mocks.ts new file mode 100644 index 00000000000000..324e8e00f37fc1 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/sample_vis.test.mocks.ts @@ -0,0 +1,3307 @@ +/* + * 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 const samplePieVis = { + type: { + name: 'pie', + title: 'Pie', + description: 'Compare parts of a whole', + icon: 'visPie', + stage: 'production', + options: { + showTimePicker: true, + showQueryBar: true, + showFilterBar: true, + showIndexSelection: true, + hierarchicalData: false, + }, + visConfig: { + defaults: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: 'right', + isDonut: true, + labels: { + show: false, + values: true, + last_level: true, + truncate: 100, + }, + }, + }, + editorConfig: { + collections: { + legendPositions: [ + { + text: 'Top', + value: 'top', + }, + { + text: 'Left', + value: 'left', + }, + { + text: 'Right', + value: 'right', + }, + { + text: 'Bottom', + value: 'bottom', + }, + ], + }, + schemas: { + all: [ + { + group: 'metrics', + name: 'metric', + title: 'Slice size', + min: 1, + max: 1, + aggFilter: ['sum', 'count', 'cardinality', 'top_hits'], + defaults: [ + { + schema: 'metric', + type: 'count', + }, + ], + editor: false, + params: [], + }, + { + group: 'buckets', + name: 'segment', + title: 'Split slices', + min: 0, + max: null, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + editor: false, + params: [], + }, + { + group: 'buckets', + name: 'split', + title: 'Split chart', + mustBeFirst: true, + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + params: [ + { + name: 'row', + default: true, + }, + ], + editor: false, + }, + ], + buckets: [null, null], + metrics: [null], + }, + }, + hidden: false, + requestHandler: 'courier', + responseHandler: 'vislib_slices', + hierarchicalData: true, + useCustomNoDataScreen: false, + }, + title: '[Flights] Airline Carrier', + description: '', + params: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: 'right', + isDonut: true, + labels: { + show: true, + values: true, + last_level: true, + truncate: 100, + }, + }, + sessionState: {}, + data: { + searchSource: { + id: 'data_source1', + requestStartHandlers: [], + inheritOptions: {}, + history: [], + fields: { + filter: [], + query: { + query: '', + language: 'kuery', + }, + index: { + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + title: 'kibana_sample_data_flights', + fieldFormatMap: { + AvgTicketPrice: { + id: 'number', + params: { + parsedUrl: { + origin: 'http://localhost:5801', + pathname: '/app/visualize', + basePath: '', + }, + pattern: '$0,0.[00]', + }, + }, + hour_of_day: { + id: 'number', + params: { + pattern: '00', + }, + }, + }, + fields: [ + { + count: 0, + name: 'AvgTicketPrice', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'Cancelled', + type: 'boolean', + esTypes: ['boolean'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'Carrier', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'Dest', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestAirportID', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestCityName', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestCountry', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestLocation', + type: 'geo_point', + esTypes: ['geo_point'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestRegion', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DestWeather', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DistanceKilometers', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'DistanceMiles', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightDelay', + type: 'boolean', + esTypes: ['boolean'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightDelayMin', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightDelayType', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightNum', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightTimeHour', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'FlightTimeMin', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'Origin', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginAirportID', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginCityName', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginCountry', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginLocation', + type: 'geo_point', + esTypes: ['geo_point'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginRegion', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'OriginWeather', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: '_id', + type: 'string', + esTypes: ['_id'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: '_index', + type: 'string', + esTypes: ['_index'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: '_score', + type: 'number', + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_source', + type: '_source', + esTypes: ['_source'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_type', + type: 'string', + esTypes: ['_type'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: 'dayOfWeek', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'timestamp', + type: 'date', + esTypes: ['date'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + script: "doc['timestamp'].value.hourOfDay", + lang: 'painless', + name: 'hour_of_day', + type: 'number', + scripted: true, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + ], + timeFieldName: 'timestamp', + metaFields: ['_source', '_id', '_type', '_index', '_score'], + version: 'WzM1LDFd', + originalSavedObjectBody: { + title: 'kibana_sample_data_flights', + timeFieldName: 'timestamp', + fields: + '[{"count":0,"name":"AvgTicketPrice","type":"number","esTypes":["float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"Cancelled","type":"boolean","esTypes":["boolean"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"Carrier","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"Dest","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestAirportID","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestCityName","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestCountry","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestLocation","type":"geo_point","esTypes":["geo_point"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestRegion","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DestWeather","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DistanceKilometers","type":"number","esTypes":["float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"DistanceMiles","type":"number","esTypes":["float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightDelay","type":"boolean","esTypes":["boolean"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightDelayMin","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightDelayType","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightNum","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightTimeHour","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"FlightTimeMin","type":"number","esTypes":["float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"Origin","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginAirportID","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginCityName","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginCountry","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginLocation","type":"geo_point","esTypes":["geo_point"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginRegion","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"OriginWeather","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"_id","type":"string","esTypes":["_id"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_index","type":"string","esTypes":["_index"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_score","type":"number","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_source","type":"_source","esTypes":["_source"],"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_type","type":"string","esTypes":["_type"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"dayOfWeek","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"timestamp","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","name":"hour_of_day","type":"number","scripted":true,"searchable":true,"aggregatable":true,"readFromDocValues":false}]', + fieldFormatMap: + '{"AvgTicketPrice":{"id":"number","params":{"parsedUrl":{"origin":"http://localhost:5801","pathname":"/app/visualize","basePath":""},"pattern":"$0,0.[00]"}},"hour_of_day":{"id":"number","params":{"parsedUrl":{"origin":"http://localhost:5801","pathname":"/app/visualize","basePath":""},"pattern":"00"}}}', + }, + shortDotsEnable: false, + fieldFormats: { + fieldFormats: {}, + defaultMap: { + ip: { + id: 'ip', + params: {}, + }, + date: { + id: 'date', + params: {}, + }, + date_nanos: { + id: 'date_nanos', + params: {}, + es: true, + }, + number: { + id: 'number', + params: {}, + }, + boolean: { + id: 'boolean', + params: {}, + }, + _source: { + id: '_source', + params: {}, + }, + _default_: { + id: 'string', + params: {}, + }, + }, + metaParamsOptions: {}, + }, + }, + }, + dependencies: { + legacy: { + loadingCount$: { + _isScalar: false, + observers: [ + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + destination: { + closed: true, + }, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [ + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 13, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 3, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + null, + ], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + null, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + }, + }, + aggs: { + typesRegistry: {}, + getResponseAggs: () => [ + { + id: '1', + enabled: true, + type: 'count', + params: {}, + schema: 'metric', + toSerializedFieldFormat: () => ({ + id: 'number', + }), + }, + { + id: '2', + enabled: true, + type: 'terms', + params: { + field: 'Carrier', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'segment', + toSerializedFieldFormat: () => ({ + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: 'Missing', + parsedUrl: { + origin: 'http://localhost:5801', + pathname: '/app/visualize', + basePath: '', + }, + }, + }), + }, + ], + }, + }, + isHierarchical: () => true, + uiState: { + vis: { + legendOpen: false, + }, + }, +}; + +export const sampleAreaVis = { + type: { + name: 'area', + title: 'Area', + description: 'Emphasize the quantity beneath a line chart', + icon: 'visArea', + stage: 'production', + options: { + showTimePicker: true, + showQueryBar: true, + showFilterBar: true, + showIndexSelection: true, + hierarchicalData: false, + }, + visConfig: { + defaults: { + type: 'area', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + filter: true, + truncate: 100, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 100, + }, + title: { + text: 'Count', + }, + }, + ], + seriesParams: [ + { + show: true, + type: 'area', + mode: 'stacked', + data: { + label: 'Count', + id: '1', + }, + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + interpolate: 'linear', + valueAxis: 'ValueAxis-1', + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'right', + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + labels: {}, + }, + }, + editorConfig: { + collections: { + legendPositions: [ + { + text: 'Top', + value: 'top', + }, + { + text: 'Left', + value: 'left', + }, + { + text: 'Right', + value: 'right', + }, + { + text: 'Bottom', + value: 'bottom', + }, + ], + positions: [ + { + text: 'Top', + value: 'top', + }, + { + text: 'Left', + value: 'left', + }, + { + text: 'Right', + value: 'right', + }, + { + text: 'Bottom', + value: 'bottom', + }, + ], + chartTypes: [ + { + text: 'Line', + value: 'line', + }, + { + text: 'Area', + value: 'area', + }, + { + text: 'Bar', + value: 'histogram', + }, + ], + axisModes: [ + { + text: 'Normal', + value: 'normal', + }, + { + text: 'Percentage', + value: 'percentage', + }, + { + text: 'Wiggle', + value: 'wiggle', + }, + { + text: 'Silhouette', + value: 'silhouette', + }, + ], + scaleTypes: [ + { + text: 'Linear', + value: 'linear', + }, + { + text: 'Log', + value: 'log', + }, + { + text: 'Square root', + value: 'square root', + }, + ], + chartModes: [ + { + text: 'Normal', + value: 'normal', + }, + { + text: 'Stacked', + value: 'stacked', + }, + ], + interpolationModes: [ + { + text: 'Straight', + value: 'linear', + }, + { + text: 'Smoothed', + value: 'cardinal', + }, + { + text: 'Stepped', + value: 'step-after', + }, + ], + thresholdLineStyles: [ + { + value: 'full', + text: 'Full', + }, + { + value: 'dashed', + text: 'Dashed', + }, + { + value: 'dot-dashed', + text: 'Dot-dashed', + }, + ], + }, + optionTabs: [ + { + name: 'advanced', + title: 'Metrics & axes', + }, + { + name: 'options', + title: 'Panel settings', + }, + ], + schemas: { + all: [ + { + group: 'metrics', + name: 'metric', + title: 'Y-axis', + aggFilter: ['!geo_centroid', '!geo_bounds'], + min: 1, + defaults: [ + { + schema: 'metric', + type: 'count', + }, + ], + max: null, + editor: false, + params: [], + }, + { + group: 'metrics', + name: 'radius', + title: 'Dot size', + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], + editor: false, + params: [], + }, + { + group: 'buckets', + name: 'segment', + title: 'X-axis', + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + editor: false, + params: [], + }, + { + group: 'buckets', + name: 'group', + title: 'Split series', + min: 0, + max: 3, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + editor: false, + params: [], + }, + { + group: 'buckets', + name: 'split', + title: 'Split chart', + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + params: [ + { + name: 'row', + default: true, + }, + ], + editor: false, + }, + ], + buckets: [null, null, null], + metrics: [null, null], + }, + }, + hidden: false, + requestHandler: 'courier', + responseHandler: 'none', + hierarchicalData: false, + useCustomNoDataScreen: false, + }, + title: '[eCommerce] Sales by Category', + description: '', + params: { + type: 'area', + grid: { + categoryLines: false, + style: { + color: '#eee', + }, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: 'category', + position: 'bottom', + show: true, + style: {}, + scale: { + type: 'linear', + }, + labels: { + show: true, + truncate: 100, + }, + title: {}, + }, + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: 'value', + position: 'left', + show: true, + style: {}, + scale: { + type: 'linear', + mode: 'normal', + }, + labels: { + show: true, + rotate: 0, + filter: false, + truncate: 100, + }, + title: { + text: 'Sum of total_quantity', + }, + }, + ], + seriesParams: [ + { + show: 'true', + type: 'area', + mode: 'stacked', + data: { + label: 'Sum of total_quantity', + id: '1', + }, + drawLinesBetweenPoints: true, + showCircles: true, + interpolate: 'linear', + valueAxis: 'ValueAxis-1', + }, + ], + addTooltip: true, + addLegend: true, + legendPosition: 'top', + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: 'full', + color: '#E7664C', + }, + labels: {}, + dimensions: { + x: { + accessor: 0, + format: { + id: 'date', + params: { + pattern: 'YYYY-MM-DD HH:mm', + }, + }, + params: { + date: true, + interval: 43200000, + format: 'YYYY-MM-DD HH:mm', + bounds: { + min: '2020-09-30T12:41:13.795Z', + max: '2020-10-15T17:00:00.000Z', + }, + }, + label: 'order_date per 12 hours', + aggType: 'date_histogram', + }, + y: [ + { + accessor: 2, + format: { + id: 'number', + params: { + parsedUrl: { + origin: 'http://localhost:5801', + pathname: '/app/visualize', + basePath: '', + }, + }, + }, + params: {}, + label: 'Sum of total_quantity', + aggType: 'sum', + }, + ], + series: [ + { + accessor: 1, + format: { + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: 'Missing', + }, + }, + params: {}, + label: 'category.keyword: Descending', + aggType: 'terms', + }, + ], + }, + }, + sessionState: {}, + data: { + searchSource: { + id: 'data_source1', + requestStartHandlers: [], + inheritOptions: {}, + history: [], + fields: { + query: { + query: '', + language: 'kuery', + }, + filter: [], + index: { + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + title: 'kibana_sample_data_ecommerce', + fieldFormatMap: { + taxful_total_price: { + id: 'number', + params: { + pattern: '$0,0.[00]', + }, + }, + }, + fields: [ + { + count: 0, + name: '_id', + type: 'string', + esTypes: ['_id'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: '_index', + type: 'string', + esTypes: ['_index'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: '_score', + type: 'number', + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_source', + type: '_source', + esTypes: ['_source'], + scripted: false, + searchable: false, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: '_type', + type: 'string', + esTypes: ['_type'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + }, + { + count: 0, + name: 'category', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'category.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'category', + }, + }, + }, + { + count: 0, + name: 'currency', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'customer_birth_date', + type: 'date', + esTypes: ['date'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'customer_first_name', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'customer_first_name.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'customer_first_name', + }, + }, + }, + { + count: 0, + name: 'customer_full_name', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'customer_full_name.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'customer_full_name', + }, + }, + }, + { + count: 0, + name: 'customer_gender', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'customer_id', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'customer_last_name', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'customer_last_name.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'customer_last_name', + }, + }, + }, + { + count: 0, + name: 'customer_phone', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'day_of_week', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'day_of_week_i', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'email', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'event.dataset', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geoip.city_name', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geoip.continent_name', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geoip.country_iso_code', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geoip.location', + type: 'geo_point', + esTypes: ['geo_point'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'geoip.region_name', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'manufacturer', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'manufacturer.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'manufacturer', + }, + }, + }, + { + count: 0, + name: 'order_date', + type: 'date', + esTypes: ['date'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'order_id', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products._id', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'products._id.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'products._id', + }, + }, + }, + { + count: 0, + name: 'products.base_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.base_unit_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.category', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'products.category.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'products.category', + }, + }, + }, + { + count: 0, + name: 'products.created_on', + type: 'date', + esTypes: ['date'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.discount_amount', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.discount_percentage', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.manufacturer', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'products.manufacturer.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'products.manufacturer', + }, + }, + }, + { + count: 0, + name: 'products.min_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.product_id', + type: 'number', + esTypes: ['long'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.product_name', + type: 'string', + esTypes: ['text'], + scripted: false, + searchable: true, + aggregatable: false, + readFromDocValues: false, + }, + { + count: 0, + name: 'products.product_name.keyword', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + subType: { + multi: { + parent: 'products.product_name', + }, + }, + }, + { + count: 0, + name: 'products.quantity', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.sku', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.tax_amount', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.taxful_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.taxless_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'products.unit_discount_amount', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'sku', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'taxful_total_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'taxless_total_price', + type: 'number', + esTypes: ['half_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'total_quantity', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'total_unique_products', + type: 'number', + esTypes: ['integer'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'type', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + { + count: 0, + name: 'user', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + ], + timeFieldName: 'order_date', + metaFields: ['_source', '_id', '_type', '_index', '_score'], + version: 'WzEzLDFd', + originalSavedObjectBody: { + title: 'kibana_sample_data_ecommerce', + timeFieldName: 'order_date', + fields: + '[{"count":0,"name":"_id","type":"string","esTypes":["_id"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_index","type":"string","esTypes":["_index"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_score","type":"number","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_source","type":"_source","esTypes":["_source"],"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_type","type":"string","esTypes":["_type"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"category","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"category.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"category"}}},{"count":0,"name":"currency","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"customer_birth_date","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"customer_first_name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"customer_first_name.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_first_name"}}},{"count":0,"name":"customer_full_name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"customer_full_name.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_full_name"}}},{"count":0,"name":"customer_gender","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"customer_id","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"customer_last_name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"customer_last_name.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_last_name"}}},{"count":0,"name":"customer_phone","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"day_of_week","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"day_of_week_i","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"email","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"event.dataset","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"geoip.city_name","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"geoip.continent_name","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"geoip.country_iso_code","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"geoip.location","type":"geo_point","esTypes":["geo_point"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"geoip.region_name","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"manufacturer","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"manufacturer.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"manufacturer"}}},{"count":0,"name":"order_date","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"order_id","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products._id","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"products._id.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products._id"}}},{"count":0,"name":"products.base_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.base_unit_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.category","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"products.category.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.category"}}},{"count":0,"name":"products.created_on","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.discount_amount","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.discount_percentage","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.manufacturer","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"products.manufacturer.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.manufacturer"}}},{"count":0,"name":"products.min_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.product_id","type":"number","esTypes":["long"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.product_name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"products.product_name.keyword","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.product_name"}}},{"count":0,"name":"products.quantity","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.sku","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.tax_amount","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.taxful_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.taxless_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"products.unit_discount_amount","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"sku","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"taxful_total_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"taxless_total_price","type":"number","esTypes":["half_float"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"total_quantity","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"total_unique_products","type":"number","esTypes":["integer"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"type","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"user","type":"string","esTypes":["keyword"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + fieldFormatMap: + '{"taxful_total_price":{"id":"number","params":{"parsedUrl":{"origin":"http://localhost:5801","pathname":"/app/visualize","basePath":""},"pattern":"$0,0.[00]"}}}', + }, + shortDotsEnable: false, + fieldFormats: { + fieldFormats: {}, + defaultMap: { + ip: { + id: 'ip', + params: {}, + }, + date: { + id: 'date', + params: {}, + }, + date_nanos: { + id: 'date_nanos', + params: {}, + es: true, + }, + number: { + id: 'number', + params: {}, + }, + boolean: { + id: 'boolean', + params: {}, + }, + _source: { + id: '_source', + params: {}, + }, + _default_: { + id: 'string', + params: {}, + }, + }, + metaParamsOptions: {}, + }, + }, + }, + dependencies: { + legacy: { + loadingCount$: { + _isScalar: false, + observers: [ + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + destination: { + closed: true, + }, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [ + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 13, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 1, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: { + closed: false, + _parentOrParents: null, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + destination: { + closed: false, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + _context: {}, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + count: 3, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: false, + hasPrev: true, + prev: 0, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: true, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [], + active: 1, + index: 2, + }, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + parent: { + closed: true, + _parentOrParents: null, + _subscriptions: null, + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: true, + concurrent: 1, + hasCompleted: true, + buffer: [ + { + _isScalar: false, + }, + ], + active: 1, + index: 1, + }, + }, + _subscriptions: [ + null, + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + subject: { + _isScalar: false, + observers: [null], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + null, + ], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + }, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + null, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + seenValue: false, + }, + _subscriptions: [null], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + }, + _subscriptions: [ + { + closed: false, + _subscriptions: null, + }, + ], + syncErrorValue: null, + syncErrorThrown: false, + syncErrorThrowable: false, + isStopped: false, + hasKey: true, + key: 0, + }, + ], + closed: false, + isStopped: false, + hasError: false, + thrownError: null, + _value: 0, + }, + }, + }, + }, + aggs: { + typesRegistry: {}, + getResponseAggs: () => [ + { + id: '1', + enabled: true, + type: 'sum', + params: { + field: 'total_quantity', + }, + schema: 'metric', + toSerializedFieldFormat: () => ({ + id: 'number', + params: { + parsedUrl: { + origin: 'http://localhost:5801', + pathname: '/app/visualize', + basePath: '', + }, + }, + }), + }, + { + id: '2', + enabled: true, + type: 'date_histogram', + params: { + field: 'order_date', + timeRange: { + from: '2020-09-30T12:41:13.795Z', + to: '2020-10-15T17:00:00.000Z', + }, + useNormalizedEsInterval: true, + scaleMetricValues: false, + interval: 'auto', + drop_partials: false, + min_doc_count: 1, + extended_bounds: {}, + }, + schema: 'segment', + toSerializedFieldFormat: () => ({ + id: 'date', + params: { pattern: 'HH:mm:ss.SSS' }, + }), + }, + { + id: '3', + enabled: true, + type: 'terms', + params: { + field: 'category.keyword', + orderBy: '1', + order: 'desc', + size: 5, + otherBucket: false, + otherBucketLabel: 'Other', + missingBucket: false, + missingBucketLabel: 'Missing', + }, + schema: 'group', + toSerializedFieldFormat: () => ({ + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: 'Missing', + parsedUrl: { + origin: 'http://localhost:5801', + pathname: '/app/visualize', + basePath: '', + }, + }, + }), + }, + ], + }, + }, + isHierarchical: () => false, + uiState: {}, +}; diff --git a/src/plugins/vis_type_vislib/public/services.ts b/src/plugins/vis_type_vislib/public/services.ts index 7257b98f2e9f5b..633fae9c7f2a61 100644 --- a/src/plugins/vis_type_vislib/public/services.ts +++ b/src/plugins/vis_type_vislib/public/services.ts @@ -19,7 +19,6 @@ import { createGetterSetter } from '../../kibana_utils/public'; import { DataPublicPluginStart } from '../../data/public'; -import { KibanaLegacyStart } from '../../kibana_legacy/public'; export const [getDataActions, setDataActions] = createGetterSetter< DataPublicPluginStart['actions'] @@ -28,7 +27,3 @@ export const [getDataActions, setDataActions] = createGetterSetter< export const [getFormatService, setFormatService] = createGetterSetter< DataPublicPluginStart['fieldFormats'] >('vislib data.fieldFormats'); - -export const [getKibanaLegacy, setKibanaLegacy] = createGetterSetter( - 'vislib kibanalegacy' -); diff --git a/src/plugins/vis_type_vislib/public/to_ast.test.ts b/src/plugins/vis_type_vislib/public/to_ast.test.ts new file mode 100644 index 00000000000000..48d3dfe254d0b6 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/to_ast.test.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from '../../visualizations/public'; +import { buildExpression } from '../../expressions/public'; + +import { BasicVislibParams } from './types'; +import { toExpressionAst } from './to_ast'; +import { sampleAreaVis } from './sample_vis.test.mocks'; + +jest.mock('../../expressions/public', () => ({ + ...(jest.requireActual('../../expressions/public') as any), + buildExpression: jest.fn().mockImplementation(() => ({ + toAst: () => ({ + type: 'expression', + chain: [], + }), + })), +})); + +jest.mock('./to_ast_esaggs', () => ({ + getEsaggsFn: jest.fn(), +})); + +describe('vislib vis toExpressionAst function', () => { + let vis: Vis; + + const params = { + timefilter: {}, + timeRange: {}, + abortSignal: {}, + } as any; + + beforeEach(() => { + vis = sampleAreaVis as any; + }); + + it('should match basic snapshot', () => { + toExpressionAst(vis, params); + const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0]; + + expect(builtExpression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/vis_type_vislib/public/to_ast.ts b/src/plugins/vis_type_vislib/public/to_ast.ts new file mode 100644 index 00000000000000..7cd55ccd32ebce --- /dev/null +++ b/src/plugins/vis_type_vislib/public/to_ast.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; + +import { VisToExpressionAst, getVisSchemas } from '../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; + +import { vislibVisName, VisTypeVislibExpressionFunctionDefinition } from './vis_type_vislib_vis_fn'; +import { BasicVislibParams } from './types'; +import { + DateHistogramParams, + Dimensions, + HistogramParams, +} from './vislib/helpers/point_series/point_series'; +import { getEsaggsFn } from './to_ast_esaggs'; + +export const toExpressionAst: VisToExpressionAst = async (vis, params) => { + const schemas = getVisSchemas(vis, params); + const dimensions: Dimensions = { + x: schemas.segment ? schemas.segment[0] : null, + y: schemas.metric, + z: schemas.radius, + width: schemas.width, + series: schemas.group, + splitRow: schemas.split_row, + splitColumn: schemas.split_column, + }; + + const responseAggs = vis.data.aggs?.getResponseAggs() ?? []; + + if (dimensions.x) { + const xAgg = responseAggs[dimensions.x.accessor] as any; + if (xAgg.type.name === 'date_histogram') { + (dimensions.x.params as DateHistogramParams).date = true; + const { esUnit, esValue } = xAgg.buckets.getInterval(); + (dimensions.x.params as DateHistogramParams).intervalESUnit = esUnit; + (dimensions.x.params as DateHistogramParams).intervalESValue = esValue; + (dimensions.x.params as DateHistogramParams).interval = moment + .duration(esValue, esUnit) + .asMilliseconds(); + (dimensions.x.params as DateHistogramParams).format = xAgg.buckets.getScaledDateFormat(); + (dimensions.x.params as DateHistogramParams).bounds = xAgg.buckets.getBounds(); + } else if (xAgg.type.name === 'histogram') { + const intervalParam = xAgg.type.paramByName('interval'); + const output = { params: {} as any }; + await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, { + abortSignal: params.abortSignal, + }); + intervalParam.write(xAgg, output); + (dimensions.x.params as HistogramParams).interval = output.params.interval; + } + } + + const visConfig = { ...vis.params }; + + (dimensions.y || []).forEach((yDimension) => { + const yAgg = responseAggs.filter(({ enabled }) => enabled)[yDimension.accessor]; + const seriesParam = (visConfig.seriesParams || []).find((param) => param.data.id === yAgg.id); + if (seriesParam) { + const usedValueAxis = (visConfig.valueAxes || []).find( + (valueAxis) => valueAxis.id === seriesParam.valueAxis + ); + if (usedValueAxis?.scale.mode === 'percentage') { + yDimension.format = { id: 'percent' }; + } + } + if (visConfig?.gauge?.percentageMode === true) { + yDimension.format = { id: 'percent' }; + } + }); + + visConfig.dimensions = dimensions; + + const configStr = JSON.stringify(visConfig).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`); + const visTypeXy = buildExpressionFunction( + vislibVisName, + { + type: vis.type.name, + visConfig: configStr, + } + ); + + const ast = buildExpression([getEsaggsFn(vis), visTypeXy]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts b/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts new file mode 100644 index 00000000000000..a7312c9d36cbbe --- /dev/null +++ b/src/plugins/vis_type_vislib/public/to_ast_esaggs.ts @@ -0,0 +1,40 @@ +/* + * 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 { Vis } from '../../visualizations/public'; +import { buildExpressionFunction } from '../../expressions/public'; +import { EsaggsExpressionFunctionDefinition } from '../../data/public'; + +import { PieVisParams } from './pie'; +import { BasicVislibParams } from './types'; + +/** + * Get esaggs expressions function + * TODO: replace this with vis.data.aggs!.toExpressionAst(); + * @param vis + */ +export function getEsaggsFn(vis: Vis | Vis) { + return buildExpressionFunction('esaggs', { + index: vis.data.indexPattern!.id!, + metricsAtAllLevels: vis.isHierarchical(), + partialRows: false, + aggConfigs: JSON.stringify(vis.data.aggs!.aggs), + includeFormatHints: false, + }); +} diff --git a/src/plugins/vis_type_vislib/public/to_ast_pie.test.ts b/src/plugins/vis_type_vislib/public/to_ast_pie.test.ts new file mode 100644 index 00000000000000..36a9a17341bc58 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/to_ast_pie.test.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from '../../visualizations/public'; +import { buildExpression } from '../../expressions/public'; + +import { PieVisParams } from './pie'; +import { samplePieVis } from './sample_vis.test.mocks'; +import { toExpressionAst } from './to_ast_pie'; + +jest.mock('../../expressions/public', () => ({ + ...(jest.requireActual('../../expressions/public') as any), + buildExpression: jest.fn().mockImplementation(() => ({ + toAst: () => ({ + type: 'expression', + chain: [], + }), + })), +})); + +jest.mock('./to_ast_esaggs', () => ({ + getEsaggsFn: jest.fn(), +})); + +describe('vislib pie vis toExpressionAst function', () => { + let vis: Vis; + + const params = { + timefilter: {}, + timeRange: {}, + abortSignal: {}, + } as any; + + beforeEach(() => { + vis = samplePieVis as any; + }); + + it('should match basic snapshot', () => { + toExpressionAst(vis, params); + const [, builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0]; + + expect(builtExpression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/vis_type_vislib/public/to_ast_pie.ts b/src/plugins/vis_type_vislib/public/to_ast_pie.ts new file mode 100644 index 00000000000000..95a5f89208ef93 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/to_ast_pie.ts @@ -0,0 +1,50 @@ +/* + * 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 { getVisSchemas, VisToExpressionAst } from '../../visualizations/public'; +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; + +import { PieVisParams } from './pie'; +import { vislibPieName, VisTypeVislibPieExpressionFunctionDefinition } from './pie_fn'; +import { getEsaggsFn } from './to_ast_esaggs'; + +export const toExpressionAst: VisToExpressionAst = async (vis, params) => { + const schemas = getVisSchemas(vis, params); + const visConfig = { + ...vis.params, + dimensions: { + metric: schemas.metric[0], + buckets: schemas.segment, + splitRow: schemas.split_row, + splitColumn: schemas.split_column, + }, + }; + + const configStr = JSON.stringify(visConfig).replace(/\\/g, `\\\\`).replace(/'/g, `\\'`); + const visTypePie = buildExpressionFunction( + vislibPieName, + { + visConfig: configStr, + } + ); + + const ast = buildExpression([getEsaggsFn(vis), visTypePie]); + + return ast.toAst(); +}; diff --git a/src/plugins/vis_type_vislib/public/types.ts b/src/plugins/vis_type_vislib/public/types.ts index a29f0bcb5c52a2..c0311edf76154a 100644 --- a/src/plugins/vis_type_vislib/public/types.ts +++ b/src/plugins/vis_type_vislib/public/types.ts @@ -29,10 +29,13 @@ import { ThresholdLineStyles, } from './utils/collections'; import { Labels, Style } from '../../charts/public'; +import { Dimensions } from './vislib/helpers/point_series/point_series'; export interface CommonVislibParams { addTooltip: boolean; + addLegend: boolean; legendPosition: Positions; + dimensions: Dimensions; } export interface Scale { @@ -87,6 +90,9 @@ export interface BasicVislibParams extends CommonVislibParams { labels: Labels; thresholdLine: ThresholdLine; valueAxes: ValueAxis[]; + gauge?: { + percentageMode: boolean; + }; grid: { categoryLines: boolean; valueAxis?: string; diff --git a/src/plugins/vis_type_vislib/public/vis_controller.tsx b/src/plugins/vis_type_vislib/public/vis_controller.tsx index 3a05030f804cad..1804d0d52ae7a6 100644 --- a/src/plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/plugins/vis_type_vislib/public/vis_controller.tsx @@ -20,127 +20,147 @@ import $ from 'jquery'; import React, { RefObject } from 'react'; -import { Positions } from './utils/collections'; -import { VisTypeVislibDependencies } from './plugin'; import { mountReactNode } from '../../../core/public/utils'; +import { ChartsPluginSetup } from '../../charts/public'; +import { PersistedState } from '../../visualizations/public'; +import { IInterpreterRenderHandlers } from '../../expressions/public'; + +import { VisTypeVislibCoreSetup } from './plugin'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; -import { VisParams, ExprVis } from '../../visualizations/public'; -import { getKibanaLegacy } from './services'; +import { BasicVislibParams } from './types'; +import { PieVisParams } from './pie'; const legendClassName = { - top: 'visLib--legend-top', - bottom: 'visLib--legend-bottom', - left: 'visLib--legend-left', - right: 'visLib--legend-right', + top: 'vislib--legend-top', + bottom: 'vislib--legend-bottom', + left: 'vislib--legend-left', + right: 'vislib--legend-right', }; -export const createVislibVisController = (deps: VisTypeVislibDependencies) => { +export type VislibVisController = InstanceType>; + +export const createVislibVisController = ( + core: VisTypeVislibCoreSetup, + charts: ChartsPluginSetup +) => { return class VislibVisController { - unmount: (() => void) | null = null; - visParams?: VisParams; + private removeListeners?: () => void; + private unmountLegend?: () => void; + legendRef: RefObject; container: HTMLDivElement; chartEl: HTMLDivElement; legendEl: HTMLDivElement; - vislibVis: any; + vislibVis?: any; - constructor(public el: Element, public vis: ExprVis) { + constructor(public el: HTMLDivElement) { this.el = el; - this.vis = vis; - this.unmount = null; this.legendRef = React.createRef(); // vis mount point this.container = document.createElement('div'); - this.container.className = 'visLib'; + this.container.className = 'vislib'; this.el.appendChild(this.container); // chart mount point this.chartEl = document.createElement('div'); - this.chartEl.className = 'visLib__chart'; + this.chartEl.className = 'vislib__chart'; this.container.appendChild(this.chartEl); // legend mount point this.legendEl = document.createElement('div'); - this.legendEl.className = 'visLib__legend'; + this.legendEl.className = 'vislib__legend'; this.container.appendChild(this.legendEl); } - render(esResponse: any, visParams: VisParams): Promise { + async render( + esResponse: any, + visParams: BasicVislibParams | PieVisParams, + handlers: IInterpreterRenderHandlers + ): Promise { if (this.vislibVis) { - this.destroy(); + this.destroy(false); + } + + // Used in functional tests to know when chart is loaded by type + this.chartEl.dataset.vislibChartType = visParams.type; + + if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { + handlers.done(); + return; + } + + const [, { kibanaLegacy }] = await core.getStartServices(); + kibanaLegacy.loadFontAwesome(); + + // @ts-expect-error + const { Vis: Vislib } = await import('./vislib/vis'); + const { uiState, event: fireEvent } = handlers; + + this.vislibVis = new Vislib(this.chartEl, visParams, core, charts); + this.vislibVis.on('brush', fireEvent); + this.vislibVis.on('click', fireEvent); + this.vislibVis.on('renderComplete', handlers.done); + this.removeListeners = () => { + this.vislibVis.off('brush', fireEvent); + this.vislibVis.off('click', fireEvent); + }; + + this.vislibVis.initVisConfig(esResponse, uiState); + + if (visParams.addLegend) { + $(this.container) + .attr('class', (i, cls) => { + return cls.replace(/vislib--legend-\S+/g, ''); + }) + .addClass((legendClassName as any)[visParams.legendPosition]); + + this.mountLegend(esResponse, visParams, fireEvent, uiState); } - getKibanaLegacy().loadFontAwesome(); - - return new Promise(async (resolve) => { - if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { - return resolve(); - } - - // @ts-expect-error - const { Vis: Vislib } = await import('./vislib/vis'); - - this.vislibVis = new Vislib(this.chartEl, visParams, deps); - this.vislibVis.on('brush', this.vis.API.events.brush); - this.vislibVis.on('click', this.vis.API.events.filter); - this.vislibVis.on('renderComplete', resolve); - - this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); - - if (visParams.addLegend) { - $(this.container) - .attr('class', (i, cls) => { - return cls.replace(/visLib--legend-\S+/g, ''); - }) - .addClass((legendClassName as any)[visParams.legendPosition]); - - this.mountLegend(esResponse, visParams.legendPosition); - } - - this.vislibVis.render(esResponse, this.vis.getUiState()); - - // refreshing the legend after the chart is rendered. - // this is necessary because some visualizations - // provide data necessary for the legend only after a render cycle. - if ( - visParams.addLegend && - CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) - ) { - this.unmountLegend(); - this.mountLegend(esResponse, visParams.legendPosition); - this.vislibVis.render(esResponse, this.vis.getUiState()); - } - }); + this.vislibVis.render(esResponse, uiState); + + // refreshing the legend after the chart is rendered. + // this is necessary because some visualizations + // provide data necessary for the legend only after a render cycle. + if ( + visParams.addLegend && + CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) + ) { + this.unmountLegend?.(); + this.mountLegend(esResponse, visParams, fireEvent, uiState); + this.vislibVis.render(esResponse, uiState); + } } - mountLegend(visData: any, position: Positions) { - this.unmount = mountReactNode( + mountLegend( + visData: unknown, + { legendPosition, addLegend }: BasicVislibParams | PieVisParams, + fireEvent: IInterpreterRenderHandlers['event'], + uiState?: PersistedState + ) { + this.unmountLegend = mountReactNode( )(this.legendEl); } - unmountLegend() { - if (this.unmount) { - this.unmount(); - } - } + destroy(clearElement = true) { + this.unmountLegend?.(); - destroy() { - if (this.unmount) { - this.unmount(); + if (clearElement) { + this.el.innerHTML = ''; } if (this.vislibVis) { - this.vislibVis.off('brush', this.vis.API.events.brush); - this.vislibVis.off('click', this.vis.API.events.filter); + this.removeListeners?.(); this.vislibVis.destroy(); delete this.vislibVis; } diff --git a/src/plugins/vis_type_vislib/public/vis_renderer.tsx b/src/plugins/vis_type_vislib/public/vis_renderer.tsx new file mode 100644 index 00000000000000..9c697f481e63e9 --- /dev/null +++ b/src/plugins/vis_type_vislib/public/vis_renderer.tsx @@ -0,0 +1,61 @@ +/* + * 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 React, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { ExpressionRenderDefinition } from '../../expressions/public'; +import { VisualizationContainer } from '../../visualizations/public'; +import { ChartsPluginSetup } from '../../charts/public'; + +import { VisTypeVislibCoreSetup } from './plugin'; +import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn'; + +function shouldShowNoResultsMessage(visData: any, visType: string): boolean { + if (['goal', 'gauge'].includes(visType)) { + return false; + } + + const rows: object[] | undefined = visData?.rows; + const isZeroHits = visData?.hits === 0 || (rows && !rows.length); + + return Boolean(isZeroHits); +} + +const VislibWrapper = lazy(() => import('./vis_wrapper')); + +export const getVislibVisRenderer: ( + core: VisTypeVislibCoreSetup, + charts: ChartsPluginSetup +) => ExpressionRenderDefinition = (core, charts) => ({ + name: vislibVisName, + reuseDomNode: true, + render: async (domNode, config, handlers) => { + const showNoResult = shouldShowNoResultsMessage(config.visData, config.visType); + + handlers.onDestroy(() => unmountComponentAtNode(domNode)); + + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 557f9930f55b1d..c5fa8f36f43e39 100644 --- a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -18,29 +18,35 @@ */ import { i18n } from '@kbn/i18n'; + import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; + // @ts-ignore import { vislibSeriesResponseHandler } from './vislib/response_handler'; +import { BasicVislibParams } from './types'; + +export const vislibVisName = 'vislib_vis'; interface Arguments { type: string; visConfig: string; } -type VisParams = Required; - -interface RenderValue { +export interface VislibRenderValue { + visData: any; visType: string; - visConfig: VisParams; + visConfig: BasicVislibParams; } -export const createVisTypeVislibVisFn = (): ExpressionFunctionDefinition< - 'vislib', +export type VisTypeVislibExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof vislibVisName, Datatable, Arguments, - Render -> => ({ - name: 'vislib', + Render +>; + +export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefinition => ({ + name: vislibVisName, type: 'render', inputTypes: ['datatable'], help: i18n.translate('visTypeVislib.functions.vislib.help', { @@ -55,23 +61,21 @@ export const createVisTypeVislibVisFn = (): ExpressionFunctionDefinition< visConfig: { types: ['string'], default: '"{}"', - help: '', + help: 'vislib vis config', }, }, fn(context, args) { - const visConfigParams = JSON.parse(args.visConfig); - const convertedData = vislibSeriesResponseHandler(context, visConfigParams.dimensions); + const visType = args.type; + const visConfig = JSON.parse(args.visConfig) as BasicVislibParams; + const visData = vislibSeriesResponseHandler(context, visConfig.dimensions); return { type: 'render', - as: 'visualization', + as: vislibVisName, value: { - visData: convertedData, - visType: args.type, - visConfig: visConfigParams, - params: { - listenOnChange: true, - }, + visData, + visConfig, + visType, }, }; }, diff --git a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts index f44d5038954839..1b43a213c618d8 100644 --- a/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts +++ b/src/plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts @@ -17,11 +17,22 @@ * under the License. */ -export { createHistogramVisTypeDefinition } from './histogram'; -export { createLineVisTypeDefinition } from './line'; -export { createPieVisTypeDefinition } from './pie'; -export { createAreaVisTypeDefinition } from './area'; -export { createHeatmapVisTypeDefinition } from './heatmap'; -export { createHorizontalBarVisTypeDefinition } from './horizontal_bar'; -export { createGaugeVisTypeDefinition } from './gauge'; -export { createGoalVisTypeDefinition } from './goal'; +import { histogramVisTypeDefinition } from './histogram'; +import { lineVisTypeDefinition } from './line'; +import { areaVisTypeDefinition } from './area'; +import { heatmapVisTypeDefinition } from './heatmap'; +import { horizontalBarVisTypeDefinition } from './horizontal_bar'; +import { gaugeVisTypeDefinition } from './gauge'; +import { goalVisTypeDefinition } from './goal'; + +export { pieVisTypeDefinition } from './pie'; + +export const visLibVisTypeDefinitions = [ + histogramVisTypeDefinition, + lineVisTypeDefinition, + areaVisTypeDefinition, + heatmapVisTypeDefinition, + horizontalBarVisTypeDefinition, + gaugeVisTypeDefinition, + goalVisTypeDefinition, +]; diff --git a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx new file mode 100644 index 00000000000000..980ba1c175885e --- /dev/null +++ b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx @@ -0,0 +1,89 @@ +/* + * 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 React, { useEffect, useMemo, useRef } from 'react'; +import { EuiResizeObserver } from '@elastic/eui'; +import { debounce } from 'lodash'; + +import { IInterpreterRenderHandlers } from '../../expressions/public'; +import { ChartsPluginSetup } from '../../charts/public'; + +import { VislibRenderValue } from './vis_type_vislib_vis_fn'; +import { createVislibVisController, VislibVisController } from './vis_controller'; +import { VisTypeVislibCoreSetup } from './plugin'; + +import './index.scss'; + +type VislibWrapperProps = VislibRenderValue & { + core: VisTypeVislibCoreSetup; + charts: ChartsPluginSetup; + handlers: IInterpreterRenderHandlers; +}; + +const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWrapperProps) => { + const chartDiv = useRef(null); + const visController = useRef(null); + + const updateChart = useMemo( + () => + debounce(() => { + if (visController.current) { + visController.current.render(visData, visConfig, handlers); + } + }, 100), + [visConfig, visData, handlers] + ); + + useEffect(() => { + if (chartDiv.current) { + const Controller = createVislibVisController(core, charts); + visController.current = new Controller(chartDiv.current); + } + return () => { + visController.current?.destroy(); + visController.current = null; + }; + }, [core, charts, handlers]); + + useEffect(updateChart, [updateChart]); + + useEffect(() => { + if (handlers.uiState) { + handlers.uiState.on('change', updateChart); + + return () => { + handlers.uiState?.off('change', updateChart); + }; + } + }, [handlers.uiState, updateChart]); + + return ( + + {(resizeRef) => ( +
+
+
+ )} + + ); +}; + +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { VislibWrapper as default }; diff --git a/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss b/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss index c03aa19140de06..843bb9d3f03ebc 100644 --- a/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss +++ b/src/plugins/vis_type_vislib/public/vislib/_vislib_vis_type.scss @@ -1,27 +1,29 @@ -.visLib { +.vislib { flex: 1 1 0; display: flex; flex-direction: row; overflow: auto; - &.visLib--legend-left { + &.vislib--legend-left { flex-direction: row-reverse; } - &.visLib--legend-right { + &.vislib--legend-right { flex-direction: row; } - &.visLib--legend-top { + &.vislib--legend-top { flex-direction: column-reverse; } - &.visLib--legend-bottom { + &.vislib--legend-bottom { flex-direction: column; } } -.visLib__chart { +.vislib__chart, +.vislib__wrapper, +.vislib__container { display: flex; flex: 1 1 auto; min-height: 0; diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss b/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss index b1a59f88a348ad..a06f0cb00787b4 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/_legend.scss @@ -13,6 +13,7 @@ $visLegendLineHeight: $euiSize; left: 0; display: flex; padding: $euiSizeXS; + margin: $euiSizeS; background-color: $euiColorEmptyShade; transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; @@ -33,13 +34,13 @@ $visLegendLineHeight: $euiSize; height: 100%; } -.visLib--legend-left { +.vislib--legend-left { .visLegend__list { margin-bottom: $euiSizeL; } } -.visLib--legend-bottom { +.vislib--legend-bottom { .visLegend__list { margin-left: $euiSizeL; } @@ -64,8 +65,8 @@ $visLegendLineHeight: $euiSize; } } - .visLib--legend-top &, - .visLib--legend-bottom & { + .vislib--legend-top &, + .vislib--legend-bottom & { width: auto; flex-direction: row; flex-wrap: wrap; diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index 65d148cfc5ef47..a3fb536d0aec53 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -41,16 +41,8 @@ jest.mock('../../../services', () => ({ }), })); -const vis = { - params: { - addLegend: true, - }, - API: { - events: { - filter: jest.fn(), - }, - }, -}; +const fireEvent = jest.fn(); + const vislibVis = { handler: { highlight: jest.fn(), @@ -96,14 +88,15 @@ const uiState = { set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)), emit: jest.fn(), setSilent: jest.fn(), -}; +} as any; const getWrapper = async (props?: Partial) => { const wrapper = mount( { }); it('should work with no handlers set', () => { - const newVis = { - ...vis, + const newProps = { vislibVis: { ...vislibVis, handler: null, @@ -197,7 +189,7 @@ describe('VisLegend Component', () => { }; expect(async () => { - wrapper = await getWrapper({ vis: newVis }); + wrapper = await getWrapper(newProps); const first = getLegendItems(wrapper).first(); first.simulate('focus'); first.simulate('blur'); @@ -216,8 +208,11 @@ describe('VisLegend Component', () => { const filterGroup = wrapper.find(EuiButtonGroup).first(); filterGroup.getElement().props.onChange('filterIn'); - expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: false }); - expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + expect(fireEvent).toHaveBeenCalledWith({ + name: 'filterBucket', + data: { data: ['valuesA'], negate: false }, + }); + expect(fireEvent).toHaveBeenCalledTimes(1); }); it('should filter in when clicked', () => { @@ -226,8 +221,11 @@ describe('VisLegend Component', () => { const filterGroup = wrapper.find(EuiButtonGroup).first(); filterGroup.getElement().props.onChange('filterOut'); - expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: true }); - expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + expect(fireEvent).toHaveBeenCalledWith({ + name: 'filterBucket', + data: { data: ['valuesA'], negate: true }, + }); + expect(fireEvent).toHaveBeenCalledTimes(1); }); }); diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index 5a2db2d21c6fe6..cec97f0cadf11e 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -23,16 +23,21 @@ import { compact, uniqBy, map, every, isUndefined } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keys, htmlIdGenerator } from '@elastic/eui'; +import { PersistedState } from '../../../../../visualizations/public'; +import { IInterpreterRenderHandlers } from '../../../../../expressions/public'; + import { getDataActions } from '../../../services'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; import { VisLegendItem } from './legend_item'; import { getPieNames } from './pie_utils'; +import { BasicVislibParams } from '../../../types'; export interface VisLegendProps { - vis: any; vislibVis: any; - visData: any; - uiState: any; + visData: unknown; + uiState?: PersistedState; + fireEvent: IInterpreterRenderHandlers['event']; + addLegend: BasicVislibParams['addLegend']; position: 'top' | 'bottom' | 'left' | 'right'; } @@ -49,7 +54,10 @@ export class VisLegend extends PureComponent { constructor(props: VisLegendProps) { super(props); - const open = props.uiState.get('vis.legendOpen', true); + + // TODO: Check when this bwc can safely be removed + const bwcLegendStateDefault = props.addLegend ?? true; + const open = props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) as boolean; this.state = { open, @@ -64,13 +72,9 @@ export class VisLegend extends PureComponent { } toggleLegend = () => { - const bwcAddLegend = this.props.vis.params.addLegend; - const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; - const newOpen = !this.props.uiState.get('vis.legendOpen', bwcLegendStateDefault); - this.setState({ open: newOpen }); - // open should be applied on template before we update uiState - setTimeout(() => { - this.props.uiState.set('vis.legendOpen', newOpen); + const newOpen = !this.state.open; + this.setState({ open: newOpen }, () => { + this.props.uiState?.set('vis.legendOpen', newOpen); }); }; @@ -79,17 +83,23 @@ export class VisLegend extends PureComponent { return; } - const colors = this.props.uiState.get('vis.colors') || {}; + const colors = this.props.uiState?.get('vis.colors') || {}; if (colors[label] === color) delete colors[label]; else colors[label] = color; - this.props.uiState.setSilent('vis.colors', null); - this.props.uiState.set('vis.colors', colors); - this.props.uiState.emit('colorChanged'); + this.props.uiState?.setSilent('vis.colors', null); + this.props.uiState?.set('vis.colors', colors); + this.props.uiState?.emit('colorChanged'); this.refresh(); }; filter = ({ values: data }: LegendItem, negate: boolean) => { - this.props.vis.API.events.filter({ data, negate }); + this.props.fireEvent({ + name: 'filterBucket', + data: { + data, + negate, + }, + }); }; canFilter = async (item: LegendItem): Promise => { @@ -172,11 +182,8 @@ export class VisLegend extends PureComponent { return; } // make sure vislib is defined at this point - if ( - this.props.uiState.get('vis.legendOpen') == null && - this.props.vis.params.addLegend != null - ) { - this.setState({ open: this.props.vis.params.addLegend }); + if (this.props.uiState?.get('vis.legendOpen') == null && this.props.addLegend != null) { + this.setState({ open: this.props.addLegend }); } if (vislibVis.visConfig) { diff --git a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx index 1bc41f9f61a1a8..ba09f12cfab2bf 100644 --- a/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx +++ b/src/plugins/vis_type_vislib/public/vislib/components/legend/legend_item.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { memo, BaseSyntheticEvent, KeyboardEvent } from 'react'; +import React, { memo, useState, BaseSyntheticEvent, KeyboardEvent } from 'react'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; @@ -30,7 +30,7 @@ import { EuiButtonEmpty, EuiPopoverProps, EuiButtonGroup, - EuiButtonGroupOption, + EuiButtonGroupOptionProps, } from '@elastic/eui'; import { legendColors, LegendItem } from './models'; @@ -62,6 +62,7 @@ const VisLegendItemComponent = ({ setColor, getColor, }: Props) => { + const [idToSelectedMap, setIdToSelectedMap] = useState({}); /** * Keydown listener for a legend entry. * This will close the details panel of this legend entry when pressing Escape. @@ -74,7 +75,7 @@ const VisLegendItemComponent = ({ } }; - const filterOptions: EuiButtonGroupOption[] = [ + const filterOptions: EuiButtonGroupOptionProps[] = [ { id: 'filterIn', label: i18n.translate('visTypeVislib.vislib.legend.filterForValueButtonAriaLabel', { @@ -96,6 +97,7 @@ const VisLegendItemComponent = ({ ]; const handleFilterChange = (id: string) => { + setIdToSelectedMap({ filterIn: id === 'filterIn', filterOut: id === 'filterOut' }); onFilter(item, id !== 'filterIn'); }; @@ -112,6 +114,7 @@ const VisLegendItemComponent = ({ options={filterOptions} onChange={handleFilterChange} data-test-subj={`legend-${item.label}-filters`} + idToSelectedMap={idToSelectedMap} /> diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts index 32536960c59cde..147bae7e3b9851 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/_init_x_axis.ts @@ -33,11 +33,11 @@ export function initXAxis(chart: Chart, table: Table) { chart.xAxisLabel = title; if ('interval' in params) { - const { interval } = params; if ('date' in params) { const { intervalESUnit, intervalESValue } = params; + chart.ordered = { - interval: moment.duration(interval), + interval: moment.duration(intervalESValue, intervalESUnit as any), intervalESUnit, intervalESValue, }; diff --git a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts index 4ba3101d34898a..f40d01e6a81238 100644 --- a/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts +++ b/src/plugins/vis_type_vislib/public/vislib/helpers/point_series/point_series.ts @@ -28,7 +28,7 @@ import { Column, Table } from '../../types'; export interface DateHistogramParams { date: boolean; - interval: string; + interval: number | string; intervalESValue: number; intervalESUnit: string; format: string; @@ -57,6 +57,9 @@ export interface Dimensions { y: Dimension[]; z?: Dimension[]; series?: Dimension | Dimension[]; + width?: Dimension[]; + splitRow?: Dimension[]; + splitColumn?: Dimension[]; } export interface Aspect { accessor: Column['id']; diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/_handler.scss b/src/plugins/vis_type_vislib/public/vislib/lib/_handler.scss deleted file mode 100644 index d1c7fc7c6278cc..00000000000000 --- a/src/plugins/vis_type_vislib/public/vislib/lib/_handler.scss +++ /dev/null @@ -1,17 +0,0 @@ -.visError { - flex: 1 1 0; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - - // From ML - .top { align-self: flex-start; } - .bottom { align-self: flex-end; } -} - -// Prevent large request errors from overflowing the container -.visError--request { - max-width: 100%; - max-height: 100%; -} diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/_index.scss b/src/plugins/vis_type_vislib/public/vislib/lib/_index.scss index b19c2dfb153b95..6751e9f28a8ee4 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/_index.scss +++ b/src/plugins/vis_type_vislib/public/vislib/lib/_index.scss @@ -1,4 +1,3 @@ @import './alerts'; -@import './handler'; @import './layout/index'; diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js b/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js index 4c50472b9d11a8..16283c6bddf260 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/dispatch.js @@ -182,7 +182,6 @@ export class Dispatch { const data = d.input || d; return { - e: d3.event, data: isSlices ? this._pieClickResponse(data) : this._seriesClickResponse(data), }; } @@ -423,7 +422,6 @@ export class Dispatch { */ createBrush(xScale, svg) { const self = this; - const visConfig = self.handler.visConfig; const { width, height } = svg.node().getBBox(); const isHorizontal = self.handler.categoryAxes[0].axisConfig.isHorizontal(); @@ -449,8 +447,6 @@ export class Dispatch { return self.emit('brush', { range, - config: visConfig, - e: d3.event, data, }); }); diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/handler.js b/src/plugins/vis_type_vislib/public/vislib/lib/handler.js index 3c1aeaa0d1d0d8..938ea3adcb9b56 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/handler.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/handler.js @@ -46,10 +46,10 @@ const markdownIt = new MarkdownIt({ * create the visualization */ export class Handler { - constructor(vis, visConfig, deps) { + constructor(vis, visConfig, uiSettings) { this.el = visConfig.get('el'); this.ChartClass = chartTypes[visConfig.get('type')]; - this.deps = deps; + this.uiSettings = uiSettings; this.charts = []; this.vis = vis; @@ -91,12 +91,18 @@ export class Handler { const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw'); if (!xRaw) return; // not sure if this is possible? return self.vis.emit(eventType, { - table: xRaw.table, - range: eventPayload.range, - column: xRaw.column, + name: 'brush', + data: { + table: xRaw.table, + range: eventPayload.range, + column: xRaw.column, + }, }); case 'click': - return self.vis.emit(eventType, eventPayload); + return self.vis.emit(eventType, { + name: 'filterBucket', + data: eventPayload, + }); } }; }); @@ -164,7 +170,7 @@ export class Handler { let loadedCount = 0; const chartSelection = selection.selectAll('.chart'); chartSelection.each(function (chartData) { - const chart = new self.ChartClass(self, this, chartData, self.deps); + const chart = new self.ChartClass(self, this, chartData, self.uiSettings); self.vis.eventNames().forEach(function (event) { self.enable(event, chart); @@ -222,7 +228,7 @@ export class Handler { // class name needs `chart` in it for the polling checkSize function // to continuously call render on resize .attr('class', 'visError chart error') - .attr('data-test-subj', 'visLibVisualizeError'); + .attr('data-test-subj', 'vislibVisualizeError'); div.append('h4').text(markdownIt.renderInline(message)); diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/handler.test.js b/src/plugins/vis_type_vislib/public/vislib/lib/handler.test.js index d50c70de1bb483..119a24d2f25d13 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/handler.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/handler.test.js @@ -157,7 +157,7 @@ dateHistogramArray.forEach(function (data, i) { const args = Array.from(arguments); expect(args.length).toBe(2); expect(args[0]).toBe('click'); - expect(args[1]).toBe(event); + expect(args[1].data).toBe(event); done(); }; diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss b/src/plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss index 96c72bd5956d27..b3fdf654f8ab71 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss +++ b/src/plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss @@ -32,7 +32,7 @@ min-height: 0; min-width: 0; overflow: hidden; - padding: ($euiSizeS + 2px) 0; + padding: $euiSizeS 0; } .visWrapper__column { @@ -142,7 +142,6 @@ min-height: 0; } - // // STYLE // @@ -153,7 +152,6 @@ // could it be easily found to apply to all chart types. // At least wrapping selectors inside .visWrapper will narrow scope. - // sass-lint:disable-block no-mergeable-selectors // Keep SVG and non-renamable selectors separately .visWrapper { @@ -221,7 +219,7 @@ .axis-title text { @include fontSize($euiFontSizeXS); font-weight: $euiFontWeightBold; - fill: $visTextColor; + fill: $euiTextColor; } .y-axis-title { diff --git a/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js b/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js index dda9d85ec43c5b..2086f744be584c 100644 --- a/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js +++ b/src/plugins/vis_type_vislib/public/vislib/lib/vis_config.js @@ -50,7 +50,6 @@ export class VisConfig { return _.get(this._values, property, defaults); } else { throw new Error(`Accessing invalid config property: ${property}`); - return defaults; } } diff --git a/src/plugins/vis_type_vislib/public/vislib/vis.js b/src/plugins/vis_type_vislib/public/vislib/vis.js index f258cb55ba2819..628b876fc50c55 100644 --- a/src/plugins/vis_type_vislib/public/vislib/vis.js +++ b/src/plugins/vis_type_vislib/public/vislib/vis.js @@ -35,13 +35,14 @@ import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../../comm * @param config {Object} Parameters that define the chart type and chart options */ export class Vis extends EventEmitter { - constructor(element, visConfigArgs, deps) { + constructor(element, visConfigArgs, core, charts) { super(); this.element = element.get ? element.get(0) : element; this.visConfigArgs = _.cloneDeep(visConfigArgs); - this.visConfigArgs.dimmingOpacity = deps.uiSettings.get(DIMMING_OPACITY_SETTING); - this.visConfigArgs.heatmapMaxBuckets = deps.uiSettings.get(HEATMAP_MAX_BUCKETS_SETTING); - this.deps = deps; + this.visConfigArgs.dimmingOpacity = core.uiSettings.get(DIMMING_OPACITY_SETTING); + this.visConfigArgs.heatmapMaxBuckets = core.uiSettings.get(HEATMAP_MAX_BUCKETS_SETTING); + this.charts = charts; + this.uiSettings = core.uiSettings; } hasLegend() { @@ -56,7 +57,7 @@ export class Vis extends EventEmitter { this.data, this.uiState, this.element, - this.deps.charts.colors.createColorLookupFunction.bind(this.deps.charts.colors) + this.charts.colors.createColorLookupFunction.bind(this.charts.colors) ); } @@ -78,7 +79,7 @@ export class Vis extends EventEmitter { this.initVisConfig(data, uiState); - this.handler = new Handler(this, this.visConfig, this.deps); + this.handler = new Handler(this, this.visConfig, this.uiSettings); this._runOnHandler('render'); } diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/_chart.js index 5ed6d3eb79f4b0..e5cb0235b65103 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/_chart.js @@ -39,13 +39,13 @@ import { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class Chart { - constructor(handler, element, chartData, deps) { + constructor(handler, element, chartData, uiSettings) { this.handler = handler; this.chartEl = element; this.chartData = chartData; this.tooltips = []; - const events = (this.events = new Dispatch(handler, deps.uiSettings)); + const events = (this.events = new Dispatch(handler, uiSettings)); const fieldFormatter = getFormatService().deserialize( this.handler.data.get('tooltipFormatter') diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/_vis_fixture.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/_vis_fixture.js index 0ffa53fc7ca9ce..11b5964eebdf34 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/_vis_fixture.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/_vis_fixture.js @@ -53,20 +53,10 @@ afterEach(function () { count = 0; }); -const getDeps = () => { - const mockUiSettings = coreMock.createSetup().uiSettings; - const charts = chartPluginMock.createStartContract(); - - return { - uiSettings: mockUiSettings, - charts: charts, - }; -}; - -export function getVis(visLibParams, element) { +export function getVis(vislibParams, element) { return new Vis( element || $visCanvas.new(), - _.defaults({}, visLibParams || {}, { + _.defaults({}, vislibParams || {}, { addTooltip: true, addLegend: true, defaultYExtents: false, @@ -74,6 +64,7 @@ export function getVis(visLibParams, element) { yAxis: {}, type: 'histogram', }), - getDeps() + coreMock.createSetup(), + chartPluginMock.createStartContract() ); } diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js index 6fdc2a134b820e..913b237a10e58f 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.test.js @@ -28,7 +28,7 @@ import { getVis } from './_vis_fixture'; describe('Vislib Gauge Chart Test Suite', function () { let vis; let chartEl; - const visLibParams = { + const vislibParams = { type: 'gauge', addTooltip: true, addLegend: false, @@ -71,7 +71,7 @@ describe('Vislib Gauge Chart Test Suite', function () { }; function generateVis(opts = {}) { - const config = _.defaultsDeep({}, opts, visLibParams); + const config = _.defaultsDeep({}, opts, vislibParams); if (vis) { vis.destroy(); $('.visChart').remove(); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js index 938d3d0ec6d743..b1acea34aabf6a 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PieChart extends Chart { - constructor(handler, chartEl, chartData, deps) { - super(handler, chartEl, chartData, deps); + constructor(handler, chartEl, chartData, uiSettings) { + super(handler, chartEl, chartData, uiSettings); const charts = this.handler.data.getVisData(); this._validatePieData(charts); this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.test.js index e2da33d0808ba6..1207db2e54bb8c 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.test.js @@ -40,7 +40,7 @@ let mockedSVGElementGetBBox; let mockedSVGElementGetComputedTextLength; describe('No global chart settings', function () { - const visLibParams1 = { + const vislibParams1 = { el: '
', type: 'pie', addLegend: true, @@ -58,7 +58,7 @@ describe('No global chart settings', function () { }); beforeEach(() => { - chart1 = getVis(visLibParams1); + chart1 = getVis(vislibParams1); mockUiState = getMockUiState(); }); @@ -153,7 +153,7 @@ describe('Vislib PieChart Class Test Suite', function () { describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () { const mockPieData = pieChartMockData[aggItem]; - const visLibParams = { + const vislibParams = { type: 'pie', addLegend: true, addTooltip: true, @@ -161,7 +161,7 @@ describe('Vislib PieChart Class Test Suite', function () { let vis; beforeEach(async () => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); const mockUiState = getMockUiState(); vis.render(mockPieData, mockUiState); }); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index 9a25d041f65678..a40d46737f05e5 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -40,10 +40,10 @@ const touchdownTmpl = _.template(touchdownTmplHtml); * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PointSeries extends Chart { - constructor(handler, chartEl, chartData, deps) { - super(handler, chartEl, chartData, deps); + constructor(handler, chartEl, chartData, uiSettings) { + super(handler, chartEl, chartData, uiSettings); - this.deps = deps; + this.uiSettings = uiSettings; this.handler = handler; this.chartData = chartData; this.chartEl = chartEl; @@ -246,7 +246,13 @@ export class PointSeries extends Chart { if (!seriArgs.show) return; const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line; - const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, self.deps); + const series = new SeriClass( + self.handler, + svg, + data.series[i], + seriArgs, + self.uiSettings + ); series.events = self.events; svg.call(series.draw()); self.series.push(series); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js index e3e2d31ecd4f4a..b65c5be330fef9 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js @@ -43,8 +43,8 @@ const defaults = { * chart */ export class AreaChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { - super(handler, chartEl, chartData, seriesConfigArgs, deps); + constructor(handler, chartEl, chartData, seriesConfigArgs, core) { + super(handler, chartEl, chartData, seriesConfigArgs, core); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.isOverlapping = this.seriesConfig.mode !== 'stacked'; diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.test.js index 3cd58060978ee7..ae15d95d560ca2 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.test.js @@ -38,7 +38,7 @@ const dataTypesArray = { stackedSeries: import('../../../fixtures/mock_data/date_histogram/_stacked_series'), }; -const visLibParams = { +const vislibParams = { type: 'area', addLegend: true, addTooltip: true, @@ -61,7 +61,7 @@ _.forOwn(dataTypesArray, function (dataType, dataTypeName) { }); beforeEach(async () => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.on('brush', _.noop); vis.render(await dataType, mockUiState); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js index 1369bf1dff68af..07bebf4eb2f83a 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js @@ -57,8 +57,8 @@ function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCou * @param chartData {Object} Elasticsearch query results for this specific chart */ export class ColumnChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { - super(handler, chartEl, chartData, seriesConfigArgs, deps); + constructor(handler, chartEl, chartData, seriesConfigArgs, core) { + super(handler, chartEl, chartData, seriesConfigArgs, core); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.labelOptions = _.defaults(handler.visConfig.get('labels', {}), defaults.showLabel); } diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.test.js index f3d8d66df2d853..d7fc177a300095 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.test.js @@ -62,7 +62,7 @@ dataTypesArray.forEach(function (dataType) { describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () { let vis; let mockUiState; - const visLibParams = { + const vislibParams = { type: 'histogram', addLegend: true, addTooltip: true, @@ -81,7 +81,7 @@ dataTypesArray.forEach(function (dataType) { }); beforeEach(() => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.on('brush', _.noop); vis.render(data, mockUiState); @@ -261,7 +261,7 @@ dataTypesArray.forEach(function (dataType) { describe('stackData method - data set with zeros in percentage mode', function () { let vis; let mockUiState; - const visLibParams = { + const vislibParams = { type: 'histogram', addLegend: true, addTooltip: true, @@ -276,7 +276,7 @@ describe('stackData method - data set with zeros in percentage mode', function ( }); beforeEach(() => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.on('brush', _.noop); }); @@ -320,7 +320,7 @@ describe('stackData method - data set with zeros in percentage mode', function ( describe('datumWidth - split chart data set with holes', function () { let vis; let mockUiState; - const visLibParams = { + const vislibParams = { type: 'histogram', addLegend: true, addTooltip: true, @@ -335,7 +335,7 @@ describe('datumWidth - split chart data set with holes', function () { }); beforeEach(() => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.on('brush', _.noop); vis.render(rowsSeriesWithHoles, mockUiState); @@ -366,7 +366,7 @@ describe('datumWidth - split chart data set with holes', function () { describe('datumWidth - monthly interval', function () { let vis; let mockUiState; - const visLibParams = { + const vislibParams = { type: 'histogram', addLegend: true, addTooltip: true, @@ -384,7 +384,7 @@ describe('datumWidth - monthly interval', function () { }); beforeEach(() => { - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.on('brush', _.noop); vis.render(seriesMonthlyInterval, mockUiState); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js index 8c727d225c6c3c..a7534add76e366 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js @@ -71,7 +71,7 @@ describe('Vislib Heatmap Chart Test Suite', function () { describe('for ' + name + ' Data', function () { let vis; let mockUiState; - const visLibParams = { + const vislibParams = { type: 'heatmap', addLegend: true, addTooltip: true, @@ -84,7 +84,7 @@ describe('Vislib Heatmap Chart Test Suite', function () { }; function generateVis(opts = {}) { - const config = _.defaultsDeep({}, opts, visLibParams); + const config = _.defaultsDeep({}, opts, vislibParams); vis = getVis(config); mockUiState = getMockUiState(); vis.on('brush', _.noop); diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js index 64fbae7d1ac8ce..983c15c004b978 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class LineChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { - super(handler, chartEl, chartData, seriesConfigArgs, deps); + constructor(handler, chartEl, chartData, seriesConfigArgs, core) { + super(handler, chartEl, chartData, seriesConfigArgs, core); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); } diff --git a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.test.js b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.test.js index a84c74c0950519..8d86df7c27da4f 100644 --- a/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.test.js +++ b/src/plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.test.js @@ -71,14 +71,14 @@ describe('Vislib Line Chart', function () { let mockUiState; beforeEach(() => { - const visLibParams = { + const vislibParams = { type: 'line', addLegend: true, addTooltip: true, drawLinesBetweenPoints: true, }; - vis = getVis(visLibParams); + vis = getVis(vislibParams); mockUiState = getMockUiState(); vis.render(data, mockUiState); vis.on('brush', _.noop); diff --git a/src/plugins/visualizations/public/components/_visualization.scss b/src/plugins/visualizations/public/components/_visualization.scss index f5e2d4fcf2862d..bde9621fd70b8e 100644 --- a/src/plugins/visualizations/public/components/_visualization.scss +++ b/src/plugins/visualizations/public/components/_visualization.scss @@ -70,10 +70,10 @@ flex-direction: column; } -.visChart__spinner { +.visChart__spinner, .visError { display: flex; flex: 1 1 auto; justify-content: center; align-items: center; + text-align: center; } - diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index a810b4b65528f7..c12a0f0759018d 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -374,6 +374,7 @@ export class VisualizeEmbeddable query: this.input.query, filters: this.input.filters, }, + searchSessionId: this.input.searchSessionId, uiState: this.vis.uiState, inspectorAdapters: this.inspectorAdapters, }; diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 081399fd1fbea2..7bd4466b23166b 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -52,6 +52,7 @@ export { ISavedVis, VisSavedObject, VisResponseValue, + VisToExpressionAst, } from './types'; export { ExprVisAPIEvents } from './expressions/vis'; export { VisualizationListItem } from './vis_types/vis_type_alias_registry'; diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index cbdecd4aac747a..2c6cfc6fb74621 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -6,12 +6,8 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' uiState='{}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles pie function 1`] = `"kibana_pie visConfig='{\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"buckets\\":[1,2]}}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`; - -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles vega function 1`] = `"vega spec='this is a test' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index c744043ed155b0..0c210a04d20075 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -21,14 +21,12 @@ import { prepareJson, prepareString, buildPipelineVisFunction, - buildVislibDimensions, buildPipeline, SchemaConfig, Schemas, } from './build_pipeline'; import { Vis } from '..'; import { dataPluginMock } from '../../../../plugins/data/public/mocks'; -import { IndexPattern, IAggConfigs } from '../../../../plugins/data/public'; import { parseExpression } from '../../../expressions/common'; describe('visualize loader pipeline helpers: build pipeline', () => { @@ -94,14 +92,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { uiState = {}; }); - it('handles vega function', () => { - const vis = { - params: { spec: 'this is a test' }, - }; - const actual = buildPipelineVisFunction.vega(vis.params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - it('handles input_control_vis function', () => { const params = { some: 'nested', @@ -144,15 +134,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { const actual = buildPipelineVisFunction.tile_map(params, schemas, uiState); expect(actual).toMatchSnapshot(); }); - - it('handles pie function', () => { - const schemas = { - ...schemasDef, - segment: [1, 2], - }; - const actual = buildPipelineVisFunction.pie({}, schemas, uiState); - expect(actual).toMatchSnapshot(); - }); }); describe('buildPipeline', () => { @@ -182,157 +163,4 @@ describe('visualize loader pipeline helpers: build pipeline', () => { expect(expression).toMatchSnapshot(); }); }); - - describe('buildVislibDimensions', () => { - const dataStart = dataPluginMock.createStartContract(); - - let aggs: IAggConfigs; - let vis: Vis; - let params: any; - - beforeEach(() => { - aggs = dataStart.search.aggs.createAggConfigs({} as IndexPattern, [ - { - id: '0', - enabled: true, - type: 'count', - schema: 'metric', - params: {}, - }, - ]); - - params = { - searchSource: null, - timefilter: dataStart.query.timefilter.timefilter, - timeRange: null, - }; - }); - - describe('test y dimension format for histogram chart', () => { - beforeEach(() => { - vis = { - // @ts-ignore - type: { - name: 'histogram', - }, - params: { - seriesParams: [ - { - data: { id: '0' }, - valueAxis: 'axis-y', - }, - ], - valueAxes: [ - { - id: 'axis-y', - scale: { - mode: 'normal', - }, - }, - ], - }, - data: { - aggs, - searchSource: {} as any, - }, - isHierarchical: () => { - return false; - }, - }; - }); - - it('with one numeric metric in regular moder', async () => { - const dimensions = await buildVislibDimensions(vis, params); - const expected = { id: 'number' }; - const actual = dimensions.y[0].format; - expect(actual).toEqual(expected); - }); - - it('with one numeric metric in percentage mode', async () => { - vis.params.valueAxes[0].scale.mode = 'percentage'; - const dimensions = await buildVislibDimensions(vis, params); - const expected = { id: 'percent' }; - const actual = dimensions.y[0].format; - expect(actual).toEqual(expected); - }); - - it('with two numeric metrics, mixed normal and percent mode should have corresponding formatters', async () => { - aggs.createAggConfig({ - id: '5', - enabled: true, - type: 'count', - schema: 'metric', - params: {}, - }); - - vis.params = { - seriesParams: [ - { - data: { id: '0' }, - valueAxis: 'axis-y-1', - }, - { - data: { id: '5' }, - valueAxis: 'axis-y-2', - }, - ], - valueAxes: [ - { - id: 'axis-y-1', - scale: { - mode: 'normal', - }, - }, - { - id: 'axis-y-2', - scale: { - mode: 'percentage', - }, - }, - ], - }; - - const dimensions = await buildVislibDimensions(vis, params); - const expectedY1 = { id: 'number' }; - const expectedY2 = { id: 'percent' }; - expect(dimensions.y[0].format).toEqual(expectedY1); - expect(dimensions.y[1].format).toEqual(expectedY2); - }); - }); - - describe('test y dimension format for gauge chart', () => { - beforeEach(() => { - vis = { - // @ts-ignore - type: { - name: 'gauge', - }, - params: { gauge: {} }, - data: { - aggs, - searchSource: {} as any, - }, - isHierarchical: () => { - return false; - }, - }; - }); - - it('with percentageMode = false', async () => { - vis.params.gauge.percentageMode = false; - const dimensions = await buildVislibDimensions(vis, params); - const expected = { id: 'number' }; - const actual = dimensions.y[0].format; - expect(actual).toEqual(expected); - }); - - it('with percentageMode = true', async () => { - vis.params.gauge.percentageMode = true; - const dimensions = await buildVislibDimensions(vis, params); - const expected = { id: 'percent' }; - const actual = dimensions.y[0].format; - expect(actual).toEqual(expected); - }); - }); - }); }); diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index eb431212166a3a..3593d62b9d2e67 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -17,8 +17,6 @@ * under the License. */ -import { get } from 'lodash'; -import moment from 'moment'; import { formatExpression, SerializedFieldFormat } from '../../../../plugins/expressions/public'; import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public'; import { Vis, VisParams } from '../types'; @@ -76,16 +74,6 @@ export interface BuildPipelineParams { abortSignal?: AbortSignal; } -const vislibCharts: string[] = [ - 'area', - 'gauge', - 'goal', - 'heatmap', - 'histogram', - 'horizontal_bar', - 'line', -]; - export const getSchemas = ( vis: Vis, { timeRange, timefilter }: BuildPipelineParams @@ -230,33 +218,7 @@ export const prepareDimension = (variable: string, data: any) => { return expr; }; -const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => { - const visConfig = vis.params; - const responseAggs = vis.data.aggs!.getResponseAggs().filter((agg: IAggConfig) => agg.enabled); - - (dimensions.y || []).forEach((yDimension) => { - const yAgg = responseAggs[yDimension.accessor]; - const seriesParam = (visConfig.seriesParams || []).find( - (param: any) => param.data.id === yAgg.id - ); - if (seriesParam) { - const usedValueAxis = (visConfig.valueAxes || []).find( - (valueAxis: any) => valueAxis.id === seriesParam.valueAxis - ); - if (get(usedValueAxis, 'scale.mode') === 'percentage') { - yDimension.format = { id: 'percent' }; - } - } - if (get(visConfig, 'gauge.percentageMode') === true) { - yDimension.format = { id: 'percent' }; - } - }); -}; - export const buildPipelineVisFunction: BuildPipelineVisFunction = { - vega: (params) => { - return `vega ${prepareString('spec', params.spec)}`; - }, input_control_vis: (params) => { return `input_control_vis ${prepareJson('visConfig', params)}`; }, @@ -281,13 +243,6 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { }; return `tilemap ${prepareJson('visConfig', visConfig)}`; }, - pie: (params, schemas) => { - const visConfig = { - ...params, - ...buildVisConfig.pie(schemas), - }; - return `kibana_pie ${prepareJson('visConfig', visConfig)}`; - }, }; const buildVisConfig: BuildVisConfigFunction = { @@ -308,55 +263,6 @@ const buildVisConfig: BuildVisConfigFunction = { }; return visConfig; }, - pie: (schemas) => { - const visConfig = {} as any; - visConfig.dimensions = { - metric: schemas.metric[0], - buckets: schemas.segment, - splitRow: schemas.split_row, - splitColumn: schemas.split_column, - }; - return visConfig; - }, -}; - -export const buildVislibDimensions = async (vis: any, params: BuildPipelineParams) => { - const schemas = getSchemas(vis, { - timeRange: params.timeRange, - timefilter: params.timefilter, - }); - const dimensions = { - x: schemas.segment ? schemas.segment[0] : null, - y: schemas.metric, - z: schemas.radius, - width: schemas.width, - series: schemas.group, - splitRow: schemas.split_row, - splitColumn: schemas.split_column, - }; - if (schemas.segment) { - const xAgg = vis.data.aggs.getResponseAggs()[dimensions.x.accessor]; - if (xAgg.type.name === 'date_histogram') { - dimensions.x.params.date = true; - const { esUnit, esValue } = xAgg.buckets.getInterval(); - dimensions.x.params.interval = moment.duration(esValue, esUnit); - dimensions.x.params.intervalESValue = esValue; - dimensions.x.params.intervalESUnit = esUnit; - dimensions.x.params.format = xAgg.buckets.getScaledDateFormat(); - dimensions.x.params.bounds = xAgg.buckets.getBounds(); - } else if (xAgg.type.name === 'histogram') { - const intervalParam = xAgg.type.paramByName('interval'); - const output = { params: {} as any }; - await intervalParam.modifyAggConfigOnSearchRequestStart(xAgg, vis.data.searchSource, { - abortSignal: params.abortSignal, - }); - intervalParam.write(xAgg, output); - dimensions.x.params.interval = output.params.interval; - } - } - - adjustVislibDimensionFormmaters(vis, dimensions); - return dimensions; }; export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => { @@ -399,11 +305,6 @@ export const buildPipeline = async (vis: Vis, params: BuildPipelineParams) => { schemas, uiState ); - } else if (vislibCharts.includes(vis.type.name)) { - const visConfig = { ...vis.params }; - visConfig.dimensions = await buildVislibDimensions(vis, params); - - pipeline += `vislib type='${vis.type.name}' ${prepareJson('visConfig', visConfig)}`; } else { const visConfig = { ...vis.params }; visConfig.dimensions = schemas; diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index 68ab3561d375c1..3c322f5e79165b 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -76,4 +76,4 @@ export interface VisToExpressionAstParams { export type VisToExpressionAst = ( vis: Vis, params: VisToExpressionAstParams -) => ExpressionAstExpression; +) => Promise | ExpressionAstExpression; diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index f6d27b54c7c640..9733e9fd685499 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -21,6 +21,7 @@ import { TriggerContextMapping } from '../../../ui_actions/public'; export interface VisualizationListItem { editUrl: string; editApp?: string; + error?: string; icon: string; id: string; stage: 'experimental' | 'beta' | 'production'; diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 02c0b36d126891..2089289b372a2a 100644 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -1032,6 +1032,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` size="s" > { .findListItems(filter, listingLimit) .then(({ total, hits }: { total: number; hits: object[] }) => ({ total, - hits: hits.filter((result: any) => isLabsEnabled || result.type.stage !== 'experimental'), + hits: hits.filter( + (result: any) => isLabsEnabled || result.type?.stage !== 'experimental' + ), })); }, [listingLimit, savedVisualizations, uiSettings] diff --git a/src/plugins/visualize/public/application/utils/get_table_columns.tsx b/src/plugins/visualize/public/application/utils/get_table_columns.tsx index 805c039d9773b4..3541c0dc31db26 100644 --- a/src/plugins/visualize/public/application/utils/get_table_columns.tsx +++ b/src/plugins/visualize/public/application/utils/get_table_columns.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { History } from 'history'; -import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink } from '@elastic/eui'; +import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -87,20 +87,24 @@ export const getTableColumns = (application: ApplicationStart, history: History) defaultMessage: 'Title', }), sortable: true, - render: (field: string, { editApp, editUrl, title }: VisualizationListItem) => ( - { - if (editApp) { - application.navigateToApp(editApp, { path: editUrl }); - } else if (editUrl) { - history.push(editUrl); - } - }} - data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} - > - {field} - - ), + render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) => + // In case an error occurs i.e. the vis has wrong type, we render the vis but without the link + !error ? ( + { + if (editApp) { + application.navigateToApp(editApp, { path: editUrl }); + } else if (editUrl) { + history.push(editUrl); + } + }} + data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} + > + {field} + + ) : ( + field + ), }, { field: 'typeTitle', @@ -108,13 +112,18 @@ export const getTableColumns = (application: ApplicationStart, history: History) defaultMessage: 'Type', }), sortable: true, - render: (field: string, record: VisualizationListItem) => ( - - {renderItemTypeIcon(record)} - {record.typeTitle} - {getBadge(record)} - - ), + render: (field: string, record: VisualizationListItem) => + !record.error ? ( + + {renderItemTypeIcon(record)} + {record.typeTitle} + {getBadge(record)} + + ) : ( + + {record.error} + + ), }, { field: 'description', diff --git a/test/api_integration/apis/saved_objects/export.js b/test/api_integration/apis/saved_objects/export.ts similarity index 88% rename from test/api_integration/apis/saved_objects/export.js rename to test/api_integration/apis/saved_objects/export.ts index 0c37e6b782a355..7254f3b3fcf316 100644 --- a/test/api_integration/apis/saved_objects/export.js +++ b/test/api_integration/apis/saved_objects/export.ts @@ -18,8 +18,12 @@ */ import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +function ndjsonToObject(input: string) { + return input.split('\n').map((str) => JSON.parse(str)); +} +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); @@ -38,7 +42,7 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - const objects = resp.text.split('\n').map(JSON.parse); + const objects = ndjsonToObject(resp.text); expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); @@ -61,7 +65,7 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - const objects = resp.text.split('\n').map(JSON.parse); + const objects = ndjsonToObject(resp.text); expect(objects).to.have.length(3); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); @@ -86,7 +90,7 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - const objects = resp.text.split('\n').map(JSON.parse); + const objects = ndjsonToObject(resp.text); expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); @@ -109,7 +113,7 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - const objects = resp.text.split('\n').map(JSON.parse); + const objects = resp.text.split('\n').map((str) => JSON.parse(str)); expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); @@ -133,7 +137,7 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - const objects = resp.text.split('\n').map(JSON.parse); + const objects = ndjsonToObject(resp.text); expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); @@ -217,6 +221,51 @@ export default function ({ getService }) { }); }); }); + + it('should export object with circular refs', async () => { + const soWithCycliRefs = [ + { + type: 'dashboard', + id: 'dashboard-a', + attributes: { + title: 'dashboard-a', + }, + references: [ + { + name: 'circular-dashboard-ref', + id: 'dashboard-b', + type: 'dashboard', + }, + ], + }, + { + type: 'dashboard', + id: 'dashboard-b', + attributes: { + title: 'dashboard-b', + }, + references: [ + { + name: 'circular-dashboard-ref', + id: 'dashboard-a', + type: 'dashboard', + }, + ], + }, + ]; + await supertest.post('/api/saved_objects/_bulk_create').send(soWithCycliRefs).expect(200); + const resp = await supertest + .post('/api/saved_objects/_export') + .send({ + includeReferencesDeep: true, + type: ['dashboard'], + }) + .expect(200); + + const objects = ndjsonToObject(resp.text); + expect(objects.find((o) => o.id === 'dashboard-a')).to.be.ok(); + expect(objects.find((o) => o.id === 'dashboard-b')).to.be.ok(); + }); }); describe('10,000 objects', () => { @@ -245,11 +294,11 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - expect(resp.headers['content-disposition']).to.eql( + expect(resp.header['content-disposition']).to.eql( 'attachment; filename="export.ndjson"' ); - expect(resp.headers['content-type']).to.eql('application/ndjson'); - const objects = resp.text.split('\n').map(JSON.parse); + expect(resp.header['content-type']).to.eql('application/ndjson'); + const objects = ndjsonToObject(resp.text); expect(objects).to.eql([ { attributes: { @@ -304,11 +353,11 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - expect(resp.headers['content-disposition']).to.eql( + expect(resp.header['content-disposition']).to.eql( 'attachment; filename="export.ndjson"' ); - expect(resp.headers['content-type']).to.eql('application/ndjson'); - const objects = resp.text.split('\n').map(JSON.parse); + expect(resp.header['content-type']).to.eql('application/ndjson'); + const objects = ndjsonToObject(resp.text); expect(objects).to.eql([ { attributes: { @@ -368,11 +417,11 @@ export default function ({ getService }) { }) .expect(200) .then((resp) => { - expect(resp.headers['content-disposition']).to.eql( + expect(resp.header['content-disposition']).to.eql( 'attachment; filename="export.ndjson"' ); - expect(resp.headers['content-type']).to.eql('application/ndjson'); - const objects = resp.text.split('\n').map(JSON.parse); + expect(resp.header['content-type']).to.eql('application/ndjson'); + const objects = ndjsonToObject(resp.text); expect(objects).to.eql([ { attributes: { @@ -443,7 +492,7 @@ export default function ({ getService }) { }); describe('10,001 objects', () => { - let customVisId; + let customVisId: string; before(async () => { await esArchiver.load('saved_objects/10k'); await supertest diff --git a/test/api_integration/apis/saved_objects/import.js b/test/api_integration/apis/saved_objects/import.ts similarity index 78% rename from test/api_integration/apis/saved_objects/import.js rename to test/api_integration/apis/saved_objects/import.ts index 1666df2c83e5a3..bdb695ef20dd1a 100644 --- a/test/api_integration/apis/saved_objects/import.js +++ b/test/api_integration/apis/saved_objects/import.ts @@ -19,8 +19,19 @@ import expect from '@kbn/expect'; import { join } from 'path'; +import dedent from 'dedent'; +import type { SavedObjectsImportError } from 'src/core/server'; +import type { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService }) { +const createConflictError = ( + object: Omit +): SavedObjectsImportError => ({ + ...object, + title: object.meta.title, + error: { type: 'conflict' }, +}); + +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -41,11 +52,6 @@ export default function ({ getService }) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', meta: { title: 'Requests', icon: 'dashboardApp' }, }; - const createError = (object, type) => ({ - ...object, - title: object.meta.title, - error: { type }, - }); describe('with kibana index', () => { describe('with basic data existing', () => { @@ -75,9 +81,9 @@ export default function ({ getService }) { success: false, successCount: 0, errors: [ - createError(indexPattern, 'conflict'), - createError(visualization, 'conflict'), - createError(dashboard, 'conflict'), + createConflictError(indexPattern), + createConflictError(visualization), + createConflictError(dashboard), ], }); }); @@ -128,6 +134,43 @@ export default function ({ getService }) { }); }); + it('should return 200 when importing SO with circular refs', async () => { + const fileBuffer = Buffer.from( + dedent` + {"attributes":{"title":"dashboard-b"},"id":"dashboard-b","references":[{"id":"dashboard-a","name":"circular-dashboard-ref","type":"dashboard"}],"type":"dashboard"} + {"attributes":{"title":"dashboard-a"},"id":"dashboard-a","references":[{"id":"dashboard-b","name":"circular-dashboard-ref","type":"dashboard"}],"type":"dashboard"} + `, + 'utf8' + ); + const resp = await supertest + .post('/api/saved_objects/_import') + .attach('file', fileBuffer, 'export.ndjson') + .expect(200); + + expect(resp.body).to.eql({ + success: true, + successCount: 2, + successResults: [ + { + id: 'dashboard-b', + meta: { + icon: 'dashboardApp', + title: 'dashboard-b', + }, + type: 'dashboard', + }, + { + id: 'dashboard-a', + meta: { + icon: 'dashboardApp', + title: 'dashboard-a', + }, + type: 'dashboard', + }, + ], + }); + }); + it('should return 400 when trying to import more than 10,000 objects', async () => { const fileChunks = []; for (let i = 0; i < 10001; i++) { diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 7fc120f9ea4745..d3c0fe834958db 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -88,7 +88,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await footer.getVisibleText()).to.have.string(rowsHardLimit); }); - describe('expand a document row', function () { + // FLAKY: https://github.com/elastic/kibana/issues/81632 + describe.skip('expand a document row', function () { const rowToInspect = 1; beforeEach(async function () { // close the toggle if open diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js index e727949da5ad30..38ed3edc7deb55 100644 --- a/test/functional/apps/getting_started/_shakespeare.js +++ b/test/functional/apps/getting_started/_shakespeare.js @@ -35,7 +35,8 @@ export default function ({ getService, getPageObjects }) { // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html - describe('Shakespeare', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/82206 + describe.skip('Shakespeare', function describeIndexTests() { // index starts on the first "count" metric at 1 // Each new metric or aggregation added to a visualization gets the next index. // So to modify a metric or aggregation tests need to keep track of the diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.ts similarity index 89% rename from test/functional/apps/management/_import_objects.js rename to test/functional/apps/management/_import_objects.ts index 3941b117e6efe8..ddabb32bf5909c 100644 --- a/test/functional/apps/management/_import_objects.js +++ b/test/functional/apps/management/_import_objects.ts @@ -20,10 +20,14 @@ import expect from '@kbn/expect'; import path from 'path'; import { keyBy } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; -const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +function uniq(input: T[]): T[] { + return [...new Set(input)]; +} -export default function ({ getService, getPageObjects }) { +export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']); @@ -67,6 +71,24 @@ export default function ({ getService, getPageObjects }) { expect(flyout['Log Agents'].relationship).to.eql('Parent'); }); + it('should import saved objects with circular refs', async function () { + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '_import_objects_circular_refs.ndjson') + ); + await PageObjects.savedObjects.checkImportSucceeded(); + await PageObjects.savedObjects.clickImportDone(); + + await PageObjects.savedObjects.clickRelationshipsByTitle('dashboard-a'); + + const flyoutContent = await PageObjects.savedObjects.getRelationshipFlyout(); + + expect(uniq(flyoutContent.map(({ relationship }) => relationship).sort())).to.eql([ + 'Child', + 'Parent', + ]); + expect(uniq(flyoutContent.map(({ title }) => title))).to.eql(['dashboard-b']); + }); + it('should provide dialog to allow the importing of saved objects with index pattern conflicts', async function () { await PageObjects.savedObjects.importFile( path.join(__dirname, 'exports', '_import_objects_conflicts.ndjson') @@ -399,21 +421,40 @@ export default function ({ getService, getPageObjects }) { expect(isSavedObjectImported).to.be(true); }); - it('should import saved objects with index patterns when index patterns does not exists', async () => { - // First, we need to delete the index pattern - await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); - await PageObjects.savedObjects.clickDelete(); - - // Then, import the objects + it('should preserve index patterns selection when switching between pages', async () => { await PageObjects.savedObjects.importFile( - path.join(__dirname, 'exports', '_import_objects_with_index_patterns.json') + path.join(__dirname, 'exports', '_import_objects_missing_all_index_patterns.json') ); - await PageObjects.savedObjects.checkImportSucceeded(); - await PageObjects.savedObjects.clickImportDone(); - const objects = await PageObjects.savedObjects.getRowTitles(); - const isSavedObjectImported = objects.includes('saved object imported with index pattern'); - expect(isSavedObjectImported).to.be(true); + await PageObjects.savedObjects.setOverriddenIndexPatternValue( + 'missing-index-pattern-1', + 'index-pattern-test-1' + ); + + await testSubjects.click('pagination-button-next'); + + await PageObjects.savedObjects.setOverriddenIndexPatternValue( + 'missing-index-pattern-7', + 'index-pattern-test-2' + ); + + await testSubjects.click('pagination-button-previous'); + + const selectedIdForMissingIndexPattern1 = await testSubjects.getAttribute( + 'managementChangeIndexSelection-missing-index-pattern-1', + 'value' + ); + + expect(selectedIdForMissingIndexPattern1).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a20'); + + await testSubjects.click('pagination-button-next'); + + const selectedIdForMissingIndexPattern7 = await testSubjects.getAttribute( + 'managementChangeIndexSelection-missing-index-pattern-7', + 'value' + ); + + expect(selectedIdForMissingIndexPattern7).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a87'); }); }); }); diff --git a/test/functional/apps/management/exports/_import_objects_circular_refs.ndjson b/test/functional/apps/management/exports/_import_objects_circular_refs.ndjson new file mode 100644 index 00000000000000..44297e9e8f3e0c --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects_circular_refs.ndjson @@ -0,0 +1,2 @@ +{"attributes":{"title":"dashboard-b"},"id":"dashboard-b","references":[{"id":"dashboard-a","name":"circular-dashboard-ref","type":"dashboard"}],"type":"dashboard"} +{"attributes":{"title":"dashboard-a"},"id":"dashboard-a","references":[{"id":"dashboard-b","name":"circular-dashboard-ref","type":"dashboard"}],"type":"dashboard"} diff --git a/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json b/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json new file mode 100644 index 00000000000000..45572b0bf34fed --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json @@ -0,0 +1,121 @@ +[ + { + "_id": "test-vis-1", + "_type": "visualization", + "_source": { + "title": "Test VIS 1", + "visState": "{\"title\":\"test vis 1\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-1\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-2", + "_type": "visualization", + "_source": { + "title": "Test VIS 2", + "visState": "{\"title\":\"test vis 2\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-2\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-3", + "_type": "visualization", + "_source": { + "title": "Test VIS 3", + "visState": "{\"title\":\"test vis 3\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-3\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-4", + "_type": "visualization", + "_source": { + "title": "Test VIS 4", + "visState": "{\"title\":\"test vis 4\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-4\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-5", + "_type": "visualization", + "_source": { + "title": "Test VIS 5", + "visState": "{\"title\":\"test vis 5\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-5\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-6", + "_type": "visualization", + "_source": { + "title": "Test VIS 6", + "visState": "{\"title\":\"test vis 6\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-6\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-7", + "_type": "visualization", + "_source": { + "title": "Test VIS 7", + "visState": "{\"title\":\"test vis 7\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-7\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.ts similarity index 94% rename from test/functional/apps/management/index.js rename to test/functional/apps/management/index.ts index d5f0c286af7a55..7365f912ea4fa1 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.ts @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, loadTestFile }) { +export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('management', function () { diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index 8e65b6488836c5..0742f14b2eeb41 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -126,6 +126,12 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv } } + async setOverriddenIndexPatternValue(oldName: string, newName: string) { + const select = await testSubjects.find(`managementChangeIndexSelection-${oldName}`); + const option = await testSubjects.findDescendant(`indexPatternOption-${newName}`, select); + await option.click(); + } + async clickCopyToSpaceByTitle(title: string) { const table = keyBy(await this.getElementsInTable(), 'title'); // should we check if table size > 0 and log error if not? diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 3ac6c83e61f145..272fc5a7914be7 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -35,12 +35,18 @@ export type CommonlyUsed = export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); - const retry = getService('retry'); const find = getService('find'); const browser = getService('browser'); const testSubjects = getService('testSubjects'); const { header } = getPageObjects(['header']); const kibanaServer = getService('kibanaServer'); + const MenuToggle = getService('MenuToggle'); + + const quickSelectTimeMenuToggle = new MenuToggle({ + name: 'QuickSelectTime Menu', + menuTestSubject: 'superDatePickerQuickMenu', + toggleButtonTestSubject: 'superDatePickerToggleQuickMenuButton', + }); class TimePicker { defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000'; @@ -158,34 +164,8 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo return await find.existsByCssSelector('.euiDatePickerRange--readOnly'); } - public async isQuickSelectMenuOpen() { - return await testSubjects.exists('superDatePickerQuickMenu'); - } - - public async openQuickSelectTimeMenu() { - log.debug('openQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (!isMenuOpen) { - log.debug('opening quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - - public async closeQuickSelectTimeMenu() { - log.debug('closeQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (isMenuOpen) { - log.debug('closing quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - public async getRefreshConfig(keepQuickSelectOpen = false) { - await this.openQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.open(); const interval = await testSubjects.getAttribute( 'superDatePickerRefreshIntervalInput', 'value' @@ -207,7 +187,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo 'superDatePickerToggleRefreshButton' ); if (!keepQuickSelectOpen) { - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } return { @@ -270,26 +250,26 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo } public async startAutoRefresh(intervalS = 3) { - await this.openQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.open(); await this.inputValue('superDatePickerRefreshIntervalInput', intervalS.toString()); const refreshConfig = await this.getRefreshConfig(true); if (refreshConfig.isPaused) { log.debug('start auto refresh'); await testSubjects.click('superDatePickerToggleRefreshButton'); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async pauseAutoRefresh() { log.debug('pauseAutoRefresh'); const refreshConfig = await this.getRefreshConfig(true); + if (!refreshConfig.isPaused) { log.debug('pause auto refresh'); await testSubjects.click('superDatePickerToggleRefreshButton'); - await this.closeQuickSelectTimeMenu(); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async resumeAutoRefresh() { @@ -300,7 +280,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.click('superDatePickerToggleRefreshButton'); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async setHistoricalDataRange() { diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index cb114866322dbb..1acea624ad4cde 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -218,7 +218,7 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr } public async expectError() { - await testSubjects.existOrFail('visLibVisualizeError'); + await testSubjects.existOrFail('vislibVisualizeError'); } public async getVisualizationRenderingCount() { diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 8cac43d97317b3..cdbbeb91887329 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -97,9 +97,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickSplitDirection(direction: string) { - const radioBtn = await find.byCssSelector( - `[data-test-subj="visEditorSplitBy"][title="${direction}"]` - ); + const radioBtn = await find.byCssSelector(`[data-test-subj="visEditorSplitBy-${direction}"]`); await radioBtn.click(); } @@ -334,10 +332,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async toggleAutoMode() { - // this is a temporary solution, should be replaced with initial after fixing the EuiToggleButton - // passing the data-test-subj attribute to a checkbox - await find.clickByCssSelector('.visEditorSidebar__controls input[type="checkbox"]'); - // await testSubjects.click('visualizeEditorAutoButton'); + await testSubjects.click('visualizeEditorAutoButton'); } public async isApplyEnabled() { diff --git a/test/functional/screenshots/baseline/tsvb_dashboard.png b/test/functional/screenshots/baseline/tsvb_dashboard.png index fe63e352aef789..b0264ce8d45925 100644 Binary files a/test/functional/screenshots/baseline/tsvb_dashboard.png and b/test/functional/screenshots/baseline/tsvb_dashboard.png differ diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index 204edcb215107a..e464eaf9436277 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -170,11 +170,27 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { public async getAttribute( selector: string, attribute: string, - timeout?: number + options?: + | number + | { + findTimeout?: number; + tryTimeout?: number; + } ): Promise { - return await retry.try(async () => { - log.debug(`TestSubjects.getAttribute(${selector}, ${attribute})`); - const element = await this.find(selector, timeout); + const findTimeout = + (typeof options === 'number' ? options : options?.findTimeout) ?? + config.get('timeouts.find'); + + const tryTimeout = + (typeof options !== 'number' ? options?.tryTimeout : undefined) ?? + config.get('timeouts.try'); + + log.debug( + `TestSubjects.getAttribute(${selector}, ${attribute}, tryTimeout=${tryTimeout}, findTimeout=${findTimeout})` + ); + + return await retry.tryForTime(tryTimeout, async () => { + const element = await this.find(selector, findTimeout); return await element.getAttribute(attribute); }); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 057ae0bd13b6e7..2c71fd8ef8f55f 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -57,6 +57,7 @@ import { import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; +import { MenuToggleProvider } from './menu_toggle'; export const services = { ...commonServiceProviders, @@ -93,4 +94,5 @@ export const services = { elasticChart: ElasticChartProvider, supertest: KibanaSupertestProvider, managementMenu: ManagementMenuProvider, + MenuToggle: MenuToggleProvider, }; diff --git a/test/functional/services/menu_toggle.ts b/test/functional/services/menu_toggle.ts new file mode 100644 index 00000000000000..073aa846f47a85 --- /dev/null +++ b/test/functional/services/menu_toggle.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function MenuToggleProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + interface Options { + name: string; + menuTestSubject: string; + toggleButtonTestSubject: string; + } + + return class MenuToggle { + private readonly name: string; + private readonly menuTestSubject: string; + private readonly toggleButtonTestSubject: string; + + constructor(options: Options) { + this.name = options.name; + this.menuTestSubject = options.menuTestSubject; + this.toggleButtonTestSubject = options.toggleButtonTestSubject; + } + + async open() { + await this.setState(true); + } + + async close() { + await this.setState(false); + } + + private async setState(expectedState: boolean) { + log.debug( + `setting menu open state [name=${this.name}] [state=${expectedState ? 'open' : 'closed'}]` + ); + + await retry.try(async () => { + // if the menu is clearly in the expected state already, bail out quickly if so + const isOpen = await testSubjects.exists(this.menuTestSubject, { timeout: 1000 }); + if (isOpen === expectedState) { + return; + } + + // toggle the view state by clicking the button + await testSubjects.click(this.toggleButtonTestSubject); + + if (expectedState === true) { + // wait for up to 10 seconds for the menu to show up, otherwise fail and retry + await testSubjects.existOrFail(this.menuTestSubject, { timeout: 10000 }); + } else { + // wait for the form to hide, otherwise fail and retry + await testSubjects.waitForDeleted(this.menuTestSubject); + } + }); + } + }; +} diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 7236ac8afbda66..1edec496f10545 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/test/interpreter_functional/snapshots/baseline/combined_test2.json b/test/interpreter_functional/snapshots/baseline/combined_test2.json index 550b3b5df12bec..4870694e6adbce 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test2.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index cf488ac7f3ffab..dd779800cd4523 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index 8c272901c4e84a..992d667fdce9f7 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index abc0d3a4469871..031c9f9ea55044 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index 1809df5e709f00..8c6fde201c8f15 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json index ec32b07ed9f2ea..14c8428c6d432a 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_3.json b/test/interpreter_functional/snapshots/baseline/partial_test_3.json index 09602eca4abf2a..595127526156e4 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_3.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test2.json b/test/interpreter_functional/snapshots/baseline/step_output_test2.json index 550b3b5df12bec..4870694e6adbce 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test2.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json index 071172c698ad7e..073fca760b9a24 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json index ad38bb28b33291..93f8d8a27d2334 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json index 997285adfe5f4f..e8c47efdbe622b 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json index 10e23d860637c8..38683082975f8f 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test2.json b/test/interpreter_functional/snapshots/session/combined_test2.json index 550b3b5df12bec..4870694e6adbce 100644 --- a/test/interpreter_functional/snapshots/session/combined_test2.json +++ b/test/interpreter_functional/snapshots/session/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ b/test/interpreter_functional/snapshots/session/combined_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ b/test/interpreter_functional/snapshots/session/final_output_test.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json index cf488ac7f3ffab..dd779800cd4523 100644 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ b/test/interpreter_functional/snapshots/session/metric_all_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json index 8c272901c4e84a..992d667fdce9f7 100644 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json index abc0d3a4469871..031c9f9ea55044 100644 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json index 1809df5e709f00..8c6fde201c8f15 100644 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"},{"id":"col-2-1","meta":{"field":"bytes","index":"logstash-*","params":{"id":"bytes","params":null},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{"field":"bytes"},"schema":"metric","type":"max"},"type":"number"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json index ec32b07ed9f2ea..14c8428c6d432a 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ b/test/interpreter_functional/snapshots/session/partial_test_1.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ b/test/interpreter_functional/snapshots/session/partial_test_2.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_3.json b/test/interpreter_functional/snapshots/session/partial_test_3.json index 09602eca4abf2a..595127526156e4 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_3.json +++ b/test/interpreter_functional/snapshots/session/partial_test_3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"region_map"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test2.json b/test/interpreter_functional/snapshots/session/step_output_test2.json index 550b3b5df12bec..4870694e6adbce 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test2.json +++ b/test/interpreter_functional/snapshots/session/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json index 59de1f285799b9..2aa601a8d36318 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ b/test/interpreter_functional/snapshots/session/step_output_test3.json @@ -1 +1 @@ -{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"metric_vis","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json index 071172c698ad7e..073fca760b9a24 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json index ad38bb28b33291..93f8d8a27d2334 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json index 997285adfe5f4f..e8c47efdbe622b 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json index 10e23d860637c8..38683082975f8f 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagloud_vis","type":"render","value":{"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json index 98a8f050bddd3e..58cb4de0d63a43 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "react": "^16.12.0", "typescript": "4.0.2" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 8c0815b896a0ed..7513e763bad7ea 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "typescript": "4.0.2" diff --git a/test/tsconfig.json b/test/tsconfig.json index a6cc2d34639b70..2949a764d4b1ad 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -2,29 +2,19 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "tsBuildInfoFile": "../build/tsbuildinfo/test", - "types": [ - "node", - "mocha", - "flot" - ] + "types": ["node", "mocha", "flot"] }, - "include": [ - "**/*", - "../typings/elastic__node_crypto.d.ts", - "typings/**/*" - ], - "exclude": [ - "plugin_functional/plugins/**/*", - "interpreter_functional/plugins/**/*" - ], + "include": ["**/*", "../typings/elastic__node_crypto.d.ts", "typings/**/*"], + "exclude": ["plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ { "path": "../src/core/tsconfig.json" }, - { "path": "../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../src/plugins/inspector/tsconfig.json" }, { "path": "../src/plugins/kibana_react/tsconfig.json" }, - { "path": "../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, + { "path": "../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../src/plugins/newsfeed/tsconfig.json" }, { "path": "../src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "../src/plugins/telemetry/tsconfig.json" }, - { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, - { "path": "../src/plugins/newsfeed/tsconfig.json" } + { "path": "../src/plugins/usage_collection/tsconfig.json" } ] } diff --git a/tsconfig.json b/tsconfig.json index 73646291e3d08a..2e4d5730d93208 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,16 +6,19 @@ "include": ["kibana.d.ts", "src/**/*", "typings/**/*", "test_utils/**/*"], "exclude": [ "src/**/__fixtures__/**/*", - "src/test_utils/**/*", "src/core/**/*", + "src/plugins/inspector/**/*", "src/plugins/kibana_legacy/**/*", - "src/plugins/kibana_utils/**/*", "src/plugins/kibana_react/**/*", - "src/plugins/usage_collection/**/*", + "src/plugins/kibana_usage_collection/**/*", + "src/plugins/kibana_utils/**/*", + "src/plugins/newsfeed/**/*", + "src/plugins/share/**/*", "src/plugins/telemetry_collection_manager/**/*", "src/plugins/telemetry/**/*", - "src/plugins/kibana_usage_collection/**/*", - "src/plugins/newsfeed/**/*" + "src/plugins/url_forwarding/**/*", + "src/plugins/usage_collection/**/*", + "src/test_utils/**/*" // In the build we actually exclude **/public/**/* from this config so that // we can run the TSC on both this and the .browser version of this config // file, but if we did it during development IDEs would not be able to find @@ -23,14 +26,16 @@ // "src/**/public/**/*" ], "references": [ - { "path": "./src/test_utils/tsconfig.json" }, { "path": "./src/core/tsconfig.json" }, - { "path": "./src/plugins/kibana_utils/tsconfig.json" }, + { "path": "./src/plugins/inspector/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" }, - { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, + { "path": "./src/plugins/kibana_utils/tsconfig.json" }, + { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/share/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, - { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, - { "path": "./src/plugins/newsfeed/tsconfig.json" } + { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/test_utils/tsconfig.json" } ] } diff --git a/tsconfig.refs.json b/tsconfig.refs.json index bb1bdc08cafd6f..a37aa7b5b57fbd 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -1,14 +1,18 @@ { "include": [], "references": [ - { "path": "./src/test_utils/tsconfig.json" }, { "path": "./src/core/tsconfig.json" }, - { "path": "./src/plugins/kibana_utils/tsconfig.json" }, + { "path": "./src/plugins/inspector/tsconfig.json" }, + { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" }, - { "path": "./src/plugins/usage_collection/tsconfig.json" }, - { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, - { "path": "./src/plugins/telemetry/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, + { "path": "./src/plugins/kibana_utils/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/share/tsconfig.json" }, + { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, + { "path": "./src/plugins/telemetry/tsconfig.json" }, + { "path": "./src/plugins/url_forwarding/tsconfig.json" }, + { "path": "./src/plugins/usage_collection/tsconfig.json" }, + { "path": "./src/test_utils/tsconfig.json" } ] } diff --git a/typings/index.d.ts b/typings/index.d.ts index db6530d3f9e0be..fc16aa32fe3f97 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -34,22 +34,3 @@ declare module '*.svg' { // eslint-disable-next-line import/no-default-export export default content; } - -type MethodKeysOf = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; -}[keyof T]; - -type PublicMethodsOf = Pick>; - -type MockedKeys = { [P in keyof T]: jest.Mocked }; - -type DeeplyMockedKeys = { - [P in keyof T]: T[P] extends (...args: any[]) => any - ? jest.MockInstance, Parameters> - : DeeplyMockedKeys; -} & - T; - -type Writable = { - -readonly [K in keyof T]: T[K]; -}; diff --git a/vars/getCheckoutInfo.groovy b/vars/getCheckoutInfo.groovy index 32a7b054bfd151..f9d797f8127c7a 100644 --- a/vars/getCheckoutInfo.groovy +++ b/vars/getCheckoutInfo.groovy @@ -2,6 +2,7 @@ def call(branchOverride) { def repoInfo = [ branch: branchOverride ?: env.ghprbSourceBranch, targetBranch: env.ghprbTargetBranch, + targetsTrackedBranch: true ] if (repoInfo.branch == null) { @@ -35,6 +36,10 @@ def call(branchOverride) { label: "determining merge point with '${repoInfo.targetBranch}' at origin", returnStdout: true ).trim() + + def pkgJson = readFile("package.json") + def releaseBranch = toJSON(pkgJson).branch + repoInfo.targetsTrackedBranch = releaseBranch == repoInfo.targetBranch } print "repoInfo: ${repoInfo}" diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index fd5412c905683e..546a6785ac2f4f 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -149,7 +149,7 @@ def getTestFailuresMessage() { def getBuildStatusIncludingMetrics() { def status = buildUtils.getBuildStatus() - if (status == 'SUCCESS' && !ciStats.getMetricsSuccess()) { + if (status == 'SUCCESS' && shouldCheckCiMetricSuccess() && !ciStats.getMetricsSuccess()) { return 'FAILURE' } @@ -297,3 +297,12 @@ def getFailedSteps() { step.displayName != 'Check out from version control' } } + +def shouldCheckCiMetricSuccess() { + // disable ciMetrics success check when a PR is targetting a non-tracked branch + if (buildState.has('checkoutInfo') && !buildState.get('checkoutInfo').targetsTrackedBranch) { + return false + } + + return true +} diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 2b712a206bfc49..8993213d91f235 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -27,7 +27,7 @@ "xpack.idxMgmt": "plugins/index_management", "xpack.indexLifecycleMgmt": "plugins/index_lifecycle_management", "xpack.infra": "plugins/infra", - "xpack.ingestManager": "plugins/ingest_manager", + "xpack.fleet": "plugins/ingest_manager", "xpack.ingestPipelines": "plugins/ingest_pipelines", "xpack.lens": "plugins/lens", "xpack.licenseMgmt": "plugins/license_management", @@ -56,9 +56,7 @@ "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability" }, - "exclude": [ - "examples" - ], + "exclude": ["examples"], "translations": [ "plugins/translations/translations/zh-CN.json", "plugins/translations/translations/ja-JP.json" diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index a90147d01e8b63..48b64a1c845883 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -89,7 +89,6 @@ export const DrilldownsWithEmbeddableExample: React.FC = () => { isOpen={openPopup} closePopover={() => setOpenPopup(false)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx index fb22e98e4a6d9b..8f1582aaacff54 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx @@ -77,7 +77,6 @@ export const DrilldownsWithoutEmbeddableExample: React.FC = () => { isOpen={openPopup} closePopover={() => setOpenPopup(false)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json index f8c1a6b53dac5d..05e5f39d4d628f 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json +++ b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json @@ -16,5 +16,6 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" } ] } diff --git a/x-pack/package.json b/x-pack/package.json index 77dc2e662dd28b..ea1b7b9713df99 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -183,6 +183,7 @@ "jest": "^26.4.2", "jest-circus": "^26.4.2", "jest-cli": "^26.4.2", + "jest-silent-reporter": "^0.2.1", "jest-styled-components": "^7.0.2", "js-search": "^1.4.3", "jsdom": "13.1.0", @@ -258,7 +259,7 @@ "venn.js": "0.2.20", "vinyl-fs": "^3.0.3", "whatwg-fetch": "^3.0.0", - "xml-crypto": "^1.4.0", + "xml-crypto": "^2.0.0", "yargs": "^15.4.1" }, "dependencies": { @@ -266,7 +267,7 @@ "@babel/runtime": "^7.11.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.10.0", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.0", diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 0c16c88ad7a89d..2c2a818eb6eb29 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { ActionsClient } from './actions_client'; type ActionsClientContract = PublicMethodsOf; diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.mock.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.mock.ts index 6b55c18241c559..59887f405a7dcf 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.mock.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { ActionsAuthorization } from './actions_authorization'; export type ActionsAuthorizationMock = jest.Mocked>; diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 23ce527d4ae0d2..74feb8ee57d485 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -90,8 +90,9 @@ describe('config validation', () => { }; test('config validation passes when only required fields are provided', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', + hasAuth: true, }; expect(validateConfig(actionType, config)).toEqual({ ...defaultValues, @@ -101,9 +102,10 @@ describe('config validation', () => { test('config validation passes when valid methods are provided', () => { ['post', 'put'].forEach((method) => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method, + hasAuth: true, }; expect(validateConfig(actionType, config)).toEqual({ ...defaultValues, @@ -127,8 +129,9 @@ describe('config validation', () => { }); test('config validation passes when a url is specified', () => { - const config: Record = { + const config: Record = { url: 'http://mylisteningserver:9200/endpoint', + hasAuth: true, }; expect(validateConfig(actionType, config)).toEqual({ ...defaultValues, @@ -155,6 +158,7 @@ describe('config validation', () => { headers: { 'Content-Type': 'application/json', }, + hasAuth: true, }; expect(validateConfig(actionType, config)).toEqual({ ...defaultValues, @@ -184,6 +188,7 @@ describe('config validation', () => { headers: { 'Content-Type': 'application/json', }, + hasAuth: true, }; expect(validateConfig(actionType, config)).toEqual({ @@ -263,6 +268,7 @@ describe('execute()', () => { headers: { aheader: 'a value', }, + hasAuth: true, }; await actionType.executor({ actionId: 'some-id', @@ -320,6 +326,7 @@ describe('execute()', () => { headers: { aheader: 'a value', }, + hasAuth: false, }; const secrets: ActionTypeSecretsType = { user: null, password: null }; await actionType.executor({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index d0ec31721685e3..dc9de86d3d951b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -42,6 +42,7 @@ const configSchemaProps = { defaultValue: WebhookMethods.POST, }), headers: nullableType(HeadersSchema), + hasAuth: schema.boolean({ defaultValue: true }), }; const ConfigSchema = schema.object(configSchemaProps); export type ActionTypeConfigType = TypeOf; @@ -128,12 +129,12 @@ export async function executor( execOptions: WebhookActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; - const { method, url, headers = {} } = execOptions.config; + const { method, url, headers = {}, hasAuth } = execOptions.config; const { body: data } = execOptions.params; const secrets: ActionTypeSecretsType = execOptions.secrets; const basicAuth = - isString(secrets.user) && isString(secrets.password) + hasAuth && isString(secrets.user) && isString(secrets.password) ? { auth: { username: secrets.user, password: secrets.password } } : {}; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index bbe298c0585a3a..39bfe2c2820e23 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server'; import { ActionsPlugin } from './plugin'; import { configSchema } from './config'; diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 0015b417d72ce6..af70fbf2ec8962 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, KibanaRequest } from 'src/core/server'; import { validateParams, validateConfig, validateSecrets } from './validate_with_schema'; import { diff --git a/x-pack/plugins/actions/server/lib/license_state.ts b/x-pack/plugins/actions/server/lib/license_state.ts index 902fadb3da1700..e8cd0aa841cd98 100644 --- a/x-pack/plugins/actions/server/lib/license_state.ts +++ b/x-pack/plugins/actions/server/lib/license_state.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import type { PublicMethodsOf } from '@kbn/utility-types'; import { Observable, Subscription } from 'rxjs'; import { assertNever } from '@kbn/std'; import { ILicense } from '../../../licensing/common/types'; diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 18cbd9f9c5fada..c8e26846515980 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -145,6 +145,12 @@ test('executes the task by calling the executor with proper parameters', async ( url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); @@ -271,6 +277,12 @@ test('uses API key when provided', async () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); @@ -310,6 +322,12 @@ test(`doesn't use API key when not provided`, async () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }, }); }); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index aeeeb4ed7d520d..93ae5a2c807f9c 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -102,6 +102,12 @@ export class TaskRunnerFactory { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; let executorResult: ActionTypeExecutorResult; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 668e8b849b8a72..599e7461ea3128 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { first, map } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { @@ -86,9 +86,10 @@ export interface PluginSetupContract { registerType< Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets, - Params extends ActionTypeParams = ActionTypeParams + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void >( - actionType: ActionType + actionType: ActionType ): void; } @@ -254,9 +255,10 @@ export class ActionsPlugin implements Plugin, Plugi registerType: < Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets, - Params extends ActionTypeParams = ActionTypeParams + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void >( - actionType: ActionType + actionType: ActionType ) => { if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) { throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`); diff --git a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts index 06f65ff23106c1..7b8b5f7b7f0e2e 100644 --- a/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/actions/server/routes/_mock_handler_arguments.ts @@ -6,6 +6,7 @@ import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { identity } from 'lodash'; +import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from '../../../../../src/core/server/mocks'; import { ActionType } from '../../common'; import { ActionsClientMock, actionsClientMock } from '../actions_client.mock'; diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/migrations.test.ts index 947d84fcfc638f..f1bd1ba2aeb609 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/migrations.test.ts @@ -58,6 +58,63 @@ describe('7.10.0', () => { }); }); +describe('7.11.0', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation( + (shouldMigrateWhenPredicate, migration) => migration + ); + }); + + test('add hasAuth = true for .webhook actions with user and password', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const action = getMockDataForWebhook({}, true); + expect(migration711(action, context)).toMatchObject({ + ...action, + attributes: { + ...action.attributes, + config: { + hasAuth: true, + }, + }, + }); + }); + + test('add hasAuth = false for .webhook actions without user and password', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const action = getMockDataForWebhook({}, false); + expect(migration711(action, context)).toMatchObject({ + ...action, + attributes: { + ...action.attributes, + config: { + hasAuth: false, + }, + }, + }); + }); +}); + +function getMockDataForWebhook( + overwrites: Record = {}, + hasUserAndPassword: boolean +): SavedObjectUnsanitizedDoc { + const secrets = hasUserAndPassword + ? { user: 'test', password: '123' } + : { user: '', password: '' }; + return { + attributes: { + name: 'abc', + actionTypeId: '.webhook', + config: {}, + secrets, + ...overwrites, + }, + id: uuid.v4(), + type: 'action', + }; +} + function getMockDataForEmail( overwrites: Record = {} ): SavedObjectUnsanitizedDoc { diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.ts b/x-pack/plugins/actions/server/saved_objects/migrations.ts index 35d30accecedb5..1e2290b14ec1b0 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/migrations.ts @@ -25,8 +25,18 @@ export function getMigrations( pipeMigrations(renameCasesConfigurationObject, addHasAuthConfigurationObject) ); + const migrationWebhookConnectorHasAuth = encryptedSavedObjects.createMigration< + RawAction, + RawAction + >( + (doc): doc is SavedObjectUnsanitizedDoc => + doc.attributes.actionTypeId === '.webhook', + pipeMigrations(addHasAuthConfigurationObject) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationActions, '7.10.0'), + '7.11.0': executeMigrationWithErrorHandling(migrationWebhookConnectorHasAuth, '7.11.0'), }; } @@ -70,7 +80,7 @@ function renameCasesConfigurationObject( const addHasAuthConfigurationObject = ( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc => { - if (doc.attributes.actionTypeId !== '.email') { + if (doc.attributes.actionTypeId !== '.email' && doc.attributes.actionTypeId !== '.webhook') { return doc; } const hasAuth = !!doc.attributes.secrets.user || !!doc.attributes.secrets.password; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index a8db8bfd7344cc..1867815bd5f903 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { ActionTypeRegistry } from './action_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { ActionsClient } from './actions_client'; diff --git a/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts index 792bd8e885ea6e..c800579fddffd5 100644 --- a/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.ts +++ b/x-pack/plugins/alerts/public/alert_navigation_registry/alert_navigation_registry.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertNavigationRegistry } from './alert_navigation_registry'; type Schema = PublicMethodsOf; diff --git a/x-pack/plugins/alerts/server/alert_type_registry.mock.ts b/x-pack/plugins/alerts/server/alert_type_registry.mock.ts index b6b36e4d4b7e08..39d15eba014c9b 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.mock.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertTypeRegistry } from './alert_type_registry'; type Schema = PublicMethodsOf; diff --git a/x-pack/plugins/alerts/server/alerts_client.mock.ts b/x-pack/plugins/alerts/server/alerts_client.mock.ts index b28e9f805f725a..c9063457fac804 100644 --- a/x-pack/plugins/alerts/server/alerts_client.mock.ts +++ b/x-pack/plugins/alerts/server/alerts_client.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertsClient } from './alerts_client'; type Schema = PublicMethodsOf; diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts index 55c2f3ddd18a41..4e457cdb12bd39 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -60,6 +60,12 @@ const fakeRequest = ({ url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as Request; diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization.mock.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization.mock.ts index 3728daa946d5b9..171e3978d0d0d2 100644 --- a/x-pack/plugins/alerts/server/authorization/alerts_authorization.mock.ts +++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertsAuthorization } from './alerts_authorization'; type Schema = PublicMethodsOf; diff --git a/x-pack/plugins/alerts/server/index.ts b/x-pack/plugins/alerts/server/index.ts index 72d3a652205653..1e442c5196cf20 100644 --- a/x-pack/plugins/alerts/server/index.ts +++ b/x-pack/plugins/alerts/server/index.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertsClient as AlertsClientClass } from './alerts_client'; import { PluginInitializerContext } from '../../../../src/core/server'; import { AlertingPlugin } from './plugin'; diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index b13a1c62f66022..ece7d31d2f7fdc 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -149,6 +149,12 @@ describe('Alerting Plugin', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: jest.fn(), } as unknown) as KibanaRequest; await startContract.getAlertsClientWithRequest(fakeRequest); diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 03302d5e6e7db3..565b2992b1f7a7 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { first, map } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { SecurityPluginSetup } from '../../security/server'; diff --git a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts index 548495866ec210..3d13fc65ab260e 100644 --- a/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerts/server/routes/_mock_handler_arguments.ts @@ -11,6 +11,7 @@ import { ILegacyClusterClient, } from 'kibana/server'; import { identity } from 'lodash'; +import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from '../../../../../src/core/server/mocks'; import { alertsClientMock, AlertsClientMock } from '../alerts_client.mock'; import { AlertType } from '../../common'; diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 8e345d6ff66a8a..17d5fcd31b745c 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -364,6 +364,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -662,6 +668,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); }); @@ -694,6 +706,12 @@ describe('Task Runner', () => { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, }); }); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 7da16717aef9f0..76125da20d552f 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { pickBy, mapValues, without } from 'lodash'; import { Logger, KibanaRequest } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; @@ -101,6 +101,12 @@ export class TaskRunner { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts index 944c4dc64ce7a0..df6f306c6ccc5a 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner_factory.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, KibanaRequest, ISavedObjectsRepository } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 74153d1ca6b1df..42eef9bba10e52 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 02456f9b2050f4..6edf56fb9a1ae8 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -91,3 +91,5 @@ export function isSpanGroupingSupported(type?: string, subtype?: string) { nongroupedSubType === 'all' || nongroupedSubType === subtype ); } + +export const SERVICE_MAP_TIMEOUT_ERROR = 'ServiceMapTimeoutError'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts b/x-pack/plugins/apm/common/ui_settings_keys.ts similarity index 55% rename from x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts rename to x-pack/plugins/apm/common/ui_settings_keys.ts index fe5a0d7932cbbf..38922fa445a471 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/attempt_to_uri_decode.ts +++ b/x-pack/plugins/apm/common/ui_settings_keys.ts @@ -4,12 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export const attemptToURIDecode = (value: string) => { - let result: string; - try { - result = decodeURI(decodeURIComponent(value)); - } catch (e) { - result = value; - } - return result; -}; +export const enableCorrelations = 'apm:enableCorrelations'; +export const enableServiceOverview = 'apm:enableServiceOverview'; diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js index c37326ee5b1964..d77d3ba0bec0f2 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js +++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js @@ -1,3 +1,3 @@ module.exports = { - "__version": "4.9.0" + "__version": "5.5.0" } diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index ab2bf20b36ed4b..50c620dca9ddfa 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -11,7 +11,7 @@ import { loginAndWaitForPage } from '../../integration/helpers'; export const DEFAULT_TIMEOUT = 60 * 1000; Given(`a user browses the APM UI application`, () => { - // open service overview page + // Open service inventory page loginAndWaitForPage(`/app/apm/services`, { from: '2020-06-01T14:59:32.686Z', to: '2020-06-16T16:59:36.219Z', diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts index a8edf862ab256c..452d8b719b3cb5 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts @@ -12,7 +12,7 @@ import { verifyClientMetrics } from './client_metrics_helper'; export const DEFAULT_TIMEOUT = { timeout: 60 * 1000 }; Given(`a user browses the APM UI application for RUM Data`, () => { - // open service overview page + // Open UX landing page const RANGE_FROM = 'now-24h'; const RANGE_TO = 'now'; loginAndWaitForPage( @@ -26,7 +26,7 @@ Given(`a user browses the APM UI application for RUM Data`, () => { }); Then(`should have correct client metrics`, () => { - const metrics = ['4 ms', '58 ms', '55']; + const metrics = ['80 ms', '4 ms', '76 ms', '55']; verifyClientMetrics(metrics, true); }); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts index 5c2109bb518c2d..88287286c66c52 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts @@ -56,7 +56,9 @@ Then(/^it filters the client metrics "([^"]*)"$/, (filterName) => { cy.get('.euiStat__title-isLoading').should('not.be.visible'); const data = - filterName === 'os' ? ['5 ms', '64 ms', '8'] : ['4 ms', '55 ms', '28']; + filterName === 'os' + ? ['82 ms', '5 ms', '77 ms', '8'] + : ['75 ms', '4 ms', '71 ms', '28']; verifyClientMetrics(data, true); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts index 0dbefb73e68c44..9e10e2fd599149 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts @@ -12,12 +12,7 @@ Then(`it displays list of relevant js errors`, () => { cy.get('.euiBasicTable-loading').should('not.be.visible'); cy.get('.euiStat__title-isLoading').should('not.be.visible'); - getDataTestSubj('uxJsErrorsTotal').should('have.text', 'Total errors110'); - - getDataTestSubj('uxJsErrorRate').should( - 'have.text', - 'Error rate100 %Error rate 100 %' - ); + getDataTestSubj('uxJsErrorsTotal').should('have.text', 'Total errors112'); getDataTestSubj('uxJsErrorTable').within(() => { cy.get('tr.euiTableRow', DEFAULT_TIMEOUT) diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts index 314254883b2fd0..44802bbce62088 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts @@ -16,7 +16,7 @@ When('the user changes the selected percentile', () => { }); Then(`it displays client metric related to that percentile`, () => { - const metrics = ['14 ms', '131 ms', '55']; + const metrics = ['165 ms', '14 ms', '151 ms', '55']; verifyClientMetrics(metrics, false); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts index 20c6a3fb72aa99..609d0d18f5bc8a 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts @@ -15,7 +15,7 @@ When('the user changes the selected service name', () => { }); Then(`it displays relevant client metrics`, () => { - const metrics = ['4 ms', '58 ms', '55']; + const metrics = ['80 ms', '4 ms', '76 ms', '55']; verifyClientMetrics(metrics, false); }); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts index 75b9a9c8041684..3dc98625baf851 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts @@ -24,8 +24,8 @@ Then(`it displays top pages in the suggestion popover`, () => { listOfUrls.should('have.length', 5); const actualUrlsText = [ - 'http://opbeans-node:3000/dashboardPage views: 17Page load duration: 109 ms', - 'http://opbeans-node:3000/ordersPage views: 14Page load duration: 72 ms', + 'http://opbeans-node:3000/dashboardTotal page views: 17Page load duration: 109 ms (median)', + 'http://opbeans-node:3000/ordersTotal page views: 14Page load duration: 72 ms (median)', ]; cy.get('li.euiSelectableListItem') @@ -55,7 +55,7 @@ Then(`it should filter results based on query`, () => { listOfUrls.should('have.length', 1); const actualUrlsText = [ - 'http://opbeans-node:3000/customersPage views: 10Page load duration: 76 ms', + 'http://opbeans-node:3000/customersTotal page views: 10Page load duration: 76 ms (median)', ]; cy.get('li.euiSelectableListItem') diff --git a/x-pack/plugins/apm/e2e/yarn.lock b/x-pack/plugins/apm/e2e/yarn.lock index fc63189e97ea39..87331c72e8025a 100644 --- a/x-pack/plugins/apm/e2e/yarn.lock +++ b/x-pack/plugins/apm/e2e/yarn.lock @@ -689,14 +689,6 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" -"@babel/runtime-corejs3@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d" - integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - "@babel/runtime@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" @@ -760,7 +752,7 @@ lodash.clonedeep "4.5.0" watchify "3.11.1" -"@cypress/listr-verbose-renderer@0.4.1": +"@cypress/listr-verbose-renderer@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= @@ -770,7 +762,7 @@ date-fns "^1.27.2" figures "^1.7.0" -"@cypress/request@2.88.5": +"@cypress/request@^2.88.5": version "2.88.5" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.5.tgz#8d7ecd17b53a849cfd5ab06d5abe7d84976375d7" integrity sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA== @@ -819,7 +811,7 @@ debug "4.1.1" lodash "4.17.15" -"@cypress/xvfb@1.2.4": +"@cypress/xvfb@^1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== @@ -884,22 +876,22 @@ resolved "https://registry.yarnpkg.com/@types/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.14.1.tgz#9787f4e89553ebc6359ce157a26ad51ed14aa98b" integrity sha512-CpYsiQ49UrOmadhFg0G5RkokPUmGGctD01mOWjNxFxHw5VgIRv33L2RyFHL8klaAI4HaedGN3Tcj4HTQ65hn+A== -"@types/node@^14.0.14": - version "14.0.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" - integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== +"@types/node@>=10.17.17 <10.20.0": + version "10.17.43" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.43.tgz#af70c6de04841d9216fc2b58e427c8a823b83418" + integrity sha512-F7xV2kxZGb3seVP3UQt3msHcoDCtDi8WNO/UCzNLhRwaYVT4yJO1ndcV+vCTnY+jiAVqyLZq/VJbRE/AhwqEag== "@types/retry@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== -"@types/sinonjs__fake-timers@6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" - integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== +"@types/sinonjs__fake-timers@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" + integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== -"@types/sizzle@2.3.2": +"@types/sizzle@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== @@ -1189,15 +1181,23 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -arch@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" - integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== +arch@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== argparse@^1.0.7: version "1.0.10" @@ -1289,6 +1289,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" @@ -1376,6 +1381,11 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +binary-extensions@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -1383,6 +1393,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +blob-util@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + bluebird@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" @@ -1393,7 +1408,7 @@ bluebird@3.7.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== -bluebird@3.7.2, bluebird@^3.4.1, bluebird@^3.5.5: +bluebird@^3.4.1, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1427,7 +1442,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -1712,7 +1727,7 @@ cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== -cachedir@2.3.0: +cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== @@ -1744,15 +1759,6 @@ chai@^4.1.2: pathval "^1.1.0" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1764,6 +1770,15 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -1772,12 +1787,20 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -check-more-types@2.24.0: +check-more-types@2.24.0, check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= @@ -1801,6 +1824,21 @@ chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.4.1: + version "3.4.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" + integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.5.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" @@ -1862,13 +1900,13 @@ cli-spinners@^2.2.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== -cli-table3@0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" - integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw== +cli-table3@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== dependencies: object-assign "^4.1.0" - string-width "^2.1.1" + string-width "^4.2.0" optionalDependencies: colors "^1.1.2" @@ -1978,17 +2016,17 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^2.19.0, commander@^2.20.0, commander@^2.9.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -common-tags@1.8.0: +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -2073,11 +2111,6 @@ core-js-compat@^3.1.1: browserslist "^4.8.3" semver "7.0.0" -core-js-pure@^3.0.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" - integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== - core-js@^2.4.0: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -2129,16 +2162,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: version "3.12.0" @@ -2234,48 +2265,49 @@ cypress-cucumber-preprocessor@^2.5.2: minimist "^1.2.0" through "^2.3.8" -cypress@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.9.0.tgz#c188a3864ddf841c0fdc81a9e4eff5cf539cd1c1" - integrity sha512-qGxT5E0j21FPryzhb0OBjCdhoR/n1jXtumpFFSBPYWsaZZhNaBvc3XlBUDEZKkkXPsqUFYiyhWdHN/zo0t5FcA== - dependencies: - "@cypress/listr-verbose-renderer" "0.4.1" - "@cypress/request" "2.88.5" - "@cypress/xvfb" "1.2.4" - "@types/sinonjs__fake-timers" "6.0.1" - "@types/sizzle" "2.3.2" - arch "2.1.2" - bluebird "3.7.2" - cachedir "2.3.0" - chalk "2.4.2" - check-more-types "2.24.0" - cli-table3 "0.5.1" - commander "4.1.1" - common-tags "1.8.0" - debug "4.1.1" - eventemitter2 "6.4.2" - execa "1.0.0" - executable "4.1.1" - extract-zip "1.7.0" - fs-extra "8.1.0" - getos "3.2.1" - is-ci "2.0.0" - is-installed-globally "0.3.2" - lazy-ass "1.6.0" - listr "0.14.3" - lodash "4.17.15" - log-symbols "3.0.0" - minimist "1.2.5" - moment "2.26.0" - ospath "1.2.2" - pretty-bytes "5.3.0" - ramda "0.26.1" - request-progress "3.0.0" - supports-color "7.1.0" - tmp "0.1.0" - untildify "4.0.0" - url "0.11.0" - yauzl "2.10.0" +cypress@^5.0.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.5.0.tgz#1da0355794a43247f8a80cb7f505e83e1cf847cb" + integrity sha512-UHEiTca8AUTevbT2pWkHQlxoHtXmbq+h6Eiu/Mz8DqpNkF98zjTBLv/HFiKJUU5rQzp9EwSWtms33p5TWCJ8tQ== + dependencies: + "@cypress/listr-verbose-renderer" "^0.4.1" + "@cypress/request" "^2.88.5" + "@cypress/xvfb" "^1.2.4" + "@types/sinonjs__fake-timers" "^6.0.1" + "@types/sizzle" "^2.3.2" + arch "^2.1.2" + blob-util "2.0.2" + bluebird "^3.7.2" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-table3 "~0.6.0" + commander "^4.1.1" + common-tags "^1.8.0" + debug "^4.1.1" + eventemitter2 "^6.4.2" + execa "^4.0.2" + executable "^4.1.1" + extract-zip "^1.7.0" + fs-extra "^9.0.1" + getos "^3.2.1" + is-ci "^2.0.0" + is-installed-globally "^0.3.2" + lazy-ass "^1.6.0" + listr "^0.14.3" + lodash "^4.17.19" + log-symbols "^4.0.0" + minimist "^1.2.5" + moment "^2.27.0" + ospath "^1.2.2" + pretty-bytes "^5.4.1" + ramda "~0.26.1" + request-progress "^3.0.0" + supports-color "^7.2.0" + tmp "~0.2.1" + untildify "^4.0.0" + url "^0.11.0" + yauzl "^2.10.0" d@1, d@^1.0.1: version "1.0.1" @@ -2330,18 +2362,18 @@ debug@^3.0.1, debug@^3.1.0: dependencies: ms "^2.1.1" +debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= -decamelize@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-3.2.0.tgz#84b8e8f4f8c579f938e35e2cc7024907e0090851" - integrity sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw== - dependencies: - xregexp "^4.2.4" - decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -2542,7 +2574,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: +enhanced-resolve@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== @@ -2551,6 +2583,15 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" + integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -2633,10 +2674,10 @@ esutils@^2.0.0, esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter2@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.2.tgz#f31f8b99d45245f0edbc5b00797830ff3b388970" - integrity sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw== +eventemitter2@^6.4.2: + version "6.4.3" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820" + integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ== events@^2.0.0: version "2.1.0" @@ -2656,20 +2697,22 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -execa@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" +execa@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" + integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" -executable@4.1.1: +executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== @@ -2735,7 +2778,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@1.7.0: +extract-zip@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== @@ -2911,14 +2954,15 @@ fs-extra@7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== +fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== dependencies: + at-least-node "^1.0.0" graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" + jsonfile "^6.0.1" + universalify "^1.0.0" fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -2943,6 +2987,11 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2963,10 +3012,10 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" @@ -2975,7 +3024,7 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -getos@3.2.1: +getos@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== @@ -3002,6 +3051,13 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + glob@^7.0.0, glob@^7.1.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -3148,6 +3204,11 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -3257,12 +3318,19 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-buffer@^1.1.0, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-ci@2.0.0: +is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== @@ -3352,14 +3420,14 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" -is-installed-globally@0.3.2: +is-installed-globally@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== @@ -3413,6 +3481,11 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3557,6 +3630,15 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonfile@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" + integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== + dependencies: + universalify "^1.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3616,7 +3698,7 @@ labeled-stream-splicer@^2.0.0: inherits "^2.0.1" stream-splicer "^2.0.0" -lazy-ass@1.6.0: +lazy-ass@1.6.0, lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= @@ -3650,7 +3732,7 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@0.14.3: +listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -3714,12 +3796,10 @@ lodash@4.17.15, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17. resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -log-symbols@3.0.0, log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== log-symbols@^1.0.2: version "1.0.2" @@ -3728,6 +3808,20 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" +log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== + dependencies: + chalk "^2.4.2" + +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -3809,6 +3903,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -3888,16 +3987,16 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.5, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -3964,10 +4063,10 @@ module-deps@^6.0.0: through2 "^2.0.0" xtend "^4.0.0" -moment@2.26.0: - version "2.26.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" - integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== +moment@^2.27.0: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== move-concurrently@^1.0.1: version "1.0.1" @@ -3986,7 +4085,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -4037,11 +4136,6 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - no-case@^2.2.0: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" @@ -4100,17 +4194,17 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: - path-key "^2.0.0" + path-key "^3.0.0" number-is-nan@^1.0.0: version "1.0.1" @@ -4228,7 +4322,7 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -ospath@1.2.2: +ospath@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= @@ -4240,11 +4334,6 @@ outpipe@^1.1.0: dependencies: shell-quote "^1.4.2" -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - p-limit@^2.0.0, p-limit@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" @@ -4369,10 +4458,10 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: version "1.0.6" @@ -4410,6 +4499,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + picomatch@^2.0.5: version "2.2.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" @@ -4437,10 +4531,10 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -pretty-bytes@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" - integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== +pretty-bytes@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== private@^0.1.6: version "0.1.8" @@ -4559,7 +4653,7 @@ ramda@0.25.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ== -ramda@0.26.1: +ramda@~0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== @@ -4617,6 +4711,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" + integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== + dependencies: + picomatch "^2.2.1" + regenerate-unicode-properties@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" @@ -4639,11 +4740,6 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.4: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== - regenerator-transform@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb" @@ -4698,7 +4794,7 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -request-progress@3.0.0: +request-progress@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= @@ -4778,6 +4874,13 @@ rimraf@^2.5.4, rimraf@^2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -4911,17 +5014,17 @@ shasum@^1.0.0: json-stable-stringify "~0.0.0" sha.js "~2.4.4" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: - shebang-regex "^1.0.0" + shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.4.2, shell-quote@^1.6.1: version "1.7.2" @@ -4933,7 +5036,7 @@ sigmund@^1.0.1: resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= @@ -5253,10 +5356,10 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== subarg@^1.0.0: version "1.0.0" @@ -5265,13 +5368,6 @@ subarg@^1.0.0: dependencies: minimist "^1.1.0" -supports-color@7.1.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -5284,6 +5380,20 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -5379,12 +5489,12 @@ title-case@^2.1.1: no-case "^2.2.0" upper-case "^1.0.3" -tmp@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== dependencies: - rimraf "^2.6.3" + rimraf "^3.0.0" to-arraybuffer@^1.0.0: version "1.0.1" @@ -5567,6 +5677,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -5575,7 +5690,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -untildify@4.0.0: +untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== @@ -5602,7 +5717,7 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url@0.11.0, url@^0.11.0, url@~0.11.0: +url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -5697,14 +5812,23 @@ watchify@3.11.1: through2 "^2.0.0" xtend "^4.0.0" -watchpack@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" - integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA== +watchpack-chokidar2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" + integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== dependencies: chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" + integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== + dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.0" wcwidth@^1.0.1: version "1.0.1" @@ -5721,10 +5845,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.43.0: - version "4.43.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6" - integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g== +webpack@^4.41.5: + version "4.44.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" + integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -5734,7 +5858,7 @@ webpack@^4.43.0: ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" + enhanced-resolve "^4.3.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" @@ -5747,7 +5871,7 @@ webpack@^4.43.0: schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" - watchpack "^1.6.1" + watchpack "^1.7.4" webpack-sources "^1.4.1" which-module@^2.0.0: @@ -5755,10 +5879,10 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" @@ -5791,13 +5915,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xregexp@^4.2.4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" - integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== - dependencies: - "@babel/runtime-corejs3" "^7.8.3" - xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -5826,13 +5943,13 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^15.4.0: - version "15.4.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.0.tgz#53949fb768309bac1843de9b17b80051e9805ec2" - integrity sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw== +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0" - decamelize "^3.2.0" + decamelize "^1.2.0" find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" @@ -5843,7 +5960,7 @@ yargs@^15.4.0: y18n "^4.0.0" yargs-parser "^18.1.2" -yauzl@2.10.0, yauzl@^2.10.0: +yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index 2baddbe572a528..d8f54c7bfc94fd 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -28,6 +28,7 @@ import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { px, units } from '../style/variables'; +import { createStaticIndexPattern } from '../services/rest/index_pattern'; const CsmMainContainer = styled.div` padding: ${px(units.plus)}; @@ -114,6 +115,12 @@ export const renderApp = ( ) => { createCallApmApi(core.http); + // Automatically creates static index pattern and stores as saved object + createStaticIndexPattern().catch((e) => { + // eslint-disable-next-line no-console + console.log('Error creating static index pattern', e); + }); + ReactDOM.render( setPopoverOpen(false)} button={ diff --git a/x-pack/plugins/apm/public/components/app/Home/index.tsx b/x-pack/plugins/apm/public/components/app/Home/index.tsx index 446f7b978a434e..c4224bf676abb4 100644 --- a/x-pack/plugins/apm/public/components/app/Home/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Home/index.tsx @@ -20,12 +20,12 @@ import { ApmHeader } from '../../shared/ApmHeader'; import { EuiTabLink } from '../../shared/EuiTabLink'; import { AnomalyDetectionSetupLink } from '../../shared/Links/apm/AnomalyDetectionSetupLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; -import { ServiceOverviewLink } from '../../shared/Links/apm/ServiceOverviewLink'; +import { ServiceInventoryLink } from '../../shared/Links/apm/service_inventory_link'; import { SettingsLink } from '../../shared/Links/apm/SettingsLink'; import { TraceOverviewLink } from '../../shared/Links/apm/TraceOverviewLink'; import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; import { ServiceMap } from '../ServiceMap'; -import { ServiceOverview } from '../ServiceOverview'; +import { ServiceInventory } from '../service_inventory'; import { TraceOverview } from '../TraceOverview'; import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; @@ -37,13 +37,13 @@ function getHomeTabs({ const homeTabs = [ { link: ( - + {i18n.translate('xpack.apm.home.servicesTabLabel', { defaultMessage: 'Services', })} - + ), - render: () => , + render: () => , name: 'services', }, { diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 0d61ca8e398455..f96dc14e342645 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -92,6 +92,12 @@ function ServiceDetailsNodes( return ; } +function ServiceDetailsOverview( + props: RouteComponentProps<{ serviceName: string }> +) { + return ; +} + function ServiceDetailsServiceMap( props: RouteComponentProps<{ serviceName: string }> ) { @@ -215,6 +221,14 @@ export const routes: APMRouteDefinition[] = [ `/services/${props.match.params.serviceName}/transactions` )(props), } as APMRouteDefinition<{ serviceName: string }>, + { + exact: true, + path: '/services/:serviceName/overview', + breadcrumb: i18n.translate('xpack.apm.breadcrumb.overviewTitle', { + defaultMessage: 'Overview', + }), + component: ServiceDetailsOverview, + } as APMRouteDefinition<{ serviceName: string }>, // errors { exact: true, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 0b4dcea5d12e00..b6924b95526996 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -7,7 +7,13 @@ import * as React from 'react'; import numeral from '@elastic/numeral'; import styled from 'styled-components'; import { useContext, useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiToolTip } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiToolTip, + EuiIconTip, +} from '@elastic/eui'; import { useFetcher } from '../../../../hooks/useFetcher'; import { I18LABELS } from '../translations'; import { useUxQuery } from '../hooks/useUxQuery'; @@ -70,11 +76,35 @@ export function ClientMetrics() { return ( + + + {I18LABELS.totalPageLoad} + + + } + isLoading={status !== 'success'} + /> + + {I18LABELS.backEnd} + + + } isLoading={status !== 'success'} /> @@ -82,7 +112,15 @@ export function ClientMetrics() { + {I18LABELS.frontEnd} + + + } isLoading={status !== 'success'} /> diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index 7a7c0518175551..58f00604b8fda1 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -15,7 +15,6 @@ import { EuiToolTip, } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { useFetcher } from '../../../../hooks/useFetcher'; @@ -102,11 +101,6 @@ export function JSErrors() { }); }; - const errorRate = - totalPageViews > 0 - ? ((data?.totalErrorPages ?? 0) / totalPageViews) * 100 - : 0; - const totalErrors = data?.totalErrors ?? 0; return ( @@ -133,20 +127,6 @@ export function JSErrors() { isLoading={status !== 'success'} /> - - - {' '} -

{I18LABELS.pageLoadDuration}

+

+ {I18LABELS.pageLoad} ({getPercentileLabel(percentile!)}) +

diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx index d1cfe1d63f88e2..f25761bd8abad8 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumHome.tsx @@ -30,6 +30,7 @@ export function RumHome() { wrap style={{ flexWrap: 'wrap-reverse' }} justifyContent="flexEnd" + gutterSize="s" > diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx index d9d3d232993717..7bd9b2c87814b6 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx @@ -10,6 +10,7 @@ import React, { useRef, useState, KeyboardEvent, + useEffect, } from 'react'; import { EuiFlexGroup, @@ -67,6 +68,7 @@ interface Props { searchValue: string; onClose: () => void; popoverIsOpen: boolean; + initialValue?: string; setPopoverIsOpen: React.Dispatch>; } @@ -80,6 +82,7 @@ export function SelectableUrlList({ onClose, popoverIsOpen, setPopoverIsOpen, + initialValue, }: Props) { const [darkMode] = useUiSetting$('theme:darkMode'); @@ -92,6 +95,9 @@ export function SelectableUrlList({ if (evt.key.toLowerCase() === 'enter') { onTermChange(); setPopoverIsOpen(false); + if (searchRef) { + searchRef.blur(); + } } }; @@ -126,6 +132,16 @@ export function SelectableUrlList({ } }; + useEffect(() => { + if (searchRef && initialValue) { + searchRef.value = initialValue; + } + + // only want to call it at initial render to set value + // coming from initial value/url + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchRef]); + const loadingMessage = ( @@ -165,12 +181,12 @@ export function SelectableUrlList({ renderOption={selectableRenderOptions} singleSelection={false} searchProps={{ - placeholder: I18LABELS.searchByUrl, isClearable: true, onFocus: searchOnFocus, onBlur: searchOnBlur, onInput: onSearchInput, inputRef: setSearchRef, + placeholder: I18LABELS.searchByUrl, }} listProps={{ rowHeight: 68, @@ -197,7 +213,7 @@ export function SelectableUrlList({ {searchValue}, icon: ( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx new file mode 100644 index 00000000000000..abafdf089748bf --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { createMemoryHistory } from 'history'; +import * as fetcherHook from '../../../../../../hooks/useFetcher'; +import { SelectableUrlList } from '../SelectableUrlList'; +import { render } from '../../../utils/test_helper'; + +describe('SelectableUrlList', () => { + it('it uses search term value from url', () => { + jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: {}, + status: fetcherHook.FETCH_STATUS.SUCCESS, + refetch: jest.fn(), + }); + + const customHistory = createMemoryHistory({ + initialEntries: ['/?searchTerm=blog'], + }); + + const { getByDisplayValue } = render( + , + { customHistory } + ); + expect(getByDisplayValue('blog')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index 661f4406990f65..f9aeb484cbdf98 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -16,6 +16,7 @@ import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; import { SelectableUrlList } from './SelectableUrlList'; import { UrlOption } from './RenderOption'; import { useUxQuery } from '../../hooks/useUxQuery'; +import { getPercentileLabel } from '../../UXMetrics/translations'; interface Props { onChange: (value: string[]) => void; @@ -26,13 +27,13 @@ export function URLSearch({ onChange: onFilterChange }: Props) { const { uiFilters, urlParams } = useUrlParams(); - const { searchTerm } = urlParams; + const { searchTerm, percentile } = urlParams; const [popoverIsOpen, setPopoverIsOpen] = useState(false); - const [searchValue, setSearchValue] = useState(''); + const [searchValue, setSearchValue] = useState(searchTerm ?? ''); - const [debouncedValue, setDebouncedValue] = useState(''); + const [debouncedValue, setDebouncedValue] = useState(searchTerm ?? ''); useDebounce( () => { @@ -44,12 +45,16 @@ export function URLSearch({ onChange: onFilterChange }: Props) { const updateSearchTerm = useCallback( (searchTermN: string) => { + const newQuery = { + ...toQuery(history.location.search), + searchTerm: searchTermN || undefined, + }; + if (!searchTermN) { + delete newQuery.searchTerm; + } const newLocation = { ...history.location, - search: fromQuery({ - ...toQuery(history.location.search), - searchTerm: searchTermN, - }), + search: fromQuery(newQuery), }; history.push(newLocation); }, @@ -100,12 +105,17 @@ export function URLSearch({ onChange: onFilterChange }: Props) { setCheckedUrls(clickedItems.map((item) => item.url)); }; + const percTitle = getPercentileLabel(percentile!); + const items: UrlOption[] = (data?.items ?? []).map((item) => ({ label: item.url, key: item.url, meta: [ I18LABELS.pageViews + ': ' + item.count, - I18LABELS.pageLoadDuration + ': ' + formatToSec(item.pld), + I18LABELS.pageLoadDuration + + ': ' + + formatToSec(item.pld) + + ` (${percTitle})`, ], url: item.url, checked: checkedUrls?.includes(item.url) ? 'on' : undefined, @@ -133,6 +143,7 @@ export function URLSearch({ onChange: onFilterChange }: Props) {

{I18LABELS.url}

+ {FCP_LABEL} + + + } isLoading={loading} />
@@ -77,7 +88,12 @@ export function KeyUXMetrics({ data, loading }: Props) { + {TBT_LABEL} + + + } isLoading={loading} />
@@ -85,11 +101,19 @@ export function KeyUXMetrics({ data, loading }: Props) { + {NO_OF_LONG_TASK} + + + } isLoading={status !== 'success'} /> @@ -97,7 +121,15 @@ export function KeyUXMetrics({ data, loading }: Props) { + {LONGEST_LONG_TASK} + + + } isLoading={status !== 'success'} /> @@ -105,7 +137,15 @@ export function KeyUXMetrics({ data, loading }: Props) { + {SUM_LONG_TASKS} + + + } isLoading={status !== 'success'} /> diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx index cced16691b712e..3a6323a747a702 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx @@ -19,7 +19,7 @@ describe('KeyUXMetrics', () => { status: fetcherHook.FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); - const { getByText } = render( + const { getAllByText } = render( { lcpRanks: [69, 17, 14], fidRanks: [83, 6, 11], clsRanks: [90, 7, 3], + coreVitalPages: 1000, }} /> ); - expect(getByText('Longest long task duration 271 ms')).toBeInTheDocument(); - expect(getByText('Total long tasks duration 520 ms')).toBeInTheDocument(); - expect(getByText('No. of long tasks 3')).toBeInTheDocument(); - expect(getByText('Total blocking time 271 ms')).toBeInTheDocument(); - expect(getByText('First contentful paint 1.27 s')).toBeInTheDocument(); + const checkText = (text: string) => { + return (content: any, node: any) => { + return node?.textContent?.includes(text); + }; + }; + + expect( + getAllByText(checkText('Longest long task duration271 ms'))[0] + ).toBeInTheDocument(); + expect( + getAllByText(checkText('Total long tasks duration520 ms'))[0] + ).toBeInTheDocument(); + expect( + getAllByText(checkText('No. of long tasks3'))[0] + ).toBeInTheDocument(); + expect( + getAllByText(checkText('Total blocking time271 ms'))[0] + ).toBeInTheDocument(); + expect( + getAllByText(checkText('First contentful paint1.27 s'))[0] + ).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 9ff524db30366b..983e3be1c21a9f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -18,8 +18,15 @@ import { KeyUXMetrics } from './KeyUXMetrics'; import { useFetcher } from '../../../../hooks/useFetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { CoreVitals } from '../../../../../../observability/public'; +import { CsmSharedContext } from '../CsmSharedContext'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { getPercentileLabel } from './translations'; export function UXMetrics() { + const { + urlParams: { percentile }, + } = useUrlParams(); + const uxQuery = useUxQuery(); const { data, status } = useFetcher( @@ -37,12 +44,18 @@ export function UXMetrics() { [uxQuery] ); + const { + sharedData: { totalPageViews }, + } = useContext(CsmSharedContext); + return ( - -

{I18LABELS.userExperienceMetrics}

+ +

+ {I18LABELS.metrics} ({getPercentileLabel(percentile!)}) +

@@ -54,7 +67,12 @@ export function UXMetrics() { - +
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts index 8f3a71f669ecf2..3795f2f102237d 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/translations.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { I18LABELS } from '../translations'; export const DATA_UNDEFINED_LABEL = i18n.translate( 'xpack.apm.rum.coreVitals.dataUndefined', @@ -17,10 +18,26 @@ export const FCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fcp', { defaultMessage: 'First contentful paint', }); +export const FCP_TOOLTIP = i18n.translate( + 'xpack.apm.rum.coreVitals.fcpTooltip', + { + defaultMessage: + 'First contentful paint (FCP) focusses on the initial rendering and measures the time from when the page starts loading to when any part of the page’s content is displayed on the screen.', + } +); + export const TBT_LABEL = i18n.translate('xpack.apm.rum.coreVitals.tbt', { defaultMessage: 'Total blocking time', }); +export const TBT_TOOLTIP = i18n.translate( + 'xpack.apm.rum.coreVitals.tbtTooltip', + { + defaultMessage: + 'Total blocking time (TBT) is the sum of the blocking time (duration above 50 ms) for each long task that occurs between the First contentful paint and the time when the transaction is completed.', + } +); + export const NO_OF_LONG_TASK = i18n.translate( 'xpack.apm.rum.uxMetrics.noOfLongTasks', { @@ -28,6 +45,14 @@ export const NO_OF_LONG_TASK = i18n.translate( } ); +export const NO_OF_LONG_TASK_TOOLTIP = i18n.translate( + 'xpack.apm.rum.uxMetrics.noOfLongTasksTooltip', + { + defaultMessage: + 'The number of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', + } +); + export const LONGEST_LONG_TASK = i18n.translate( 'xpack.apm.rum.uxMetrics.longestLongTasks', { @@ -35,9 +60,36 @@ export const LONGEST_LONG_TASK = i18n.translate( } ); +export const LONGEST_LONG_TASK_TOOLTIP = i18n.translate( + 'xpack.apm.rum.uxMetrics.longestLongTasksTooltip', + { + defaultMessage: + 'The duration of the longest long task, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', + } +); + export const SUM_LONG_TASKS = i18n.translate( 'xpack.apm.rum.uxMetrics.sumLongTasks', { defaultMessage: 'Total long tasks duration', } ); + +export const SUM_LONG_TASKS_TOOLTIP = i18n.translate( + 'xpack.apm.rum.uxMetrics.sumLongTasksTooltip', + { + defaultMessage: + 'The total duration of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', + } +); + +export const getPercentileLabel = (value: number) => { + if (value === 50) return I18LABELS.median; + + return i18n.translate('xpack.apm.ux.percentiles.label', { + defaultMessage: '{value}th Perc.', + values: { + value, + }, + }); +}; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx index 18cd7d79cc69f2..04c7e3cc00287f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx @@ -45,13 +45,11 @@ export function UserPercentile() { { value: '50', text: I18LABELS.percentile50thMedian, - dropdownDisplay: I18LABELS.percentile50thMedian, 'data-test-subj': 'p50Percentile', }, { value: '75', text: I18LABELS.percentile75th, - dropdownDisplay: I18LABELS.percentile75th, 'data-test-subj': 'p75Percentile', }, { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts index a8c4d67305c989..75df1381d8a1d5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/translations.ts @@ -10,6 +10,9 @@ export const I18LABELS = { dataMissing: i18n.translate('xpack.apm.rum.dashboard.dataMissing', { defaultMessage: 'N/A', }), + totalPageLoad: i18n.translate('xpack.apm.rum.dashboard.totalPageLoad', { + defaultMessage: 'Total', + }), backEnd: i18n.translate('xpack.apm.rum.dashboard.backend', { defaultMessage: 'Backend', }), @@ -17,7 +20,7 @@ export const I18LABELS = { defaultMessage: 'Frontend', }), pageViews: i18n.translate('xpack.apm.rum.dashboard.pageViews', { - defaultMessage: 'Page views', + defaultMessage: 'Total page views', }), percPageLoaded: i18n.translate('xpack.apm.rum.dashboard.pagesLoaded.label', { defaultMessage: 'Pages loaded', @@ -34,6 +37,9 @@ export const I18LABELS = { defaultMessage: 'Page load duration', } ), + pageLoad: i18n.translate('xpack.apm.rum.dashboard.pageLoad.label', { + defaultMessage: 'Page load', + }), pageLoadDistribution: i18n.translate( 'xpack.apm.rum.dashboard.pageLoadDistribution.label', { @@ -79,8 +85,11 @@ export const I18LABELS = { defaultMessage: 'Operating system', } ), - userExperienceMetrics: i18n.translate('xpack.apm.rum.userExperienceMetrics', { - defaultMessage: 'User experience metrics', + metrics: i18n.translate('xpack.apm.ux.metrics', { + defaultMessage: 'Metrics', + }), + median: i18n.translate('xpack.apm.ux.median', { + defaultMessage: 'median', }), avgPageLoadDuration: i18n.translate( 'xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration', @@ -153,6 +162,21 @@ export const I18LABELS = { noData: i18n.translate('xpack.apm.ux.visitorBreakdown.noData', { defaultMessage: 'No data.', }), + // Helper tooltips + totalPageLoadTooltip: i18n.translate( + 'xpack.apm.rum.dashboard.tooltips.totalPageLoad', + { + defaultMessage: 'Total represents the full page load duration', + } + ), + frontEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.frontEnd', { + defaultMessage: + 'Frontend time represents the total page load duration minus the backend time', + }), + backEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.backEnd', { + defaultMessage: + 'Backend time represents time to first byte (TTFB), which is when the first response packet is received after the request has been made', + }), }; export const VisitorBreakdownLabel = i18n.translate( diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx new file mode 100644 index 00000000000000..5522cad5690bc3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render as testLibRender } from '@testing-library/react'; +import { CoreStart } from 'kibana/public'; +import { of } from 'rxjs'; +import { createMemoryHistory } from 'history'; +import { Router } from 'react-router-dom'; +import { MemoryHistory } from 'history'; +import { EuiThemeProvider } from '../../../../../../observability/public'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; +import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; + +export const core = ({ + http: { + basePath: { + prepend: jest.fn(), + }, + }, + uiSettings: { + get: (key: string) => true, + get$: (key: string) => of(true), + }, +} as unknown) as CoreStart; + +export const render = ( + component: React.ReactNode, + options: { customHistory: MemoryHistory } +) => { + const history = options?.customHistory ?? createMemoryHistory(); + + history.location.key = 'TestKeyForTesting'; + + return testLibRender( + + + + {component} + + + + ); +}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx index cbb6d9a8fbe414..d51e4a2dd3d7c2 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceDetailTabs.tsx @@ -8,6 +8,7 @@ import { EuiTabs } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name'; +import { enableServiceOverview } from '../../../../common/ui_settings_keys'; import { useAgentName } from '../../../hooks/useAgentName'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { EuiTabLink } from '../../shared/EuiTabLink'; @@ -15,21 +16,41 @@ import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; import { MetricOverviewLink } from '../../shared/Links/apm/MetricOverviewLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { ServiceNodeOverviewLink } from '../../shared/Links/apm/ServiceNodeOverviewLink'; +import { ServiceOverviewLink } from '../../shared/Links/apm/service_overview_link'; import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; import { ErrorGroupOverview } from '../ErrorGroupOverview'; import { ServiceMap } from '../ServiceMap'; import { ServiceMetrics } from '../ServiceMetrics'; import { ServiceNodeOverview } from '../ServiceNodeOverview'; +import { ServiceOverview } from '../service_overview'; import { TransactionOverview } from '../TransactionOverview'; interface Props { serviceName: string; - tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map'; + tab: + | 'errors' + | 'metrics' + | 'nodes' + | 'overview' + | 'service-map' + | 'transactions'; } export function ServiceDetailTabs({ serviceName, tab }: Props) { const { agentName } = useAgentName(); - const { serviceMapEnabled } = useApmPluginContext().config; + const { uiSettings } = useApmPluginContext().core; + + const overviewTab = { + link: ( + + {i18n.translate('xpack.apm.serviceDetails.overviewTabLabel', { + defaultMessage: 'Overview', + })} + + ), + render: () => , + name: 'overview', + }; const transactionsTab = { link: ( @@ -57,7 +78,23 @@ export function ServiceDetailTabs({ serviceName, tab }: Props) { name: 'errors', }; - const tabs = [transactionsTab, errorsTab]; + const serviceMapTab = { + link: ( + + {i18n.translate('xpack.apm.home.serviceMapTabLabel', { + defaultMessage: 'Service Map', + })} + + ), + render: () => , + name: 'service-map', + }; + + const tabs = [transactionsTab, errorsTab, serviceMapTab]; + + if (uiSettings.get(enableServiceOverview)) { + tabs.unshift(overviewTab); + } if (isJavaAgentName(agentName)) { const nodesListTab = { @@ -89,22 +126,6 @@ export function ServiceDetailTabs({ serviceName, tab }: Props) { tabs.push(metricsTab); } - const serviceMapTab = { - link: ( - - {i18n.translate('xpack.apm.home.serviceMapTabLabel', { - defaultMessage: 'Service Map', - })} - - ), - render: () => , - name: 'service-map', - }; - - if (serviceMapEnabled) { - tabs.push(serviceMapTab); - } - const selectedTab = tabs.find((serviceTab) => serviceTab.name === tab); return ( diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index d167b6a9a05659..752f9b7fda243e 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -10,6 +10,7 @@ import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, isActivePlatinumLicense, + SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { useLicense } from '../../../hooks/useLicense'; @@ -22,6 +23,7 @@ import { Cytoscape } from './Cytoscape'; import { getCytoscapeDivStyle } from './cytoscape_options'; import { EmptyBanner } from './EmptyBanner'; import { EmptyPrompt } from './empty_prompt'; +import { TimeoutPrompt } from './timeout_prompt'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; @@ -61,7 +63,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { const license = useLicense(); const { urlParams } = useUrlParams(); - const { data = { elements: [] }, status } = useFetcher(() => { + const { data = { elements: [] }, status, error } = useFetcher(() => { // When we don't have a license or a valid license, don't make the request. if (!license || !isActivePlatinumLicense(license)) { return; @@ -109,6 +111,20 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { ); } + if ( + status === FETCH_STATUS.FAILURE && + error && + 'body' in error && + error.body.statusCode === 500 && + error.body.message === SERVICE_MAP_TIMEOUT_ERROR + ) { + return ( + + + + ); + } + return (
+ {i18n.translate('xpack.apm.serviceMap.timeoutPromptTitle', { + defaultMessage: 'Service map timeout', + })} + + } + body={ +

+ {i18n.translate('xpack.apm.serviceMap.timeoutPromptDescription', { + defaultMessage: `Timed out while fetching data for service map. Limit the scope by selecting a smaller time range, or use configuration setting '{configName}' with a reduced value.`, + values: { + configName: isGlobalServiceMap + ? 'xpack.apm.serviceMapFingerprintGlobalBucketSize' + : 'xpack.apm.serviceMapFingerprintBucketSize', + }, + })} +

+ } + actions={} + /> + ); +} + +function ApmSettingsDocLink() { + return ( + + {i18n.translate('xpack.apm.serviceMap.timeoutPrompt.docsLink', { + defaultMessage: 'Learn more about APM settings in the docs', + })} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx deleted file mode 100644 index 21681f42d4b52f..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx +++ /dev/null @@ -1,26 +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 { NoServicesMessage } from '../NoServicesMessage'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; - -describe('NoServicesMessage', () => { - Object.values(FETCH_STATUS).forEach((status) => { - [true, false].forEach((historicalDataFound) => { - it(`status: ${status} and historicalDataFound: ${historicalDataFound}`, () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap deleted file mode 100644 index d027422961c99d..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NoServicesMessage status: failure and historicalDataFound: false 1`] = ``; - -exports[`NoServicesMessage status: failure and historicalDataFound: true 1`] = ``; - -exports[`NoServicesMessage status: loading and historicalDataFound: false 1`] = ``; - -exports[`NoServicesMessage status: loading and historicalDataFound: true 1`] = ``; - -exports[`NoServicesMessage status: pending and historicalDataFound: false 1`] = ` - - } - body={ - -

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- } - title={ -
- Looks like you don't have any APM services installed. Let's add some! -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: pending and historicalDataFound: true 1`] = ` - - No services found -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: success and historicalDataFound: false 1`] = ` - - } - body={ - -

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- } - title={ -
- Looks like you don't have any APM services installed. Let's add some! -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: success and historicalDataFound: true 1`] = ` - - No services found -
- } - titleSize="s" -/> -`; diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap deleted file mode 100644 index 49f30910ee0f10..00000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap +++ /dev/null @@ -1,690 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Service Overview -> View should render empty message, when list is empty and historical data is found 1`] = ` -NodeList [ - - -
- -
- -
- No services found -
-
- -
-
-
- - , -] -`; - -exports[`Service Overview -> View should render getting started message, when list is empty and no historical data is found 1`] = ` -NodeList [ - - -
- -
- -
- Looks like you don't have any APM services installed. Let's add some! -
-
-
-

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- - - - , -] -`; - -exports[`Service Overview -> View should render services, when list is not empty 1`] = ` -NodeList [ - .c1 { - font-size: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.c0 { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip .apmServiceList__serviceNameContainer { - width: calc(100% - 24px); -} - - - -
- Health -
-
- - - - Warning - - - -
- - -
- Name -
-
- - - - - -
- - -
- Environment -
-
- - - - - 2 environments - - - - -
- - -
- Avg. response time -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 ms -
-
-
- - -
- Trans. per minute -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 tpm -
-
-
- - -
- Error rate % -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
-
-
- - , - .c1 { - font-size: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.c0 { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip .apmServiceList__serviceNameContainer { - width: calc(100% - 24px); -} - - - -
- Health -
-
- - - - Unknown - - - -
- - -
- Name -
-
- - -
-
- go -
- -
-
-
-
- - -
- Environment -
-
- - -
- Avg. response time -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 ms -
-
-
- - -
- Trans. per minute -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 tpm -
-
-
- - -
- Error rate % -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
-
-
- - , -] -`; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index 8e32c55da9161c..dfc78028c35968 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -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 { EuiButton, EuiFlexGroup, @@ -37,6 +36,14 @@ export function AgentConfigurations() { return ( <> + +

+ {i18n.translate('xpack.apm.agentConfig.titleText', { + defaultMessage: 'Agent remote configuration', + })} +

+
+ @@ -44,7 +51,7 @@ export function AgentConfigurations() {

{i18n.translate( 'xpack.apm.agentConfig.configurationsPanelTitle', - { defaultMessage: 'Agent remote configuration' } + { defaultMessage: 'Configurations' } )}

diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx index 68e75d595363d9..53794ca9965ff8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx @@ -24,11 +24,11 @@ describe('ApmIndices', () => { ); expect(getByText('Indices')).toMatchInlineSnapshot(` -

Indices -

+ `); expect(spy).toHaveBeenCalledTimes(2); diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 145fb9683cb610..fac947b3ec68eb 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -163,23 +163,24 @@ export function ApmIndices() { }; return ( - - - - -

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

-
- - -

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

+ <> + +

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

+
+ + + {i18n.translate('xpack.apm.settings.apmIndices.description', { + defaultMessage: `The APM UI uses index patterns to query your APM indices. If you've customized the index names that APM Server writes events to, you may need to update these patterns for the APM UI to work. Settings here take precedence over those set in kibana.yml.`, + })} + + + + + {APM_INDEX_LABELS.map(({ configurationName, label }) => { const matchedConfiguration = data.find( @@ -239,11 +240,10 @@ export function ApmIndices() { -
-
-
- - -
+
+
+ +
+ ); } diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx index 2e860ebe22c0fc..56b3eaf425af7f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateCustomLinkButton.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export function CreateCustomLinkButton({ onClick }: { onClick: () => void }) { return ( - + {i18n.translate( 'xpack.apm.settings.customizeUI.customLink.createCustomLink', { defaultMessage: 'Create custom link' } diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx index 22d8749d788346..2017aa42e1c5a6 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx @@ -3,7 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiTitle } from '@elastic/eui'; + +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -11,28 +12,14 @@ export function Title() { return ( - + -

+

{i18n.translate('xpack.apm.settings.customizeUI.customLink', { defaultMessage: 'Custom Links', })} -

-
- - - +
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index 45a7fa2a118f2c..a7d7cf40ba849a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -4,19 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { INVALID_LICENSE } from '../../../../../../common/custom_link'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher'; import { useLicense } from '../../../../../hooks/useLicense'; -import { useFetcher, FETCH_STATUS } from '../../../../../hooks/useFetcher'; +import { LicensePrompt } from '../../../../shared/LicensePrompt'; +import { CreateCustomLinkButton } from './CreateCustomLinkButton'; import { CustomLinkFlyout } from './CustomLinkFlyout'; import { CustomLinkTable } from './CustomLinkTable'; import { EmptyPrompt } from './EmptyPrompt'; import { Title } from './Title'; -import { CreateCustomLinkButton } from './CreateCustomLinkButton'; -import { LicensePrompt } from '../../../../shared/LicensePrompt'; export function CustomLinkOverview() { const license = useLicense(); @@ -82,8 +89,13 @@ export function CustomLinkOverview() {
)}
- - + + + {i18n.translate('xpack.apm.settings.customizeUI.customLink.info', { + defaultMessage: + 'These links will be shown in the Actions context menu for transactions.', + })} + {hasValidLicense ? ( showEmptyPrompt ? ( diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx index 6e95df0dddd840..137dcfcdbb4f07 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx @@ -80,7 +80,7 @@ export function JobsList({ data, status, onAddEnvironments }: Props) { - + {i18n.translate( 'xpack.apm.settings.anomalyDetection.jobList.addEnvironments', { diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index aa710508022156..e770116ac27593 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -36,8 +36,8 @@ export function Settings({ children, location }: SettingsProps) { <> - {i18n.translate('xpack.apm.settings.returnToOverviewLinkLabel', { - defaultMessage: 'Return to overview', + {i18n.translate('xpack.apm.settings.returnLinkLabel', { + defaultMessage: 'Return to inventory', })} diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx index dd632db0f15fec..67eb22d48aae61 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx @@ -15,7 +15,7 @@ import { APMLink } from '../../../shared/Links/apm/APMLink'; export function MLCallout({ onDismiss }: { onDismiss: () => void }) { return ( - {i18n.translate('xpack.apm.serviceOverview.toastText', { + {i18n.translate('xpack.apm.serviceInventory.toastText', { defaultMessage: "You're running Elastic Stack 7.0+ and we've detected incompatible data from a previous 6.x version. If you want to view this data in APM, you should migrate it. See more in ", })} @@ -74,7 +74,7 @@ export function ServiceOverview() { })} > {i18n.translate( - 'xpack.apm.serviceOverview.upgradeAssistantLink', + 'xpack.apm.serviceInventory.upgradeAssistantLinkText', { defaultMessage: 'the upgrade assistant', } @@ -86,6 +86,10 @@ export function ServiceOverview() { } }, [data.hasLegacyData, core.http.basePath, core.notifications.toasts]); + // The page is called "service inventory" to avoid confusion with the + // "service overview", but this is tracked in some dashboards because it's the + // initial landing page for APM, so it stays as "services_overview" (plural.) + // for backward compatibility. useTrackPageview({ app: 'apm', path: 'services_overview' }); useTrackPageview({ app: 'apm', path: 'services_overview', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx new file mode 100644 index 00000000000000..0fc2a2b4cdcef0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { NoServicesMessage } from './no_services_message'; + +function Wrapper({ children }: { children?: ReactNode }) { + return {children}; +} + +describe('NoServicesMessage', () => { + Object.values(FETCH_STATUS).forEach((status) => { + [true, false].forEach((historicalDataFound) => { + describe(`when status is ${status}`, () => { + describe(`when historicalDataFound is ${historicalDataFound}`, () => { + it('renders', () => { + expect(() => + render( + , + { wrapper: Wrapper } + ) + ).not.toThrowError(); + }); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx similarity index 79% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 06e9008d5aebed..d394c7db625545 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -7,22 +7,22 @@ import { render, waitFor } from '@testing-library/react'; import { CoreStart } from 'kibana/public'; import { merge } from 'lodash'; -import React, { FunctionComponent, ReactChild } from 'react'; +import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import { ServiceHealthStatus } from '../../../../../common/service_health_status'; -import { ServiceOverview } from '..'; -import { EuiThemeProvider } from '../../../../../../observability/public'; -import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; +import { ServiceHealthStatus } from '../../../../common/service_health_status'; +import { ServiceInventory } from '.'; +import { EuiThemeProvider } from '../../../../../observability/public'; +import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useAnomalyDetectionJobs from '../../../../hooks/useAnomalyDetectionJobs'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; -import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters'; -import * as urlParamsHooks from '../../../../hooks/useUrlParams'; -import { SessionStorageMock } from '../../../../services/__test__/SessionStorageMock'; +} from '../../../context/ApmPluginContext/MockApmPluginContext'; +import * as useAnomalyDetectionJobs from '../../../hooks/useAnomalyDetectionJobs'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; +import * as urlParamsHooks from '../../../hooks/useUrlParams'; +import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, @@ -31,7 +31,7 @@ const KibanaReactContext = createKibanaReactContext({ const addWarning = jest.fn(); const httpGet = jest.fn(); -function wrapper({ children }: { children: ReactChild }) { +function wrapper({ children }: { children?: ReactNode }) { const mockPluginContext = (merge({}, mockApmPluginContextValue, { core: { http: { @@ -58,13 +58,7 @@ function wrapper({ children }: { children: ReactChild }) { ); } -function renderServiceOverview() { - return render(, { wrapper } as { - wrapper: FunctionComponent<{}>; - }); -} - -describe('Service Overview -> View', () => { +describe('ServiceInventory', () => { beforeEach(() => { // @ts-expect-error global.sessionStorage = new SessionStorageMock(); @@ -129,13 +123,13 @@ describe('Service Overview -> View', () => { ], }); - const { container, findByText } = renderServiceOverview(); + const { container, findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); await findByText('My Python Service'); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2); }); it('should render getting started message, when list is empty and no historical data is found', async () => { @@ -145,17 +139,17 @@ describe('Service Overview -> View', () => { items: [], }); - const { container, findByText } = renderServiceOverview(); + const { findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); // wait for elements to be rendered - await findByText( + const gettingStartedMessage = await findByText( "Looks like you don't have any APM services installed. Let's add some!" ); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(gettingStartedMessage).not.toBeEmpty(); }); it('should render empty message, when list is empty and historical data is found', async () => { @@ -165,13 +159,13 @@ describe('Service Overview -> View', () => { items: [], }); - const { container, findByText } = renderServiceOverview(); + const { findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); - await findByText('No services found'); + const noServicesText = await findByText('No services found'); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(noServicesText).not.toBeEmpty(); }); describe('when legacy data is found', () => { @@ -182,7 +176,7 @@ describe('Service Overview -> View', () => { items: [], }); - renderServiceOverview(); + render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -203,7 +197,7 @@ describe('Service Overview -> View', () => { items: [], }); - renderServiceOverview(); + render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -229,7 +223,7 @@ describe('Service Overview -> View', () => { ], }); - const { queryByText } = renderServiceOverview(); + const { queryByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -256,7 +250,7 @@ describe('Service Overview -> View', () => { ], }); - const { queryAllByText } = renderServiceOverview(); + const { queryAllByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx new file mode 100644 index 00000000000000..81f23b64275082 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -0,0 +1,246 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { useTrackPageview } from '../../../../../observability/public'; +import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink'; +import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; +import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink'; + +const rowHeight = 310; +const latencyChartRowHeight = 230; + +const Row = styled(EuiFlexItem)` + height: ${rowHeight}px; +`; + +const LatencyChartRow = styled(EuiFlexItem)` + height: ${latencyChartRowHeight}px; +`; + +const TableLinkFlexItem = styled(EuiFlexItem)` + & > a { + text-align: right; + } +`; + +interface ServiceOverviewProps { + serviceName: string; +} + +export function ServiceOverview({ serviceName }: ServiceOverviewProps) { + useTrackPageview({ app: 'apm', path: 'service_overview' }); + useTrackPageview({ app: 'apm', path: 'service_overview', delay: 15000 }); + + return ( + + + + + Search bar + + + Comparison picker + + + Date picker + + + + + + +

+ {i18n.translate('xpack.apm.serviceOverview.latencyChartTitle', { + defaultMessage: 'Latency', + })} +

+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.trafficChartTitle', + { + defaultMessage: 'Traffic', + } + )} +

+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableTitle', + { + defaultMessage: 'Transactions', + } + )} +

+
+
+ + + {i18n.translate( + 'xpack.apm.serviceOverview.transactionsTableLinkText', + { + defaultMessage: 'View transactions', + } + )} + + +
+
+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.errorRateChartTitle', + { + defaultMessage: 'Error rate', + } + )} +

+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.errorsTableTitle', + { + defaultMessage: 'Errors', + } + )} +

+
+
+ + + {i18n.translate( + 'xpack.apm.serviceOverview.errorsTableLinkText', + { + defaultMessage: 'View errors', + } + )} + + +
+
+
+
+
+ + + + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.averageDurationBySpanTypeChartTitle', + { + defaultMessage: 'Average duration by span type', + } + )} +

+
+
+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.dependenciesTableTitle', + { + defaultMessage: 'Dependencies', + } + )} +

+
+
+ + + {i18n.translate( + 'xpack.apm.serviceOverview.dependenciesTableLinkText', + { + defaultMessage: 'View service map', + } + )} + + +
+
+
+
+
+ + + + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.instancesLatencyDistributionChartTitle', + { + defaultMessage: 'Instances latency distribution', + } + )} +

+
+
+
+ + + +

+ {i18n.translate( + 'xpack.apm.serviceOverview.instancesTableTitle', + { + defaultMessage: 'Instances', + } + )} +

+
+
+
+
+
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx new file mode 100644 index 00000000000000..4e2063930a9c9d --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { ServiceOverview } from './'; + +function Wrapper({ children }: { children?: ReactNode }) { + return ( + + {children} + + ); +} + +describe('ServiceOverview', () => { + it('renders', () => { + expect(() => + render(, { + wrapper: Wrapper, + }) + ).not.toThrowError(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx similarity index 90% rename from x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx rename to x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx index 2081fc47679037..e3fa03a4d4f86a 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx @@ -14,7 +14,7 @@ import { APMLink, APMLinkExtendProps } from './APMLink'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -function ServiceOverviewLink(props: APMLinkExtendProps) { +function ServiceInventoryLink(props: APMLinkExtendProps) { const { urlParams } = useUrlParams(); const persistedFilters = pickKeys(urlParams, 'host', 'agentName'); @@ -22,4 +22,4 @@ function ServiceOverviewLink(props: APMLinkExtendProps) { return ; } -export { ServiceOverviewLink }; +export { ServiceInventoryLink }; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/service_overview_link.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_overview_link.tsx new file mode 100644 index 00000000000000..5d7859e7362c72 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_overview_link.tsx @@ -0,0 +1,23 @@ +/* + * 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. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { APMLink, APMLinkExtendProps } from './APMLink'; + +interface ServiceOverviewLinkProps extends APMLinkExtendProps { + serviceName: string; +} + +export function ServiceOverviewLink({ + serviceName, + ...rest +}: ServiceOverviewLinkProps) { + return ; +} diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/plugins/apm/public/hooks/useFetcher.tsx index 5d65424844c5ae..6add0e8a2b4802 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/useFetcher.tsx @@ -21,7 +21,7 @@ export enum FETCH_STATUS { export interface FetcherResult { data?: Data; status: FETCH_STATUS; - error?: Error; + error?: IHttpFetchError; } // fetcher functions can return undefined OR a promise. Previously we had a more simple type diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 560a1a077931bc..cc0151afba63c8 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -81,14 +81,14 @@ export class ApmPlugin implements Plugin { if (plugins.observability) { const getApmDataHelper = async () => { const { - fetchOverviewPageData, + fetchObservabilityOverviewPageData, hasData, createCallApmApi, - } = await import('./services/rest/apm_overview_fetchers'); + } = await import('./services/rest/apm_observability_overview_fetchers'); // have to do this here as well in case app isn't mounted yet createCallApmApi(core.http); - return { fetchOverviewPageData, hasData }; + return { fetchObservabilityOverviewPageData, hasData }; }; plugins.observability.dashboard.register({ appName: 'apm', @@ -98,7 +98,7 @@ export class ApmPlugin implements Plugin { }, fetchData: async (params: FetchDataParams) => { const dataHelper = await getApmDataHelper(); - return await dataHelper.fetchOverviewPageData(params); + return await dataHelper.fetchObservabilityOverviewPageData(params); }, }); diff --git a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts similarity index 89% rename from x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts rename to x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts index 4e306c93805d05..22ec317fca64bc 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts @@ -5,7 +5,10 @@ */ import moment from 'moment'; -import { fetchOverviewPageData, hasData } from './apm_overview_fetchers'; +import { + fetchObservabilityOverviewPageData, + hasData, +} from './apm_observability_overview_fetchers'; import * as createCallApmApi from './createCallApmApi'; describe('Observability dashboard data', () => { @@ -37,7 +40,7 @@ describe('Observability dashboard data', () => { }); }); - describe('fetchOverviewPageData', () => { + describe('fetchObservabilityOverviewPageData', () => { it('returns APM data with series and stats', async () => { callApmApiMock.mockImplementation(() => Promise.resolve({ @@ -49,7 +52,7 @@ describe('Observability dashboard data', () => { ], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { @@ -80,7 +83,7 @@ describe('Observability dashboard data', () => { transactionCoordinates: [], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { @@ -107,7 +110,7 @@ describe('Observability dashboard data', () => { transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { diff --git a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts similarity index 96% rename from x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts rename to x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index 422c7b882e5dc4..bc1db4eed1d9e6 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -13,7 +13,7 @@ import { callApmApi } from './createCallApmApi'; export { createCallApmApi } from './createCallApmApi'; -export const fetchOverviewPageData = async ({ +export const fetchObservabilityOverviewPageData = async ({ absoluteTime, relativeTime, bucketSize, diff --git a/x-pack/plugins/apm/public/setHelpExtension.ts b/x-pack/plugins/apm/public/setHelpExtension.ts index b6e745dcf16559..f895fbc36ed031 100644 --- a/x-pack/plugins/apm/public/setHelpExtension.ts +++ b/x-pack/plugins/apm/public/setHelpExtension.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import url from 'url'; import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; @@ -20,10 +19,7 @@ export function setHelpExtension({ chrome, http }: CoreStart) { }, { linkType: 'custom', - href: url.format({ - pathname: http.basePath.prepend('/app/kibana'), - hash: '/management/stack/upgrade_assistant', - }), + href: http.basePath.prepend('/app/management/stack/upgrade_assistant'), content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', { defaultMessage: 'Upgrade assistant', }), diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md index d6fdb5f52291ce..0adfb99e7164e9 100644 --- a/x-pack/plugins/apm/readme.md +++ b/x-pack/plugins/apm/readme.md @@ -1,8 +1,8 @@ # Documentation for APM UI developers -### Setup local environment +## Local environment setup -#### Kibana +### Kibana ``` git clone git@github.com:elastic/kibana.git @@ -11,15 +11,15 @@ yarn kbn bootstrap yarn start --no-base-path ``` -#### APM Server, Elasticsearch and data +### APM Server, Elasticsearch and data To access an elasticsearch instance that has live data you have two options: -##### A. Connect to Elasticsearch on Cloud (internal devs only) +#### A. Connect to Elasticsearch on Cloud (internal devs only) Find the credentials for the cluster [here](https://github.com/elastic/apm-dev/blob/master/docs/credentials/apm-ui-clusters.md#apmelstcco) -##### B. Start Elastic Stack and APM data generators +#### B. Start Elastic Stack and APM data generators ``` git clone git@github.com:elastic/apm-integration-testing.git @@ -29,6 +29,8 @@ cd apm-integration-testing/ _Docker Compose is required_ +## Testing + ### E2E (Cypress) tests ```sh @@ -109,23 +111,23 @@ The API tests for "trial" are located in `x-pack/test/apm_api_integration/trial/ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) -### Linting +## Linting _Note: Run the following commands from `kibana/`._ -#### Prettier +### Prettier ``` yarn prettier "./x-pack/plugins/apm/**/*.{tsx,ts,js}" --write ``` -#### ESLint +### ESLint ``` yarn eslint ./x-pack/plugins/apm --fix ``` -### Setup default APM users +## Setup default APM users APM behaves differently depending on which the role and permissions a logged in user has. For testing purposes APM uses 3 custom users: @@ -144,20 +146,35 @@ node x-pack/plugins/apm/scripts/setup-kibana-security.js --role-suffix Advanced Settings > Observability. + +## Further resources - [Cypress integration tests](./e2e/README.md) - [VSCode setup instructions](./dev_docs/vscode_setup.md) diff --git a/x-pack/plugins/apm/scripts/precommit.js b/x-pack/plugins/apm/scripts/precommit.js index 87da3c1db8b28d..ec38ec88f55031 100644 --- a/x-pack/plugins/apm/scripts/precommit.js +++ b/x-pack/plugins/apm/scripts/precommit.js @@ -25,7 +25,7 @@ const tasks = new Listr( [ resolve(__dirname, './jest.js'), '--reporters', - resolve(__dirname, './node_modules/jest-silent-reporter'), + resolve(__dirname, '../../../../node_modules/jest-silent-reporter'), '--collect-coverage', 'false', ], diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap index 1fafa080824439..b89c46f6e3fc56 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap @@ -9,31 +9,35 @@ Object { }, "body": Object { "aggs": Object { - "backEnd": Object { - "percentiles": Object { - "field": "transaction.marks.agent.timeToFirstByte", - "hdr": Object { - "number_of_significant_value_digits": 3, + "hasFetchStartField": Object { + "aggs": Object { + "backEnd": Object { + "percentiles": Object { + "field": "transaction.marks.agent.timeToFirstByte", + "hdr": Object { + "number_of_significant_value_digits": 3, + }, + "percents": Array [ + 50, + ], + }, }, - "percents": Array [ - 50, - ], - }, - }, - "domInteractive": Object { - "percentiles": Object { - "field": "transaction.marks.agent.domInteractive", - "hdr": Object { - "number_of_significant_value_digits": 3, + "totalPageLoadDuration": Object { + "percentiles": Object { + "field": "transaction.duration.us", + "hdr": Object { + "number_of_significant_value_digits": 3, + }, + "percents": Array [ + 50, + ], + }, }, - "percents": Array [ - 50, - ], }, - }, - "pageViews": Object { - "value_count": Object { - "field": "transaction.duration.us", + "filter": Object { + "exists": Object { + "field": "transaction.marks.navigationTiming.fetchStart", + }, }, }, }, @@ -54,11 +58,6 @@ Object { "transaction.type": "page-load", }, }, - Object { - "exists": Object { - "field": "transaction.marks.navigationTiming.fetchStart", - }, - }, Object { "term": Object { "service.environment": "test", @@ -68,6 +67,7 @@ Object { }, }, "size": 0, + "track_total_hits": true, }, } `; @@ -139,11 +139,6 @@ Object { "agent.name": "rum-js", }, }, - Object { - "term": Object { - "transaction.type": "page-load", - }, - }, Object { "term": Object { "service.language.name": "javascript", @@ -545,11 +540,6 @@ Object { "transaction.type": "page-load", }, }, - Object { - "exists": Object { - "field": "transaction.marks.navigationTiming.fetchStart", - }, - }, Object { "term": Object { "service.environment": "test", @@ -590,6 +580,13 @@ Object { ], }, }, + "coreVitalPages": Object { + "filter": Object { + "exists": Object { + "field": "transaction.experience", + }, + }, + }, "fcp": Object { "percentiles": Object { "field": "transaction.marks.agent.firstContentfulPaint", @@ -670,11 +667,6 @@ Object { "service.environment": "test", }, }, - Object { - "term": Object { - "user_agent.name": "Chrome", - }, - }, ], }, }, diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts index 6d596246d6af92..6685a60f84f057 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_client_metrics.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TRANSACTION_DURATION } from '../../../common/elasticsearch_fieldnames'; import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { - TRANSACTION_DOM_INTERACTIVE, TRANSACTION_TIME_TO_FIRST_BYTE, + TRANSACTION_DURATION, } from '../../../common/elasticsearch_fieldnames'; export async function getClientMetrics({ @@ -25,32 +24,36 @@ export async function getClientMetrics({ const projection = getRumPageLoadTransactionsProjection({ setup, urlQuery, + checkFetchStartFieldExists: false, }); const params = mergeProjection(projection, { body: { size: 0, + track_total_hits: true, aggs: { - pageViews: { - value_count: { - field: TRANSACTION_DURATION, + hasFetchStartField: { + filter: { + exists: { field: 'transaction.marks.navigationTiming.fetchStart' }, }, - }, - backEnd: { - percentiles: { - field: TRANSACTION_TIME_TO_FIRST_BYTE, - percents: [percentile], - hdr: { - number_of_significant_value_digits: 3, + aggs: { + totalPageLoadDuration: { + percentiles: { + field: TRANSACTION_DURATION, + percents: [percentile], + hdr: { + number_of_significant_value_digits: 3, + }, + }, }, - }, - }, - domInteractive: { - percentiles: { - field: TRANSACTION_DOM_INTERACTIVE, - percents: [percentile], - hdr: { - number_of_significant_value_digits: 3, + backEnd: { + percentiles: { + field: TRANSACTION_TIME_TO_FIRST_BYTE, + percents: [percentile], + hdr: { + number_of_significant_value_digits: 3, + }, + }, }, }, }, @@ -59,18 +62,21 @@ export async function getClientMetrics({ }); const { apmEventClient } = setup; - const response = await apmEventClient.search(params); - const { backEnd, domInteractive, pageViews } = response.aggregations!; + const { + hasFetchStartField: { backEnd, totalPageLoadDuration }, + } = response.aggregations!; const pkey = percentile.toFixed(1); - // Divide by 1000 to convert ms into seconds + const totalPageLoadDurationValue = totalPageLoadDuration.values[pkey] ?? 0; + const totalPageLoadDurationValueMs = totalPageLoadDurationValue / 1000; // Microseconds to milliseconds + const backendValue = backEnd.values[pkey] ?? 0; + return { - pageViews, - backEnd: { value: backEnd.values[pkey] || 0 }, - frontEnd: { - value: (domInteractive.values[pkey] || 0) - (backEnd.values[pkey] || 0), - }, + pageViews: { value: response.hits.total.value ?? 0 }, + totalPageLoadDuration: { value: totalPageLoadDurationValueMs }, + backEnd: { value: backendValue }, + frontEnd: { value: totalPageLoadDurationValueMs - backendValue }, }; } diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts index c1a602c33feae1..f483acba4b9349 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts @@ -20,6 +20,7 @@ export async function getPageViewTrends({ const projection = getRumPageLoadTransactionsProjection({ setup, urlQuery, + checkFetchStartFieldExists: false, }); let breakdownItem: BreakdownItem | null = null; if (breakdowns) { diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts index c5baf0b529eb42..76a718bbb2a02a 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_web_core_vitals.ts @@ -12,7 +12,6 @@ import { FCP_FIELD, FID_FIELD, LCP_FIELD, - USER_AGENT_NAME, TBT_FIELD, } from '../../../common/elasticsearch_fieldnames'; @@ -35,17 +34,17 @@ export async function getWebCoreVitals({ size: 0, query: { bool: { - filter: [ - ...projection.body.query.bool.filter, - { - term: { - [USER_AGENT_NAME]: 'Chrome', - }, - }, - ], + filter: [...projection.body.query.bool.filter], }, }, aggs: { + coreVitalPages: { + filter: { + exists: { + field: 'transaction.experience', + }, + }, + }, lcp: { percentiles: { field: LCP_FIELD, @@ -104,13 +103,22 @@ export async function getWebCoreVitals({ const { apmEventClient } = setup; const response = await apmEventClient.search(params); - const { lcp, cls, fid, tbt, fcp, lcpRanks, fidRanks, clsRanks } = - response.aggregations ?? {}; + const { + lcp, + cls, + fid, + tbt, + fcp, + lcpRanks, + fidRanks, + clsRanks, + coreVitalPages, + } = response.aggregations ?? {}; const getRanksPercentages = ( - ranks: Array<{ key: number; value: number }> + ranks?: Array<{ key: number; value: number }> ) => { - const ranksVal = ranks.map(({ value }) => value?.toFixed(0) ?? 0); + const ranksVal = ranks?.map(({ value }) => value?.toFixed(0) ?? 0) ?? []; return [ Number(ranksVal?.[0]), Number(ranksVal?.[1]) - Number(ranksVal?.[0]), @@ -118,23 +126,26 @@ export async function getWebCoreVitals({ ]; }; - const defaultRanks = [ - { value: 0, key: 0 }, - { value: 0, key: 0 }, - ]; + const defaultRanks = [100, 0, 0]; const pkey = percentile.toFixed(1); - // Divide by 1000 to convert ms into seconds return { - cls: String(cls?.values[pkey]?.toFixed(2) || 0), - fid: fid?.values[pkey] ?? 0, - lcp: lcp?.values[pkey] ?? 0, + coreVitalPages: coreVitalPages?.doc_count ?? 0, + cls: cls?.values[pkey]?.toFixed(3) || null, + fid: fid?.values[pkey], + lcp: lcp?.values[pkey], tbt: tbt?.values[pkey] ?? 0, - fcp: fcp?.values[pkey] ?? 0, + fcp: fcp?.values[pkey], - lcpRanks: getRanksPercentages(lcpRanks?.values ?? defaultRanks), - fidRanks: getRanksPercentages(fidRanks?.values ?? defaultRanks), - clsRanks: getRanksPercentages(clsRanks?.values ?? defaultRanks), + lcpRanks: lcp?.values[pkey] + ? getRanksPercentages(lcpRanks?.values) + : defaultRanks, + fidRanks: fid?.values[pkey] + ? getRanksPercentages(fidRanks?.values) + : defaultRanks, + clsRanks: cls?.values[pkey] + ? getRanksPercentages(clsRanks?.values) + : defaultRanks, }; } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index dfc4e02c25a7f5..524b9bfdc78912 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { uniq, take, sortBy } from 'lodash'; +import Boom from 'boom'; import { ProcessorEvent } from '../../../common/processor_event'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { rangeFilter } from '../../../common/utils/range_filter'; @@ -15,6 +16,7 @@ import { SPAN_DESTINATION_SERVICE_RESOURCE, } from '../../../common/elasticsearch_fieldnames'; import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; const MAX_TRACES_TO_INSPECT = 1000; @@ -122,26 +124,30 @@ export async function getTraceSampleIds({ }, }; - const tracesSampleResponse = await apmEventClient.search(params); + try { + const tracesSampleResponse = await apmEventClient.search(params); + // make sure at least one trace per composite/connection bucket + // is queried + const traceIdsWithPriority = + tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) => + bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ + traceId: sampleDocBucket.key as string, + priority: index, + })) + ) || []; - // make sure at least one trace per composite/connection bucket - // is queried - const traceIdsWithPriority = - tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) => - bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ - traceId: sampleDocBucket.key as string, - priority: index, - })) - ) || []; + const traceIds = take( + uniq( + sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) + ), + MAX_TRACES_TO_INSPECT + ); - const traceIds = take( - uniq( - sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) - ), - MAX_TRACES_TO_INSPECT - ); - - return { - traceIds, - }; + return { traceIds }; + } catch (error) { + if ('displayName' in error && error.displayName === 'RequestTimeout') { + throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR); + } + throw error; + } } diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index b417f8689b2290..d3341b6c1b1638 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { i18n } from '@kbn/i18n'; import { combineLatest, Observable } from 'rxjs'; import { map, take } from 'rxjs/operators'; @@ -36,6 +37,7 @@ import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_cust import { createApmApi } from './routes/create_apm_api'; import { apmIndices, apmTelemetry } from './saved_objects'; import { createElasticCloudInstructions } from './tutorial/elastic_cloud'; +import { uiSettings } from './ui_settings'; export interface APMPluginSetup { config$: Observable; @@ -75,6 +77,8 @@ export class APMPlugin implements Plugin { core.savedObjects.registerType(apmIndices); core.savedObjects.registerType(apmTelemetry); + core.uiSettings.register(uiSettings); + if (plugins.actions && plugins.alerts) { registerApmAlerts({ alerts: plugins.alerts, diff --git a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts index 530ca36b5dc9f3..9c33da2278694c 100644 --- a/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts +++ b/x-pack/plugins/apm/server/projections/rum_page_load_transactions.ts @@ -17,9 +17,11 @@ import { TRANSACTION_PAGE_LOAD } from '../../common/transaction_types'; export function getRumPageLoadTransactionsProjection({ setup, urlQuery, + checkFetchStartFieldExists = true, }: { setup: Setup & SetupTimeRange; urlQuery?: string; + checkFetchStartFieldExists?: boolean; }) { const { start, end, esFilter } = setup; @@ -27,13 +29,17 @@ export function getRumPageLoadTransactionsProjection({ filter: [ { range: rangeFilter(start, end) }, { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, - { - // Adding this filter to cater for some inconsistent rum data - // not available on aggregated transactions - exists: { - field: 'transaction.marks.navigationTiming.fetchStart', - }, - }, + ...(checkFetchStartFieldExists + ? [ + { + // Adding this filter to cater for some inconsistent rum data + // not available on aggregated transactions + exists: { + field: 'transaction.marks.navigationTiming.fetchStart', + }, + }, + ] + : []), ...(urlQuery ? [ { @@ -74,7 +80,6 @@ export function getRumErrorsProjection({ filter: [ { range: rangeFilter(start, end) }, { term: { [AGENT_NAME]: 'rum-js' } }, - { term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } }, { term: { [SERVICE_LANGUAGE_NAME]: 'javascript', diff --git a/x-pack/plugins/apm/server/ui_settings.ts b/x-pack/plugins/apm/server/ui_settings.ts new file mode 100644 index 00000000000000..fe5b11d89d716d --- /dev/null +++ b/x-pack/plugins/apm/server/ui_settings.ts @@ -0,0 +1,48 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { UiSettingsParams } from '../../../../src/core/types'; +import { + enableCorrelations, + enableServiceOverview, +} from '../common/ui_settings_keys'; + +/** + * uiSettings definitions for APM. + */ +export const uiSettings: Record> = { + [enableCorrelations]: { + category: ['Observability'], + name: i18n.translate('xpack.apm.enableCorrelationsExperimentName', { + defaultMessage: 'APM Correlations', + }), + value: false, + description: i18n.translate( + 'xpack.apm.enableCorrelationsExperimentDescription', + { + defaultMessage: + 'Enable the experimental correlations UI and API endpoint in APM.', + } + ), + schema: schema.boolean(), + }, + [enableServiceOverview]: { + category: ['Observability'], + name: i18n.translate('xpack.apm.enableServiceOverviewExperimentName', { + defaultMessage: 'APM Service overview', + }), + value: false, + description: i18n.translate( + 'xpack.apm.enableServiceOverviewExperimentDescription', + { + defaultMessage: 'Enable the Overview tab for services in APM.', + } + ), + schema: schema.boolean(), + }, +}; diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts index 8b3163e44915a3..a869c1e1186fa0 100644 --- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts +++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts @@ -145,6 +145,15 @@ export interface AggregationOptionsByType { >; keyed?: boolean; } & AggregationSourceOptions; + range: { + field: string; + ranges: Array< + | { key?: string; from: string | number } + | { key?: string; to: string | number } + | { key?: string; from: string | number; to: string | number } + >; + keyed?: boolean; + }; auto_date_histogram: { buckets: number; } & AggregationSourceOptions; @@ -324,6 +333,18 @@ interface AggregationResponsePart< ? Record : { buckets: DateRangeBucket[] }; }; + range: { + buckets: TAggregationOptionsMap extends { range: { keyed: true } } + ? Record< + string, + DateRangeBucket & + SubAggregationResponseOf + > + : Array< + DateRangeBucket & + SubAggregationResponseOf + >; + }; auto_date_histogram: { buckets: Array< DateHistogramBucket & diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index d7c7cd9e1a32f0..9bdb66cc16c06a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -131,6 +131,7 @@ class ImageUpload extends React.Component { onChange={this.changeUrlType} isFullWidth className="canvasSidebar__buttonGroup" + legend={strings.getUrlTypeChangeLegend()} /> ); diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index 51c86f66043300..b7d25a32818f89 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -870,6 +870,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.alignRightOption', { defaultMessage: 'Align right', }), + getAlignmentOptionsControlLegend: () => + i18n.translate('xpack.canvas.textStylePicker.alignmentOptionsControl', { + defaultMessage: 'Alignment options', + }), getFontColorLabel: () => i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', { defaultMessage: 'Font Color', @@ -886,6 +890,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', { defaultMessage: 'Underline', }), + getStyleOptionsControlLegend: () => + i18n.translate('xpack.canvas.textStylePicker.styleOptionsControl', { + defaultMessage: 'Style options', + }), }, TimePicker: { getApplyButtonLabel: () => @@ -1061,6 +1069,10 @@ export const ComponentStrings = { }), }, VarConfigVarValueField: { + getBooleanOptionsLegend: () => + i18n.translate('xpack.canvas.varConfigVarValueField.booleanOptionsLegend', { + defaultMessage: 'Boolean value', + }), getFalseOption: () => i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', { defaultMessage: 'False', diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts index bc282db203be2e..0347a9c5444aa9 100644 --- a/x-pack/plugins/canvas/i18n/ui.ts +++ b/x-pack/plugins/canvas/i18n/ui.ts @@ -181,6 +181,10 @@ export const ArgumentStrings = { url: URL, }, }), + getUrlTypeChangeLegend: () => + i18n.translate('xpack.canvas.uis.arguments.imageUpload.urlTypes.changeLegend', { + defaultMessage: 'Image upload type', + }), }, Number: { getDisplayName: () => diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot index 8225f6414385d9..a6dbe675ee9d6b 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot @@ -146,6 +146,7 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = ` aria-labelledby="CanvasAssetManagerLabel" className="euiProgress euiProgress--native euiProgress--s euiProgress--secondary" max={25000} + style={null} value={0} />
@@ -163,6 +164,11 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = `
@@ -654,6 +661,11 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` -
-
+ + -
-
+ + -
+ +
@@ -366,110 +354,122 @@ exports[`Storyshots components/TextStylePicker default 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Alignment options +
-
- - -
-
+ +
-
+ +
+ +
@@ -733,110 +733,98 @@ exports[`Storyshots components/TextStylePicker interactive 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Style options +
-
- - -
-
+ + -
-
+ + -
+ +
@@ -844,110 +832,122 @@ exports[`Storyshots components/TextStylePicker interactive 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Alignment options +
-
- - -
-
+ +
-
+ +
+ +
diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx index c501e78a5e338a..7371eacd92fd1f 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx +++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx @@ -162,6 +162,7 @@ export const TextStylePicker: FC = ({ type="multi" isIconOnly className="canvasSidebar__buttonGroup" + legend={strings.getStyleOptionsControlLegend()} /> @@ -172,6 +173,7 @@ export const TextStylePicker: FC = ({ idSelected={align} onChange={(optionId: string) => doChange('align', optionId)} className="canvasSidebar__buttonGroup" + legend={strings.getAlignmentOptionsControlLegend()} /> diff --git a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot index f1fac7e6ce4776..5c9e0b62241265 100644 --- a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot @@ -64,6 +64,11 @@ Array [ -
-
+ +
+ +
@@ -307,6 +315,11 @@ Array [ className="euiButton euiButton--secondary euiButton--small euiButton--fill" disabled={false} onClick={[Function]} + style={ + Object { + "minWidth": undefined, + } + } type="button" > = ({ type, value, onChange }) => { }} buttonSize="compressed" isFullWidth + legend={strings.getBooleanOptionsLegend()} /> ); } diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot index 6bce2be335b783..de78772bcb124c 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot @@ -14,9 +14,14 @@ exports[`Storyshots components/WorkpadHeader/ElementMenu default 1`] = ` >
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot index 73d350a9c1ee19..7d7b2a0ae9341a 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot @@ -1445,7 +1445,7 @@ exports[`Storyshots shareables/Canvas component 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
App renders properly 1`] = `
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot index 2fe222b4238a3f..28c01c3fba8e5b 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot @@ -1398,7 +1398,7 @@ exports[`Storyshots shareables/Footer contextual: austin 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
can navigate Autoplay Settings 1`] = `
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx index 4d827da0c36394..4e67b1c67a27fd 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx @@ -81,7 +81,6 @@ export const SettingsComponent: FC = ({ refs }) => { isOpen={isPopoverOpen} button={button} panelPaddingSize="none" - withTitle anchorPosition="upRight" insert={ refs.stage.current ? { sibling: refs.stage.current, position: 'after' } : undefined diff --git a/x-pack/plugins/case/server/client/cases/create.test.ts b/x-pack/plugins/case/server/client/cases/create.test.ts new file mode 100644 index 00000000000000..f253dd9f4feb41 --- /dev/null +++ b/x-pack/plugins/case/server/client/cases/create.test.ts @@ -0,0 +1,335 @@ +/* + * 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 { ConnectorTypes, CasePostRequest } from '../../../common/api'; + +import { + createMockSavedObjectsRepository, + mockCaseConfigure, + mockCases, +} from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; + +describe('create', () => { + beforeEach(async () => { + jest.restoreAllMocks(); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2019-11-25T21:54:48.952Z'), + })); + }); + + describe('happy path', () => { + test('it creates the case correctly', async () => { + const postCase = { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + tags: ['defacement'], + connector: { + id: '123', + name: 'Jira', + type: ConnectorTypes.jira, + fields: { issueType: 'Task', priority: 'High', parent: null }, + }, + } as CasePostRequest; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseConfigureSavedObject: mockCaseConfigure, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.create({ theCase: postCase }); + + expect(res).toEqual({ + id: 'mock-it', + comments: [], + totalComment: 0, + closed_at: null, + closed_by: null, + connector: { + id: '123', + name: 'Jira', + type: ConnectorTypes.jira, + fields: { issueType: 'Task', priority: 'High', parent: null }, + }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { full_name: 'Awesome D00d', email: 'd00d@awesome.com', username: 'awesome' }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + updated_at: null, + updated_by: null, + version: 'WzksMV0=', + }); + + expect( + caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + ).toEqual([ + { + attributes: { + action: 'create', + action_at: '2019-11-25T21:54:48.952Z', + action_by: { + email: 'd00d@awesome.com', + full_name: 'Awesome D00d', + username: 'awesome', + }, + action_field: ['description', 'status', 'tags', 'title', 'connector'], + new_value: + '{"description":"This is a brand new case of a bad meanie defacing data","title":"Super Bad Security Issue","tags":["defacement"],"connector":{"id":"123","name":"Jira","type":".jira","fields":{"issueType":"Task","priority":"High","parent":null}}}', + old_value: null, + }, + references: [ + { + id: 'mock-it', + name: 'associated-cases', + type: 'cases', + }, + ], + }, + ]); + }); + + test('it creates the case without connector in the configuration', async () => { + const postCase = { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + tags: ['defacement'], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.create({ theCase: postCase }); + + expect(res).toEqual({ + id: 'mock-it', + comments: [], + totalComment: 0, + closed_at: null, + closed_by: null, + connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { full_name: 'Awesome D00d', email: 'd00d@awesome.com', username: 'awesome' }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + updated_at: null, + updated_by: null, + version: 'WzksMV0=', + }); + }); + + test('Allow user to create case without authentication', async () => { + const postCase = { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + tags: ['defacement'], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true); + const res = await caseClient.client.create({ theCase: postCase }); + + expect(res).toEqual({ + id: 'mock-it', + comments: [], + totalComment: 0, + closed_at: null, + closed_by: null, + connector: { id: 'none', name: 'none', type: ConnectorTypes.none, fields: null }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + email: null, + full_name: null, + username: null, + }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'open', + tags: ['defacement'], + updated_at: null, + updated_by: null, + version: 'WzksMV0=', + }); + }); + }); + + describe('unhappy path', () => { + test('it throws when missing title', async () => { + expect.assertions(1); + const postCase = { + description: 'This is a brand new case of a bad meanie defacing data', + tags: ['defacement'], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .create({ theCase: postCase }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when missing description', async () => { + expect.assertions(1); + const postCase = { + title: 'a title', + tags: ['defacement'], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .create({ theCase: postCase }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when missing tags', async () => { + expect.assertions(1); + const postCase = { + title: 'a title', + description: 'This is a brand new case of a bad meanie defacing data', + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .create({ theCase: postCase }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when missing connector ', async () => { + expect.assertions(1); + const postCase = { + title: 'a title', + description: 'This is a brand new case of a bad meanie defacing data', + tags: ['defacement'], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .create({ theCase: postCase }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when connector missing the right fields', async () => { + expect.assertions(1); + const postCase = { + title: 'a title', + description: 'This is a brand new case of a bad meanie defacing data', + tags: ['defacement'], + connector: { + id: '123', + name: 'Jira', + type: ConnectorTypes.jira, + fields: {}, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .create({ theCase: postCase }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws if you passing status for a new case', async () => { + expect.assertions(1); + const postCase = { + title: 'a title', + description: 'This is a brand new case of a bad meanie defacing data', + tags: ['defacement'], + status: 'closed', + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull()); + }); + + it(`Returns an error if postNewCase throws`, async () => { + const postCase = { + description: 'Throw an error', + title: 'Super Bad Security Issue', + tags: ['error'], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }; + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + + caseClient.client.create({ theCase: postCase }).catch((e) => expect(e).not.toBeNull()); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/cases/create.ts b/x-pack/plugins/case/server/client/cases/create.ts new file mode 100644 index 00000000000000..3379099419a758 --- /dev/null +++ b/x-pack/plugins/case/server/client/cases/create.ts @@ -0,0 +1,78 @@ +/* + * 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 Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { flattenCaseSavedObject, transformNewCase } from '../../routes/api/utils'; + +import { + CasePostRequestRt, + throwErrors, + excess, + CaseResponseRt, + CaseResponse, +} from '../../../common/api'; +import { buildCaseUserActionItem } from '../../services/user_actions/helpers'; +import { + getConnectorFromConfiguration, + transformCaseConnectorToEsConnector, +} from '../../routes/api/cases/helpers'; + +import { CaseClientCreate, CaseClientFactoryArguments } from '../types'; + +export const create = ({ + savedObjectsClient, + caseService, + caseConfigureService, + userActionService, + request, +}: CaseClientFactoryArguments) => async ({ theCase }: CaseClientCreate): Promise => { + const query = pipe( + excess(CasePostRequestRt).decode(theCase), + fold(throwErrors(Boom.badRequest), identity) + ); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { username, full_name, email } = await caseService.getUser({ request }); + const createdDate = new Date().toISOString(); + const myCaseConfigure = await caseConfigureService.find({ client: savedObjectsClient }); + const caseConfigureConnector = getConnectorFromConfiguration(myCaseConfigure); + + const newCase = await caseService.postNewCase({ + client: savedObjectsClient, + attributes: transformNewCase({ + createdDate, + newCase: query, + username, + full_name, + email, + connector: transformCaseConnectorToEsConnector(query.connector ?? caseConfigureConnector), + }), + }); + + await userActionService.postUserActions({ + client: savedObjectsClient, + actions: [ + buildCaseUserActionItem({ + action: 'create', + actionAt: createdDate, + actionBy: { username, full_name, email }, + caseId: newCase.id, + fields: ['description', 'status', 'tags', 'title', 'connector'], + newValue: JSON.stringify(query), + }), + ], + }); + + return CaseResponseRt.encode( + flattenCaseSavedObject({ + savedObject: newCase, + }) + ); +}; diff --git a/x-pack/plugins/case/server/client/cases/update.test.ts b/x-pack/plugins/case/server/client/cases/update.test.ts new file mode 100644 index 00000000000000..62d897999c11a4 --- /dev/null +++ b/x-pack/plugins/case/server/client/cases/update.test.ts @@ -0,0 +1,383 @@ +/* + * 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 { ConnectorTypes, CasesPatchRequest } from '../../../common/api'; +import { + createMockSavedObjectsRepository, + mockCaseNoConnectorId, + mockCases, +} from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; + +describe('update', () => { + beforeEach(async () => { + jest.restoreAllMocks(); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2019-11-25T21:54:48.952Z'), + })); + }); + + describe('happy path', () => { + test('it closes the case correctly', async () => { + const patchCases = { + cases: [ + { + id: 'mock-id-1', + status: 'closed' as const, + version: 'WzAsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.update({ cases: patchCases }); + + expect(res).toEqual([ + { + closed_at: '2019-11-25T21:54:48.952Z', + closed_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + comments: [], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { email: 'testemail@elastic.co', full_name: 'elastic', username: 'elastic' }, + description: 'This is a brand new case of a bad meanie defacing data', + id: 'mock-id-1', + external_service: null, + status: 'closed', + tags: ['defacement'], + title: 'Super Bad Security Issue', + totalComment: 0, + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + version: 'WzE3LDFd', + }, + ]); + + expect( + caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + ).toEqual([ + { + attributes: { + action: 'update', + action_at: '2019-11-25T21:54:48.952Z', + action_by: { + email: 'd00d@awesome.com', + full_name: 'Awesome D00d', + username: 'awesome', + }, + action_field: ['status'], + new_value: 'closed', + old_value: 'open', + }, + references: [ + { + id: 'mock-id-1', + name: 'associated-cases', + type: 'cases', + }, + ], + }, + ]); + }); + + test('it opens the case correctly', async () => { + const patchCases = { + cases: [ + { + id: 'mock-id-1', + status: 'open' as const, + version: 'WzAsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: [ + { ...mockCases[0], attributes: { ...mockCases[0].attributes, status: 'closed' } }, + ...mockCases.slice(1), + ], + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.update({ cases: patchCases }); + + expect(res).toEqual([ + { + closed_at: null, + closed_by: null, + comments: [], + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { email: 'testemail@elastic.co', full_name: 'elastic', username: 'elastic' }, + description: 'This is a brand new case of a bad meanie defacing data', + id: 'mock-id-1', + external_service: null, + status: 'open', + tags: ['defacement'], + title: 'Super Bad Security Issue', + totalComment: 0, + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + version: 'WzE3LDFd', + }, + ]); + }); + + test('it updates a case without a connector.id', async () => { + const patchCases = { + cases: [ + { + id: 'mock-no-connector_id', + status: 'closed' as const, + version: 'WzAsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: [mockCaseNoConnectorId], + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.update({ cases: patchCases }); + + expect(res).toEqual([ + { + id: 'mock-no-connector_id', + comments: [], + totalComment: 0, + closed_at: '2019-11-25T21:54:48.952Z', + closed_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { full_name: 'elastic', email: 'testemail@elastic.co', username: 'elastic' }, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: 'closed', + tags: ['defacement'], + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, + version: 'WzE3LDFd', + }, + ]); + }); + + test('it updates the connector correctly', async () => { + const patchCases = ({ + cases: [ + { + id: 'mock-id-3', + connector: { + id: '456', + name: 'My connector 2', + type: ConnectorTypes.jira, + fields: { issueType: 'Bug', priority: 'Low', parent: null }, + }, + version: 'WzUsMV0=', + }, + ], + } as unknown) as CasesPatchRequest; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.update({ cases: patchCases }); + + expect(res).toEqual([ + { + id: 'mock-id-3', + comments: [], + totalComment: 0, + closed_at: null, + closed_by: null, + connector: { + id: '456', + name: 'My connector 2', + type: ConnectorTypes.jira, + fields: { issueType: 'Bug', priority: 'Low', parent: null }, + }, + created_at: '2019-11-25T22:32:17.947Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + description: 'Oh no, a bad meanie going LOLBins all over the place!', + external_service: null, + title: 'Another bad one', + status: 'open', + tags: ['LOLBins'], + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'Awesome D00d', + email: 'd00d@awesome.com', + username: 'awesome', + }, + version: 'WzE3LDFd', + }, + ]); + }); + }); + + describe('unhappy path', () => { + test('it throws when missing id', async () => { + expect.assertions(1); + const patchCases = { + cases: [ + { + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + version: 'WzUsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .update({ cases: patchCases }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when missing version', async () => { + expect.assertions(1); + const patchCases = { + cases: [ + { + id: 'mock-id-3', + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + // @ts-expect-error + .update({ cases: patchCases }) + .catch((e) => expect(e).not.toBeNull()); + }); + + test('it throws when fields are identical', async () => { + expect.assertions(1); + const patchCases = { + cases: [ + { + id: 'mock-id-1', + status: 'open' as const, + version: 'WzAsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .update({ cases: patchCases }) + .catch((e) => + expect(e.message).toBe('All update fields are identical to current version.') + ); + }); + + test('it throws when case does not exist', async () => { + const patchCases = { + cases: [ + { + id: 'not-exists', + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + version: 'WzUsMV0=', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .update({ cases: patchCases }) + .catch((e) => + expect(e.message).toBe( + 'These cases not-exists do not exist. Please check you have the correct ids.' + ) + ); + }); + + test('it throws when cases conflicts', async () => { + expect.assertions(1); + const patchCases = { + cases: [ + { + id: 'mock-id-1', + version: 'WzAsMV1=', + title: 'Super Bad Security Issue', + }, + ], + }; + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .update({ cases: patchCases }) + .catch((e) => + expect(e.message).toBe( + 'These cases mock-id-1 has been updated. Please refresh before saving additional updates.' + ) + ); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/cases/update.ts b/x-pack/plugins/case/server/client/cases/update.ts new file mode 100644 index 00000000000000..424f51ee40f082 --- /dev/null +++ b/x-pack/plugins/case/server/client/cases/update.ts @@ -0,0 +1,155 @@ +/* + * 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 Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { flattenCaseSavedObject } from '../../routes/api/utils'; + +import { + throwErrors, + excess, + CasesResponseRt, + CasesPatchRequestRt, + ESCasePatchRequest, + CasePatchRequest, + CasesResponse, +} from '../../../common/api'; +import { buildCaseUserActions } from '../../services/user_actions/helpers'; +import { + getCaseToUpdate, + transformCaseConnectorToEsConnector, +} from '../../routes/api/cases/helpers'; + +import { CaseClientUpdate, CaseClientFactoryArguments } from '../types'; + +export const update = ({ + savedObjectsClient, + caseService, + userActionService, + request, +}: CaseClientFactoryArguments) => async ({ cases }: CaseClientUpdate): Promise => { + const query = pipe( + excess(CasesPatchRequestRt).decode(cases), + fold(throwErrors(Boom.badRequest), identity) + ); + + const myCases = await caseService.getCases({ + client: savedObjectsClient, + caseIds: query.cases.map((q) => q.id), + }); + + let nonExistingCases: CasePatchRequest[] = []; + const conflictedCases = query.cases.filter((q) => { + const myCase = myCases.saved_objects.find((c) => c.id === q.id); + + if (myCase && myCase.error) { + nonExistingCases = [...nonExistingCases, q]; + return false; + } + return myCase == null || myCase?.version !== q.version; + }); + + if (nonExistingCases.length > 0) { + throw Boom.notFound( + `These cases ${nonExistingCases + .map((c) => c.id) + .join(', ')} do not exist. Please check you have the correct ids.` + ); + } + + if (conflictedCases.length > 0) { + throw Boom.conflict( + `These cases ${conflictedCases + .map((c) => c.id) + .join(', ')} has been updated. Please refresh before saving additional updates.` + ); + } + + const updateCases: ESCasePatchRequest[] = query.cases.map((updateCase) => { + const currentCase = myCases.saved_objects.find((c) => c.id === updateCase.id); + const { connector, ...thisCase } = updateCase; + return currentCase != null + ? getCaseToUpdate(currentCase.attributes, { + ...thisCase, + ...(connector != null + ? { connector: transformCaseConnectorToEsConnector(connector) } + : {}), + }) + : { id: thisCase.id, version: thisCase.version }; + }); + + const updateFilterCases = updateCases.filter((updateCase) => { + const { id, version, ...updateCaseAttributes } = updateCase; + return Object.keys(updateCaseAttributes).length > 0; + }); + + if (updateFilterCases.length > 0) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { username, full_name, email } = await caseService.getUser({ request }); + const updatedDt = new Date().toISOString(); + const updatedCases = await caseService.patchCases({ + client: savedObjectsClient, + cases: updateFilterCases.map((thisCase) => { + const { id: caseId, version, ...updateCaseAttributes } = thisCase; + let closedInfo = {}; + if (updateCaseAttributes.status && updateCaseAttributes.status === 'closed') { + closedInfo = { + closed_at: updatedDt, + closed_by: { email, full_name, username }, + }; + } else if (updateCaseAttributes.status && updateCaseAttributes.status === 'open') { + closedInfo = { + closed_at: null, + closed_by: null, + }; + } + return { + caseId, + updatedAttributes: { + ...updateCaseAttributes, + ...closedInfo, + updated_at: updatedDt, + updated_by: { email, full_name, username }, + }, + version, + }; + }), + }); + + const returnUpdatedCase = myCases.saved_objects + .filter((myCase) => + updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id) + ) + .map((myCase) => { + const updatedCase = updatedCases.saved_objects.find((c) => c.id === myCase.id); + return flattenCaseSavedObject({ + savedObject: { + ...myCase, + ...updatedCase, + attributes: { ...myCase.attributes, ...updatedCase?.attributes }, + references: myCase.references, + version: updatedCase?.version ?? myCase.version, + }, + }); + }); + + await userActionService.postUserActions({ + client: savedObjectsClient, + actions: buildCaseUserActions({ + originalCases: myCases.saved_objects, + updatedCases: updatedCases.saved_objects, + actionDate: updatedDt, + actionBy: { email, full_name, username }, + }), + }); + + return CasesResponseRt.encode(returnUpdatedCase); + } + throw Boom.notAcceptable('All update fields are identical to current version.'); +}; diff --git a/x-pack/plugins/case/server/client/comments/add.test.ts b/x-pack/plugins/case/server/client/comments/add.test.ts new file mode 100644 index 00000000000000..8a316740e41e07 --- /dev/null +++ b/x-pack/plugins/case/server/client/comments/add.test.ts @@ -0,0 +1,212 @@ +/* + * 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 { + createMockSavedObjectsRepository, + mockCaseComments, + mockCases, +} from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; + +describe('addComment', () => { + beforeEach(async () => { + jest.restoreAllMocks(); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2020-10-23T21:54:48.952Z'), + })); + }); + + describe('happy path', () => { + test('it adds a comment correctly', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.addComment({ + caseId: 'mock-id-1', + comment: { comment: 'Wow, good luck catching that bad meanie!' }, + }); + + expect(res.id).toEqual('mock-id-1'); + expect(res.totalComment).toEqual(res.comments!.length); + expect(res.comments![res.comments!.length - 1]).toEqual({ + comment: 'Wow, good luck catching that bad meanie!', + created_at: '2020-10-23T21:54:48.952Z', + created_by: { + email: 'd00d@awesome.com', + full_name: 'Awesome D00d', + username: 'awesome', + }, + id: 'mock-comment', + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + version: 'WzksMV0=', + }); + }); + + test('it updates the case correctly after adding a comment', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.addComment({ + caseId: 'mock-id-1', + comment: { comment: 'Wow, good luck catching that bad meanie!' }, + }); + + expect(res.updated_at).toEqual('2020-10-23T21:54:48.952Z'); + expect(res.updated_by).toEqual({ + email: 'd00d@awesome.com', + full_name: 'Awesome D00d', + username: 'awesome', + }); + }); + + test('it creates a user action', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + await caseClient.client.addComment({ + caseId: 'mock-id-1', + comment: { comment: 'Wow, good luck catching that bad meanie!' }, + }); + + expect( + caseClient.services.userActionService.postUserActions.mock.calls[0][0].actions + ).toEqual([ + { + attributes: { + action: 'create', + action_at: '2020-10-23T21:54:48.952Z', + action_by: { + email: 'd00d@awesome.com', + full_name: 'Awesome D00d', + username: 'awesome', + }, + action_field: ['comment'], + new_value: 'Wow, good luck catching that bad meanie!', + old_value: null, + }, + references: [ + { + id: 'mock-id-1', + name: 'associated-cases', + type: 'cases', + }, + { + id: 'mock-comment', + name: 'associated-cases-comments', + type: 'cases-comments', + }, + ], + }, + ]); + }); + + test('it allow user to create comments without authentications', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true); + const res = await caseClient.client.addComment({ + caseId: 'mock-id-1', + comment: { comment: 'Wow, good luck catching that bad meanie!' }, + }); + + expect(res.id).toEqual('mock-id-1'); + expect(res.comments![res.comments!.length - 1]).toEqual({ + comment: 'Wow, good luck catching that bad meanie!', + created_at: '2020-10-23T21:54:48.952Z', + created_by: { + email: null, + full_name: null, + username: null, + }, + id: 'mock-comment', + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + version: 'WzksMV0=', + }); + }); + }); + + describe('unhappy path', () => { + test('it throws when missing comment', async () => { + expect.assertions(3); + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .addComment({ + caseId: 'mock-id-1', + // @ts-expect-error + comment: {}, + }) + .catch((e) => { + expect(e).not.toBeNull(); + expect(e.isBoom).toBe(true); + expect(e.output.statusCode).toBe(400); + }); + }); + + test('it throws when the case does not exists', async () => { + expect.assertions(3); + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .addComment({ + caseId: 'not-exists', + comment: { comment: 'Wow, good luck catching that bad meanie!' }, + }) + .catch((e) => { + expect(e).not.toBeNull(); + expect(e.isBoom).toBe(true); + expect(e.output.statusCode).toBe(404); + }); + }); + + test('it throws when postNewCase throws', async () => { + expect.assertions(3); + + const savedObjectsClient = createMockSavedObjectsRepository({ + caseSavedObject: mockCases, + caseCommentSavedObject: mockCaseComments, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + caseClient.client + .addComment({ + caseId: 'mock-id-1', + comment: { comment: 'Throw an error' }, + }) + .catch((e) => { + expect(e).not.toBeNull(); + expect(e.isBoom).toBe(true); + expect(e.output.statusCode).toBe(400); + }); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts new file mode 100644 index 00000000000000..765eb2c873765b --- /dev/null +++ b/x-pack/plugins/case/server/client/comments/add.ts @@ -0,0 +1,126 @@ +/* + * 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 Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { flattenCaseSavedObject, transformNewComment } from '../../routes/api/utils'; + +import { + throwErrors, + excess, + CaseResponseRt, + CommentRequestRt, + CaseResponse, +} from '../../../common/api'; +import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; + +import { CaseClientAddComment, CaseClientFactoryArguments } from '../types'; +import { CASE_SAVED_OBJECT } from '../../saved_object_types'; + +export const addComment = ({ + savedObjectsClient, + caseService, + userActionService, + request, +}: CaseClientFactoryArguments) => async ({ + caseId, + comment, +}: CaseClientAddComment): Promise => { + const query = pipe( + excess(CommentRequestRt).decode(comment), + fold(throwErrors(Boom.badRequest), identity) + ); + + const myCase = await caseService.getCase({ + client: savedObjectsClient, + caseId, + }); + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { username, full_name, email } = await caseService.getUser({ request }); + const createdDate = new Date().toISOString(); + + const [newComment, updatedCase] = await Promise.all([ + caseService.postNewComment({ + client: savedObjectsClient, + attributes: transformNewComment({ + createdDate, + ...query, + username, + full_name, + email, + }), + references: [ + { + type: CASE_SAVED_OBJECT, + name: `associated-${CASE_SAVED_OBJECT}`, + id: myCase.id, + }, + ], + }), + caseService.patchCase({ + client: savedObjectsClient, + caseId, + updatedAttributes: { + updated_at: createdDate, + updated_by: { username, full_name, email }, + }, + version: myCase.version, + }), + ]); + + const totalCommentsFindByCases = await caseService.getAllCaseComments({ + client: savedObjectsClient, + caseId, + options: { + fields: [], + page: 1, + perPage: 1, + }, + }); + + const [comments] = await Promise.all([ + caseService.getAllCaseComments({ + client: savedObjectsClient, + caseId, + options: { + fields: [], + page: 1, + perPage: totalCommentsFindByCases.total, + }, + }), + userActionService.postUserActions({ + client: savedObjectsClient, + actions: [ + buildCommentUserActionItem({ + action: 'create', + actionAt: createdDate, + actionBy: { username, full_name, email }, + caseId: myCase.id, + commentId: newComment.id, + fields: ['comment'], + newValue: query.comment, + }), + ], + }), + ]); + + return CaseResponseRt.encode( + flattenCaseSavedObject({ + savedObject: { + ...myCase, + ...updatedCase, + attributes: { ...myCase.attributes, ...updatedCase.attributes }, + version: updatedCase.version ?? myCase.version, + references: myCase.references, + }, + comments: comments.saved_objects, + }) + ); +}; diff --git a/x-pack/plugins/case/server/client/index.test.ts b/x-pack/plugins/case/server/client/index.test.ts new file mode 100644 index 00000000000000..1ecdc8ea96dea8 --- /dev/null +++ b/x-pack/plugins/case/server/client/index.test.ts @@ -0,0 +1,68 @@ +/* + * 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 { KibanaRequest } from 'kibana/server'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { createCaseClient } from '.'; +import { + createCaseServiceMock, + createConfigureServiceMock, + createUserActionServiceMock, +} from '../services/mocks'; + +import { create } from './cases/create'; +import { update } from './cases/update'; +import { addComment } from './comments/add'; + +jest.mock('./cases/create'); +jest.mock('./cases/update'); +jest.mock('./comments/add'); + +const caseService = createCaseServiceMock(); +const caseConfigureService = createConfigureServiceMock(); +const userActionService = createUserActionServiceMock(); +const savedObjectsClient = savedObjectsClientMock.create(); +const request = {} as KibanaRequest; + +const createMock = create as jest.Mock; +const updateMock = update as jest.Mock; +const addCommentMock = addComment as jest.Mock; + +describe('createCaseClient()', () => { + test('it creates the client correctly', async () => { + createCaseClient({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }); + + expect(createMock).toHaveBeenCalledWith({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }); + + expect(updateMock).toHaveBeenCalledWith({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }); + + expect(addCommentMock).toHaveBeenCalledWith({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/index.ts b/x-pack/plugins/case/server/client/index.ts new file mode 100644 index 00000000000000..75e9e3c4cfebcf --- /dev/null +++ b/x-pack/plugins/case/server/client/index.ts @@ -0,0 +1,44 @@ +/* + * 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 { CaseClientFactoryArguments, CaseClient } from './types'; +import { create } from './cases/create'; +import { update } from './cases/update'; +import { addComment } from './comments/add'; + +export { CaseClient } from './types'; + +export const createCaseClient = ({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, +}: CaseClientFactoryArguments): CaseClient => { + return { + create: create({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }), + update: update({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }), + addComment: addComment({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + }), + }; +}; diff --git a/x-pack/plugins/case/server/client/mocks.ts b/x-pack/plugins/case/server/client/mocks.ts new file mode 100644 index 00000000000000..243dd884f9ef65 --- /dev/null +++ b/x-pack/plugins/case/server/client/mocks.ts @@ -0,0 +1,53 @@ +/* + * 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 { KibanaRequest } from 'kibana/server'; +import { loggingSystemMock } from '../../../../../src/core/server/mocks'; +import { CaseService, CaseConfigureService, CaseUserActionServiceSetup } from '../services'; +import { CaseClient } from './types'; +import { authenticationMock } from '../routes/api/__fixtures__'; +import { createCaseClient } from '.'; + +export type CaseClientMock = jest.Mocked; +export const createCaseClientMock = (): CaseClientMock => ({ + create: jest.fn(), + update: jest.fn(), + addComment: jest.fn(), +}); + +export const createCaseClientWithMockSavedObjectsClient = async ( + savedObjectsClient: any, + badAuth: boolean = false +): Promise<{ + client: CaseClient; + services: { userActionService: jest.Mocked }; +}> => { + const log = loggingSystemMock.create().get('case'); + const request = {} as KibanaRequest; + + const caseServicePlugin = new CaseService(log); + const caseConfigureServicePlugin = new CaseConfigureService(log); + + const caseService = await caseServicePlugin.setup({ + authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), + }); + const caseConfigureService = await caseConfigureServicePlugin.setup(); + const userActionService = { + postUserActions: jest.fn(), + getUserActions: jest.fn(), + }; + + return { + client: createCaseClient({ + savedObjectsClient, + request, + caseService, + caseConfigureService, + userActionService, + }), + services: { userActionService }, + }; +}; diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts new file mode 100644 index 00000000000000..8db7d8a5747d72 --- /dev/null +++ b/x-pack/plugins/case/server/client/types.ts @@ -0,0 +1,46 @@ +/* + * 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 { KibanaRequest, SavedObjectsClientContract } from '../../../../../src/core/server'; +import { + CasePostRequest, + CasesPatchRequest, + CommentRequest, + CaseResponse, + CasesResponse, +} from '../../common/api'; +import { + CaseConfigureServiceSetup, + CaseServiceSetup, + CaseUserActionServiceSetup, +} from '../services'; + +export interface CaseClientCreate { + theCase: CasePostRequest; +} + +export interface CaseClientUpdate { + cases: CasesPatchRequest; +} + +export interface CaseClientAddComment { + caseId: string; + comment: CommentRequest; +} + +export interface CaseClientFactoryArguments { + savedObjectsClient: SavedObjectsClientContract; + request: KibanaRequest; + caseConfigureService: CaseConfigureServiceSetup; + caseService: CaseServiceSetup; + userActionService: CaseUserActionServiceSetup; +} + +export interface CaseClient { + create: (args: CaseClientCreate) => Promise; + update: (args: CaseClientUpdate) => Promise; + addComment: (args: CaseClientAddComment) => Promise; +} diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 9cf045da3e700b..5398f8ed0ae836 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -5,10 +5,17 @@ */ import { first, map } from 'rxjs/operators'; -import { Logger, PluginInitializerContext } from 'kibana/server'; -import { CoreSetup } from 'src/core/server'; +import { + IContextProvider, + KibanaRequest, + Logger, + PluginInitializerContext, + RequestHandler, +} from 'kibana/server'; +import { CoreSetup, CoreStart } from 'src/core/server'; import { SecurityPluginSetup } from '../../security/server'; +import { APP_ID } from '../common/constants'; import { ConfigType } from './config'; import { initCaseApi } from './routes/api'; @@ -18,7 +25,15 @@ import { caseCommentSavedObjectType, caseUserActionSavedObjectType, } from './saved_object_types'; -import { CaseConfigureService, CaseService, CaseUserActionService } from './services'; +import { + CaseConfigureService, + CaseConfigureServiceSetup, + CaseService, + CaseServiceSetup, + CaseUserActionService, + CaseUserActionServiceSetup, +} from './services'; +import { createCaseClient } from './client'; function createConfig$(context: PluginInitializerContext) { return context.config.create().pipe(map((config) => config)); @@ -30,6 +45,9 @@ export interface PluginsSetup { export class CasePlugin { private readonly log: Logger; + private caseService?: CaseServiceSetup; + private caseConfigureService?: CaseConfigureServiceSetup; + private userActionService?: CaseUserActionServiceSetup; constructor(private readonly initializerContext: PluginInitializerContext) { this.log = this.initializerContext.logger.get(); @@ -47,36 +65,83 @@ export class CasePlugin { core.savedObjects.registerType(caseConfigureSavedObjectType); core.savedObjects.registerType(caseUserActionSavedObjectType); - const caseServicePlugin = new CaseService(this.log); - const caseConfigureServicePlugin = new CaseConfigureService(this.log); - const userActionServicePlugin = new CaseUserActionService(this.log); - this.log.debug( `Setting up Case Workflow with core contract [${Object.keys( core )}] and plugins [${Object.keys(plugins)}]` ); - const caseService = await caseServicePlugin.setup({ + this.caseService = await new CaseService(this.log).setup({ authentication: plugins.security != null ? plugins.security.authc : null, }); - const caseConfigureService = await caseConfigureServicePlugin.setup(); - const userActionService = await userActionServicePlugin.setup(); + this.caseConfigureService = await new CaseConfigureService(this.log).setup(); + this.userActionService = await new CaseUserActionService(this.log).setup(); + + core.http.registerRouteHandlerContext( + APP_ID, + this.createRouteHandlerContext({ + core, + caseService: this.caseService, + caseConfigureService: this.caseConfigureService, + userActionService: this.userActionService, + }) + ); const router = core.http.createRouter(); initCaseApi({ - caseConfigureService, - caseService, - userActionService, + caseService: this.caseService, + caseConfigureService: this.caseConfigureService, + userActionService: this.userActionService, router, }); } - public start() { + public async start(core: CoreStart) { this.log.debug(`Starting Case Workflow`); + + const getCaseClientWithRequest = async (request: KibanaRequest) => { + return createCaseClient({ + savedObjectsClient: core.savedObjects.getScopedClient(request), + request, + caseService: this.caseService!, + caseConfigureService: this.caseConfigureService!, + userActionService: this.userActionService!, + }); + }; + + return { + getCaseClientWithRequest, + }; } public stop() { this.log.debug(`Stopping Case Workflow`); } + + private createRouteHandlerContext = ({ + core, + caseService, + caseConfigureService, + userActionService, + }: { + core: CoreSetup; + caseService: CaseServiceSetup; + caseConfigureService: CaseConfigureServiceSetup; + userActionService: CaseUserActionServiceSetup; + }): IContextProvider, typeof APP_ID> => { + return async (context, request) => { + const [{ savedObjects }] = await core.getStartServices(); + return { + getCaseClient: () => { + return createCaseClient({ + savedObjectsClient: savedObjects.getScopedClient(request), + caseService, + caseConfigureService, + userActionService, + request, + }); + }, + }; + }; + }; } diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts index c2df91148a53a5..8bbd419e6315b9 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -39,7 +39,15 @@ export const createMockSavedObjectsRepository = ({ } const result = caseSavedObject.filter((s) => s.id === id); if (!result.length) { - throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + return { + id, + type, + error: { + statusCode: 404, + error: 'Not Found', + message: 'Saved object [cases/not-exist] not found', + }, + }; } return result[0]; }), diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 265970b1abdec8..e7ea381da9955b 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject } from 'kibana/server'; +import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import { ESCasesConfigureAttributes, CommentAttributes, @@ -325,3 +325,12 @@ export const mockCaseConfigure: Array> = version: 'WzYsMV0=', }, ]; + +export const mockCaseConfigureFind: Array> = [ + { + page: 1, + per_page: 5, + total: mockCaseConfigure.length, + saved_objects: [{ ...mockCaseConfigure[0], score: 0 }], + }, +]; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts index d947ffbaf181df..67890599fa4170 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts @@ -4,13 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; +import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; +import { loggingSystemMock } from 'src/core/server/mocks'; import { actionsClientMock } from '../../../../../actions/server/mocks'; +import { createCaseClient } from '../../../client'; +import { CaseService, CaseConfigureService } from '../../../services'; import { getActions } from '../__mocks__/request_responses'; +import { authenticationMock } from '../__fixtures__'; -export const createRouteContext = (client: any) => { +export const createRouteContext = async (client: any, badAuth = false) => { const actionsMock = actionsClientMock.create(); actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions())); + const log = loggingSystemMock.create().get('case'); + + const caseServicePlugin = new CaseService(log); + const caseConfigureServicePlugin = new CaseConfigureService(log); + + const caseService = await caseServicePlugin.setup({ + authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), + }); + const caseConfigureService = await caseConfigureServicePlugin.setup(); + const caseClient = createCaseClient({ + savedObjectsClient: client, + request: {} as KibanaRequest, + caseService, + caseConfigureService, + userActionService: { + postUserActions: jest.fn(), + getUserActions: jest.fn(), + }, + }); return ({ core: { @@ -19,5 +42,8 @@ export const createRouteContext = (client: any) => { }, }, actions: { getActionsClient: () => actionsMock }, + case: { + getCaseClient: () => caseClient, + }, } as unknown) as RequestHandlerContext; }; diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts index 67cb9984095709..986ad3a54496fc 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.test.ts @@ -32,7 +32,7 @@ describe('DELETE comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -52,7 +52,7 @@ describe('DELETE comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts index 24a03b217ab7c2..23f64151a78d7e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/get_comment.test.ts @@ -32,7 +32,7 @@ describe('GET comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -57,7 +57,7 @@ describe('GET comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseCommentSavedObject: mockCaseComments, }) diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts index 04473e302e4680..400e8ca404ca5e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.test.ts @@ -35,7 +35,7 @@ describe('PATCH comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -63,7 +63,7 @@ describe('PATCH comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -87,7 +87,7 @@ describe('PATCH comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts index 9006470f36f368..acc23815e3a394 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.test.ts @@ -26,6 +26,7 @@ describe('POST comment', () => { toISOString: jest.fn().mockReturnValue('2019-11-25T21:54:48.952Z'), })); }); + it(`Posts a new comment`, async () => { const request = httpServerMock.createKibanaRequest({ path: CASE_COMMENTS_URL, @@ -38,7 +39,7 @@ describe('POST comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -51,6 +52,7 @@ describe('POST comment', () => { 'mock-comment' ); }); + it(`Returns an error if the case does not exist`, async () => { const request = httpServerMock.createKibanaRequest({ path: CASE_COMMENTS_URL, @@ -63,7 +65,7 @@ describe('POST comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -74,6 +76,7 @@ describe('POST comment', () => { expect(response.status).toEqual(404); expect(response.payload.isBoom).toEqual(true); }); + it(`Returns an error if postNewCase throws`, async () => { const request = httpServerMock.createKibanaRequest({ path: CASE_COMMENTS_URL, @@ -86,7 +89,7 @@ describe('POST comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -97,6 +100,7 @@ describe('POST comment', () => { expect(response.status).toEqual(400); expect(response.payload.isBoom).toEqual(true); }); + it(`Allow user to create comments without authentications`, async () => { routeHandler = await createRoute(initPostCommentApi, 'post', true); @@ -111,11 +115,12 @@ describe('POST comment', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, - }) + }), + true ); const response = await routeHandler(theContext, request, kibanaResponseFactory); diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts index 3c5b72eba5d131..08d442bccf2cb9 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts @@ -5,24 +5,12 @@ */ import { schema } from '@kbn/config-schema'; -import Boom from 'boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; - -import { CaseResponseRt, CommentRequestRt, excess, throwErrors } from '../../../../../common/api'; -import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; -import { buildCommentUserActionItem } from '../../../../services/user_actions/helpers'; -import { escapeHatch, transformNewComment, wrapError, flattenCaseSavedObject } from '../../utils'; +import { escapeHatch, wrapError } from '../../utils'; import { RouteDeps } from '../../types'; import { CASE_COMMENTS_URL } from '../../../../../common/constants'; +import { CommentRequest } from '../../../../../common/api'; -export function initPostCommentApi({ - caseConfigureService, - caseService, - router, - userActionService, -}: RouteDeps) { +export function initPostCommentApi({ router }: RouteDeps) { router.post( { path: CASE_COMMENTS_URL, @@ -34,101 +22,17 @@ export function initPostCommentApi({ }, }, async (context, request, response) => { - try { - const client = context.core.savedObjects.client; - const caseId = request.params.case_id; - const query = pipe( - excess(CommentRequestRt).decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const myCase = await caseService.getCase({ - client, - caseId, - }); - - // eslint-disable-next-line @typescript-eslint/naming-convention - const { username, full_name, email } = await caseService.getUser({ request, response }); - const createdDate = new Date().toISOString(); - - const [newComment, updatedCase] = await Promise.all([ - caseService.postNewComment({ - client, - attributes: transformNewComment({ - createdDate, - ...query, - username, - full_name, - email, - }), - references: [ - { - type: CASE_SAVED_OBJECT, - name: `associated-${CASE_SAVED_OBJECT}`, - id: myCase.id, - }, - ], - }), - caseService.patchCase({ - client, - caseId, - updatedAttributes: { - updated_at: createdDate, - updated_by: { username, full_name, email }, - }, - version: myCase.version, - }), - ]); - - const totalCommentsFindByCases = await caseService.getAllCaseComments({ - client, - caseId, - options: { - fields: [], - page: 1, - perPage: 1, - }, - }); + if (!context.case) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); + } - const [comments] = await Promise.all([ - caseService.getAllCaseComments({ - client, - caseId, - options: { - fields: [], - page: 1, - perPage: totalCommentsFindByCases.total, - }, - }), - userActionService.postUserActions({ - client, - actions: [ - buildCommentUserActionItem({ - action: 'create', - actionAt: createdDate, - actionBy: { username, full_name, email }, - caseId: myCase.id, - commentId: newComment.id, - fields: ['comment'], - newValue: query.comment, - }), - ], - }), - ]); + const caseClient = context.case.getCaseClient(); + const caseId = request.params.case_id; + const comment = request.body as CommentRequest; + try { return response.ok({ - body: CaseResponseRt.encode( - flattenCaseSavedObject({ - savedObject: { - ...myCase, - ...updatedCase, - attributes: { ...myCase.attributes, ...updatedCase.attributes }, - version: updatedCase.version ?? myCase.version, - references: myCase.references, - }, - comments: comments.saved_objects, - }) - ), + body: await caseClient.addComment({ caseId, comment }), }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts index 45ce19fca9d205..cc4f208758369e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts @@ -29,7 +29,7 @@ describe('GET configuration', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -49,7 +49,7 @@ describe('GET configuration', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [{ ...mockCaseConfigure[0], version: undefined }], }) @@ -87,7 +87,7 @@ describe('GET configuration', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [], }) @@ -105,7 +105,7 @@ describe('GET configuration', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-find' }], }) diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts index ee4dcc8e81b959..2eab4ac756361e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.test.ts @@ -29,7 +29,7 @@ describe('GET connectors', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -106,7 +106,7 @@ describe('GET connectors', () => { method: 'get', }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts index 8fcb769225d447..261cd3e6b08846 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.test.ts @@ -39,7 +39,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -72,7 +72,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -110,7 +110,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -141,7 +141,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [], }) @@ -163,7 +163,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -190,7 +190,7 @@ describe('PATCH configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts index 27df19d8f823a8..7ef3bdb4a700a4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.test.ts @@ -37,7 +37,7 @@ describe('POST configuration', () => { body: newConfiguration, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -72,7 +72,7 @@ describe('POST configuration', () => { body: newConfiguration, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -112,7 +112,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -137,7 +137,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -162,7 +162,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -187,7 +187,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -212,7 +212,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -234,7 +234,7 @@ describe('POST configuration', () => { caseConfigureSavedObject: mockCaseConfigure, }); - const context = createRouteContext(savedObjectRepository); + const context = await createRouteContext(savedObjectRepository); const res = await routeHandler(context, req, kibanaResponseFactory); @@ -253,7 +253,7 @@ describe('POST configuration', () => { caseConfigureSavedObject: [], }); - const context = createRouteContext(savedObjectRepository); + const context = await createRouteContext(savedObjectRepository); const res = await routeHandler(context, req, kibanaResponseFactory); @@ -275,7 +275,7 @@ describe('POST configuration', () => { ], }); - const context = createRouteContext(savedObjectRepository); + const context = await createRouteContext(savedObjectRepository); const res = await routeHandler(context, req, kibanaResponseFactory); @@ -291,7 +291,7 @@ describe('POST configuration', () => { body: newConfiguration, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-find' }], }) @@ -309,7 +309,7 @@ describe('POST configuration', () => { body: newConfiguration, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: [{ ...mockCaseConfigure[0], id: 'throw-error-delete' }], }) @@ -334,7 +334,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -360,7 +360,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -385,7 +385,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) @@ -406,7 +406,7 @@ describe('POST configuration', () => { }, }); - const context = createRouteContext( + const context = await createRouteContext( createMockSavedObjectsRepository({ caseConfigureSavedObject: mockCaseConfigure, }) diff --git a/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts index e655339e05eb12..3970534140cd8f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/delete_cases.test.ts @@ -32,7 +32,7 @@ describe('DELETE case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -51,7 +51,7 @@ describe('DELETE case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -70,7 +70,7 @@ describe('DELETE case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCasesErrorTriggerData, caseCommentSavedObject: mockCaseComments, @@ -89,7 +89,7 @@ describe('DELETE case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCasesErrorTriggerData, caseCommentSavedObject: mockCasesErrorTriggerData, diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts index df27551d2c922e..b2ba8b2fcb33ab 100644 --- a/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/find_cases.test.ts @@ -29,7 +29,7 @@ describe('FIND all cases', () => { method: 'get', }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -46,7 +46,7 @@ describe('FIND all cases', () => { method: 'get', }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -63,7 +63,7 @@ describe('FIND all cases', () => { method: 'get', }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: [mockCaseNoConnectorId], }) @@ -80,7 +80,7 @@ describe('FIND all cases', () => { method: 'get', }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: [mockCaseNoConnectorId], caseConfigureSavedObject: mockCaseConfigure, diff --git a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts index 224da4464e1c2e..01de9abac16afe 100644 --- a/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/get_case.test.ts @@ -39,7 +39,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -70,7 +70,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -94,7 +94,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -119,7 +119,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCasesErrorTriggerData, }) @@ -142,7 +142,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: [mockCaseNoConnectorId], }) @@ -171,7 +171,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: [mockCaseNoConnectorId], caseConfigureSavedObject: mockCaseConfigure, @@ -201,7 +201,7 @@ describe('GET case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseConfigureSavedObject: mockCaseConfigure, diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts index c0d19edcad91f2..ea69ee77c58020 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts @@ -43,7 +43,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -93,7 +93,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseConfigureSavedObject: mockCaseConfigure, @@ -144,7 +144,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: [mockCaseNoConnectorId], }) @@ -170,7 +170,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -201,7 +201,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -232,7 +232,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -257,7 +257,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseCommentSavedObject: mockCaseComments, @@ -283,7 +283,7 @@ describe('PATCH cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts index 79e2e99731546c..873671a909801f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts @@ -4,31 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; - -import { - CasesPatchRequestRt, - CasesResponseRt, - CasePatchRequest, - excess, - throwErrors, - ESCasePatchRequest, -} from '../../../../common/api'; -import { escapeHatch, wrapError, flattenCaseSavedObject } from '../utils'; +import { escapeHatch, wrapError } from '../utils'; import { RouteDeps } from '../types'; -import { getCaseToUpdate, transformCaseConnectorToEsConnector } from './helpers'; -import { buildCaseUserActions } from '../../../services/user_actions/helpers'; import { CASES_URL } from '../../../../common/constants'; +import { CasesPatchRequest } from '../../../../common/api'; -export function initPatchCasesApi({ - caseConfigureService, - caseService, - router, - userActionService, -}: RouteDeps) { +export function initPatchCasesApi({ router }: RouteDeps) { router.patch( { path: CASES_URL, @@ -37,126 +18,17 @@ export function initPatchCasesApi({ }, }, async (context, request, response) => { - try { - const client = context.core.savedObjects.client; - const query = pipe( - excess(CasesPatchRequestRt).decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - const myCases = await caseService.getCases({ - client, - caseIds: query.cases.map((q) => q.id), - }); - - let nonExistingCases: CasePatchRequest[] = []; - const conflictedCases = query.cases.filter((q) => { - const myCase = myCases.saved_objects.find((c) => c.id === q.id); - - if (myCase && myCase.error) { - nonExistingCases = [...nonExistingCases, q]; - return false; - } - return myCase == null || myCase?.version !== q.version; - }); - if (nonExistingCases.length > 0) { - throw Boom.notFound( - `These cases ${nonExistingCases - .map((c) => c.id) - .join(', ')} do not exist. Please check you have the correct ids.` - ); - } - if (conflictedCases.length > 0) { - throw Boom.conflict( - `These cases ${conflictedCases - .map((c) => c.id) - .join(', ')} has been updated. Please refresh before saving additional updates.` - ); - } + if (!context.case) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); + } - const updateCases: ESCasePatchRequest[] = query.cases.map((updateCase) => { - const currentCase = myCases.saved_objects.find((c) => c.id === updateCase.id); - const { connector, ...thisCase } = updateCase; - return currentCase != null - ? getCaseToUpdate(currentCase.attributes, { - ...thisCase, - ...(connector != null - ? { connector: transformCaseConnectorToEsConnector(connector) } - : {}), - }) - : { id: thisCase.id, version: thisCase.version }; - }); + const caseClient = context.case.getCaseClient(); + const cases = request.body as CasesPatchRequest; - const updateFilterCases = updateCases.filter((updateCase) => { - const { id, version, ...updateCaseAttributes } = updateCase; - return Object.keys(updateCaseAttributes).length > 0; + try { + return response.ok({ + body: await caseClient.update({ cases }), }); - - if (updateFilterCases.length > 0) { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { username, full_name, email } = await caseService.getUser({ request, response }); - const updatedDt = new Date().toISOString(); - const updatedCases = await caseService.patchCases({ - client, - cases: updateFilterCases.map((thisCase) => { - const { id: caseId, version, ...updateCaseAttributes } = thisCase; - let closedInfo = {}; - if (updateCaseAttributes.status && updateCaseAttributes.status === 'closed') { - closedInfo = { - closed_at: updatedDt, - closed_by: { email, full_name, username }, - }; - } else if (updateCaseAttributes.status && updateCaseAttributes.status === 'open') { - closedInfo = { - closed_at: null, - closed_by: null, - }; - } - return { - caseId, - updatedAttributes: { - ...updateCaseAttributes, - ...closedInfo, - updated_at: updatedDt, - updated_by: { email, full_name, username }, - }, - version, - }; - }), - }); - - const returnUpdatedCase = myCases.saved_objects - .filter((myCase) => - updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id) - ) - .map((myCase) => { - const updatedCase = updatedCases.saved_objects.find((c) => c.id === myCase.id); - return flattenCaseSavedObject({ - savedObject: { - ...myCase, - ...updatedCase, - attributes: { ...myCase.attributes, ...updatedCase?.attributes }, - references: myCase.references, - version: updatedCase?.version ?? myCase.version, - }, - }); - }); - - await userActionService.postUserActions({ - client, - actions: buildCaseUserActions({ - originalCases: myCases.saved_objects, - updatedCases: updatedCases.saved_objects, - actionDate: updatedDt, - actionBy: { email, full_name, username }, - }), - }); - - return response.ok({ - body: CasesResponseRt.encode(returnUpdatedCase), - }); - } - throw Boom.notAcceptable('All update fields are identical to current version.'); } catch (error) { return response.customError(wrapError(error)); } diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index be1ed4166ab7b1..1e1b19baa1c479 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -45,7 +45,7 @@ describe('POST cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -80,7 +80,7 @@ describe('POST cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseConfigureSavedObject: mockCaseConfigure, @@ -110,7 +110,7 @@ describe('POST cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -132,7 +132,7 @@ describe('POST cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -162,11 +162,12 @@ describe('POST cases', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseConfigureSavedObject: mockCaseConfigure, - }) + }), + true ); const response = await routeHandler(theContext, request, kibanaResponseFactory); diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.ts index 5d8113b685741e..663d502d548d5e 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.ts @@ -4,25 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; +import { wrapError, escapeHatch } from '../utils'; -import { flattenCaseSavedObject, transformNewCase, wrapError, escapeHatch } from '../utils'; - -import { CasePostRequestRt, throwErrors, excess, CaseResponseRt } from '../../../../common/api'; -import { buildCaseUserActionItem } from '../../../services/user_actions/helpers'; import { RouteDeps } from '../types'; import { CASES_URL } from '../../../../common/constants'; -import { getConnectorFromConfiguration, transformCaseConnectorToEsConnector } from './helpers'; +import { CasePostRequest } from '../../../../common/api'; -export function initPostCaseApi({ - caseService, - caseConfigureService, - router, - userActionService, -}: RouteDeps) { +export function initPostCaseApi({ router }: RouteDeps) { router.post( { path: CASES_URL, @@ -31,53 +19,15 @@ export function initPostCaseApi({ }, }, async (context, request, response) => { - try { - const client = context.core.savedObjects.client; - const query = pipe( - excess(CasePostRequestRt).decode(request.body), - fold(throwErrors(Boom.badRequest), identity) - ); - - // eslint-disable-next-line @typescript-eslint/naming-convention - const { username, full_name, email } = await caseService.getUser({ request, response }); - const createdDate = new Date().toISOString(); - const myCaseConfigure = await caseConfigureService.find({ client }); - const caseConfigureConnector = getConnectorFromConfiguration(myCaseConfigure); - - const newCase = await caseService.postNewCase({ - client, - attributes: transformNewCase({ - createdDate, - newCase: query, - username, - full_name, - email, - connector: transformCaseConnectorToEsConnector( - query.connector ?? caseConfigureConnector - ), - }), - }); - - await userActionService.postUserActions({ - client, - actions: [ - buildCaseUserActionItem({ - action: 'create', - actionAt: createdDate, - actionBy: { username, full_name, email }, - caseId: newCase.id, - fields: ['description', 'status', 'tags', 'title', 'connector'], - newValue: JSON.stringify(query), - }), - ], - }); + if (!context.case) { + return response.badRequest({ body: 'RouteHandlerContext is not registered for cases' }); + } + const caseClient = context.case.getCaseClient(); + const theCase = request.body as CasePostRequest; + try { return response.ok({ - body: CaseResponseRt.encode( - flattenCaseSavedObject({ - savedObject: newCase, - }) - ), + body: await caseClient.create({ theCase }), }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts index c68b4b0c91735a..eee59a974b37b6 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/push_case.test.ts @@ -44,7 +44,7 @@ describe('Push case', () => { body: caseExternalServiceRequestBody, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) @@ -66,7 +66,7 @@ describe('Push case', () => { body: caseExternalServiceRequestBody, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, caseConfigureSavedObject: [ @@ -97,7 +97,7 @@ describe('Push case', () => { }, }); - const theContext = createRouteContext( + const theContext = await createRouteContext( createMockSavedObjectsRepository({ caseSavedObject: mockCases, }) diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index 2202bda2be0879..90066bf29ae661 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -121,7 +121,7 @@ export const flattenCaseSavedObjects = ( export const flattenCaseSavedObject = ({ savedObject, comments = [], - totalComment = 0, + totalComment = comments.length, }: { savedObject: SavedObject; comments?: Array>; diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index 3db83331a0ab98..cab8cb499c3fae 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -96,7 +96,7 @@ interface PatchComments extends ClientArgs { interface GetUserArgs { request: KibanaRequest; - response: KibanaResponseFactory; + response?: KibanaResponseFactory; } interface CaseServiceDeps { diff --git a/x-pack/plugins/case/server/services/mocks.ts b/x-pack/plugins/case/server/services/mocks.ts new file mode 100644 index 00000000000000..287f80a60ab076 --- /dev/null +++ b/x-pack/plugins/case/server/services/mocks.ts @@ -0,0 +1,43 @@ +/* + * 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 { CaseConfigureServiceSetup, CaseServiceSetup, CaseUserActionServiceSetup } from '.'; + +export type CaseServiceMock = jest.Mocked; +export type CaseConfigureServiceMock = jest.Mocked; +export type CaseUserActionServiceMock = jest.Mocked; + +export const createCaseServiceMock = (): CaseServiceMock => ({ + deleteCase: jest.fn(), + deleteComment: jest.fn(), + findCases: jest.fn(), + getAllCaseComments: jest.fn(), + getCase: jest.fn(), + getCases: jest.fn(), + getComment: jest.fn(), + getTags: jest.fn(), + getReporters: jest.fn(), + getUser: jest.fn(), + postNewCase: jest.fn(), + postNewComment: jest.fn(), + patchCase: jest.fn(), + patchCases: jest.fn(), + patchComment: jest.fn(), + patchComments: jest.fn(), +}); + +export const createConfigureServiceMock = (): CaseConfigureServiceMock => ({ + delete: jest.fn(), + get: jest.fn(), + find: jest.fn(), + patch: jest.fn(), + post: jest.fn(), +}); + +export const createUserActionServiceMock = (): CaseUserActionServiceMock => ({ + getUserActions: jest.fn(), + postUserActions: jest.fn(), +}); diff --git a/x-pack/plugins/case/server/types.ts b/x-pack/plugins/case/server/types.ts new file mode 100644 index 00000000000000..b95060ef304527 --- /dev/null +++ b/x-pack/plugins/case/server/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CaseClient } from './client'; + +export interface CaseRequestContext { + getCaseClient: () => CaseClient; +} + +declare module 'src/core/server' { + interface RequestHandlerContext { + case?: CaseRequestContext; + } +} diff --git a/x-pack/plugins/console_extensions/README.md b/x-pack/plugins/console_extensions/README.md new file mode 100644 index 00000000000000..49d83d2888d6be --- /dev/null +++ b/x-pack/plugins/console_extensions/README.md @@ -0,0 +1,3 @@ +# Console extensions + +This plugin provides autocomplete definitions of licensed APIs to the OSS Console plugin. \ No newline at end of file diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index ce26e4e71a5db0..7edb6276b8158b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -143,7 +143,6 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ closePopover={() => setShowPopover(false)} button={button} panelPaddingSize="none" - withTitle repositionOnScroll > diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index d0c597532f6edf..e1d8a2b3671a25 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -12,8 +12,9 @@ import { } from '../../../../../../../src/plugins/ui_actions/public'; /** - * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER - * This function appends APPLY_FILTER_TRIGGER to list of triggers if VALUE_CLICK_TRIGGER or SELECT_RANGE_TRIGGER + * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER. + * This function appends APPLY_FILTER_TRIGGER to the list of triggers if either VALUE_CLICK_TRIGGER + * or SELECT_RANGE_TRIGGER was executed. * * TODO: this probably should be part of uiActions infrastructure, * but dynamic implementation of nested trigger doesn't allow to statically express such relations diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index d5547ff8097cc0..ff54e0812975db 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -129,7 +129,7 @@ describe('isCompatible', () => { }); }); - test('not compatible if no triggers intersection', async () => { + test('not compatible if no triggers intersect', async () => { await assertNonCompatibility({ actionFactoriesTriggers: [], }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index a2192808c2d401..a417deb47db537 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -10,9 +10,12 @@ import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/pub import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { isEnhancedEmbeddable, - embeddableEnhancedContextMenuDrilldownGrouping, + embeddableEnhancedDrilldownGrouping, } from '../../../../../../embeddable_enhanced/public'; -import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; +import { + CONTEXT_MENU_TRIGGER, + EmbeddableContext, +} from '../../../../../../../../src/plugins/embeddable/public'; import { StartDependencies } from '../../../../plugin'; import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; import { ensureNestedTriggers } from '../drilldown_shared'; @@ -27,7 +30,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} viewMode={'create'} dynamicActionManager={embeddable.enhancements.dynamicActions} - triggers={ensureNestedTriggers(embeddable.supportedTriggers())} + triggers={[...ensureNestedTriggers(embeddable.supportedTriggers()), CONTEXT_MENU_TRIGGER]} placeContext={{ embeddable }} /> ), diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 56ef25005078b4..1f0570445a8fc7 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -10,12 +10,16 @@ import { reactToUiComponent, toMountPoint, } from '../../../../../../../../src/plugins/kibana_react/public'; -import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableContext, + ViewMode, + CONTEXT_MENU_TRIGGER, +} from '../../../../../../../../src/plugins/embeddable/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; import { isEnhancedEmbeddable, - embeddableEnhancedContextMenuDrilldownGrouping, + embeddableEnhancedDrilldownGrouping, } from '../../../../../../embeddable_enhanced/public'; import { StartDependencies } from '../../../../plugin'; import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; @@ -31,7 +35,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} viewMode={'manage'} dynamicActionManager={embeddable.enhancements.dynamicActions} - triggers={ensureNestedTriggers(embeddable.supportedTriggers())} + triggers={[...ensureNestedTriggers(embeddable.supportedTriggers()), CONTEXT_MENU_TRIGGER]} placeContext={{ embeddable }} /> ), diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts index 67fc1a98ad4d1f..4b1e4b34da86a5 100644 --- a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts +++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse as parseUrl } from 'url'; import { OnPostAuthHandler, OnPostAuthToolkit, @@ -45,7 +46,7 @@ describe('DashboardOnlyModeRequestInterceptor', () => { test('should not redirects for not app/* requests', async () => { const request = ({ url: { - path: 'api/test', + pathname: 'api/test', }, } as unknown) as KibanaRequest; @@ -57,7 +58,7 @@ describe('DashboardOnlyModeRequestInterceptor', () => { test('should not redirects not authenticated users', async () => { const request = ({ url: { - path: '/app/home', + pathname: '/app/home', }, } as unknown) as KibanaRequest; @@ -70,10 +71,9 @@ describe('DashboardOnlyModeRequestInterceptor', () => { function testRedirectToDashboardModeApp(url: string) { describe(`requests to url:"${url}"`, () => { test('redirects to the dashboard_mode app instead', async () => { + const { pathname, search, hash } = parseUrl(url); const request = ({ - url: { - path: url, - }, + url: { pathname, search, hash }, credentials: { roles: [DASHBOARD_ONLY_MODE_ROLE], }, diff --git a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts index 4378c818f087c5..9978d18142ff58 100644 --- a/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts +++ b/x-pack/plugins/dashboard_mode/server/interceptors/dashboard_mode_request_interceptor.ts @@ -22,7 +22,7 @@ export const setupDashboardModeRequestInterceptor = ({ getUiSettingsClient, }: DashboardModeRequestInterceptorDependencies) => (async (request, response, toolkit) => { - const path = request.url.path || ''; + const path = request.url.pathname; const isAppRequest = path.startsWith('/app/'); if (!isAppRequest) { diff --git a/x-pack/plugins/data_enhanced/README.md b/x-pack/plugins/data_enhanced/README.md new file mode 100644 index 00000000000000..8f3ae7ac3cd131 --- /dev/null +++ b/x-pack/plugins/data_enhanced/README.md @@ -0,0 +1,25 @@ +# data_enhanced + +The `data_enhanced` plugin is the x-pack counterpart to the OSS `data` plugin. + +It exists to provide Elastic-licensed services, or parts of services, which +enhance existing OSS functionality from `data`. + +Currently the `data_enhanced` plugin doesn't return any APIs which you can +consume directly, however it is possible that you are indirectly relying on the +enhanced functionality that it provides via the OSS `data` plugin. + +Here is the functionality it adds: + +## KQL Autocomplete + +The OSS autocomplete service provides suggestions for field names and values +based on suggestion providers which are registered to the service. This plugin +registers the autocomplete provider for KQL to the OSS service. + +## Async, Rollup, and EQL Search Strategies + +This plugin enhances the OSS search service with an ES search strategy that +uses async search (or rollups) behind the scenes. It also registers an EQL +search strategy. + diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index 3187b41a2c55f1..f47d2b39a89a98 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.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 type { MockedKeys } from '@kbn/utility-types/jest'; import { coreMock } from '../../../../../src/core/public/mocks'; import { EnhancedSearchInterceptor } from './search_interceptor'; import { CoreSetup, CoreStart } from 'kibana/public'; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index aee32a7c62759d..3226ecf6f0ddab 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -64,7 +64,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { abortSignal: options.abortSignal, timeout: this.searchTimeout, }); - const aborted$ = from(toPromise(combinedSignal)); + const abortedPromise = toPromise(combinedSignal); const strategy = options?.strategy || ENHANCED_ES_SEARCH_STRATEGY; this.pendingCount$.next(this.pendingCount$.getValue() + 1); @@ -90,7 +90,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { }) ); }), - takeUntil(aborted$), + takeUntil(from(abortedPromise.promise)), catchError((e: any) => { // If we haven't received the response to the initial request, including the ID, then // we don't need to send a follow-up request to delete this search. Otherwise, we @@ -103,6 +103,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); cleanup(); + abortedPromise.cleanup(); }) ); } diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 85e92d0827daab..807dfeed21d1fc 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -6,7 +6,11 @@ import React from 'react'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; -import { ChartActionContext, IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { + ChartActionContext, + CONTEXT_MENU_TRIGGER, + IEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { SELECT_RANGE_TRIGGER, @@ -34,7 +38,10 @@ interface UrlDrilldownDeps { export type ActionContext = ChartActionContext; export type Config = UrlDrilldownConfig; -export type UrlTrigger = typeof VALUE_CLICK_TRIGGER | typeof SELECT_RANGE_TRIGGER; +export type UrlTrigger = + | typeof CONTEXT_MENU_TRIGGER + | typeof VALUE_CLICK_TRIGGER + | typeof SELECT_RANGE_TRIGGER; export interface ActionFactoryContext extends BaseActionFactoryContext { embeddable?: IEmbeddable; } @@ -58,7 +65,7 @@ export class UrlDrilldown implements Drilldown = ({ diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts index 6989819da2b0bb..a93e150deee8fc 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts @@ -87,25 +87,25 @@ describe('VALUE_CLICK_TRIGGER', () => { ]) as ValueClickTriggerEventScope; expect(mockEventScope.points.length).toBeGreaterThan(3); expect(mockEventScope.points).toMatchInlineSnapshot(` - Array [ - Object { - "key": "event.points.0.key", - "value": "event.points.0.value", - }, - Object { - "key": "event.points.1.key", - "value": "event.points.1.value", - }, - Object { - "key": "event.points.2.key", - "value": "event.points.2.value", - }, - Object { - "key": "event.points.3.key", - "value": "event.points.3.value", - }, - ] - `); + Array [ + Object { + "key": "event.points.0.key", + "value": "event.points.0.value", + }, + Object { + "key": "event.points.1.key", + "value": "event.points.1.value", + }, + Object { + "key": "event.points.2.key", + "value": "event.points.2.value", + }, + Object { + "key": "event.points.3.key", + "value": "event.points.3.value", + }, + ] + `); }); }); @@ -130,3 +130,12 @@ describe('VALUE_CLICK_TRIGGER', () => { }); }); }); + +describe('CONTEXT_MENU_TRIGGER', () => { + test('getMockEventScope() results in empty scope', () => { + const mockEventScope = getMockEventScope([ + 'CONTEXT_MENU_TRIGGER', + ]) as ValueClickTriggerEventScope; + expect(mockEventScope).toEqual({}); + }); +}); diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts index 0f66cb144c967a..234af380689e95 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts @@ -14,11 +14,15 @@ import { IEmbeddable, isRangeSelectTriggerContext, isValueClickTriggerContext, + isContextMenuTriggerContext, RangeSelectContext, ValueClickContext, } from '../../../../../../src/plugins/embeddable/public'; import type { ActionContext, ActionFactoryContext, UrlTrigger } from './url_drilldown'; -import { SELECT_RANGE_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/ui_actions/public'; type ContextScopeInput = ActionContext | ActionFactoryContext; @@ -101,7 +105,10 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld * URL drilldown event scope, * available as {{event.$}} */ -export type UrlDrilldownEventScope = ValueClickTriggerEventScope | RangeSelectTriggerEventScope; +export type UrlDrilldownEventScope = + | ValueClickTriggerEventScope + | RangeSelectTriggerEventScope + | ContextMenuTriggerEventScope; export type EventScopeInput = ActionContext; export interface ValueClickTriggerEventScope { key?: string; @@ -115,11 +122,15 @@ export interface RangeSelectTriggerEventScope { to?: string | number; } +export type ContextMenuTriggerEventScope = object; + export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEventScope { if (isRangeSelectTriggerContext(eventScopeInput)) { return getEventScopeFromRangeSelectTriggerContext(eventScopeInput); } else if (isValueClickTriggerContext(eventScopeInput)) { return getEventScopeFromValueClickTriggerContext(eventScopeInput); + } else if (isContextMenuTriggerContext(eventScopeInput)) { + return {}; } else { throw new Error("UrlDrilldown [getEventScope] can't build scope from not supported trigger"); } @@ -169,7 +180,9 @@ export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventSco from: new Date(Date.now() - 15 * 60 * 1000).toISOString(), // 15 minutes ago to: new Date().toISOString(), }; - } else { + } + + if (trigger === VALUE_CLICK_TRIGGER) { // number of mock points to generate // should be larger or equal of any possible data points length emitted by VALUE_CLICK_TRIGGER const nPoints = 4; @@ -184,6 +197,8 @@ export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventSco points, }; } + + return {}; } type Primitive = string | number | boolean | null; diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts index 5ea8928532c280..0aa1c0e6f08aed 100644 --- a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts +++ b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; import { UiActionsPresentableGrouping as PresentableGrouping } from '../../../../../src/plugins/ui_actions/public'; -export const contextMenuDrilldownGrouping: PresentableGrouping<{ +export const drilldownGrouping: PresentableGrouping<{ embeddable?: IEmbeddable; }> = [ { id: 'drilldowns', - getDisplayName: () => 'Drilldowns', + getDisplayName: () => + i18n.translate('xpack.embeddableEnhanced.Drilldowns', { + defaultMessage: 'Drilldowns', + }), getIconType: () => 'symlink', order: 25, }, diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts index a7916685239df5..24f8eb623abe01 100644 --- a/x-pack/plugins/embeddable_enhanced/public/index.ts +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -20,4 +20,4 @@ export function plugin(context: PluginInitializerContext) { export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; export { isEnhancedEmbeddable } from './embeddables'; -export { contextMenuDrilldownGrouping as embeddableEnhancedContextMenuDrilldownGrouping } from './actions'; +export { drilldownGrouping as embeddableEnhancedDrilldownGrouping } from './actions'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts index fb1b6db45e762d..44b040d19909c5 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { ISavedObjectTypeRegistry, KibanaRequest, diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts index 72af8060de8270..f68e920e14de93 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { IRouter, Logger } from '../../../../../src/core/server'; import { ConfigType } from '../config'; import { EncryptionKeyRotationService } from '../crypto'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts index ced4dda48fcd2a..acc2d2247efcb0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts @@ -57,24 +57,24 @@ describe('Key rotation routes', () => { const queryValidator = (routeConfig.validate as any).query as Type; expect( queryValidator.validate({ - batchSize: 100, + batch_size: 100, type: 'some-type', }) ).toEqual({ - batchSize: 100, + batch_size: 100, type: 'some-type', }); - expect(queryValidator.validate({ batchSize: 1 })).toEqual({ batchSize: 1 }); - expect(queryValidator.validate({ batchSize: 10000 })).toEqual({ batchSize: 10000 }); - expect(queryValidator.validate({})).toEqual({ batchSize: 10000 }); + expect(queryValidator.validate({ batch_size: 1 })).toEqual({ batch_size: 1 }); + expect(queryValidator.validate({ batch_size: 10000 })).toEqual({ batch_size: 10000 }); + expect(queryValidator.validate({})).toEqual({ batch_size: 10000 }); - expect(() => queryValidator.validate({ batchSize: 0 })).toThrowErrorMatchingInlineSnapshot( - `"[batchSize]: Value must be equal to or greater than [1]."` + expect(() => queryValidator.validate({ batch_size: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[batch_size]: Value must be equal to or greater than [1]."` ); expect(() => - queryValidator.validate({ batchSize: 10001 }) + queryValidator.validate({ batch_size: 10001 }) ).toThrowErrorMatchingInlineSnapshot( - `"[batchSize]: Value must be equal to or lower than [10000]."` + `"[batch_size]: Value must be equal to or lower than [10000]."` ); expect(() => queryValidator.validate({ type: 100 })).toThrowErrorMatchingInlineSnapshot( @@ -106,7 +106,7 @@ describe('Key rotation routes', () => { const unhandledException = new Error('Something went wrong.'); mockEncryptionKeyRotationService.rotate.mockRejectedValue(unhandledException); - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(500); @@ -117,7 +117,7 @@ describe('Key rotation routes', () => { }); it('returns whatever `rotate` returns.', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); mockEncryptionKeyRotationService.rotate.mockResolvedValue({ total: 3, successful: 6, @@ -132,7 +132,7 @@ describe('Key rotation routes', () => { }); it('returns 429 if called while rotation is in progress.', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); mockEncryptionKeyRotationService.rotate.mockResolvedValue({ total: 3, successful: 6, diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts index 48b29387106ee3..c74f67220472c0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts @@ -28,7 +28,7 @@ export function defineKeyRotationRoutes({ path: '/api/encrypted_saved_objects/_rotate_key', validate: { query: schema.object({ - batchSize: schema.number({ + batch_size: schema.number({ min: 1, max: DEFAULT_MAX_RESULT_WINDOW, defaultValue: DEFAULT_MAX_RESULT_WINDOW, @@ -60,7 +60,7 @@ export function defineKeyRotationRoutes({ try { return response.ok({ body: await encryptionKeyRotationService.rotate(request, { - batchSize: request.query.batchSize, + batchSize: request.query.batch_size, type: request.query.type, }), }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index 0e5be4e4eee5aa..0191c1fbb830d5 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { StartServicesAccessor, SavedObject, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx index 567c77792583d2..7af3a1d1272e3f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx @@ -7,10 +7,12 @@ import React from 'react'; import { EuiPage, EuiPageContent } from '@elastic/eui'; +import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; import { ErrorStatePrompt } from '../../../shared/error_state'; export const ErrorConnecting: React.FC = () => ( + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx index e5fdcc30890597..c9bf03c5d4700a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx @@ -45,7 +45,6 @@ export const FilterableUsersPopover: React.FC = ( isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" - withTitle={true} > = isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" - withTitle={true} > {contentSourceCountHeading}
{sources}
diff --git a/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.test.ts index c3e2aff6551c94..440f540ccc8574 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.test.ts @@ -20,6 +20,8 @@ describe('Enterprise Search Telemetry Usage Collector', () => { get: () => ({ attributes: { 'ui_viewed.overview': 10, + 'ui_viewed.setup_guide': 5, + 'ui_error.cannot_connect': 1, 'ui_clicked.app_search': 2, 'ui_clicked.workplace_search': 3, }, @@ -53,6 +55,10 @@ describe('Enterprise Search Telemetry Usage Collector', () => { expect(savedObjectsCounts).toEqual({ ui_viewed: { overview: 10, + setup_guide: 5, + }, + ui_error: { + cannot_connect: 1, }, ui_clicked: { app_search: 2, @@ -74,6 +80,10 @@ describe('Enterprise Search Telemetry Usage Collector', () => { expect(savedObjectsCounts).toEqual({ ui_viewed: { overview: 0, + setup_guide: 0, + }, + ui_error: { + cannot_connect: 0, }, ui_clicked: { app_search: 0, diff --git a/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.ts b/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.ts index a124a185b9a349..d6bd8bd9305f58 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/telemetry.ts @@ -13,6 +13,10 @@ import { getSavedObjectAttributesFromRepo } from '../lib/telemetry'; interface ITelemetry { ui_viewed: { overview: number; + setup_guide: number; + }; + ui_error: { + cannot_connect: number; }; ui_clicked: { app_search: number; @@ -38,6 +42,10 @@ export const registerTelemetryUsageCollector = ( schema: { ui_viewed: { overview: { type: 'long' }, + setup_guide: { type: 'long' }, + }, + ui_error: { + cannot_connect: { type: 'long' }, }, ui_clicked: { app_search: { type: 'long' }, @@ -63,6 +71,10 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log const defaultTelemetrySavedObject: ITelemetry = { ui_viewed: { overview: 0, + setup_guide: 0, + }, + ui_error: { + cannot_connect: 0, }, ui_clicked: { app_search: 0, @@ -78,6 +90,10 @@ const fetchTelemetryMetrics = async (savedObjects: SavedObjectsServiceStart, log return { ui_viewed: { overview: get(savedObjectAttributes, 'ui_viewed.overview', 0), + setup_guide: get(savedObjectAttributes, 'ui_viewed.setup_guide', 0), + }, + ui_error: { + cannot_connect: get(savedObjectAttributes, 'ui_error.cannot_connect', 0), }, ui_clicked: { app_search: get(savedObjectAttributes, 'ui_clicked.app_search', 0), diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index 2bddc9f1c80bd7..5bd15ce411002b 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -21,7 +21,6 @@ describe('callEnterpriseSearchConfigAPI', () => { accessCheckTimeoutWarning: 100, }; const mockRequest = { - url: { path: '/app/kibana' }, headers: { authorization: '==someAuth' }, }; const mockDependencies = { diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 8c3e6e11b75c5b..fa9f9c36052a10 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -6,6 +6,7 @@ import { reject, isUndefined } from 'lodash'; import { SearchResponse, Client } from 'elasticsearch'; +import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger, LegacyClusterClient } from 'src/core/server'; import { IValidatedEvent, SAVED_OBJECT_REL_PRIMARY } from '../types'; diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index 3273fe847080f8..d9846428b94888 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -114,7 +114,7 @@ describe('EventLogStart', () => { ).toEqual(result); expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( - esContext.esNames.alias, + esContext.esNames.indexPattern, undefined, 'saved-object-type', 'saved-object-id', @@ -195,7 +195,7 @@ describe('EventLogStart', () => { ).toEqual(result); expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( - esContext.esNames.alias, + esContext.esNames.indexPattern, undefined, 'saved-object-type', 'saved-object-id', @@ -322,6 +322,12 @@ function FakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectGetter, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts index 32fd99d1700267..b7de4acb9428cf 100644 --- a/x-pack/plugins/event_log/server/event_log_client.ts +++ b/x-pack/plugins/event_log/server/event_log_client.ts @@ -92,7 +92,7 @@ export class EventLogClient implements IEventLogClient { await this.savedObjectGetter(type, id); return await this.esContext.esAdapter.queryEventsBySavedObject( - this.esContext.esNames.alias, + this.esContext.esNames.indexPattern, namespace, type, id, diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index 0a5b169e87d4d3..db6f4a1ad0f279 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -56,6 +56,12 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts index b0ce5605d0e51f..0ea5419b113d2d 100644 --- a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -6,6 +6,7 @@ import { identity, merge } from 'lodash'; import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'src/core/server'; +import type { MethodKeysOf } from '@kbn/utility-types'; import { httpServerMock } from 'src/core/server/mocks'; import { IEventLogClient } from '../types'; diff --git a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts index 6a02d54c875145..076260ab2fe530 100644 --- a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts +++ b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts @@ -93,6 +93,12 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, + // TODO: Remove once we upgrade to hapi v18 + _core: { + info: { + uri: 'http://localhost', + }, + }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/features/server/ui_capabilities_for_features.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.ts index d582dbfdab50c3..03773a4d8f616f 100644 --- a/x-pack/plugins/features/server/ui_capabilities_for_features.ts +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.ts @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import { RecursiveReadonly } from '@kbn/utility-types'; +import type { RecursiveReadonly, Writable } from '@kbn/utility-types'; import { Capabilities as UICapabilities } from '../../../../src/core/server'; import { ElasticsearchFeature, KibanaFeature } from '../common'; diff --git a/x-pack/plugins/file_upload/README.md b/x-pack/plugins/file_upload/README.md new file mode 100644 index 00000000000000..0d4b4da61ccf69 --- /dev/null +++ b/x-pack/plugins/file_upload/README.md @@ -0,0 +1,3 @@ +# File upload + +Backend and core front-end react-components for GeoJson file upload. Only supports the Maps plugin. \ No newline at end of file diff --git a/x-pack/plugins/global_search/common/license_checker.ts b/x-pack/plugins/global_search/common/license_checker.ts index d201b31802b32f..7524d0ad52e458 100644 --- a/x-pack/plugins/global_search/common/license_checker.ts +++ b/x-pack/plugins/global_search/common/license_checker.ts @@ -5,6 +5,7 @@ */ import { Observable, Subscription } from 'rxjs'; +import type { PublicMethodsOf } from '@kbn/utility-types'; import { ILicense } from '../../licensing/common/types'; export type LicenseState = { valid: false; message: string } | { valid: true }; diff --git a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index f4006d6bf142bb..ec5da1f44223c5 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -116,7 +116,7 @@ export function FieldEditor({ return (
{ component.update(); }; - const setSelectedNodeAttribute = (phase: string) => + const setSelectedNodeAttribute = (phase: Phases) => createFormSetValueAction(`${phase}-selectedNodeAttrs`); - const setReplicas = async (value: string) => { - await createFormToggleAction('warm-setReplicasSwitch')(true); - await createFormSetValueAction('warm-selectedReplicaCount')(value); + const setReplicas = (phase: Phases) => async (value: string) => { + await createFormToggleAction(`${phase}-setReplicasSwitch`)(true); + await createFormSetValueAction(`${phase}-selectedReplicaCount`)(value); }; const setShrink = async (value: string) => { @@ -167,6 +167,8 @@ export const setup = async () => { await createFormSetValueAction('warm-selectedPrimaryShardCount')(value); }; + const setFreeze = createFormToggleAction('freezeSwitch'); + return { ...testBed, actions: { @@ -189,13 +191,23 @@ export const setup = async () => { setMinAgeUnits: setMinAgeUnits('warm'), setDataAllocation: setDataAllocation('warm'), setSelectedNodeAttribute: setSelectedNodeAttribute('warm'), - setReplicas, + setReplicas: setReplicas('warm'), setShrink, toggleForceMerge: toggleForceMerge('warm'), setForcemergeSegments: setForcemergeSegmentsCount('warm'), setBestCompression: setBestCompression('warm'), setIndexPriority: setIndexPriority('warm'), }, + cold: { + enable: enable('cold'), + setMinAgeValue: setMinAgeValue('cold'), + setMinAgeUnits: setMinAgeUnits('cold'), + setDataAllocation: setDataAllocation('cold'), + setSelectedNodeAttribute: setSelectedNodeAttribute('cold'), + setReplicas: setReplicas('cold'), + setFreeze, + setIndexPriority: setIndexPriority('cold'), + }, }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index fccffde3f793f2..11fadf51f27f8d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -15,6 +15,7 @@ import { NEW_SNAPSHOT_POLICY_NAME, SNAPSHOT_POLICY_NAME, DEFAULT_POLICY, + POLICY_WITH_MIGRATE_OFF, POLICY_WITH_INCLUDE_EXCLUDE, POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION, POLICY_WITH_NODE_ROLE_ALLOCATION, @@ -199,26 +200,6 @@ describe('', () => { `); }); - test('default allocation with replicas set', async () => { - const { actions } = testBed; - await actions.warm.enable(true); - await actions.warm.setReplicas('123'); - await actions.savePolicy(); - const latestRequest = server.requests[server.requests.length - 1]; - const warmPhaseActions = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm - .actions; - expect(warmPhaseActions).toMatchInlineSnapshot(` - Object { - "allocate": Object { - "number_of_replicas": 123, - }, - "set_priority": Object { - "priority": 50, - }, - } - `); - }); - test('setting warm phase on rollover to "true"', async () => { const { actions } = testBed; await actions.warm.enable(true); @@ -252,6 +233,7 @@ describe('', () => { test('preserves include, exclude allocation settings', async () => { const { actions } = testBed; await actions.warm.setDataAllocation('node_attrs'); + await actions.warm.setSelectedNodeAttribute('test:123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const warmPhaseAllocate = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm @@ -264,6 +246,101 @@ describe('', () => { "include": Object { "abc": "123", }, + "require": Object { + "test": "123", + }, + } + `); + }); + }); + }); + + describe('cold phase', () => { + describe('serialization', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('default values', async () => { + const { actions } = testBed; + + await actions.cold.enable(true); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + expect(entirePolicy.phases.cold).toMatchInlineSnapshot(` + Object { + "actions": Object { + "set_priority": Object { + "priority": 0, + }, + }, + "min_age": "0d", + } + `); + }); + + test('setting all values', async () => { + const { actions } = testBed; + + await actions.cold.enable(true); + await actions.cold.setMinAgeValue('123'); + await actions.cold.setMinAgeUnits('s'); + await actions.cold.setDataAllocation('node_attrs'); + await actions.cold.setSelectedNodeAttribute('test:123'); + await actions.cold.setReplicas('123'); + await actions.cold.setFreeze(true); + await actions.cold.setIndexPriority('123'); + + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + + expect(entirePolicy).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "cold": Object { + "actions": Object { + "allocate": Object { + "number_of_replicas": 123, + "require": Object { + "test": "123", + }, + }, + "freeze": Object {}, + "set_priority": Object { + "priority": 123, + }, + }, + "min_age": "123s", + }, + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "0ms", + }, + }, } `); }); @@ -385,6 +462,33 @@ describe('', () => { }); describe('data allocation', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([POLICY_WITH_MIGRATE_OFF]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('setting node_attr based allocation, but not selecting node attribute', async () => { + const { actions } = testBed; + await actions.warm.setDataAllocation('node_attrs'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const warmPhase = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm; + + expect(warmPhase.actions.migrate).toEqual({ enabled: false }); + }); + describe('node roles', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadPolicies([POLICY_WITH_NODE_ROLE_ALLOCATION]); @@ -401,15 +505,32 @@ describe('', () => { const { component } = testBed; component.update(); }); - test('showing "default" type', () => { + + test('detecting use of the recommended allocation type', () => { const { find } = testBed; - expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toContain( - 'recommended' - ); - expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).not.toContain( - 'Custom' - ); - expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).not.toContain('Off'); + const selectedDataAllocation = find( + 'warm-dataTierAllocationControls.dataTierSelect' + ).text(); + expect(selectedDataAllocation).toBe('Use warm nodes (recommended)'); + }); + + test('setting replicas serialization', async () => { + const { actions } = testBed; + await actions.warm.setReplicas('123'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + const warmPhaseActions = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm + .actions; + expect(warmPhaseActions).toMatchInlineSnapshot(` + Object { + "allocate": Object { + "number_of_replicas": 123, + }, + "set_priority": Object { + "priority": 50, + }, + } + `); }); }); describe('node attr and none', () => { @@ -429,9 +550,12 @@ describe('', () => { component.update(); }); - test('showing "custom" and "off" types', () => { + test('detecting use of the custom allocation type', () => { + const { find } = testBed; + expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toBe('Custom'); + }); + test('detecting use of the "off" allocation type', () => { const { find } = testBed; - expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toContain('Custom'); expect(find('cold-dataTierAllocationControls.dataTierSelect').text()).toContain('Off'); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts index c6d27ca890b54f..e8ebc2963d16a5 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/index.ts @@ -17,4 +17,5 @@ export type TestSubjects = | 'hot-selectedMaxDocuments' | 'hot-selectedMaxAge' | 'hot-selectedMaxAgeUnits' + | 'freezeSwitch' | string; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index 4ba6cee7b027f3..4a3fedfb264ac1 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -119,9 +119,6 @@ const noRollover = async (rendered: ReactWrapper) => { }); rendered.update(); }; -const getNodeAttributeSelectLegacy = (rendered: ReactWrapper, phase: string) => { - return rendered.find(`select#${phase}-selectedNodeAttrs`); -}; const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { return findTestSubject(rendered, `${phase}-selectedNodeAttrs`); }; @@ -142,15 +139,6 @@ const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: strin }); rendered.update(); }; -const setPhaseIndexPriorityLegacy = ( - rendered: ReactWrapper, - phase: string, - priority: string | number -) => { - const priorityInput = rendered.find(`input#${phase}-phaseIndexPriority`); - priorityInput.simulate('change', { target: { value: priority } }); - rendered.update(); -}; const setPhaseIndexPriority = async ( rendered: ReactWrapper, phase: string, @@ -184,7 +172,7 @@ describe('edit policy', () => { */ const waitForFormLibValidation = (rendered: ReactWrapper) => { act(() => { - jest.advanceTimersByTime(1000); + jest.runAllTimers(); }); rendered.update(); }; @@ -394,7 +382,7 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await setPhaseIndexPriority(rendered, 'hot', '-1'); waitForFormLibValidation(rendered); - expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); }); describe('warm phase', () => { @@ -519,7 +507,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelectLegacy(rendered, 'warm').exists()).toBeFalsy(); + expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); }); test('should show warning instead of node attributes input when none exist', async () => { http.setupNodeListResponse({ @@ -534,7 +522,7 @@ describe('edit policy', () => { expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'warm'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelectLegacy(rendered, 'warm').exists()).toBeFalsy(); + expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); }); test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); @@ -625,8 +613,9 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfterLegacy(rendered, 'cold', '0'); - await save(rendered); + await setPhaseAfter(rendered, 'cold', '0'); + waitForFormLibValidation(rendered); + rendered.update(); expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save cold phase with -1 for after', async () => { @@ -634,9 +623,9 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfterLegacy(rendered, 'cold', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'cold', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); test('should show spinner for node attributes input when loading', async () => { server.respondImmediately = false; @@ -646,7 +635,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelectLegacy(rendered, 'cold').exists()).toBeFalsy(); + expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); }); test('should show warning instead of node attributes input when none exist', async () => { http.setupNodeListResponse({ @@ -661,7 +650,7 @@ describe('edit policy', () => { expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelectLegacy(rendered, 'cold').exists()).toBeFalsy(); + expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); }); test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); @@ -671,7 +660,7 @@ describe('edit policy', () => { expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelectLegacy(rendered, 'cold'); + const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); expect(nodeAttributesSelect.exists()).toBeTruthy(); expect(nodeAttributesSelect.find('option').length).toBe(2); }); @@ -683,7 +672,7 @@ describe('edit policy', () => { expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelectLegacy(rendered, 'cold'); + const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); expect(nodeAttributesSelect.exists()).toBeTruthy(); expect(findTestSubject(rendered, 'cold-viewNodeDetailsFlyoutButton').exists()).toBeFalsy(); expect(nodeAttributesSelect.find('option').length).toBe(2); @@ -702,10 +691,10 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfterLegacy(rendered, 'cold', '1'); - setPhaseIndexPriorityLegacy(rendered, 'cold', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'cold', '1'); + await setPhaseIndexPriority(rendered, 'cold', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); test('should show default allocation warning when no node roles are found', async () => { http.setupNodeListResponse({ diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 813fcd9c253f1f..5692decbbf7a88 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -116,7 +116,6 @@ export interface ForcemergeAction { export interface LegacyPolicy { name: string; phases: { - cold: ColdPhase; delete: DeletePhase; }; } @@ -159,14 +158,6 @@ export interface PhaseWithForcemergeAction { bestCompressionEnabled: boolean; } -export interface ColdPhase - extends CommonPhaseSettings, - PhaseWithMinAge, - PhaseWithAllocationAction, - PhaseWithIndexPriority { - freezeEnabled: boolean; -} - export interface DeletePhase extends CommonPhaseSettings, PhaseWithMinAge { waitForSnapshotPolicy: string; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 136b68727672aa..23d7387aa70763 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SerializedPhase, ColdPhase, DeletePhase, SerializedPolicy } from '../../../common/types'; +import { SerializedPhase, DeletePhase, SerializedPolicy } from '../../../common/types'; export const defaultSetPriority: string = '100'; @@ -24,17 +24,6 @@ export const defaultPolicy: SerializedPolicy = { }, }; -export const defaultNewColdPhase: ColdPhase = { - phaseEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - selectedNodeAttrs: '', - selectedReplicaCount: '', - freezeEnabled: false, - phaseIndexPriority: '0', - dataTierAllocationType: 'default', -}; - export const defaultNewDeletePhase: DeletePhase = { phaseEnabled: false, selectedMinimumAge: '0', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/cloud_data_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/cloud_data_tier_callout.tsx deleted file mode 100644 index fc87b553ba5214..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/cloud_data_tier_callout.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React, { FunctionComponent } from 'react'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; - -import { useKibana } from '../../../../../shared_imports'; - -const deployment = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.body.elasticDeploymentLink', - { - defaultMessage: 'deployment', - } -); - -const i18nTexts = { - title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.coldTierTitle', { - defaultMessage: 'Create a cold tier', - }), - body: (deploymentUrl?: string) => { - return ( - - {deployment} - - ) : ( - deployment - ), - }} - /> - ); - }, -}; - -export const CloudDataTierCallout: FunctionComponent = () => { - const { - services: { cloud }, - } = useKibana(); - - return ( - - {i18nTexts.body(cloud?.cloudDeploymentUrl)} - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.scss deleted file mode 100644 index 62ec3f303e1e8c..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.scss +++ /dev/null @@ -1,9 +0,0 @@ -.indexLifecycleManagement__phase__dataTierAllocation { - &__controlSection { - background-color: $euiColorLightestShade; - padding-top: $euiSizeM; - padding-left: $euiSizeM; - padding-right: $euiSizeM; - padding-bottom: $euiSizeM; - } -} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.tsx deleted file mode 100644 index f58f36fc45a0cb..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/data_tier_allocation.tsx +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FunctionComponent, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiText, EuiFormRow, EuiSpacer, EuiSuperSelect, EuiSuperSelectOption } from '@elastic/eui'; - -import { DataTierAllocationType } from '../../../../../../common/types'; -import { NodeAllocation } from './node_allocation'; -import { SharedProps } from './types'; - -import './data_tier_allocation.scss'; - -type SelectOptions = EuiSuperSelectOption; - -const i18nTexts = { - allocationFieldLabel: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.allocationFieldLabel', - { defaultMessage: 'Data tier options' } - ), - allocationOptions: { - warm: { - default: { - input: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.input', - { defaultMessage: 'Use warm nodes (recommended)' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.helpText', - { defaultMessage: 'Move data to nodes in the warm tier.' } - ), - }, - none: { - inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.input', - { defaultMessage: 'Off' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.helpText', - { defaultMessage: 'Do not move data in the warm phase.' } - ), - }, - custom: { - inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.input', - { defaultMessage: 'Custom' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.helpText', - { defaultMessage: 'Move data based on node attributes.' } - ), - }, - }, - cold: { - default: { - input: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.input', - { defaultMessage: 'Use cold nodes (recommended)' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText', - { defaultMessage: 'Move data to nodes in the cold tier.' } - ), - }, - none: { - inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.input', - { defaultMessage: 'Off' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.helpText', - { defaultMessage: 'Do not move data in the cold phase.' } - ), - }, - custom: { - inputDisplay: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input', - { defaultMessage: 'Custom' } - ), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText', - { defaultMessage: 'Move data based on node attributes.' } - ), - }, - }, - }, -}; - -export const DataTierAllocation: FunctionComponent = (props) => { - const { phaseData, setPhaseData, phase, hasNodeAttributes, disableDataTierOption } = props; - - useEffect(() => { - if (disableDataTierOption && phaseData.dataTierAllocationType === 'default') { - /** - * @TODO - * This is a slight hack because we only know we should disable the "default" option further - * down the component tree (i.e., after the policy has been deserialized). - * - * We reset the value to "custom" if we deserialized to "default". - * - * It would be better if we had all the information we needed before deserializing and - * were able to handle this at the deserialization step instead of patching further down - * the component tree - this should be a future refactor. - */ - setPhaseData('dataTierAllocationType', 'custom'); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( -
- - setPhaseData('dataTierAllocationType', value)} - options={ - [ - disableDataTierOption - ? undefined - : { - 'data-test-subj': 'defaultDataAllocationOption', - value: 'default', - inputDisplay: i18nTexts.allocationOptions[phase].default.input, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].default.input} - -

- {i18nTexts.allocationOptions[phase].default.helpText} -

-
- - ), - }, - { - 'data-test-subj': 'customDataAllocationOption', - value: 'custom', - inputDisplay: i18nTexts.allocationOptions[phase].custom.inputDisplay, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].custom.inputDisplay} - -

- {i18nTexts.allocationOptions[phase].custom.helpText} -

-
- - ), - }, - { - 'data-test-subj': 'noneDataAllocationOption', - value: 'none', - inputDisplay: i18nTexts.allocationOptions[phase].none.inputDisplay, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].none.inputDisplay} - -

- {i18nTexts.allocationOptions[phase].none.helpText} -

-
- - ), - }, - ].filter(Boolean) as SelectOptions[] - } - /> -
- {phaseData.dataTierAllocationType === 'custom' && hasNodeAttributes && ( - <> - -
- -
- - )} -
- ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/default_allocation_notice.tsx deleted file mode 100644 index 3d0052c69607b4..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/default_allocation_notice.tsx +++ /dev/null @@ -1,106 +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 { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; -import { EuiCallOut } from '@elastic/eui'; - -import { PhaseWithAllocation, DataTierRole } from '../../../../../../common/types'; - -import { AllocationNodeRole } from '../../../../lib'; - -const i18nTextsNodeRoleToDataTier: Record = { - data_hot: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierHotLabel', { - defaultMessage: 'hot', - }), - data_warm: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierWarmLabel', { - defaultMessage: 'warm', - }), - data_cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierColdLabel', { - defaultMessage: 'cold', - }), -}; - -const i18nTexts = { - notice: { - warm: { - title: i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title', - { defaultMessage: 'No nodes assigned to the warm tier' } - ), - body: (nodeRole: DataTierRole) => - i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm', { - defaultMessage: - 'This policy will move data in the warm phase to {tier} tier nodes instead.', - values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, - }), - }, - cold: { - title: i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold.title', - { defaultMessage: 'No nodes assigned to the cold tier' } - ), - body: (nodeRole: DataTierRole) => - i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold', { - defaultMessage: - 'This policy will move data in the cold phase to {tier} tier nodes instead.', - values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, - }), - }, - }, - warning: { - warm: { - title: i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableTitle', - { defaultMessage: 'No nodes assigned to the warm tier' } - ), - body: i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableBody', - { - defaultMessage: - 'Assign at least one node to the warm or hot tier to use role-based allocation. The policy will fail to complete allocation if there are no available nodes.', - } - ), - }, - cold: { - title: i18n.translate( - 'xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle', - { defaultMessage: 'No nodes assigned to the cold tier' } - ), - body: i18n.translate( - 'xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableBody', - { - defaultMessage: - 'Assign at least one node to the cold, warm, or hot tier to use role-based allocation. The policy will fail to complete allocation if there are no available nodes.', - } - ), - }, - }, -}; - -interface Props { - phase: PhaseWithAllocation; - targetNodeRole: AllocationNodeRole; -} - -export const DefaultAllocationNotice: FunctionComponent = ({ phase, targetNodeRole }) => { - const content = - targetNodeRole === 'none' ? ( - - {i18nTexts.warning[phase].body} - - ) : ( - - {i18nTexts.notice[phase].body(targetNodeRole)} - - ); - - return content; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/index.ts deleted file mode 100644 index 937e3dd28da973..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/index.ts +++ /dev/null @@ -1,13 +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. - */ - -export { NodesDataProvider } from './node_data_provider'; -export { NodeAllocation } from './node_allocation'; -export { NodeAttrsDetails } from './node_attrs_details'; -export { DataTierAllocation } from './data_tier_allocation'; -export { DefaultAllocationNotice } from './default_allocation_notice'; -export { NoNodeAttributesWarning } from './no_node_attributes_warning'; -export { CloudDataTierCallout } from './cloud_data_tier_callout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/no_node_attributes_warning.tsx deleted file mode 100644 index 69185277f64ce6..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/no_node_attributes_warning.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FunctionComponent } from 'react'; -import { EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { PhaseWithAllocation } from '../../../../../../common/types'; - -const i18nTexts = { - title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.nodeAttributesMissingLabel', { - defaultMessage: 'No custom node attributes configured', - }), - warm: { - body: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.warm.nodeAttributesMissingDescription', - { - defaultMessage: - 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Warm nodes will be used instead.', - } - ), - }, - cold: { - body: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.cold.nodeAttributesMissingDescription', - { - defaultMessage: - 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Cold nodes will be used instead.', - } - ), - }, -}; - -export const NoNodeAttributesWarning: FunctionComponent<{ phase: PhaseWithAllocation }> = ({ - phase, -}) => { - return ( - - {i18nTexts[phase].body} - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_allocation.tsx deleted file mode 100644 index a57a6ba4ff2c65..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_allocation.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useState, FunctionComponent } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { EuiSelect, EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; - -import { PhaseWithAllocationAction } from '../../../../../../common/types'; -import { propertyof } from '../../../../services/policies/policy_validation'; - -import { ErrableFormRow } from '../form_errors'; - -import { NodeAttrsDetails } from './node_attrs_details'; -import { SharedProps } from './types'; -import { LearnMoreLink } from '../learn_more_link'; - -const learnMoreLink = ( - - } - docPath="modules-cluster.html#cluster-shard-allocation-settings" - /> -); - -const i18nTexts = { - doNotModifyAllocationOption: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption', - { defaultMessage: 'Do not modify allocation configuration' } - ), -}; - -export const NodeAllocation: FunctionComponent = ({ - phase, - setPhaseData, - errors, - phaseData, - isShowingErrors, - nodes, -}) => { - const [selectedNodeAttrsForDetails, setSelectedNodeAttrsForDetails] = useState( - null - ); - - const nodeOptions = Object.keys(nodes).map((attrs) => ({ - text: `${attrs} (${nodes[attrs].length})`, - value: attrs, - })); - - nodeOptions.sort((a, b) => a.value.localeCompare(b.value)); - - // check that this string is a valid property - const nodeAttrsProperty = propertyof('selectedNodeAttrs'); - - return ( - <> - -

- -

-
- - - {/* - TODO: this field component must be revisited to support setting multiple require values and to support - setting `include and exclude values on ILM policies. See https://github.com/elastic/kibana/issues/77344 - */} - setSelectedNodeAttrsForDetails(phaseData.selectedNodeAttrs)} - > - - - ) : null - } - > - { - setPhaseData(nodeAttrsProperty, e.target.value); - }} - /> - - - {selectedNodeAttrsForDetails ? ( - setSelectedNodeAttrsForDetails(null)} - /> - ) : null} - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_attrs_details.tsx deleted file mode 100644 index c29495d13eb8ed..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_attrs_details.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { - EuiFlyoutBody, - EuiFlyout, - EuiTitle, - EuiInMemoryTable, - EuiSpacer, - EuiPortal, - EuiLoadingContent, - EuiCallOut, - EuiButton, -} from '@elastic/eui'; - -import { useLoadNodeDetails } from '../../../../services/api'; - -interface Props { - close: () => void; - selectedNodeAttrs: string; -} - -export const NodeAttrsDetails: React.FunctionComponent = ({ close, selectedNodeAttrs }) => { - const { data, isLoading, error, resendRequest } = useLoadNodeDetails(selectedNodeAttrs); - let content; - if (isLoading) { - content = ; - } else if (error) { - const { statusCode, message } = error; - content = ( - - } - color="danger" - > -

- {message} ({statusCode}) -

- - - -
- ); - } else { - content = ( - - ); - } - return ( - - - - -

- -

-
- - {content} -
-
-
- ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_data_provider.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_data_provider.tsx deleted file mode 100644 index a7c0f3ec7c8660..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/node_data_provider.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiButton, EuiCallOut, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { ListNodesRouteResponse } from '../../../../../../common/types'; -import { useLoadNodes } from '../../../../services/api'; - -interface Props { - children: (data: ListNodesRouteResponse) => JSX.Element; -} - -export const NodesDataProvider = ({ children }: Props): JSX.Element => { - const { isLoading, data, error, resendRequest } = useLoadNodes(); - - if (isLoading) { - return ( - <> - - - - ); - } - - const renderError = () => { - if (error) { - const { statusCode, message } = error; - return ( - <> - - } - color="danger" - > -

- {message} ({statusCode}) -

- - - -
- - - - ); - } - return null; - }; - - return ( - <> - {renderError()} - {/* `data` will always be defined because we use an initial value when loading */} - {children(data!)} - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/types.ts deleted file mode 100644 index d3dd536d97df00..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/data_tier_allocation/types.ts +++ /dev/null @@ -1,28 +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 { - ListNodesRouteResponse, - PhaseWithAllocation, - PhaseWithAllocationAction, -} from '../../../../../../common/types'; -import { PhaseValidationErrors } from '../../../../services/policies/policy_validation'; - -export interface SharedProps { - phase: PhaseWithAllocation; - errors?: PhaseValidationErrors; - phaseData: PhaseWithAllocationAction; - setPhaseData: (dataKey: keyof PhaseWithAllocationAction, value: string) => void; - isShowingErrors: boolean; - nodes: ListNodesRouteResponse['nodesByAttributes']; - hasNodeAttributes: boolean; - /** - * When on Cloud we want to disable the data tier allocation option when we detect that we are not - * using node roles in our Node config yet. See {@link ListNodesRouteResponse} for information about how this is - * detected. - */ - disableDataTierOption: boolean; -} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge_legacy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge_legacy.tsx deleted file mode 100644 index 0b0dbe273c0240..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge_legacy.tsx +++ /dev/null @@ -1,131 +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. - */ - -/** - * PLEASE NOTE: This component is currently duplicated. A version of this component wired up with - * the form lib lives in ./phases/shared - */ - -import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiDescribedFormGroup, - EuiFieldNumber, - EuiFormRow, - EuiSpacer, - EuiSwitch, - EuiTextColor, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { LearnMoreLink } from './learn_more_link'; -import { ErrableFormRow } from './form_errors'; -import { Phases, PhaseWithForcemergeAction } from '../../../../../common/types'; -import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; - -const forcemergeLabel = i18n.translate('xpack.indexLifecycleMgmt.forcemerge.enableLabel', { - defaultMessage: 'Force merge data', -}); - -const bestCompressionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel', - { - defaultMessage: 'Compress stored fields', - } -); - -interface Props { - errors?: PhaseValidationErrors; - phase: keyof Phases & string; - phaseData: PhaseWithForcemergeAction; - setPhaseData: (dataKey: keyof PhaseWithForcemergeAction, value: boolean | string) => void; - isShowingErrors: boolean; -} - -export const Forcemerge: React.FunctionComponent = ({ - errors, - phaseData, - phase, - setPhaseData, - isShowingErrors, -}) => { - return ( - - - - } - description={ - - {' '} - - - } - titleSize="xs" - fullWidth - > - { - setPhaseData('forceMergeEnabled', e.target.checked); - }} - aria-controls="forcemergeContent" - /> - - -
- {phaseData.forceMergeEnabled ? ( - <> - - { - setPhaseData('selectedForceMergeSegments', e.target.value); - }} - min={1} - /> - - - } - > - { - setPhaseData('bestCompressionEnabled', e.target.checked); - }} - /> - - - ) : null} -
-
- ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index 2b774b00b98a9f..a04608338718e3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -11,16 +11,7 @@ export { MinAgeInput } from './min_age_input_legacy'; export { OptionalLabel } from './optional_label'; export { PhaseErrorMessage } from './phase_error_message'; export { PolicyJsonFlyout } from './policy_json_flyout'; -export { SetPriorityInput } from './set_priority_input_legacy'; export { SnapshotPolicies } from './snapshot_policies'; -export { - DataTierAllocation, - NodeAllocation, - NodeAttrsDetails, - NodesDataProvider, - DefaultAllocationNotice, -} from './data_tier_allocation'; export { DescribedFormField } from './described_form_field'; -export { Forcemerge } from './forcemerge_legacy'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx deleted file mode 100644 index da6c358aa67c15..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FunctionComponent, Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; - -import { EuiFieldNumber, EuiDescribedFormGroup, EuiSwitch, EuiTextColor } from '@elastic/eui'; - -import { ColdPhase as ColdPhaseInterface, Phases } from '../../../../../../common/types'; - -import { useFormData } from '../../../../../shared_imports'; - -import { PhaseValidationErrors } from '../../../../services/policies/policy_validation'; - -import { - LearnMoreLink, - ActiveBadge, - PhaseErrorMessage, - OptionalLabel, - ErrableFormRow, - SetPriorityInput, - MinAgeInput, - DescribedFormField, -} from '../'; - -import { DataTierAllocationFieldLegacy, useRolloverPath } from './shared'; - -const i18nTexts = { - freezeLabel: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel', { - defaultMessage: 'Freeze index', - }), - dataTierAllocation: { - description: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.dataTier.description', { - defaultMessage: - 'Move data to nodes optimized for less frequent, read-only access. Store data in the cold phase on less-expensive hardware.', - }), - }, -}; - -const coldProperty: keyof Phases = 'cold'; -const phaseProperty = (propertyName: keyof ColdPhaseInterface) => propertyName; - -interface Props { - setPhaseData: (key: keyof ColdPhaseInterface & string, value: string | boolean) => void; - phaseData: ColdPhaseInterface; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; -} -export const ColdPhase: FunctionComponent = ({ - setPhaseData, - phaseData, - errors, - isShowingErrors, -}) => { - const [formData] = useFormData({ - watch: [useRolloverPath], - }); - - const hotPhaseRolloverEnabled = get(formData, useRolloverPath); - - return ( -
- <> - {/* Section title group; containing min age */} - -

- -

{' '} - {phaseData.phaseEnabled && !isShowingErrors ? : null} - -
- } - titleSize="s" - description={ - -

- -

- - } - id={`${coldProperty}-${phaseProperty('phaseEnabled')}`} - checked={phaseData.phaseEnabled} - onChange={(e) => { - setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); - }} - aria-controls="coldPhaseContent" - /> -
- } - fullWidth - > - {phaseData.phaseEnabled ? ( - - errors={errors} - phaseData={phaseData} - phase={coldProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - rolloverEnabled={hotPhaseRolloverEnabled} - /> - ) : null} - - {phaseData.phaseEnabled ? ( - - {/* Data tier allocation section */} - - - {/* Replicas section */} - - {i18n.translate('xpack.indexLifecycleMgmt.coldPhase.replicasTitle', { - defaultMessage: 'Replicas', - })} - - } - description={i18n.translate( - 'xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasDescription', - { - defaultMessage: - 'Set the number of replicas. Remains the same as the previous phase by default.', - } - )} - switchProps={{ - label: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel', - { defaultMessage: 'Set replicas' } - ), - initialValue: Boolean(phaseData.selectedReplicaCount), - onChange: (v) => { - if (!v) { - setPhaseData('selectedReplicaCount', ''); - } - }, - }} - fullWidth - > - - - - - } - isShowingErrors={isShowingErrors} - errors={errors?.selectedReplicaCount} - > - { - setPhaseData(phaseProperty('selectedReplicaCount'), e.target.value); - }} - min={0} - /> - - - {/* Freeze section */} - - - - } - description={ - - {' '} - - - } - fullWidth - titleSize="xs" - > - { - setPhaseData(phaseProperty('freezeEnabled'), e.target.checked); - }} - label={i18nTexts.freezeLabel} - aria-label={i18nTexts.freezeLabel} - /> - - - errors={errors} - phaseData={phaseData} - phase={coldProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - /> - - ) : null} - -
- ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx new file mode 100644 index 00000000000000..84e955a91ad7ce --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; + +import { EuiDescribedFormGroup, EuiTextColor } from '@elastic/eui'; + +import { Phases } from '../../../../../../../common/types'; + +import { + useFormData, + useFormContext, + UseField, + ToggleField, + NumericField, +} from '../../../../../../shared_imports'; + +import { useEditPolicyContext } from '../../../edit_policy_context'; + +import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, DescribedFormField } from '../../'; + +import { MinAgeInputField, DataTierAllocationField, SetPriorityInput } from '../shared'; + +const i18nTexts = { + dataTierAllocation: { + description: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.dataTier.description', { + defaultMessage: + 'Move data to nodes optimized for less frequent, read-only access. Store data in the cold phase on less-expensive hardware.', + }), + }, +}; + +const coldProperty: keyof Phases = 'cold'; + +const formFieldPaths = { + enabled: '_meta.cold.enabled', +}; + +export const ColdPhase: FunctionComponent = () => { + const { originalPolicy } = useEditPolicyContext(); + const form = useFormContext(); + + const [formData] = useFormData({ + watch: [formFieldPaths.enabled], + }); + + const enabled = get(formData, formFieldPaths.enabled); + const isShowingErrors = form.isValid === false; + + return ( +
+ <> + {/* Section title group; containing min age */} + +

+ +

{' '} + {enabled && !isShowingErrors ? : null} + +
+ } + titleSize="s" + description={ + <> +

+ +

+ + + } + fullWidth + > + {enabled && } + + {enabled && ( + <> + {/* Data tier allocation section */} + + + {/* Replicas section */} + + {i18n.translate('xpack.indexLifecycleMgmt.coldPhase.replicasTitle', { + defaultMessage: 'Replicas', + })} + + } + description={i18n.translate( + 'xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasDescription', + { + defaultMessage: + 'Set the number of replicas. Remains the same as the previous phase by default.', + } + )} + switchProps={{ + 'data-test-subj': 'cold-setReplicasSwitch', + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel', + { defaultMessage: 'Set replicas' } + ), + initialValue: Boolean( + originalPolicy.phases.cold?.actions?.allocate?.number_of_replicas + ), + }} + fullWidth + > + + + {/* Freeze section */} + + + + } + description={ + + {' '} + + + } + fullWidth + titleSize="xs" + > + + + + + )} + + + ); +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/index.ts similarity index 79% rename from x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/index.ts index 9326d13851387b..df79607f33dbcc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/shared/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { attemptToURIDecode } from './attempt_to_uri_decode'; +export { ColdPhase } from './cold_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx index 338e5367a1d0df..56a59270b18af4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx @@ -19,7 +19,7 @@ const i18nTexts = { 'xpack.indexLifecycleMgmt.editPolicy.warm.nodeAttributesMissingDescription', { defaultMessage: - 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Warm nodes will be used instead.', + 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation.', } ), }, @@ -28,7 +28,7 @@ const i18nTexts = { 'xpack.indexLifecycleMgmt.editPolicy.cold.nodeAttributesMissingDescription', { defaultMessage: - 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Cold nodes will be used instead.', + 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation.', } ), }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_legacy_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_legacy_field.tsx deleted file mode 100644 index d64df468620e65..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_legacy_field.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FunctionComponent } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiDescribedFormGroup, EuiFormRow, EuiSpacer } from '@elastic/eui'; - -import { useKibana } from '../../../../../../shared_imports'; -import { PhaseWithAllocationAction, PhaseWithAllocation } from '../../../../../../../common/types'; -import { PhaseValidationErrors } from '../../../../../services/policies/policy_validation'; -import { getAvailableNodeRoleForPhase, isNodeRoleFirstPreference } from '../../../../../lib'; - -import { - DataTierAllocation, - DefaultAllocationNotice, - NoNodeAttributesWarning, - NodesDataProvider, - CloudDataTierCallout, -} from '../../data_tier_allocation'; - -const i18nTexts = { - title: i18n.translate('xpack.indexLifecycleMgmt.common.dataTier.title', { - defaultMessage: 'Data allocation', - }), -}; - -interface Props { - description: React.ReactNode; - phase: PhaseWithAllocation; - setPhaseData: (dataKey: keyof PhaseWithAllocationAction, value: string) => void; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; - phaseData: PhaseWithAllocationAction; -} - -/** - * Top-level layout control for the data tier allocation field. - */ -export const DataTierAllocationFieldLegacy: FunctionComponent = ({ - description, - phase, - phaseData, - setPhaseData, - isShowingErrors, - errors, -}) => { - const { - services: { cloud }, - } = useKibana(); - - return ( - - {({ nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig }) => { - const hasDataNodeRoles = Object.keys(nodesByRoles).some((nodeRole) => - // match any of the "data_" roles, including data_content. - nodeRole.trim().startsWith('data_') - ); - const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length); - - const renderNotice = () => { - switch (phaseData.dataTierAllocationType) { - case 'default': - const isCloudEnabled = cloud?.isCloudEnabled ?? false; - if (isCloudEnabled && phase === 'cold') { - const isUsingNodeRolesAllocation = - !isUsingDeprecatedDataRoleConfig && hasDataNodeRoles; - const hasNoNodesWithNodeRole = !nodesByRoles.data_cold?.length; - - if (isUsingNodeRolesAllocation && hasNoNodesWithNodeRole) { - // Tell cloud users they can deploy nodes on cloud. - return ( - <> - - - - ); - } - } - - const allocationNodeRole = getAvailableNodeRoleForPhase(phase, nodesByRoles); - if ( - allocationNodeRole === 'none' || - !isNodeRoleFirstPreference(phase, allocationNodeRole) - ) { - return ( - <> - - - - ); - } - break; - case 'custom': - if (!hasNodeAttrs) { - return ( - <> - - - - ); - } - break; - default: - return null; - } - }; - - return ( - {i18nTexts.title}} - description={description} - fullWidth - > - - <> - - - {/* Data tier related warnings and call-to-action notices */} - {renderNotice()} - - - - ); - }} - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts index 0cae3eea6316b9..6355dab89771d7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts @@ -6,8 +6,6 @@ export { useRolloverPath } from '../../../constants'; -export { DataTierAllocationFieldLegacy } from './data_tier_allocation_legacy_field'; - export { DataTierAllocationField } from './data_tier_allocation_field'; export { Forcemerge } from './forcemerge_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 7b1a4f44b5de63..06c16e8bdd5ab4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -48,16 +48,21 @@ const i18nTexts = { const warmProperty: keyof Phases = 'warm'; +const formFieldPaths = { + enabled: '_meta.warm.enabled', + warmPhaseOnRollover: '_meta.warm.warmPhaseOnRollover', +}; + export const WarmPhase: FunctionComponent = () => { const { originalPolicy } = useEditPolicyContext(); const form = useFormContext(); const [formData] = useFormData({ - watch: [useRolloverPath, '_meta.warm.enabled', '_meta.warm.warmPhaseOnRollover'], + watch: [useRolloverPath, formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], }); - const enabled = get(formData, '_meta.warm.enabled'); + const enabled = get(formData, formFieldPaths.enabled); const hotPhaseRolloverEnabled = get(formData, useRolloverPath); - const warmPhaseOnRollover = get(formData, '_meta.warm.warmPhaseOnRollover'); + const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover); const isShowingErrors = form.isValid === false; return ( @@ -88,7 +93,7 @@ export const WarmPhase: FunctionComponent = () => { />

{ {hotPhaseRolloverEnabled && ( = ({ ...legacyPolicy.phases, hot: p.phases.hot, warm: p.phases.warm, + cold: p.phases.cold, }, }); } else { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input_legacy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input_legacy.tsx deleted file mode 100644 index 5efbfabdf093d6..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input_legacy.tsx +++ /dev/null @@ -1,85 +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. - */ - -/** - * PLEASE NOTE: This component is currently duplicated. A version of this component wired up with - * the form lib lives in ./phases/shared - */ - -import React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui'; - -import { LearnMoreLink } from './'; -import { OptionalLabel } from './'; -import { ErrableFormRow } from './'; -import { PhaseWithIndexPriority, Phases } from '../../../../../common/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; - -interface Props { - errors?: PhaseValidationErrors; - phase: keyof Phases & string; - phaseData: T; - setPhaseData: (dataKey: keyof T & string, value: any) => void; - isShowingErrors: boolean; -} -export const SetPriorityInput = ({ - errors, - phaseData, - phase, - setPhaseData, - isShowingErrors, -}: React.PropsWithChildren>) => { - const phaseIndexPriorityProperty = propertyof('phaseIndexPriority'); - return ( - - - - } - description={ - - {' '} - - - } - titleSize="xs" - fullWidth - > - - - - - } - isShowingErrors={isShowingErrors} - errors={errors?.phaseIndexPriority} - > - { - setPhaseData(phaseIndexPriorityProperty, e.target.value); - }} - min={0} - /> - - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts index 760c6ad713ea04..f0294a5391d21d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts @@ -15,18 +15,27 @@ import { determineDataTierAllocationType } from '../../lib'; import { FormInternal } from './types'; export const deserializer = (policy: SerializedPolicy): FormInternal => { + const { + phases: { hot, warm, cold }, + } = policy; + const _meta: FormInternal['_meta'] = { hot: { - useRollover: Boolean(policy.phases.hot?.actions?.rollover), - forceMergeEnabled: Boolean(policy.phases.hot?.actions?.forcemerge), - bestCompression: policy.phases.hot?.actions?.forcemerge?.index_codec === 'best_compression', + useRollover: Boolean(hot?.actions?.rollover), + forceMergeEnabled: Boolean(hot?.actions?.forcemerge), + bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', }, warm: { - enabled: Boolean(policy.phases.warm), - warmPhaseOnRollover: Boolean(policy.phases.warm?.min_age === '0ms'), - forceMergeEnabled: Boolean(policy.phases.warm?.actions?.forcemerge), - bestCompression: policy.phases.warm?.actions?.forcemerge?.index_codec === 'best_compression', - dataTierAllocationType: determineDataTierAllocationType(policy.phases.warm?.actions), + enabled: Boolean(warm), + warmPhaseOnRollover: Boolean(warm?.min_age === '0ms'), + forceMergeEnabled: Boolean(warm?.actions?.forcemerge), + bestCompression: warm?.actions?.forcemerge?.index_codec === 'best_compression', + dataTierAllocationType: determineDataTierAllocationType(warm?.actions), + }, + cold: { + enabled: Boolean(cold), + dataTierAllocationType: determineDataTierAllocationType(cold?.actions), + freezeEnabled: Boolean(cold?.actions?.freeze), }, }; @@ -63,6 +72,20 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { draft._meta.warm.minAgeUnit = minAge.units; } } + + if (draft.phases.cold) { + if (draft.phases.cold.actions?.allocate?.require) { + Object.entries(draft.phases.cold.actions.allocate.require).forEach((entry) => { + draft._meta.cold.allocationNodeAttribute = entry.join(':'); + }); + } + + if (draft.phases.cold.min_age) { + const minAge = splitSizeAndUnits(draft.phases.cold.min_age); + draft.phases.cold.min_age = minAge.size; + draft._meta.cold.minAgeUnit = minAge.units; + } + } } ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index eecdfb4871a676..5397f5da2d6bb2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -92,6 +92,7 @@ const mergeAllSerializedPolicies = ( ...legacySerializedPolicy.phases, hot: serializedPolicy.phases.hot, warm: serializedPolicy.phases.warm, + cold: serializedPolicy.phases.cold, }, }; }; @@ -195,10 +196,6 @@ export const EditPolicy: React.FunctionComponent = ({ [setPolicy] ); - const setColdPhaseData = useCallback( - (key: string, value: any) => setPhaseData('cold', key, value), - [setPhaseData] - ); const setDeletePhaseData = useCallback( (key: string, value: any) => setPhaseData('delete', key, value), [setPhaseData] @@ -342,14 +339,7 @@ export const EditPolicy: React.FunctionComponent = ({ - 0 - } - setPhaseData={setColdPhaseData} - phaseData={policy.phases.cold} - /> + diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index a80382e87539c2..070f03f74b9549 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -11,7 +11,11 @@ import { defaultSetPriority, defaultPhaseIndexPriority } from '../../constants'; import { FormInternal } from './types'; -import { ifExistsNumberGreaterThanZero, rolloverThresholdsValidator } from './form_validations'; +import { + ifExistsNumberGreaterThanZero, + ifExistsNumberNonNegative, + rolloverThresholdsValidator, +} from './form_validations'; import { i18nTexts } from './i18n_texts'; @@ -69,6 +73,30 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, }, + cold: { + enabled: { + defaultValue: false, + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.activateColdPhaseSwitchLabel', + { defaultMessage: 'Activate cold phase' } + ), + }, + freezeEnabled: { + defaultValue: false, + label: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel', { + defaultMessage: 'Freeze index', + }), + }, + minAgeUnit: { + defaultValue: 'd', + }, + dataTierAllocationType: { + label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, + }, + allocationNodeAttribute: { + label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, + }, + }, }, phases: { hot: { @@ -138,7 +166,7 @@ export const schema: FormSchema = { priority: { defaultValue: defaultSetPriority as any, label: i18nTexts.editPolicy.setPriorityFieldLabel, - validations: [{ validator: ifExistsNumberGreaterThanZero }], + validations: [{ validator: ifExistsNumberNonNegative }], serializer: serializers.stringToNumber, }, }, @@ -217,7 +245,48 @@ export const schema: FormSchema = { priority: { defaultValue: defaultPhaseIndexPriority as any, label: i18nTexts.editPolicy.setPriorityFieldLabel, - validations: [{ validator: ifExistsNumberGreaterThanZero }], + validations: [{ validator: ifExistsNumberNonNegative }], + serializer: serializers.stringToNumber, + }, + }, + }, + }, + cold: { + min_age: { + defaultValue: '0', + validations: [ + { + validator: (arg) => + numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, + })({ + ...arg, + value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), + }), + }, + ], + }, + actions: { + allocate: { + number_of_replicas: { + label: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel', { + defaultMessage: 'Number of replicas (optional)', + }), + validations: [ + { + validator: ifExistsNumberGreaterThanZero, + }, + ], + serializer: serializers.stringToNumber, + }, + }, + set_priority: { + priority: { + defaultValue: '0' as any, + label: i18nTexts.editPolicy.setPriorityFieldLabel, + validations: [{ validator: ifExistsNumberNonNegative }], serializer: serializers.stringToNumber, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts index 37ca4e9def340f..9c855ccb41624f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts @@ -12,18 +12,36 @@ import { i18nTexts } from './i18n_texts'; const { numberGreaterThanField } = fieldValidators; -export const ifExistsNumberGreaterThanZero: ValidationFunc = (arg) => { - if (arg.value) { - return numberGreaterThanField({ - than: 0, - message: i18nTexts.editPolicy.errors.numberGreatThan0Required, - })({ - ...arg, - value: parseInt(arg.value, 10), - }); - } +const createIfNumberExistsValidator = ({ + than, + message, +}: { + than: number; + message: string; +}): ValidationFunc => { + return (arg) => { + if (arg.value) { + return numberGreaterThanField({ + than, + message, + })({ + ...arg, + value: parseInt(arg.value, 10), + }); + } + }; }; +export const ifExistsNumberGreaterThanZero = createIfNumberExistsValidator({ + than: 0, + message: i18nTexts.editPolicy.errors.numberGreatThan0Required, +}); + +export const ifExistsNumberNonNegative = createIfNumberExistsValidator({ + than: -1, + message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, +}); + /** * A special validation type used to keep track of validation errors for * the rollover threshold values not being set (e.g., age and doc count) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts index 90e81528f5afe6..564b5a2c4e3979 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts @@ -31,6 +31,11 @@ const serializeAllocateAction = ( [name]: value, }, }; + } else { + // The form has been configured to use node attribute based allocation but no node attribute + // was selected. We fall back to what was originally selected in this case. This might be + // migrate.enabled: "false" + actions.migrate = originalActions.migrate; } // copy over the original include and exclude values until we can set them in the form. @@ -129,5 +134,36 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( } } + /** + * COLD PHASE SERIALIZATION + */ + if (policy.phases.cold) { + if (policy.phases.cold.min_age) { + policy.phases.cold.min_age = `${policy.phases.cold.min_age}${_meta.cold.minAgeUnit}`; + } + + policy.phases.cold.actions = serializeAllocateAction( + _meta.cold, + policy.phases.cold.actions, + originalPolicy?.phases.cold?.actions + ); + + if ( + policy.phases.cold.actions.allocate && + !policy.phases.cold.actions.allocate.require && + !isNumber(policy.phases.cold.actions.allocate.number_of_replicas) && + isEmpty(policy.phases.cold.actions.allocate.include) && + isEmpty(policy.phases.cold.actions.allocate.exclude) + ) { + // remove allocate action if it does not define require or number of nodes + // and both include and exclude are empty objects (ES will fail to parse if we don't) + delete policy.phases.cold.actions.allocate; + } + + if (_meta.cold.freezeEnabled) { + policy.phases.cold.actions.freeze = {}; + } + } + return policy; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 6fcfbd050c69d0..1884f8dbc06191 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -13,20 +13,29 @@ export interface DataAllocationMetaFields { allocationNodeAttribute?: string; } -interface HotPhaseMetaFields { - useRollover: boolean; +export interface MinAgeField { + minAgeUnit?: string; +} + +export interface ForcemergeFields { forceMergeEnabled: boolean; bestCompression: boolean; +} + +interface HotPhaseMetaFields extends ForcemergeFields { + useRollover: boolean; maxStorageSizeUnit?: string; maxAgeUnit?: string; } -interface WarmPhaseMetaFields extends DataAllocationMetaFields { +interface WarmPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, ForcemergeFields { enabled: boolean; - forceMergeEnabled: boolean; - bestCompression: boolean; warmPhaseOnRollover: boolean; - minAgeUnit?: string; +} + +interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { + enabled: boolean; + freezeEnabled: boolean; } /** @@ -40,5 +49,6 @@ export interface FormInternal extends SerializedPolicy { _meta: { hot: HotPhaseMetaFields; warm: WarmPhaseMetaFields; + cold: ColdPhaseMetaFields; }; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx index 3481a2f0d4a2a6..7b6521fd8a9ef8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/table_content.tsx @@ -193,7 +193,7 @@ export const TableContent: React.FunctionComponent = ({ icon: 'list', onClick: () => { navigateToApp('management', { - path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`, true)}`, + path: `/data/index_management${getIndexListUri(`ilm.policy:"${policy.name}"`, true)}`, }); }, }); @@ -306,7 +306,6 @@ export const TableContent: React.FunctionComponent = ({ isOpen={isPolicyPopoverOpen(policy.name)} closePopover={closePolicyPopover} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts deleted file mode 100644 index faf3954f93fd8c..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts +++ /dev/null @@ -1,154 +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 { isEmpty } from 'lodash'; -import { AllocateAction, ColdPhase, SerializedColdPhase } from '../../../../common/types'; -import { serializedPhaseInitialization } from '../../constants'; -import { isNumber, splitSizeAndUnits } from './policy_serialization'; -import { - numberRequiredMessage, - PhaseValidationErrors, - positiveNumberRequiredMessage, -} from './policy_validation'; -import { determineDataTierAllocationTypeLegacy } from '../../lib'; -import { serializePhaseWithAllocation } from './shared'; - -export const coldPhaseInitialization: ColdPhase = { - phaseEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - selectedNodeAttrs: '', - selectedReplicaCount: '', - freezeEnabled: false, - phaseIndexPriority: '', - dataTierAllocationType: 'default', -}; - -export const coldPhaseFromES = (phaseSerialized?: SerializedColdPhase): ColdPhase => { - const phase = { ...coldPhaseInitialization }; - if (phaseSerialized === undefined || phaseSerialized === null) { - return phase; - } - - phase.phaseEnabled = true; - - if (phaseSerialized.actions) { - phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy(phaseSerialized.actions); - } - - if (phaseSerialized.min_age) { - const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age); - phase.selectedMinimumAge = minAge; - phase.selectedMinimumAgeUnits = minAgeUnits; - } - - if (phaseSerialized.actions) { - const actions = phaseSerialized.actions; - if (actions.allocate) { - const allocate = actions.allocate; - if (allocate.require) { - Object.entries(allocate.require).forEach((entry) => { - phase.selectedNodeAttrs = entry.join(':'); - }); - if (allocate.number_of_replicas) { - phase.selectedReplicaCount = allocate.number_of_replicas.toString(); - } - } - } - - if (actions.freeze) { - phase.freezeEnabled = true; - } - - if (actions.set_priority) { - phase.phaseIndexPriority = actions.set_priority.priority - ? actions.set_priority.priority.toString() - : ''; - } - } - - return phase; -}; - -export const coldPhaseToES = ( - phase: ColdPhase, - originalPhase: SerializedColdPhase | undefined -): SerializedColdPhase => { - if (!originalPhase) { - originalPhase = { ...serializedPhaseInitialization }; - } - - const esPhase = { ...originalPhase }; - - if (isNumber(phase.selectedMinimumAge)) { - esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; - } - - esPhase.actions = serializePhaseWithAllocation(phase, esPhase.actions); - - if (isNumber(phase.selectedReplicaCount)) { - esPhase.actions.allocate = esPhase.actions.allocate || ({} as AllocateAction); - esPhase.actions.allocate.number_of_replicas = parseInt(phase.selectedReplicaCount, 10); - } else { - if (esPhase.actions.allocate) { - delete esPhase.actions.allocate.number_of_replicas; - } - } - - if ( - esPhase.actions.allocate && - !esPhase.actions.allocate.require && - !isNumber(esPhase.actions.allocate.number_of_replicas) && - isEmpty(esPhase.actions.allocate.include) && - isEmpty(esPhase.actions.allocate.exclude) - ) { - // remove allocate action if it does not define require or number of nodes - // and both include and exclude are empty objects (ES will fail to parse if we don't) - delete esPhase.actions.allocate; - } - - if (phase.freezeEnabled) { - esPhase.actions.freeze = {}; - } else { - delete esPhase.actions.freeze; - } - - if (isNumber(phase.phaseIndexPriority)) { - esPhase.actions.set_priority = { - priority: parseInt(phase.phaseIndexPriority, 10), - }; - } else { - delete esPhase.actions.set_priority; - } - - return esPhase; -}; - -export const validateColdPhase = (phase: ColdPhase): PhaseValidationErrors => { - if (!phase.phaseEnabled) { - return {}; - } - - const phaseErrors = {} as PhaseValidationErrors; - - // index priority is optional, but if it's set, it needs to be a positive number - if (phase.phaseIndexPriority) { - if (!isNumber(phase.phaseIndexPriority)) { - phaseErrors.phaseIndexPriority = [numberRequiredMessage]; - } else if (parseInt(phase.phaseIndexPriority, 10) < 0) { - phaseErrors.phaseIndexPriority = [positiveNumberRequiredMessage]; - } - } - - // min age needs to be a positive number - if (!isNumber(phase.selectedMinimumAge)) { - phaseErrors.selectedMinimumAge = [numberRequiredMessage]; - } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { - phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; - } - - return { ...phaseErrors }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts index 0be6ab35217360..19481b39a2c806 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts @@ -7,9 +7,7 @@ // eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import { deserializePolicy, legacySerializePolicy } from './policy_serialization'; -import { defaultNewColdPhase, defaultNewDeletePhase } from '../../constants'; -import { DataTierAllocationType } from '../../../../common/types'; -import { coldPhaseInitialization } from './cold_phase'; +import { defaultNewDeletePhase } from '../../constants'; describe('Policy serialization', () => { test('serialize a policy using "default" data allocation', () => { @@ -18,12 +16,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - dataTierAllocationType: 'default', - selectedNodeAttrs: 'another:thing', - phaseEnabled: true, - }, delete: { ...defaultNewDeletePhase }, }, }, @@ -31,24 +23,12 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - cold: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, }, } ) ).toEqual({ name: 'test', - phases: { - cold: { - actions: { - set_priority: { - priority: 0, - }, - }, - min_age: '0d', - }, - }, + phases: {}, }); }); @@ -58,12 +38,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - dataTierAllocationType: 'custom', - selectedNodeAttrs: 'another:thing', - phaseEnabled: true, - }, delete: { ...defaultNewDeletePhase }, }, }, @@ -71,37 +45,12 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - cold: { - actions: { - allocate: { - include: { keep: 'this' }, - exclude: { keep: 'this' }, - require: { something: 'here' }, - }, - }, - }, }, } ) ).toEqual({ name: 'test', - phases: { - cold: { - actions: { - allocate: { - include: { keep: 'this' }, - exclude: { keep: 'this' }, - require: { - another: 'thing', - }, - }, - set_priority: { - priority: 0, - }, - }, - min_age: '0d', - }, - }, + phases: {}, }); }); @@ -111,12 +60,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - dataTierAllocationType: 'custom', - selectedNodeAttrs: '', - phaseEnabled: true, - }, delete: { ...defaultNewDeletePhase }, }, }, @@ -124,26 +67,13 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - cold: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, }, } ) ).toEqual({ // There should be no allocation action in any phases... name: 'test', - phases: { - cold: { - actions: { - allocate: { include: {}, exclude: {}, require: { something: 'here' } }, - set_priority: { - priority: 0, - }, - }, - min_age: '0d', - }, - }, + phases: {}, }); }); @@ -153,12 +83,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - dataTierAllocationType: 'none', - selectedNodeAttrs: 'ignore:this', - phaseEnabled: true, - }, delete: { ...defaultNewDeletePhase }, }, }, @@ -166,39 +90,20 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - cold: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, }, } ) ).toEqual({ // There should be no allocation action in any phases... name: 'test', - phases: { - cold: { - actions: { - migrate: { - enabled: false, - }, - set_priority: { - priority: 0, - }, - }, - min_age: '0d', - }, - }, + phases: {}, }); }); test('serialization does not alter the original policy', () => { const originalPolicy = { name: 'test', - phases: { - cold: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, - }, + phases: {}, }; const originalClone = cloneDeep(originalPolicy); @@ -206,13 +111,6 @@ describe('Policy serialization', () => { const deserializedPolicy = { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - dataTierAllocationType: 'none' as DataTierAllocationType, - selectedNodeAttrs: 'ignore:this', - phaseEnabled: true, - }, - delete: { ...defaultNewDeletePhase }, }, }; @@ -227,9 +125,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - }, delete: { ...defaultNewDeletePhase }, }, }, @@ -276,9 +171,6 @@ describe('Policy serialization', () => { ).toEqual({ name: 'test', phases: { - cold: { - ...coldPhaseInitialization, - }, delete: { ...defaultNewDeletePhase }, }, }); @@ -290,9 +182,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - cold: { - ...defaultNewColdPhase, - }, delete: { ...defaultNewDeletePhase }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts index 32c7e698b09203..55e9d88dcd383a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts @@ -6,13 +6,8 @@ import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common/types'; -import { - defaultNewColdPhase, - defaultNewDeletePhase, - serializedPhaseInitialization, -} from '../../constants'; +import { defaultNewDeletePhase, serializedPhaseInitialization } from '../../constants'; -import { coldPhaseFromES, coldPhaseToES } from './cold_phase'; import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; export const splitSizeAndUnits = (field: string): { size: string; units: string } => { @@ -46,7 +41,6 @@ export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => return { name: newPolicyName, phases: { - cold: { ...defaultNewColdPhase }, delete: { ...defaultNewDeletePhase }, }, }; @@ -61,7 +55,6 @@ export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => { return { name, phases: { - cold: coldPhaseFromES(phases.cold), delete: deletePhaseFromES(phases.delete), }, }; @@ -79,10 +72,6 @@ export const legacySerializePolicy = ( phases: {}, } as SerializedPolicy; - if (policy.phases.cold.phaseEnabled) { - serializedPolicy.phases.cold = coldPhaseToES(policy.phases.cold, originalEsPolicy.phases.cold); - } - if (policy.phases.delete.phaseEnabled) { serializedPolicy.phases.delete = deletePhaseToES( policy.phases.delete, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts index a113cb68a23496..79c909c433f331 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ColdPhase, DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types'; -import { validateColdPhase } from './cold_phase'; +import { DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types'; import { validateDeletePhase } from './delete_phase'; export const propertyof = (propertyName: keyof T & string) => propertyName; @@ -82,7 +81,6 @@ export type PhaseValidationErrors = { }; export interface ValidationErrors { - cold: PhaseValidationErrors; delete: PhaseValidationErrors; policyName: string[]; } @@ -120,17 +118,12 @@ export const validatePolicy = ( } } - const coldPhaseErrors = validateColdPhase(policy.phases.cold); const deletePhaseErrors = validateDeletePhase(policy.phases.delete); - const isValid = - policyNameErrors.length === 0 && - Object.keys(coldPhaseErrors).length === 0 && - Object.keys(deletePhaseErrors).length === 0; + const isValid = policyNameErrors.length === 0 && Object.keys(deletePhaseErrors).length === 0; return [ isValid, { policyName: [...policyNameErrors], - cold: coldPhaseErrors, delete: deletePhaseErrors, }, ]; @@ -145,9 +138,6 @@ export const findFirstError = (errors?: ValidationErrors): string | undefined => return propertyof('policyName'); } - if (Object.keys(errors.cold).length > 0) { - return `${propertyof('cold')}.${Object.keys(errors.cold)[0]}`; - } if (Object.keys(errors.delete).length > 0) { return `${propertyof('delete')}.${Object.keys(errors.delete)[0]}`; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts deleted file mode 100644 index c29ae3ab228315..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/shared/serialize_phase_with_allocation.ts +++ /dev/null @@ -1,42 +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. - */ - -// Prefer importing entire lodash library, e.g. import { get } from "lodash" -// eslint-disable-next-line no-restricted-imports -import cloneDeep from 'lodash/cloneDeep'; - -import { - AllocateAction, - PhaseWithAllocationAction, - SerializedPhase, -} from '../../../../../common/types'; - -export const serializePhaseWithAllocation = ( - phase: PhaseWithAllocationAction, - originalPhaseActions: SerializedPhase['actions'] = {} -): SerializedPhase['actions'] => { - const esPhaseActions: SerializedPhase['actions'] = cloneDeep(originalPhaseActions); - - if (phase.dataTierAllocationType === 'custom' && phase.selectedNodeAttrs) { - const [name, value] = phase.selectedNodeAttrs.split(':'); - esPhaseActions.allocate = esPhaseActions.allocate || ({} as AllocateAction); - esPhaseActions.allocate.require = { - [name]: value, - }; - } else if (phase.dataTierAllocationType === 'none') { - esPhaseActions.migrate = { enabled: false }; - if (esPhaseActions.allocate) { - delete esPhaseActions.allocate; - } - } else if (phase.dataTierAllocationType === 'default') { - if (esPhaseActions.allocate) { - delete esPhaseActions.allocate.require; - } - delete esPhaseActions.migrate; - } - - return esPhaseActions; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts index c77e3d22f0e37c..2f1c7798e7a4d2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts @@ -9,7 +9,6 @@ import { UIM_CONFIG_WARM_PHASE, UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_FREEZE_INDEX, - defaultNewColdPhase, defaultPhaseIndexPriority, } from '../constants/'; @@ -23,7 +22,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultNewColdPhase.phaseIndexPriority, 10), + priority: parseInt(defaultPhaseIndexPriority, 10), }, }, }, @@ -69,7 +68,7 @@ describe('getUiMetricsForPhases', () => { actions: { freeze: {}, set_priority: { - priority: parseInt(defaultNewColdPhase.phaseIndexPriority, 10), + priority: parseInt(defaultPhaseIndexPriority, 10), }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index 305b35b23e4d8b..274d3d1ca97f3f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -13,7 +13,6 @@ import { UIM_CONFIG_FREEZE_INDEX, UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_WARM_PHASE, - defaultNewColdPhase, defaultSetPriority, defaultPhaseIndexPriority, } from '../constants'; @@ -55,8 +54,7 @@ export function getUiMetricsForPhases(phases: Phases): string[] { const isColdPhasePriorityChanged = phases.cold && phases.cold.actions.set_priority && - phases.cold.actions.set_priority.priority !== - parseInt(defaultNewColdPhase.phaseIndexPriority, 10); + phases.cold.actions.set_priority.priority !== parseInt(defaultPhaseIndexPriority, 10); // If the priority is different than the default, we'll consider it a user interaction, // even if the user has set it to undefined. return ( diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx index d711863c309e90..afdf726ea02f99 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx @@ -158,7 +158,6 @@ export class IndexLifecycleSummary extends Component { button={button} isOpen={this.state.showPhaseExecutionPopover} closePopover={this.closePhaseExecutionPopover} - withTitle > ({ storageSize: '1b', maxTimeStamp: 420, }); + +export const createDataStreamBackingIndex = (indexName: string, dataStreamName: string) => ({ + health: '', + status: '', + primary: '', + replica: '', + documents: '', + documents_deleted: '', + size: '', + primary_size: '', + name: indexName, + data_stream: dataStreamName, +}); + +export const createNonDataStreamIndex = (name: string) => ({ + health: 'green', + status: 'open', + primary: 1, + replica: 1, + documents: 10000, + documents_deleted: 100, + size: '156kb', + primary_size: '156kb', + name, +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index ade4a62ceb1989..23539659d2af2e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -9,7 +9,13 @@ import { act } from 'react-dom/test-utils'; import { API_BASE_PATH } from '../../../common/constants'; import { setupEnvironment } from '../helpers'; -import { DataStreamsTabTestBed, setup, createDataStreamPayload } from './data_streams_tab.helpers'; +import { + DataStreamsTabTestBed, + setup, + createDataStreamPayload, + createDataStreamBackingIndex, + createNonDataStreamIndex, +} from './data_streams_tab.helpers'; describe('Data Streams tab', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -85,29 +91,8 @@ describe('Data Streams tab', () => { } = httpRequestsMockHelpers; setLoadIndicesResponse([ - { - health: '', - status: '', - primary: '', - replica: '', - documents: '', - documents_deleted: '', - size: '', - primary_size: '', - name: 'data-stream-index', - data_stream: 'dataStream1', - }, - { - health: 'green', - status: 'open', - primary: 1, - replica: 1, - documents: 10000, - documents_deleted: 100, - size: '156kb', - primary_size: '156kb', - name: 'non-data-stream-index', - }, + createDataStreamBackingIndex('data-stream-index', 'dataStream1'), + createNonDataStreamIndex('non-data-stream-index'), ]); const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); @@ -260,4 +245,46 @@ describe('Data Streams tab', () => { }); }); }); + + describe('when there are special characters', () => { + beforeEach(async () => { + const { + setLoadIndicesResponse, + setLoadDataStreamsResponse, + setLoadDataStreamResponse, + } = httpRequestsMockHelpers; + + setLoadIndicesResponse([ + createDataStreamBackingIndex('data-stream-index', '%dataStream'), + createDataStreamBackingIndex('data-stream-index2', 'dataStream2'), + ]); + + const dataStreamDollarSign = createDataStreamPayload('%dataStream'); + setLoadDataStreamsResponse([dataStreamDollarSign]); + setLoadDataStreamResponse(dataStreamDollarSign); + + testBed = await setup(); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + }); + + describe('detail panel', () => { + test('opens when the data stream name in the table is clicked', async () => { + const { actions, findDetailPanel, findDetailPanelTitle } = testBed; + await actions.clickNameAt(0); + expect(findDetailPanel().length).toBe(1); + expect(findDetailPanelTitle()).toBe('%dataStream'); + }); + + test('clicking the indices count navigates to the backing indices', async () => { + const { table, actions } = testBed; + await actions.clickIndicesAt(0); + expect(table.getMetaData('indexTable').tableCellsValues).toEqual([ + ['', '', '', '', '', '', '', '%dataStream'], + ]); + }); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx index 0f5bc64c358b9e..6d9aa58d6c86b8 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/component_template_details.tsx @@ -20,12 +20,17 @@ import { EuiBadge, } from '@elastic/eui'; -import { SectionLoading, TabSettings, TabAliases, TabMappings } from '../shared_imports'; +import { + SectionLoading, + TabSettings, + TabAliases, + TabMappings, + attemptToURIDecode, +} from '../shared_imports'; import { useComponentTemplatesContext } from '../component_templates_context'; import { TabSummary } from './tab_summary'; import { ComponentTemplateTabs, TabType } from './tabs'; import { ManageButton, ManageAction } from './manage_button'; -import { attemptToDecodeURI } from '../lib'; export interface Props { componentTemplateName: string; @@ -47,7 +52,7 @@ export const ComponentTemplateDetailsFlyoutContent: React.FunctionComponent { const { api } = useComponentTemplatesContext(); - const decodedComponentTemplateName = attemptToDecodeURI(componentTemplateName); + const decodedComponentTemplateName = attemptToURIDecode(componentTemplateName); const { data: componentTemplateDetails, isLoading, error } = api.useLoadComponentTemplate( decodedComponentTemplateName diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx index c3a4f9b4dce350..ebb174acc91a26 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_details/manage_button.tsx @@ -80,7 +80,6 @@ export const ManageButton: React.FunctionComponent = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx index 05f7f53969dedd..00ea3ebf794eed 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx @@ -11,9 +11,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ScopedHistory } from 'kibana/public'; import { EuiLink, EuiText, EuiSpacer } from '@elastic/eui'; +import { attemptToURIDecode } from '../../../../shared_imports'; import { SectionLoading, ComponentTemplateDeserialized, GlobalFlyout } from '../shared_imports'; import { UIM_COMPONENT_TEMPLATE_LIST_LOAD } from '../constants'; -import { attemptToDecodeURI } from '../lib'; import { useComponentTemplatesContext } from '../component_templates_context'; import { ComponentTemplateDetailsFlyoutContent, @@ -84,7 +84,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ }), icon: 'pencil', handleActionClick: () => - goToEditComponentTemplate(attemptToDecodeURI(componentTemplateName)), + goToEditComponentTemplate(attemptToURIDecode(componentTemplateName)), }, { name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.cloneActionLabel', { @@ -92,7 +92,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ }), icon: 'copy', handleActionClick: () => - goToCloneComponentTemplate(attemptToDecodeURI(componentTemplateName)), + goToCloneComponentTemplate(attemptToURIDecode(componentTemplateName)), }, { name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.deleteButtonLabel', { @@ -103,7 +103,7 @@ export const ComponentTemplateList: React.FunctionComponent = ({ details._kbnMeta.usedBy.length > 0, closePopoverOnClick: true, handleActionClick: () => { - setComponentTemplatesToDelete([attemptToDecodeURI(componentTemplateName)]); + setComponentTemplatesToDelete([attemptToURIDecode(componentTemplateName)]); }, }, ]; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx index 941e8ec362de25..8939e58ffd94f1 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx @@ -36,7 +36,6 @@ export const CreateButtonPopOver = ({ anchorPosition = 'upCenter' }: Props) => { isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition={anchorPosition} repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx index 94db623f313c79..6c03fcf5d99729 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_clone/component_template_clone.tsx @@ -9,9 +9,8 @@ import { RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SectionLoading } from '../../shared_imports'; +import { SectionLoading, attemptToURIDecode } from '../../shared_imports'; import { useComponentTemplatesContext } from '../../component_templates_context'; -import { attemptToDecodeURI } from '../../lib'; import { ComponentTemplateCreate } from '../component_template_create'; export interface Params { @@ -20,7 +19,7 @@ export interface Params { export const ComponentTemplateClone: FunctionComponent> = (props) => { const { sourceComponentTemplateName } = props.match.params; - const decodedSourceName = attemptToDecodeURI(sourceComponentTemplateName); + const decodedSourceName = attemptToURIDecode(sourceComponentTemplateName); const { toasts, api } = useComponentTemplatesContext(); diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx index 2bd3dfb34acb9a..934f86f7d7590c 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_edit/component_template_edit.tsx @@ -9,8 +9,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { useComponentTemplatesContext } from '../../component_templates_context'; -import { ComponentTemplateDeserialized, SectionLoading } from '../../shared_imports'; -import { attemptToDecodeURI } from '../../lib'; +import { + ComponentTemplateDeserialized, + SectionLoading, + attemptToURIDecode, +} from '../../shared_imports'; import { ComponentTemplateForm } from '../component_template_form'; interface MatchParams { @@ -28,7 +31,7 @@ export const ComponentTemplateEdit: React.FunctionComponent(false); const [saveError, setSaveError] = useState(null); - const decodedName = attemptToDecodeURI(name); + const decodedName = attemptToURIDecode(name); const { error, data: componentTemplate, isLoading } = api.useLoadComponentTemplate(decodedName); @@ -50,7 +53,9 @@ export const ComponentTemplateEdit: React.FunctionComponent { - let result: string; - - try { - result = decodeURI(value); - result = decodeURIComponent(result); - } catch (e) { - result = decodeURIComponent(value); - } - - return result; -}; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts index ffd78925c16a07..4a9771bd5ba8a7 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts @@ -20,6 +20,7 @@ export { NotAuthorizedSection, Forms, GlobalFlyout, + attemptToURIDecode, } from '../../../../../../../src/plugins/es_ui_shared/public'; export { diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 4f2a5c4a27b7aa..19286523055f5c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -20,13 +20,17 @@ import { } from '@elastic/eui'; import { ScopedHistory } from 'kibana/public'; -import { reactRouterNavigate, extractQueryParams } from '../../../../shared_imports'; +import { + reactRouterNavigate, + extractQueryParams, + attemptToURIDecode, +} from '../../../../shared_imports'; import { useAppContext } from '../../../app_context'; import { SectionError, SectionLoading, Error } from '../../../components'; import { useLoadDataStreams } from '../../../services/api'; -import { encodePathForReactRouter, decodePathFromReactRouter } from '../../../services/routing'; +import { getIndexListUri } from '../../../services/routing'; import { documentationService } from '../../../services/documentation'; -import { Section } from '../../home'; +import { Section } from '../home'; import { DataStreamTable } from './data_stream_table'; import { DataStreamDetailPanel } from './data_stream_detail_panel'; @@ -206,7 +210,7 @@ export const DataStreamList: React.FunctionComponent { history.push(`/${Section.DataStreams}`); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 642123c5114199..992d569bf3a427 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -12,9 +12,8 @@ import { ScopedHistory } from 'kibana/public'; import { DataStream } from '../../../../../../common/types'; import { UseRequestResponse, reactRouterNavigate } from '../../../../../shared_imports'; -import { encodePathForReactRouter } from '../../../../services/routing'; +import { getDataStreamDetailsLink, getIndexListUri } from '../../../../services/routing'; import { DataHealth } from '../../../../components'; -import { Section } from '../../../home'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; import { humanizeTimeStamp } from '../humanize_time_stamp'; @@ -45,13 +44,11 @@ export const DataStreamTable: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (name: DataStream['name'], item: DataStream) => { + render: (name: DataStream['name']) => { return ( {name} @@ -108,12 +105,7 @@ export const DataStreamTable: React.FunctionComponent = ({ render: (indices: DataStream['indices'], dataStream) => ( {indices.length} diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 6e96ef56d683f9..cf6fcfc238e06b 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -746,7 +746,6 @@ export class IndexActionsContextMenu extends Component { isOpen={this.state.isPopoverOpen} closePopover={this.closePopover} panelPaddingSize="none" - withTitle anchorPosition={anchorPosition} repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index b4e003b667074b..7b09f20091110a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -35,9 +35,9 @@ import { } from '@elastic/eui'; import { UIM_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; -import { reactRouterNavigate } from '../../../../../shared_imports'; +import { reactRouterNavigate, attemptToURIDecode } from '../../../../../shared_imports'; import { REFRESH_RATE_INDEX_LIST } from '../../../../constants'; -import { encodePathForReactRouter } from '../../../../services/routing'; +import { getDataStreamDetailsLink } from '../../../../services/routing'; import { documentationService } from '../../../../services/documentation'; import { AppContextConsumer } from '../../../../app_context'; import { renderBadges } from '../../../../lib/render_badges'; @@ -107,7 +107,7 @@ export class IndexTable extends Component { const { location, filterChanged } = this.props; const { filter } = qs.parse((location && location.search) || ''); if (filter) { - const decodedFilter = decodeURIComponent(filter); + const decodedFilter = attemptToURIDecode(filter); try { const filter = EuiSearchBar.Query.parse(decodedFilter); @@ -279,7 +279,7 @@ export class IndexTable extends Component { diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx index 7ec6f1f94a2ab9..29b841f3bdc7a5 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx @@ -13,7 +13,7 @@ import { UseRequestResponse, reactRouterNavigate } from '../../../../../../share import { TemplateListItem } from '../../../../../../../common'; import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../../common/constants'; import { TemplateDeleteModal } from '../../../../../components'; -import { encodePathForReactRouter } from '../../../../../services/routing'; +import { getTemplateDetailsLink } from '../../../../../services/routing'; import { useServices } from '../../../../../app_context'; import { TemplateContentIndicator } from '../../../../../components/shared'; import { TemplateTypeIndicator } from '../../components'; @@ -53,10 +53,7 @@ export const LegacyTemplateTable: React.FunctionComponent = ({ uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) )} data-test-subj="templateDetailsLink" diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx index 48083f324de3da..1b1b9ad013c375 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx @@ -34,7 +34,6 @@ import { import { UseRequestResponse } from '../../../../../shared_imports'; import { TemplateDeleteModal, SectionLoading, SectionError, Error } from '../../../../components'; import { useLoadIndexTemplate } from '../../../../services/api'; -import { decodePathFromReactRouter } from '../../../../services/routing'; import { useServices } from '../../../../app_context'; import { TabAliases, TabMappings, TabSettings } from '../../../../components/shared'; import { TemplateTypeIndicator } from '../components'; @@ -103,11 +102,7 @@ export const TemplateDetailsContent = ({ reload, }: Props) => { const { uiMetricService } = useServices(); - const decodedTemplateName = decodePathFromReactRouter(templateName); - const { error, data: templateDetails, isLoading } = useLoadIndexTemplate( - decodedTemplateName, - isLegacy - ); + const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(templateName, isLegacy); const isCloudManaged = templateDetails?._kbnMeta.type === 'cloudManaged'; const [templateToDelete, setTemplateToDelete] = useState< Array<{ name: string; isLegacy?: boolean }> @@ -120,7 +115,7 @@ export const TemplateDetailsContent = ({

- {decodedTemplateName} + {templateName} {templateDetails && ( <>   @@ -267,7 +262,6 @@ export const TemplateDetailsContent = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > @@ -303,8 +297,7 @@ export const TemplateDetailsContent = ({ defaultMessage: 'Delete', }), icon: 'trash', - onClick: () => - setTemplateToDelete([{ name: decodedTemplateName, isLegacy }]), + onClick: () => setTemplateToDelete([{ name: templateName, isLegacy }]), disabled: isCloudManaged, }, ], diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index c711f457123fb1..f3e82223c30e6c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -36,6 +36,7 @@ import { TemplateTable } from './template_table'; import { TemplateDetails } from './template_details'; import { LegacyTemplateTable } from './legacy_templates/template_table'; import { FilterListButton, Filters } from './components'; +import { attemptToURIDecode } from '../../../../shared_imports'; type FilterName = 'managed' | 'cloudManaged' | 'system'; interface MatchParams { @@ -100,7 +101,7 @@ export const TemplateList: React.FunctionComponent = ({ return ( <> uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) + {...reactRouterNavigate(history, getTemplateDetailsLink(name), () => + uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) )} data-test-subj="templateDetailsLink" > diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx index 2aaecbd64ee28d..5bb355d90478bb 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx @@ -11,9 +11,10 @@ import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui'; import { TemplateDeserialized } from '../../../../common'; import { TemplateForm, SectionLoading, SectionError, Error } from '../../components'; import { breadcrumbService } from '../../services/breadcrumbs'; -import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing'; +import { getTemplateDetailsLink } from '../../services/routing'; import { saveTemplate, useLoadIndexTemplate } from '../../services/api'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; +import { attemptToURIDecode } from '../../../shared_imports'; interface MatchParams { name: string; @@ -26,7 +27,7 @@ export const TemplateClone: React.FunctionComponent { - const decodedTemplateName = decodePathFromReactRouter(name); + const decodedTemplateName = attemptToURIDecode(name); const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx index 6bdcd03fa5ca40..3e62f7f880f749 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx @@ -11,9 +11,10 @@ import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@e import { TemplateDeserialized } from '../../../../common'; import { breadcrumbService } from '../../services/breadcrumbs'; import { useLoadIndexTemplate, updateTemplate } from '../../services/api'; -import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing'; +import { getTemplateDetailsLink } from '../../services/routing'; import { SectionLoading, SectionError, TemplateForm, Error } from '../../components'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; +import { attemptToURIDecode } from '../../../shared_imports'; interface MatchParams { name: string; @@ -26,7 +27,7 @@ export const TemplateEdit: React.FunctionComponent { - const decodedTemplateName = decodePathFromReactRouter(name); + const decodedTemplateName = attemptToURIDecode(name); const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); @@ -51,7 +52,7 @@ export const TemplateEdit: React.FunctionComponent { diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index 68bf06409e6ab5..d12198d6b17a5b 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -6,9 +6,8 @@ export const getTemplateListLink = () => `/templates`; -export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHash = false) => { - const baseUrl = `/templates/${encodePathForReactRouter(name)}`; - let url = withHash ? `#${baseUrl}` : baseUrl; +export const getTemplateDetailsLink = (name: string, isLegacy?: boolean) => { + let url = `/templates/${encodeURIComponent(name)}`; if (isLegacy) { url = `${url}?legacy=${isLegacy}`; } @@ -16,7 +15,7 @@ export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHas }; export const getTemplateEditLink = (name: string, isLegacy?: boolean) => { - let url = `/edit_template/${encodePathForReactRouter(name)}`; + let url = `/edit_template/${encodeURIComponent(name)}`; if (isLegacy) { url = `${url}?legacy=true`; } @@ -24,7 +23,7 @@ export const getTemplateEditLink = (name: string, isLegacy?: boolean) => { }; export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => { - let url = `/clone_template/${encodePathForReactRouter(name)}`; + let url = `/clone_template/${encodeURIComponent(name)}`; if (isLegacy) { url = `${url}?legacy=true`; } @@ -32,9 +31,7 @@ export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => { }; export const getILMPolicyPath = (policyName: string) => { - return encodeURI( - `/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}` - ); + return `/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}`; }; export const getIndexListUri = (filter?: string, includeHiddenIndices?: boolean) => { @@ -53,18 +50,6 @@ export const getIndexListUri = (filter?: string, includeHiddenIndices?: boolean) return '/indices'; }; -export const decodePathFromReactRouter = (pathname: string): string => { - let decodedPath; - try { - decodedPath = decodeURI(pathname); - decodedPath = decodeURIComponent(decodedPath); - } catch (_error) { - decodedPath = decodeURIComponent(pathname); - } - return decodeURIComponent(decodedPath); +export const getDataStreamDetailsLink = (name: string) => { + return encodeURI(`/data_streams/${encodeURIComponent(name)}`); }; - -// Need to add some additonal encoding/decoding logic to work with React Router -// For background, see: https://github.com/ReactTraining/history/issues/505 -export const encodePathForReactRouter = (pathname: string): string => - encodeURIComponent(encodeURIComponent(pathname)); diff --git a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts index 3075a114eedd11..6cf533d67662ff 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.mock.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 type { PublicMethodsOf } from '@kbn/utility-types'; import { ExtensionsService, ExtensionsSetup } from './extensions_service'; export type ExtensionsSetupMock = jest.Mocked; diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index acb3eb512e2c18..360d3ba473db8b 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -14,6 +14,7 @@ export { Forms, extractQueryParams, GlobalFlyout, + attemptToURIDecode, } from '../../../../src/plugins/es_ui_shared/public'; export { diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index 34edcb6fb7477d..fa93d9bd0c563b 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -83,15 +83,16 @@ export function registerGetOneRoute({ router, license, lib: { isEsError } }: Rou license.guardApiRoute(async (ctx, req, res) => { const { name } = req.params as TypeOf; const { callAsCurrentUser } = ctx.dataManagement!.client; - try { const [ { data_streams: dataStream }, { data_streams: dataStreamsStats }, ] = await Promise.all([ - callAsCurrentUser('dataManagement.getDataStream', { name }), + callAsCurrentUser('dataManagement.getDataStream', { + name, + }), ctx.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { - path: `/_data_stream/${name}/_stats`, + path: `/_data_stream/${encodeURIComponent(name)}/_stats`, method: 'GET', query: { human: true, diff --git a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts index 5014aeb52a3f7f..d38b4690fed713 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts @@ -5,6 +5,7 @@ */ import * as rt from 'io-ts'; +import { jsonArrayRT } from '../../typed_json'; import { logEntriesCursorRT } from './common'; export const LOG_ENTRIES_PATH = '/api/log_entries/entries'; @@ -54,7 +55,7 @@ export const logMessageConstantPartRT = rt.type({ }); export const logMessageFieldPartRT = rt.type({ field: rt.string, - value: rt.unknown, + value: jsonArrayRT, highlights: rt.array(rt.string), }); @@ -64,7 +65,7 @@ export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt export const logFieldColumnRT = rt.type({ columnId: rt.string, field: rt.string, - value: rt.unknown, + value: jsonArrayRT, highlights: rt.array(rt.string), }); export const logMessageColumnRT = rt.type({ diff --git a/x-pack/plugins/infra/common/http_api/log_entries/item.ts b/x-pack/plugins/infra/common/http_api/log_entries/item.ts index 02335d68402c0f..5f9457b8228aca 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/item.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/item.ts @@ -16,7 +16,7 @@ export const logEntriesItemRequestRT = rt.type({ export type LogEntriesItemRequest = rt.TypeOf; -const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string }); +const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.array(rt.string) }); const logEntriesItemRT = rt.type({ id: rt.string, index: rt.string, diff --git a/x-pack/plugins/infra/common/http_api/metrics_api.ts b/x-pack/plugins/infra/common/http_api/metrics_api.ts index 41657fdce2153b..14b7c24e576946 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_api.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_api.ts @@ -30,6 +30,7 @@ export const MetricsAPIRequestRT = rt.intersection([ }), rt.partial({ groupBy: rt.array(groupByRT), + modules: rt.array(rt.string), afterKey: rt.union([rt.null, afterKeyObjectRT]), limit: rt.union([rt.number, rt.null, rt.undefined]), filters: rt.array(rt.object), diff --git a/x-pack/plugins/infra/common/typed_json.ts b/x-pack/plugins/infra/common/typed_json.ts index 98b5456fe44b84..0ff9e42942ef20 100644 --- a/x-pack/plugins/infra/common/typed_json.ts +++ b/x-pack/plugins/infra/common/typed_json.ts @@ -4,11 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -export type JsonValue = null | boolean | number | string | JsonObject | JsonArray; +import * as rt from 'io-ts'; +import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface JsonArray extends Array {} +export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]); -export interface JsonObject { - [key: string]: JsonValue; -} +export const jsonValueRT: rt.Type = rt.recursion('JsonValue', () => + rt.union([jsonScalarRT, jsonArrayRT, jsonObjectRT]) +); + +export const jsonArrayRT: rt.Type = rt.recursion('JsonArray', () => + rt.array(jsonValueRT) +); + +export const jsonObjectRT: rt.Type = rt.recursion('JsonObject', () => + rt.record(rt.string, jsonValueRT) +); + +export { JsonValue, JsonArray, JsonObject }; diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx index 02c3ea29c1846d..7b15acfb6c4e2a 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -127,7 +127,7 @@ export const AlertPreview: React.FC = (props) => { defaultMessage: 'Preview', })} fullWidth - compressed + display="rowCompressed" > <> diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index f47f30c280b2a5..66d547eb50d9c2 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -365,7 +365,7 @@ export const Expressions: React.FC = (props) => { defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.', })} fullWidth - compressed + display="rowCompressed" > {(alertsContext.metadata && ( { setAggFieldPopoverOpen(false); }} - withTitle anchorPosition={popupPosition ?? 'downRight'} zIndex={8000} > diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx index 9c215b89f46348..4dca4792718324 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx @@ -61,7 +61,6 @@ export const NodeTypeExpression = ({ setAggTypePopoverOpen(false); }} ownFocus - withTitle anchorPosition={popupPosition ?? 'downLeft'} >
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index c71a3b6b13338e..92c01727034234 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -364,7 +364,7 @@ export const Expressions: React.FC = (props) => { defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.', })} fullWidth - compressed + display="rowCompressed" > {(alertsContext.metadata && ( = (props) => { 'Create an alert for every unique value. For example: "host.id" or "cloud.region".', })} fullWidth - compressed + display="rowCompressed" > { { { { { { { .filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field)) .reduce((acc, fieldItem) => { const { field, value } = fieldItem; - try { - const parsedValue = decodeOrThrow(rt.array(rt.string))(JSON.parse(value)); - return acc.concat(parsedValue.map((val) => `${field}:${val}`)); - } catch (e) { - return acc.concat([`${field}:${value}`]); - } + return acc.concat(value.map((val) => `${field}:${val}`)); }, []); if (searchExpressions.length === 0) { @@ -119,7 +112,7 @@ const getUptimeLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => { const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => { const traceIdEntry = logItem.fields.find( - ({ field, value }) => value != null && field === 'trace.id' + ({ field, value }) => value[0] != null && field === 'trace.id' ); if (!traceIdEntry) { @@ -127,7 +120,7 @@ const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => { } const timestampField = logItem.fields.find(({ field }) => field === '@timestamp'); - const timestamp = timestampField ? timestampField.value : null; + const timestamp = timestampField ? timestampField.value[0] : null; const { rangeFrom, rangeTo } = timestamp ? (() => { const from = new Date(timestamp); @@ -142,6 +135,6 @@ const getAPMLink = (logItem: LogEntriesItem): LinkDescriptor | undefined => { return { app: 'apm', - hash: getTraceUrl({ traceId: traceIdEntry.value, rangeFrom, rangeTo }), + hash: getTraceUrl({ traceId: traceIdEntry.value[0], rangeFrom, rangeTo }), }; }; diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index 76ffada510e51c..b07d8c9dce23c5 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -94,7 +94,7 @@ export const LogEntryFlyout = ({ onClick={createFilterHandler(item)} /> - {item.value} + {formatValue(item.value)} ), }, @@ -147,3 +147,7 @@ export const InfraFlyoutLoadingPanel = euiStyled.div` bottom: 0; left: 0; `; + +function formatValue(value: string[]) { + return value.length > 1 ? value.join(', ') : value[0]; +} diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx new file mode 100644 index 00000000000000..7caf34c0cd6b72 --- /dev/null +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/field_value.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import stringify from 'json-stable-stringify'; +import React from 'react'; +import { euiStyled } from '../../../../../observability/public'; +import { JsonArray, JsonValue } from '../../../../common/typed_json'; +import { ActiveHighlightMarker, highlightFieldValue, HighlightMarker } from './highlighting'; + +export const FieldValue: React.FC<{ + highlightTerms: string[]; + isActiveHighlight: boolean; + value: JsonArray; +}> = React.memo(({ highlightTerms, isActiveHighlight, value }) => { + if (value.length === 1) { + return ( + <> + {highlightFieldValue( + formatValue(value[0]), + highlightTerms, + isActiveHighlight ? ActiveHighlightMarker : HighlightMarker + )} + + ); + } else if (value.length > 1) { + return ( +
    + {value.map((entry, i) => ( + + {highlightFieldValue( + formatValue(entry), + highlightTerms, + isActiveHighlight ? ActiveHighlightMarker : HighlightMarker + )} + + ))} +
+ ); + } + + return null; +}); + +const formatValue = (value: JsonValue): string => { + if (typeof value === 'string') { + return value; + } + + return stringify(value); +}; + +const CommaSeparatedLi = euiStyled.li` + display: inline; + &:not(:last-child) { + margin-right: 1ex; + &::after { + content: ','; + } + } +`; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx index 51488f088e75ae..9b039b7f2f3ba4 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_column.tsx @@ -39,7 +39,9 @@ export const LogEntryColumn = euiStyled.div.attrs(() => ({ overflow: hidden; `; -export const LogEntryColumnContent = euiStyled.div` +export const LogEntryColumnContent = euiStyled.div.attrs({ + 'data-test-subj': 'LogEntryColumnContent', +})` flex: 1 0 0%; padding: 2px ${COLUMN_PADDING}px; `; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx index be140a810f1646..c627d6eda22a57 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx @@ -61,7 +61,7 @@ export const LogEntryContextMenu: React.FC = ({ fill aria-label={ariaLabel || DEFAULT_MENU_LABEL} onClick={onOpen} - style={{ minWidth: 'auto' }} + minWidth="auto" > diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx index d6068b6e60992a..5813f08497a743 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx @@ -4,85 +4,113 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount } from 'enzyme'; +import { render } from '@testing-library/react'; import React from 'react'; - import { EuiThemeProvider } from '../../../../../observability/public'; +import { LogFieldColumn } from '../../../../common/http_api'; import { LogEntryFieldColumn } from './log_entry_field_column'; -import { LogColumn } from '../../../../common/http_api'; describe('LogEntryFieldColumn', () => { - it('should output a